Compare commits
No commits in common. "b2d94f9751b07e3438200eaf39503d6779c39111" and "24285db68678e77bc3c83914b7f7e920a5c47733" have entirely different histories.
b2d94f9751
...
24285db686
35 changed files with 2764 additions and 1290 deletions
24
README.md
24
README.md
|
@ -7,7 +7,7 @@ Idris files.
|
|||
## Authors Note
|
||||
|
||||
This entire book is a single literate code base, the source code is available at
|
||||
<https://git.stranger.systems/Idris/advent>.
|
||||
https://git.stranger.systems/Idris/advent
|
||||
|
||||
The solutions contained in this project are intended to be read in sequential
|
||||
order, though can reasonably be read in any order if you have a good level of
|
||||
|
@ -25,7 +25,7 @@ mailing list on source hut.
|
|||
While this project is intended to read more like a book, while it is still a
|
||||
work in progress, you can follow its development as a psuedo-blog by subscribing
|
||||
to the rss feed for the repository in your feed reader:
|
||||
<https://git.stranger.systems/Idris/advent.rss>.
|
||||
https://git.stranger.systems/Idris/advent.rss
|
||||
|
||||
## Index of non-day modules
|
||||
|
||||
|
@ -59,26 +59,6 @@ solution.
|
|||
Provider wrappers over the standard library `IOArray` type to make them more
|
||||
ergonomic to use.
|
||||
|
||||
- [Parser](src/Parser.md)
|
||||
|
||||
Effectful parser mini-library
|
||||
|
||||
- [Interface](src/Parser/Interface.md)
|
||||
|
||||
Effectful parser API
|
||||
|
||||
- [ParserState](src/Parser/ParserState.md)
|
||||
|
||||
Internal state of a parser
|
||||
|
||||
- [Numbers](src/Parser/Numbers.md)
|
||||
|
||||
Parsers for numerical values in multiple bases
|
||||
|
||||
- [JSON](src/Parser/JSON.md)
|
||||
|
||||
JSON Parser
|
||||
|
||||
## Index of years and days
|
||||
|
||||
- 2015
|
||||
|
|
|
@ -19,7 +19,6 @@ depends = base
|
|||
, tailrec
|
||||
, eff
|
||||
, elab-util
|
||||
, sop
|
||||
, ansi
|
||||
, if-unsolved-implicit
|
||||
, c-ffi
|
||||
|
@ -31,10 +30,6 @@ modules = Runner
|
|||
, Util.Eff
|
||||
, Util.Digits
|
||||
, Array
|
||||
, Parser
|
||||
, Parser.Interface
|
||||
, Parser.Numbers
|
||||
, Parser.JSON
|
||||
|
||||
-- main file (i.e. file to load at REPL)
|
||||
main = Main
|
||||
|
|
|
@ -8,6 +8,3 @@ title = "Idris 2 by Highly Contrived Example"
|
|||
[build]
|
||||
create-missing = false
|
||||
use-default-preprocessors = false
|
||||
|
||||
[output.html]
|
||||
preferred-dark-theme = "ayu"
|
||||
|
|
|
@ -53,9 +53,6 @@ sub katla($src, $ttc-src) {
|
|||
# TODO: Post process them to set themeing correctly
|
||||
$output ~~ s:g/'<style>' .* '</style>'//;
|
||||
$output ~~ s:g/'<br />'//;
|
||||
$output ~~ s:g/'\\*'/*/;
|
||||
$output ~~ s:g/'\\_'/_/;
|
||||
$output ~~ s:g/'\\\\'/\\/;
|
||||
$output ~~ s:g/'<code'/<pre><code/;
|
||||
$output ~~ s:g/'</code>'/<\/code><\/pre>/;
|
||||
$output ~~ s:g/'class="IdrisKeyword"'/class="hljs-keyword"/;
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
# Parsing Utilties
|
||||
|
||||
```idris
|
||||
module Parser
|
||||
|
||||
import public Parser.Interface as Parser
|
||||
import public Parser.ParserState as Parser
|
||||
```
|
|
@ -1,332 +0,0 @@
|
|||
# The interface of a `Parser`
|
||||
|
||||
```idris
|
||||
module Parser.Interface
|
||||
|
||||
import public Data.List1
|
||||
|
||||
import public Parser.ParserState
|
||||
|
||||
import public Control.Eff
|
||||
|
||||
export infixr 4 >|
|
||||
export infixr 5 >&
|
||||
```
|
||||
|
||||
## Parser Errors
|
||||
|
||||
Combine the parser state at time of error with an error message.
|
||||
|
||||
```idris
|
||||
public export
|
||||
data ParseError : Type where
|
||||
-- TODO: Rename this constructor
|
||||
MkParseError : (state : ParserInternal Id) -> (message : String) -> ParseError
|
||||
BeforeParse : (message : String) -> ParseError
|
||||
NestedErrors : (state : ParserInternal Id) -> (message : String)
|
||||
-> (rest : List ParseError) -> ParseError
|
||||
```
|
||||
|
||||
```idris hide
|
||||
export
|
||||
Show ParseError where
|
||||
show (MkParseError state message) =
|
||||
let (line, col) = positionPair state
|
||||
(line, col) = (show line, show col)
|
||||
position = show state.position.index
|
||||
in "Error at line \{line}, column \{col} (\{position}): \{message}"
|
||||
show (BeforeParse message) =
|
||||
"Error before parsing: \{message}"
|
||||
show (NestedErrors state message rest) =
|
||||
let rest = assert_total $joinBy "\n" . map ((" " ++) . show) $ rest
|
||||
(line, col) = positionPair state
|
||||
(line, col) = (show line, show col)
|
||||
position = show state.position.index
|
||||
first = "Error at line \{line}, column \{col} (\{position}): \{message}"
|
||||
in "\{first}\n\{rest}"
|
||||
```
|
||||
|
||||
## Type Alias
|
||||
|
||||
```idris
|
||||
public export
|
||||
Parser : Type -> Type
|
||||
Parser a = Eff [ParserState, Except ParseError] a
|
||||
```
|
||||
|
||||
## Error Generation
|
||||
|
||||
Provide a few effectful actions to generate an error from an error message, and
|
||||
either return it or throw it.
|
||||
|
||||
```idris
|
||||
export
|
||||
parseError : Has ParserState fs => (message : String) -> Eff fs ParseError
|
||||
parseError message = do
|
||||
state <- save
|
||||
pure $ MkParseError state message
|
||||
|
||||
export
|
||||
throwParseError : Has ParserState fs => Has (Except ParseError) fs =>
|
||||
(message : String) -> Eff fs a
|
||||
throwParseError message = do
|
||||
err <- parseError message
|
||||
throw err
|
||||
|
||||
export
|
||||
guardMaybe : Has ParserState fs => Has (Except ParseError) fs =>
|
||||
(message : String) -> Eff fs (Maybe a) -> Eff fs a
|
||||
guardMaybe message x = do
|
||||
Just x <- x
|
||||
| _ => throwParseError message
|
||||
pure x
|
||||
|
||||
export
|
||||
replaceError : (message : String) -> Parser (a -> Parser b)
|
||||
replaceError message = do
|
||||
state <- save
|
||||
pure (\_ => throw $ MkParseError state message)
|
||||
```
|
||||
|
||||
## Running a parser
|
||||
|
||||
We will use the phrasing "rundown" to refer to running all the effects in the
|
||||
parser effect stack except `ParserState`, which is left in the effect stack to
|
||||
facilitate handling in the context of another monad or effect stack, since it
|
||||
benefits from mutability.
|
||||
|
||||
Rundown a parser, accepting the first returning parse, which may be failing or
|
||||
succeding, and automatically generating a "no valid parses" message in the event
|
||||
no paths in the `Choice` effect produce a returning parse.
|
||||
|
||||
```idris
|
||||
export
|
||||
rundownFirst : (f : Parser a) -> Eff [ParserState] (Either ParseError a)
|
||||
rundownFirst f =
|
||||
runExcept f
|
||||
```
|
||||
|
||||
Provide wrappers for `rundownFirst` for evaluating it in various contexts.
|
||||
|
||||
```idris
|
||||
export
|
||||
runFirstIO : (f : Parser a) -> String -> IO (Either ParseError a)
|
||||
runFirstIO f str = do
|
||||
Just state <- newInternalIO str
|
||||
| _ => pure . Left $ BeforeParse "Empty input"
|
||||
runEff (rundownFirst f) [handleParserStateIO state]
|
||||
|
||||
export
|
||||
runFirstIODebug : (f : Parser a) -> String -> IO (Either ParseError a)
|
||||
runFirstIODebug f str = do
|
||||
Just state <- newInternalIO str
|
||||
| _ => pure . Left $ BeforeParse "Empty input"
|
||||
runEff (rundownFirst f) [handleParserStateIODebug state]
|
||||
|
||||
export
|
||||
runFirst : (f : Parser a) -> String -> Eff fs (Either ParseError a)
|
||||
runFirst f str = do
|
||||
Just state <- pure $ newInternal str
|
||||
| _ => pure . Left $ BeforeParse "Empty input"
|
||||
(result, _) <- lift . runParserState state . rundownFirst $ f
|
||||
pure result
|
||||
|
||||
export
|
||||
runFirst' : (f : Parser a) -> String -> Either ParseError a
|
||||
runFirst' f str = extract $ runFirst f str {fs = []}
|
||||
```
|
||||
|
||||
## Utility functionality
|
||||
|
||||
### Parser combinators
|
||||
|
||||
Try to run a computation in the context of the `Parser` effect stack, if it
|
||||
fails (via `Except`), reset the state and resort to the supplied callback
|
||||
|
||||
Also supply a version specialized to ignore the error value, returning `Just a`
|
||||
if the parse succeeds, and `Nothing` if it fails.
|
||||
|
||||
```idris
|
||||
export
|
||||
try : (f : Parser a) -> (err : ParseError -> Parser a) -> Parser a
|
||||
try f err = do
|
||||
starting_state <- save
|
||||
result <- lift . runExcept $ f
|
||||
case result of
|
||||
Left error => do
|
||||
load starting_state
|
||||
err error
|
||||
Right result => pure result
|
||||
|
||||
export
|
||||
tryMaybe : (f : Parser a) -> Parser (Maybe a)
|
||||
tryMaybe f = try (map Just f) (\_ => pure Nothing)
|
||||
|
||||
export
|
||||
tryEither : (f : Parser a) -> Parser (Either ParseError a)
|
||||
tryEither f = try (map Right f) (pure . Left)
|
||||
```
|
||||
|
||||
Attempt to parse one of the given input parsers, in the provided order, invoking
|
||||
the provided error action on failure.
|
||||
|
||||
The state will not be modified when an input parser fails
|
||||
|
||||
```idris
|
||||
export
|
||||
oneOfE : (err : String) -> List (Parser a) -> Parser a
|
||||
oneOfE err xs = do
|
||||
start <- save
|
||||
oneOfE' err start [] xs
|
||||
where
|
||||
oneOfE' : (err : String) -> (start : ParserInternal Id)
|
||||
-> (errs : List ParseError) -> List (Parser a) -> Parser a
|
||||
oneOfE' err start errs [] = do
|
||||
throw $ NestedErrors start err (reverse errs)
|
||||
oneOfE' err start errs (x :: xs) = do
|
||||
x <- tryEither x
|
||||
case x of
|
||||
Right val => pure val
|
||||
Left error => oneOfE' err start (error :: errs) xs
|
||||
```
|
||||
|
||||
Attempt to parse 0+ of an item
|
||||
|
||||
```idris
|
||||
export
|
||||
many : (f : Parser a) -> Parser (List a)
|
||||
many f = do
|
||||
Just next <- tryMaybe f
|
||||
| _ => pure []
|
||||
map (next ::) $ many f
|
||||
```
|
||||
|
||||
Attempt to parse 1+ of an item, invoking the supplied error action on failure
|
||||
|
||||
```idris
|
||||
export
|
||||
atLeastOne : (err : ParseError -> Parser (List1 a)) -> (f : Parser a)
|
||||
-> Parser (List1 a)
|
||||
atLeastOne err f = do
|
||||
Right next <- tryEither f
|
||||
| Left e => err e
|
||||
map (next :::) $ many f
|
||||
```
|
||||
|
||||
Lift a parser producing a `List` or `List1` of `Char` into a parser producing a
|
||||
`String`
|
||||
|
||||
```idris
|
||||
-- TODO: Rename these
|
||||
export
|
||||
liftString : Parser (List Char) -> Parser String
|
||||
liftString x = do
|
||||
xs <- x
|
||||
pure $ pack xs
|
||||
|
||||
export
|
||||
liftString' : Parser (List1 Char) -> Parser String
|
||||
liftString' x = liftString $ map forget x
|
||||
```
|
||||
|
||||
Attempt to parse a specified character
|
||||
|
||||
```idris
|
||||
export
|
||||
charExact : Char -> Parser Char
|
||||
charExact c = do
|
||||
result <- charExact' c
|
||||
case result of
|
||||
GotChar char => pure char
|
||||
GotError err => throwParseError "Got \{show err} Expected \{show c}"
|
||||
EndOfInput => throwParseError "End of input"
|
||||
```
|
||||
|
||||
Attempt to parse one of a list of chars
|
||||
|
||||
```idris
|
||||
export
|
||||
theseChars : List Char -> Parser Char
|
||||
theseChars cs = do
|
||||
pnote "Parsing one of: \{show cs}"
|
||||
result <- charPredicate (\x => any (== x) cs)
|
||||
case result of
|
||||
GotChar char => pure char
|
||||
GotError err => throwParseError "Got \{show err} Expected one of \{show cs}"
|
||||
EndOfInput => throwParseError "End of input"
|
||||
```
|
||||
|
||||
Attempt to parse an exact string
|
||||
|
||||
```idris
|
||||
export
|
||||
exactString : String -> Parser String
|
||||
exactString str with (asList str)
|
||||
exactString "" | [] = do
|
||||
pnote "Parsing the empty string"
|
||||
pure ""
|
||||
exactString input@(strCons c str) | (c :: x) = do
|
||||
pnote "Parsing exact string \{show input}"
|
||||
GotChar next <- charPredicate (== c)
|
||||
| GotError err => throwParseError "Got \{show err} expected \{show c}"
|
||||
| EndOfInput => throwParseError "End of input"
|
||||
rest <- exactString str | x
|
||||
pure input
|
||||
```
|
||||
|
||||
Wrap a parser in delimiter characters, discarding the value of the delimiters
|
||||
|
||||
```idris
|
||||
export
|
||||
delimited : (before, after : Char) -> Parser a -> Parser a
|
||||
delimited before after x = do
|
||||
pnote "Parsing delimited by \{show before} \{show after}"
|
||||
starting_state <- save
|
||||
_ <- charExact before
|
||||
Right val <- tryEither x
|
||||
| Left err => do
|
||||
load starting_state
|
||||
throw err
|
||||
Just _ <- tryMaybe $ charExact after
|
||||
| _ => do
|
||||
load starting_state
|
||||
throw $ MkParseError starting_state "Unmatched delimiter \{show before}"
|
||||
pure val
|
||||
```
|
||||
|
||||
Consume any number of characters of the provided character class and discard the
|
||||
result. Also a version for doing so on both sides of a provided parser
|
||||
|
||||
```idris
|
||||
export
|
||||
nom : Parser Char -> Parser ()
|
||||
nom x = do
|
||||
pnote "Nomming"
|
||||
_ <- many x
|
||||
pure ()
|
||||
|
||||
export
|
||||
surround : (around : Parser Char) -> (item : Parser a) -> Parser a
|
||||
surround around item = do
|
||||
pnote "Surrounding"
|
||||
nom around
|
||||
val <- item
|
||||
nom around
|
||||
pure val
|
||||
```
|
||||
|
||||
### Composition of boolean functions
|
||||
|
||||
```idris
|
||||
||| Return true if both of the predicates evaluate to true
|
||||
public export
|
||||
(>&) : (a : e -> Bool) -> (b : e -> Bool) -> (e -> Bool)
|
||||
(>&) a b x = a x && b x
|
||||
```
|
||||
|
||||
```idris
|
||||
||| Return true if either of the predicates evaulates to true
|
||||
public export
|
||||
(>|) : (a : e -> Bool) -> (b : e -> Bool) -> (e -> Bool)
|
||||
(>|) a b x = a x || b x
|
||||
```
|
|
@ -1,285 +0,0 @@
|
|||
# JSON Parser
|
||||
|
||||
```idris
|
||||
module Parser.JSON
|
||||
|
||||
import public Parser
|
||||
import public Parser.Numbers
|
||||
|
||||
import Structures.Dependent.DList
|
||||
```
|
||||
|
||||
```idris hide
|
||||
import System
|
||||
import Derive.Prelude
|
||||
import Generics.Derive
|
||||
|
||||
%hide Generics.Derive.Eq
|
||||
%hide Generics.Derive.Ord
|
||||
%hide Generics.Derive.Show
|
||||
|
||||
%language ElabReflection
|
||||
```
|
||||
|
||||
## JSON components
|
||||
|
||||
Types a JSON value is allowed to have
|
||||
|
||||
```idris
|
||||
public export
|
||||
data JSONType : Type where
|
||||
TObject : JSONType
|
||||
TArray : JSONType
|
||||
TString : JSONType
|
||||
TNumber : JSONType
|
||||
TBool : JSONType
|
||||
TNull : JSONType
|
||||
%runElab derive "JSONType" [Generic, Meta, Eq, Ord, Show, DecEq]
|
||||
%name JSONType type, type2, type3
|
||||
```
|
||||
|
||||
A JSON value indexed by its type
|
||||
|
||||
```idris
|
||||
public export
|
||||
data JSONValue : JSONType -> Type where
|
||||
VObject : {types : List JSONType}
|
||||
-> DList JSONType (\t => (String, JSONValue t)) types -> JSONValue TObject
|
||||
VArray : {types : List JSONType}
|
||||
-> DList JSONType JSONValue types -> JSONValue TArray
|
||||
VString : (s : String) -> JSONValue TString
|
||||
VNumber : (d : Double) -> JSONValue TNumber
|
||||
VBool : (b : Bool) -> JSONValue TBool
|
||||
VNull : JSONValue TNull
|
||||
%name JSONValue value, value2, value3
|
||||
```
|
||||
|
||||
```idris hide
|
||||
Show (JSONValue t) where
|
||||
show (VObject xs) =
|
||||
let xs = dMap (\_,(key, value) => "\"\{key}\":\{show value}") xs
|
||||
in assert_total $ "{\{joinBy "," xs}}"
|
||||
show (VArray xs) =
|
||||
let xs = dMap (\_,e => show e) xs
|
||||
in assert_total $ "[\{joinBy "," xs}]"
|
||||
show (VString s) = "\"\{s}\""
|
||||
show (VNumber d) = show d
|
||||
show (VBool False) = "false"
|
||||
show (VBool True) = "true"
|
||||
show VNull = "null"
|
||||
|
||||
-- TODO: Deal with keys potentially having different orders in different objects
|
||||
Eq (JSONValue t) where
|
||||
(VObject xs) == (VObject ys) =
|
||||
assert_total $ xs $== ys
|
||||
(VArray xs) == (VArray ys) =
|
||||
assert_total $ xs $== ys
|
||||
(VString s) == (VString str) = s == str
|
||||
(VNumber d) == (VNumber dbl) = d == dbl
|
||||
(VBool b) == (VBool x) = b == x
|
||||
VNull == VNull = True
|
||||
|
||||
%hide Language.Reflection.TT.WithFC.value
|
||||
```
|
||||
|
||||
## Parsers
|
||||
|
||||
We are going to get mutually recursive here. Instead of using a `mutual` block,
|
||||
we will use the more modern style of declaring all our types ahead of our
|
||||
definitions.
|
||||
|
||||
```idris
|
||||
export
|
||||
object : Parser (JSONValue TObject)
|
||||
export
|
||||
array : Parser (JSONValue TArray)
|
||||
export
|
||||
string : Parser (JSONValue TString)
|
||||
export
|
||||
number : Parser (JSONValue TNumber)
|
||||
export
|
||||
bool : Parser (JSONValue TBool)
|
||||
export
|
||||
null : Parser (JSONValue TNull)
|
||||
```
|
||||
|
||||
Define a `whitespace` character class based on the json specifications
|
||||
|
||||
```idris
|
||||
whitespace : Parser Char
|
||||
whitespace = do
|
||||
pnote "Whitespace character"
|
||||
theseChars [' ', '\n', '\r', '\t']
|
||||
```
|
||||
|
||||
Convenience function
|
||||
|
||||
```idris
|
||||
dpairize : {t : JSONType} ->
|
||||
Parser (JSONValue t) -> Parser (t' : JSONType ** JSONValue t')
|
||||
dpairize x = do
|
||||
x <- x
|
||||
pure (_ ** x)
|
||||
```
|
||||
|
||||
Top level json value parser
|
||||
|
||||
```idris
|
||||
export
|
||||
value : Parser (t : JSONType ** JSONValue t)
|
||||
value = do
|
||||
pnote "JSON Value"
|
||||
surround whitespace $ oneOfE
|
||||
"Expected JSON Value"
|
||||
[
|
||||
dpairize object
|
||||
, dpairize array
|
||||
, dpairize string
|
||||
, dpairize number
|
||||
, dpairize bool
|
||||
, dpairize null
|
||||
]
|
||||
```
|
||||
|
||||
Now go through our json value types
|
||||
|
||||
```idris
|
||||
object = do
|
||||
pnote "JSON Object"
|
||||
oneOfE
|
||||
"Expected Object"
|
||||
[emptyObject, occupiedObject]
|
||||
where
|
||||
emptyObject : Parser (JSONValue TObject)
|
||||
emptyObject = do
|
||||
delimited '{' '}' (nom whitespace)
|
||||
pure $ VObject Nil
|
||||
keyValue : Parser (t : JSONType ** (String, JSONValue t))
|
||||
keyValue = do
|
||||
VString key <- surround whitespace string
|
||||
_ <- charExact ':'
|
||||
(typ ** val) <- value
|
||||
pure (typ ** (key, val))
|
||||
restKeyValue : Parser (t : JSONType ** (String, JSONValue t))
|
||||
restKeyValue = do
|
||||
_ <- charExact ','
|
||||
keyValue
|
||||
pairs : Parser (List1 (t : JSONType ** (String, JSONValue t)))
|
||||
pairs = do
|
||||
first <- keyValue
|
||||
rest <- many restKeyValue
|
||||
pure $ first ::: rest
|
||||
occupiedObject : Parser (JSONValue TObject)
|
||||
occupiedObject = do
|
||||
val <- delimited '{' '}' pairs
|
||||
let (types ** xs) = DList.fromList (forget val)
|
||||
pure $ VObject xs
|
||||
```
|
||||
|
||||
```idris
|
||||
array = do
|
||||
pnote "JSON Array"
|
||||
oneOfE
|
||||
"Expected Array"
|
||||
[emptyArray, occupiedArray]
|
||||
where
|
||||
emptyArray : Parser (JSONValue TArray)
|
||||
emptyArray = do
|
||||
delimited '[' ']' (nom whitespace)
|
||||
pure $ VArray Nil
|
||||
restValue : Parser (t : JSONType ** JSONValue t)
|
||||
restValue = do
|
||||
_ <- charExact ','
|
||||
value
|
||||
values : Parser (List1 (t : JSONType ** JSONValue t))
|
||||
values = do
|
||||
first <- value
|
||||
rest <- many restValue
|
||||
pure $ first ::: rest
|
||||
occupiedArray : Parser (JSONValue TArray)
|
||||
occupiedArray = do
|
||||
xs <- delimited '[' ']' values
|
||||
let (types ** xs) = DList.fromList (forget xs)
|
||||
pure $ VArray xs
|
||||
```
|
||||
|
||||
```idris
|
||||
string = do
|
||||
pnote "JSON String"
|
||||
str <- liftString $ delimited '"' '"' (many stringCharacter)
|
||||
pure $ VString str
|
||||
where
|
||||
-- TODO: Handle control characters properly
|
||||
stringCharacter : Parser Char
|
||||
stringCharacter = do
|
||||
result <- charPredicate (not . (== '"'))
|
||||
case result of
|
||||
GotChar char => pure char
|
||||
GotError err =>
|
||||
throwParseError "Expected string character, got \{show err}"
|
||||
EndOfInput => throwParseError "Unexpected end of input"
|
||||
```
|
||||
|
||||
```idris
|
||||
number = do
|
||||
pnote "JSON Number"
|
||||
d <- double
|
||||
pure $ VNumber d
|
||||
```
|
||||
|
||||
```idris
|
||||
bool = do
|
||||
pnote "JSON Bool"
|
||||
oneOfE
|
||||
"Expected Bool"
|
||||
[true, false]
|
||||
where
|
||||
true : Parser (JSONValue TBool)
|
||||
true = do
|
||||
_ <- exactString "true"
|
||||
pure $ VBool True
|
||||
false : Parser (JSONValue TBool)
|
||||
false = do
|
||||
_ <- exactString "false"
|
||||
pure $ VBool False
|
||||
```
|
||||
|
||||
```idris
|
||||
null = do
|
||||
pnote "JSON null"
|
||||
_ <- exactString "null"
|
||||
pure VNull
|
||||
```
|
||||
|
||||
## Unit tests
|
||||
|
||||
Quick smoke test
|
||||
|
||||
```idris
|
||||
-- @@test JSON Quick Smoke
|
||||
quickSmoke : IO Bool
|
||||
quickSmoke = do
|
||||
let input = "{\"string\":\"string\",\"number\":5,\"true\":true,\"false\":false,\"null\":null,\"array\":[1,2,3]}"
|
||||
putStrLn input
|
||||
Right (type ** parsed) <- runFirstIODebug value input
|
||||
| Left err => do
|
||||
printLn err
|
||||
pure False
|
||||
putStrLn "Input: \{input}\nOutput: \{show type} -> \{show parsed}"
|
||||
let reference_object =
|
||||
VObject [
|
||||
("string", VString "string")
|
||||
, ("number", VNumber 5.0)
|
||||
, ("true", VBool True)
|
||||
, ("false", VBool False)
|
||||
, ("null", VNull)
|
||||
, ("array", VArray [
|
||||
VNumber 1.0
|
||||
, VNumber 2.0
|
||||
, VNumber 3.0
|
||||
])
|
||||
]
|
||||
case type of
|
||||
TObject => pure $ parsed == reference_object
|
||||
_ => pure False
|
||||
```
|
|
@ -1,257 +0,0 @@
|
|||
# Numerical Parsers
|
||||
|
||||
```idris
|
||||
module Parser.Numbers
|
||||
|
||||
import public Parser
|
||||
|
||||
import Data.Vect
|
||||
import Control.Eff
|
||||
```
|
||||
|
||||
```idris hide
|
||||
import System
|
||||
```
|
||||
|
||||
## Base Abstraction
|
||||
|
||||
```idris
|
||||
public export
|
||||
record Base where
|
||||
constructor MkBase
|
||||
base : Nat
|
||||
digits : Vect base Char
|
||||
%name Base b
|
||||
|
||||
export
|
||||
hasDigit : Base -> Char -> Bool
|
||||
hasDigit (MkBase base digits) c = any (== c) digits
|
||||
|
||||
export
|
||||
digitValue : Base -> Char -> Maybe Nat
|
||||
digitValue (MkBase base digits) c = digitValue' digits 0
|
||||
where
|
||||
digitValue' : Vect n Char -> (idx : Nat) -> Maybe Nat
|
||||
digitValue' [] idx = Nothing
|
||||
digitValue' (x :: xs) idx =
|
||||
if x == c
|
||||
then Just idx
|
||||
else digitValue' xs (S idx)
|
||||
|
||||
public export
|
||||
base10 : Base
|
||||
base10 = MkBase 10
|
||||
['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
|
||||
|
||||
public export
|
||||
hex : Base
|
||||
hex = MkBase 16
|
||||
['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']
|
||||
```
|
||||
|
||||
## Parsers
|
||||
|
||||
### Nat
|
||||
|
||||
```idris
|
||||
export
|
||||
nat : Base -> Parser Nat
|
||||
nat b = do
|
||||
error <- replaceError "Expected digit"
|
||||
(first ::: rest) <- atLeastOne error parseDigit
|
||||
pure $ foldl (\acc, e => 10 * acc + e) first rest
|
||||
where
|
||||
parseDigit : Parser Nat
|
||||
parseDigit = do
|
||||
GotChar char <- charPredicate (hasDigit b)
|
||||
| GotError e => throwParseError "\{show e} is not a digit"
|
||||
| EndOfInput => throwParseError "End Of Input"
|
||||
case digitValue b char of
|
||||
Nothing =>
|
||||
throwParseError "Failed to parse as base \{show b.base}: \{show char}"
|
||||
Just x => pure x
|
||||
|
||||
export
|
||||
natBase10 : Parser Nat
|
||||
natBase10 = nat base10
|
||||
```
|
||||
|
||||
### Integer
|
||||
|
||||
```idris
|
||||
export
|
||||
integer : Base -> Parser Integer
|
||||
integer b = do
|
||||
negative <- map isJust . tryMaybe $ charExact '-'
|
||||
value <- map natToInteger $ nat b
|
||||
if negative
|
||||
then pure $ negate value
|
||||
else pure $ value
|
||||
|
||||
export
|
||||
integerBase10 : Parser Integer
|
||||
integerBase10 = integer base10
|
||||
```
|
||||
|
||||
### Double
|
||||
|
||||
```idris
|
||||
-- TODO: Replicate `parseDouble` logic and make this base-generic
|
||||
export
|
||||
double : Parser Double
|
||||
double = do
|
||||
starting_state <- save
|
||||
integer <- integer
|
||||
fraction <- tryMaybe fraction
|
||||
exponent <- tryMaybe exponent
|
||||
let str = case (fraction, exponent) of
|
||||
(Nothing, Nothing) =>
|
||||
integer
|
||||
(Nothing, (Just exponent)) =>
|
||||
"\{integer}e\{exponent}"
|
||||
((Just fraction), Nothing) =>
|
||||
"\{integer}.\{fraction}"
|
||||
((Just fraction), (Just exponent)) =>
|
||||
"\{integer}.\{fraction}e\{exponent}"
|
||||
Just out <- pure $ parseDouble str
|
||||
| _ =>
|
||||
throw $ MkParseError starting_state "Std failed to parse as double: \{str}"
|
||||
pure out
|
||||
where
|
||||
parseDigit : Parser Char
|
||||
parseDigit = do
|
||||
GotChar char <- charPredicate (hasDigit base10)
|
||||
| GotError e => throwParseError "\{show e} is not a digit"
|
||||
| EndOfInput => throwParseError "End Of Input"
|
||||
pure char
|
||||
integer : Parser String
|
||||
integer = do
|
||||
sign <- tryMaybe $ charExact '-'
|
||||
error <- replaceError "Expected digit"
|
||||
digits <- map forget $ atLeastOne error parseDigit
|
||||
case sign of
|
||||
Nothing => pure $ pack digits
|
||||
Just x => pure $ pack (x :: digits)
|
||||
fraction : Parser String
|
||||
fraction = do
|
||||
decimal <- charExact '.'
|
||||
error <- replaceError "Expected digit"
|
||||
digits <- map forget $ atLeastOne error parseDigit
|
||||
pure $ pack digits
|
||||
exponent : Parser String
|
||||
exponent = do
|
||||
e <- theseChars ['e', 'E']
|
||||
sign <- theseChars ['+', '-']
|
||||
error <- replaceError "Expected digit"
|
||||
digits <- map forget $ atLeastOne error parseDigit
|
||||
pure . pack $ sign :: digits
|
||||
```
|
||||
|
||||
## Unit tests
|
||||
|
||||
Test roundtripping a value through the provided parser
|
||||
|
||||
```idris
|
||||
roundtrip : Eq a => Show a => a -> (p : Parser a) -> IO Bool
|
||||
roundtrip x p = do
|
||||
let string = show x
|
||||
putStrLn "Roundtripping \{string}"
|
||||
Just state <- newInternalIO string
|
||||
| _ => do
|
||||
putStrLn "Failed to produce parser for \{string}"
|
||||
pure False
|
||||
Right result <- runEff (rundownFirst p) [handleParserStateIO state] {m = IO}
|
||||
| Left err => do
|
||||
printLn err
|
||||
pure False
|
||||
putStrLn "Input: \{string} Output: \{show result}"
|
||||
pure $ x == result
|
||||
```
|
||||
|
||||
Do some roundtrip tests with the nat parser
|
||||
|
||||
```idris
|
||||
-- @@test Nat round trip
|
||||
natRoundTrip : IO Bool
|
||||
natRoundTrip = pure $
|
||||
!(roundtrip 0 natBase10)
|
||||
&& !(roundtrip 1 natBase10)
|
||||
&& !(roundtrip 100 natBase10)
|
||||
&& !(roundtrip 1234 natBase10)
|
||||
&& !(roundtrip 1234567890 natBase10)
|
||||
&& !(roundtrip 1234567890000 natBase10)
|
||||
&& !(roundtrip 12345678901234567890 natBase10)
|
||||
```
|
||||
|
||||
```idris
|
||||
-- @@test Integer round trip
|
||||
integerRoundTrip : IO Bool
|
||||
integerRoundTrip = pure $
|
||||
!(roundtrip 0 integerBase10)
|
||||
&& !(roundtrip 1 integerBase10)
|
||||
&& !(roundtrip 100 integerBase10)
|
||||
&& !(roundtrip 1234 integerBase10)
|
||||
&& !(roundtrip 1234567890 integerBase10)
|
||||
&& !(roundtrip 1234567890000 integerBase10)
|
||||
&& !(roundtrip 12345678901234567890 integerBase10)
|
||||
&& !(roundtrip (-1) integerBase10)
|
||||
&& !(roundtrip (-100) integerBase10)
|
||||
&& !(roundtrip (-1234) integerBase10)
|
||||
&& !(roundtrip (-1234567890) integerBase10)
|
||||
&& !(roundtrip (-1234567890000) integerBase10)
|
||||
&& !(roundtrip (-12345678901234567890) integerBase10)
|
||||
```
|
||||
|
||||
Compare our parsing of a double to the standard library's
|
||||
|
||||
```idris
|
||||
compareDouble : String -> IO Bool
|
||||
compareDouble string = do
|
||||
Just state <- newInternalIO string
|
||||
| _ => do
|
||||
putStrLn "Failed to produce parser for \{string}"
|
||||
pure False
|
||||
Right result <-
|
||||
runEff (rundownFirst double) [handleParserStateIO state] {m = IO}
|
||||
| Left err => do
|
||||
printLn err
|
||||
pure False
|
||||
putStrLn "Input: \{string} Output: \{show result}"
|
||||
Just double' <- pure $ parseDouble string
|
||||
| _ => do
|
||||
printLn "Std failed to parse as double: \{string}"
|
||||
pure False
|
||||
pure $ result == double'
|
||||
```
|
||||
|
||||
```idris
|
||||
-- @@test Double Std Comparison
|
||||
doubleRoundTrip : IO Bool
|
||||
doubleRoundTrip = pure $
|
||||
!(compareDouble "0")
|
||||
&& !(compareDouble "1")
|
||||
&& !(compareDouble "100")
|
||||
&& !(compareDouble "1234")
|
||||
&& !(compareDouble "1234567890")
|
||||
&& !(compareDouble "1234567890000")
|
||||
&& !(compareDouble "12345678901234567890")
|
||||
&& !(compareDouble "-1")
|
||||
&& !(compareDouble "-100")
|
||||
&& !(compareDouble "-1234")
|
||||
&& !(compareDouble "-1234567890")
|
||||
&& !(compareDouble "-1234567890000")
|
||||
&& !(compareDouble "-12345678901234567890")
|
||||
&& !(compareDouble "0.0")
|
||||
&& !(compareDouble "1.0")
|
||||
&& !(compareDouble "-1.0")
|
||||
&& !(compareDouble "-0.0")
|
||||
&& !(compareDouble "-0.0")
|
||||
&& !(compareDouble "0.1234")
|
||||
&& !(compareDouble "0.01234")
|
||||
&& !(compareDouble "-0.1234")
|
||||
&& !(compareDouble "-0.01234")
|
||||
&& !(compareDouble "1.234e+5")
|
||||
&& !(compareDouble "1.234e-5")
|
||||
&& !(compareDouble "-1.234e+5")
|
||||
&& !(compareDouble "-1.234e-5")
|
||||
```
|
|
@ -1,369 +0,0 @@
|
|||
# Parser State
|
||||
|
||||
An effectful description of the text a parser consumes
|
||||
|
||||
```idris
|
||||
module Parser.ParserState
|
||||
|
||||
import public Data.String
|
||||
import public Data.DPair
|
||||
import public Data.Refined
|
||||
import public Data.Refined.Int64
|
||||
import public Data.SortedMap
|
||||
import public Data.IORef
|
||||
|
||||
import Data.Primitives.Interpolation
|
||||
import System.File
|
||||
|
||||
import public Control.Eff
|
||||
```
|
||||
|
||||
## Barbie Basics
|
||||
|
||||
Barbies are types that can "change their clothes", in Idris, this manifests as a
|
||||
type indexed by a type-level function that affects the types of the fields.
|
||||
|
||||
Since we know our usage here is going to be quite simple, and not even really
|
||||
making use of dependently typed fun, we are going to implement all the barbie
|
||||
functionality we need by hand, but if you feel like barbies might be a good fit
|
||||
for your problem, or you simply want to learn more, please check out a library
|
||||
like `barbies`[^1]
|
||||
|
||||
```idris
|
||||
public export
|
||||
Id : Type -> Type
|
||||
Id x = x
|
||||
```
|
||||
|
||||
## Internal State of a Parser
|
||||
|
||||
Type alias for our refined `Int64`s
|
||||
|
||||
```idris
|
||||
public export
|
||||
0 IsIndex : (length : Int64) -> Int64 -> Type
|
||||
IsIndex length = From 0 && LessThan length
|
||||
|
||||
public export
|
||||
record Index (length : Int64) where
|
||||
constructor MkIndex
|
||||
index : Int64
|
||||
{auto 0 prf : IsIndex length index}
|
||||
```
|
||||
|
||||
```idris hide
|
||||
export
|
||||
Eq (Index i) where
|
||||
x == y = x.index == y.index
|
||||
|
||||
export
|
||||
Ord (Index i) where
|
||||
compare x y = compare x.index y.index
|
||||
|
||||
export
|
||||
Show (Index i) where
|
||||
show (MkIndex index) = show index
|
||||
```
|
||||
|
||||
Stores the location we are currently at in the string, and metadata about it for
|
||||
providing good error messages. Parsing an empty input isn't very interesting, so
|
||||
we exclude inputs of length zero, since that will make other things easier.
|
||||
|
||||
```idris
|
||||
||| State representing a parser's position in the text
|
||||
public export
|
||||
record ParserInternal (f : Type -> Type) where
|
||||
constructor MkInternal
|
||||
-- IDEA: Maybe go full barbie and have this be a field, so that we can, say,
|
||||
-- read directly from a file instead of from an already loaded string using the
|
||||
-- same parser
|
||||
||| The input string
|
||||
input : String
|
||||
||| The length of the input string
|
||||
length : Int64
|
||||
{auto 0 len_prf : length = cast (strLength input)}
|
||||
||| A sorted set containing the positions of the start of each line
|
||||
line_starts : SortedMap (Index length) Nat
|
||||
||| The position of the next character to read in the input
|
||||
position : f (Index length)
|
||||
||| True if we have hit the end of input
|
||||
end_of_input : f Bool
|
||||
%name ParserInternal pi, pj, pk
|
||||
```
|
||||
|
||||
### ParserInternal Methods
|
||||
|
||||
Construct a `ParserInternal` from an input string. Will fail if the input is
|
||||
empty, because then we can't index it.
|
||||
|
||||
```idris
|
||||
export
|
||||
newInternal : (input : String) -> Maybe (ParserInternal Id)
|
||||
newInternal input =
|
||||
-- Check if we have at least one character in the input
|
||||
case refine0 0 {p = IsIndex (cast (strLength input))} of
|
||||
Nothing => Nothing
|
||||
Just (Element position _) => Just $
|
||||
MkInternal input
|
||||
(cast (strLength input))
|
||||
(mkStarts' input (MkIndex position))
|
||||
(MkIndex position)
|
||||
False
|
||||
where
|
||||
partial
|
||||
mkStarts :
|
||||
(str : String) -> (acc : List (Index (cast (strLength str)), Nat))
|
||||
-> (idx : Index (cast (strLength str))) -> (count : Nat) -> (next : Bool)
|
||||
-> List (Index (cast (strLength str)), Nat)
|
||||
mkStarts str acc idx count True =
|
||||
mkStarts str ((idx, count) :: acc) idx (S count) False
|
||||
mkStarts str acc idx count False =
|
||||
case refine0 (idx.index + 1) {p = IsIndex (cast (strLength str))} of
|
||||
Nothing => acc
|
||||
Just (Element next _) =>
|
||||
if strIndex str (cast idx.index) == '\n'
|
||||
then mkStarts str acc (MkIndex next) count True
|
||||
else mkStarts str acc (MkIndex next) count False
|
||||
mkStarts' : (str : String) -> (start : Index (cast (strLength str)))
|
||||
-> SortedMap (Index (cast (strLength str))) Nat
|
||||
mkStarts' str start =
|
||||
let
|
||||
pairs = assert_total $
|
||||
mkStarts str [] start 0 True
|
||||
in fromList pairs
|
||||
```
|
||||
|
||||
Get the current line and column number
|
||||
|
||||
```idris
|
||||
||| Returns the current position of the parser cursor in, zero indexed, (line,
|
||||
||| column) form
|
||||
export
|
||||
positionPair : (pi : ParserInternal Id) -> (Nat, Nat)
|
||||
positionPair pi =
|
||||
case lookup pi.position pi.line_starts of
|
||||
Just line => (line, 0)
|
||||
Nothing =>
|
||||
case lookupBetween pi.position pi.line_starts of
|
||||
-- There will always be at least one line start, and we would have hit
|
||||
-- the previous case if we were at the start of the first one, so if
|
||||
-- there isn't a before, we can return a nonsense value safely
|
||||
(Nothing, _) => (0, 0)
|
||||
(Just (start, linum), after) =>
|
||||
-- Our index will always be after the start of the line, for previously
|
||||
-- mentioned reasons, so this cast is safe
|
||||
let col = cast {to = Nat} $ pi.position.index - start.index
|
||||
in (linum, col)
|
||||
```
|
||||
|
||||
```idris hide
|
||||
export
|
||||
Show (ParserInternal Id) where
|
||||
show pi =
|
||||
let (line, col) = positionPair pi
|
||||
current = assert_total $ strIndex pi.input (cast pi.position.index)
|
||||
pos = pi.position.index
|
||||
eof = show pi.end_of_input
|
||||
in "Position: \{pos}(\{line}, \{col}}) Value: \{show current} EoF: \{eof}"
|
||||
```
|
||||
|
||||
### More Barbie Functionality
|
||||
|
||||
Provide the barbie analogs of `map` and `traverse` for our `ParserInternal`
|
||||
type, allowing us to change the type the values in a `ParserInternal` by mapping
|
||||
over those values.
|
||||
|
||||
```idris
|
||||
export
|
||||
bmap : ({0 a : Type} -> f a -> g a) -> ParserInternal f -> ParserInternal g
|
||||
-- bmap f = bmap_ (\_ => f)
|
||||
bmap fun (MkInternal input length line_starts position end_of_input) =
|
||||
let position' = fun position
|
||||
end_of_input' = fun end_of_input
|
||||
in MkInternal input length line_starts position' end_of_input'
|
||||
|
||||
export
|
||||
btraverse : Applicative e => ({0 a : Type} -> f a -> e (g a))
|
||||
-> ParserInternal f -> e (ParserInternal g)
|
||||
btraverse fun (MkInternal input length line_starts position end_of_input) =
|
||||
let pures = (MkInternal input length line_starts)
|
||||
in [| pures (fun position) (fun end_of_input)|]
|
||||
```
|
||||
|
||||
## Three way result
|
||||
|
||||
```idris
|
||||
||| Three way result returned from attempting to parse a single char
|
||||
public export
|
||||
data ParseCharResult : Type where
|
||||
GotChar : (char : Char) -> ParseCharResult
|
||||
GotError : (err : Char) -> ParseCharResult
|
||||
EndOfInput : ParseCharResult
|
||||
```
|
||||
|
||||
## The Effect Type
|
||||
|
||||
```idris
|
||||
export
|
||||
data ParserState : Type -> Type where
|
||||
Save : ParserState (ParserInternal Id)
|
||||
Load : (ParserInternal Id) -> ParserState ()
|
||||
-- TODO: Maybe add a ParseString that parses a string of characters as a
|
||||
-- string using efficent slicing?
|
||||
ParseChar : (predicate : Char -> Bool) -> ParserState ParseCharResult
|
||||
ParseExactChar : (char : Char) -> ParserState ParseCharResult
|
||||
ParseEoF : ParserState Bool
|
||||
Note : Lazy String -> ParserState ()
|
||||
```
|
||||
|
||||
```idris hide
|
||||
Show (ParserState t) where
|
||||
show Save = "Saving state"
|
||||
show (Load pi) = "Loading state \{show pi}"
|
||||
show (ParseChar predicate) = "Parsing char"
|
||||
show (ParseExactChar char) = "Parsing char \{show char}"
|
||||
show ParseEoF = "Parsing EoF"
|
||||
show (Note _) = "Note"
|
||||
```
|
||||
|
||||
### Actions
|
||||
|
||||
```idris
|
||||
||| Return the current state, for potential later reloading
|
||||
export
|
||||
save : Has ParserState fs => Eff fs (ParserInternal Id)
|
||||
save = send Save
|
||||
|
||||
||| Reset to the provided state
|
||||
export
|
||||
load : Has ParserState fs => ParserInternal Id -> Eff fs ()
|
||||
load pi = send $ Load pi
|
||||
|
||||
||| Attempt to parse a char, checking to see if it complies with the supplied
|
||||
||| predicate, updates the state if parsing succeeds, does not alter it in an
|
||||
||| error condition.
|
||||
export
|
||||
charPredicate : Has ParserState fs => (predicate : Char -> Bool)
|
||||
-> Eff fs ParseCharResult
|
||||
charPredicate predicate = send $ ParseChar predicate
|
||||
|
||||
||| Parse a char by exact value
|
||||
export
|
||||
charExact' : Has ParserState fs => (chr : Char) -> Eff fs ParseCharResult
|
||||
charExact' chr = send $ ParseExactChar chr
|
||||
|
||||
||| "Parse" the end of input, returning `True` if the parser state is currently
|
||||
||| at the end of the input
|
||||
export
|
||||
parseEoF : Has ParserState fs => Eff fs Bool
|
||||
parseEoF = send ParseEoF
|
||||
|
||||
||| Make a note to be output when running with a debug handler
|
||||
export
|
||||
pnote : Has ParserState fs => Lazy String -> Eff fs ()
|
||||
pnote x = send $ Note x
|
||||
```
|
||||
|
||||
## Handling a ParserState
|
||||
|
||||
### IO Context
|
||||
|
||||
```idris
|
||||
export
|
||||
handleParserStateIO : HasIO io =>
|
||||
IORef (ParserInternal IORef) -> ParserState t -> io t
|
||||
handleParserStateIO pi Save = do
|
||||
pi <- readIORef pi
|
||||
btraverse readIORef pi
|
||||
handleParserStateIO pi (Load pj) = do
|
||||
pj <- btraverse newIORef pj
|
||||
writeIORef pi pj
|
||||
handleParserStateIO pi (ParseChar predicate) = do
|
||||
pi <- readIORef pi
|
||||
False <- readIORef pi.end_of_input
|
||||
| _ => pure EndOfInput
|
||||
position <- readIORef pi.position
|
||||
let char = assert_total $ strIndex pi.input (cast position.index)
|
||||
True <- pure $ predicate char
|
||||
| _ => pure $ GotError char
|
||||
-- Our refinement type on the position forces us to check that the length is
|
||||
-- in bounds after incrementing it, if its out of bounds, set the end_of_input
|
||||
-- flag
|
||||
case refine0 (position.index + 1) {p = IsIndex pi.length} of
|
||||
Nothing => do
|
||||
writeIORef pi.end_of_input True
|
||||
pure $ GotChar char
|
||||
Just (Element next _) => do
|
||||
writeIORef pi.position $ MkIndex next
|
||||
pure $ GotChar char
|
||||
handleParserStateIO pi (ParseExactChar chr) = do
|
||||
-- TODO: do this directly?
|
||||
handleParserStateIO pi (ParseChar (== chr))
|
||||
handleParserStateIO pi ParseEoF = do
|
||||
pi <- readIORef pi
|
||||
readIORef pi.end_of_input
|
||||
-- We ignore notes in non-debug mode
|
||||
handleParserStateIO pi (Note _) = pure ()
|
||||
|
||||
export
|
||||
newInternalIO : HasIO io => String -> io $ Maybe (IORef (ParserInternal IORef))
|
||||
newInternalIO str = do
|
||||
Just internal <- pure $ newInternal str
|
||||
| _ => pure Nothing
|
||||
internal <- btraverse newIORef internal
|
||||
map Just $ newIORef internal
|
||||
```
|
||||
|
||||
Wrapper with debugging output
|
||||
|
||||
```idris
|
||||
export
|
||||
handleParserStateIODebug : HasIO io =>
|
||||
IORef (ParserInternal IORef) -> ParserState t -> io t
|
||||
handleParserStateIODebug x (Note note) = do
|
||||
state <- readIORef x
|
||||
state <- btraverse readIORef state
|
||||
_ <- fPutStrLn stderr "Note \{note} -> \{show state}"
|
||||
pure ()
|
||||
handleParserStateIODebug x y = do
|
||||
state <- readIORef x
|
||||
state <- btraverse readIORef state
|
||||
_ <- fPutStrLn stderr "\{show y} -> \{show state}"
|
||||
handleParserStateIO x y
|
||||
```
|
||||
|
||||
### State context
|
||||
|
||||
```idris
|
||||
unPS : ParserInternal Id -> ParserState a -> (a, ParserInternal Id)
|
||||
unPS pi Save = (pi, pi)
|
||||
unPS pi (Load pj) = ((), pi)
|
||||
unPS pi (ParseChar predicate) =
|
||||
if pi.end_of_input
|
||||
then (EndOfInput, pi)
|
||||
else let
|
||||
char = assert_total $ strIndex pi.input (cast pi.position.index)
|
||||
in if predicate char
|
||||
then case refine0 (pi.position.index + 1) {p = IsIndex pi.length} of
|
||||
Nothing =>
|
||||
(GotChar char, {end_of_input := True} pi)
|
||||
Just (Element next _) =>
|
||||
(GotChar char, {position := MkIndex next} pi)
|
||||
else (GotError char, pi)
|
||||
unPS pi (ParseExactChar chr) = unPS pi (ParseChar (== chr))
|
||||
unPS pi ParseEoF = (pi.end_of_input, pi)
|
||||
unPS pi (Note _) = ((), pi)
|
||||
|
||||
export
|
||||
runParserState : Has ParserState fs =>
|
||||
(s : ParserInternal Id) -> Eff fs t
|
||||
-> Eff (fs - ParserState) (t, ParserInternal Id)
|
||||
runParserState s =
|
||||
handleRelayS s (\x, y => pure (y, x)) $ \s2,ps,f =>
|
||||
let (a, st) = unPS s2 ps
|
||||
in f st a
|
||||
```
|
||||
|
||||
## Footnotes
|
||||
|
||||
[^1]: <https://github.com/stefan-hoeck/idris2-barbies>
|
|
@ -13,11 +13,6 @@
|
|||
- [Util.Eff - Effects and Effect Accessories](Util/Eff.md)
|
||||
- [Util.Digits - Pattern Matching Integers as Lists of Digits](Util/Eff.md)
|
||||
- [Array - Arrays With Constant Time Indexing and Slicing](Array.md)
|
||||
- [Parser - Recursive Descent Parsing, With Effects](Parser.md)
|
||||
- [Interface - Core Parsing Functionality](Parser/Interface.md)
|
||||
- [ParserState - Custom Effect for Parser Internal State](Parser/ParserState.md)
|
||||
- [Numbers - Parsers for Numerical Values](Parser/Numbers.md)
|
||||
- [JSON - A JSON Parser](Parser/JSON.md)
|
||||
|
||||
# Problems
|
||||
|
||||
|
|
|
@ -235,4 +235,4 @@ day11 = Both 11 part1 part2
|
|||
|
||||
## References
|
||||
|
||||
[^1]: <https://github.com/stefan-hoeck/idris2-refined/>
|
||||
[^1]: https://github.com/stefan-hoeck/idris2-refined/
|
||||
|
|
697
theme/book.js
Normal file
697
theme/book.js
Normal file
|
@ -0,0 +1,697 @@
|
|||
"use strict";
|
||||
|
||||
// Fix back button cache problem
|
||||
window.onunload = function () { };
|
||||
|
||||
// Global variable, shared between modules
|
||||
function playground_text(playground, hidden = true) {
|
||||
let code_block = playground.querySelector("code");
|
||||
|
||||
if (window.ace && code_block.classList.contains("editable")) {
|
||||
let editor = window.ace.edit(code_block);
|
||||
return editor.getValue();
|
||||
} else if (hidden) {
|
||||
return code_block.textContent;
|
||||
} else {
|
||||
return code_block.innerText;
|
||||
}
|
||||
}
|
||||
|
||||
(function codeSnippets() {
|
||||
function fetch_with_timeout(url, options, timeout = 6000) {
|
||||
return Promise.race([
|
||||
fetch(url, options),
|
||||
new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), timeout))
|
||||
]);
|
||||
}
|
||||
|
||||
var playgrounds = Array.from(document.querySelectorAll(".playground"));
|
||||
if (playgrounds.length > 0) {
|
||||
fetch_with_timeout("https://play.rust-lang.org/meta/crates", {
|
||||
headers: {
|
||||
'Content-Type': "application/json",
|
||||
},
|
||||
method: 'POST',
|
||||
mode: 'cors',
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(response => {
|
||||
// get list of crates available in the rust playground
|
||||
let playground_crates = response.crates.map(item => item["id"]);
|
||||
playgrounds.forEach(block => handle_crate_list_update(block, playground_crates));
|
||||
});
|
||||
}
|
||||
|
||||
function handle_crate_list_update(playground_block, playground_crates) {
|
||||
// update the play buttons after receiving the response
|
||||
update_play_button(playground_block, playground_crates);
|
||||
|
||||
// and install on change listener to dynamically update ACE editors
|
||||
if (window.ace) {
|
||||
let code_block = playground_block.querySelector("code");
|
||||
if (code_block.classList.contains("editable")) {
|
||||
let editor = window.ace.edit(code_block);
|
||||
editor.addEventListener("change", function (e) {
|
||||
update_play_button(playground_block, playground_crates);
|
||||
});
|
||||
// add Ctrl-Enter command to execute rust code
|
||||
editor.commands.addCommand({
|
||||
name: "run",
|
||||
bindKey: {
|
||||
win: "Ctrl-Enter",
|
||||
mac: "Ctrl-Enter"
|
||||
},
|
||||
exec: _editor => run_rust_code(playground_block)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// updates the visibility of play button based on `no_run` class and
|
||||
// used crates vs ones available on https://play.rust-lang.org
|
||||
function update_play_button(pre_block, playground_crates) {
|
||||
var play_button = pre_block.querySelector(".play-button");
|
||||
|
||||
// skip if code is `no_run`
|
||||
if (pre_block.querySelector('code').classList.contains("no_run")) {
|
||||
play_button.classList.add("hidden");
|
||||
return;
|
||||
}
|
||||
|
||||
// get list of `extern crate`'s from snippet
|
||||
var txt = playground_text(pre_block);
|
||||
var re = /extern\s+crate\s+([a-zA-Z_0-9]+)\s*;/g;
|
||||
var snippet_crates = [];
|
||||
var item;
|
||||
while (item = re.exec(txt)) {
|
||||
snippet_crates.push(item[1]);
|
||||
}
|
||||
|
||||
// check if all used crates are available on play.rust-lang.org
|
||||
var all_available = snippet_crates.every(function (elem) {
|
||||
return playground_crates.indexOf(elem) > -1;
|
||||
});
|
||||
|
||||
if (all_available) {
|
||||
play_button.classList.remove("hidden");
|
||||
} else {
|
||||
play_button.classList.add("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
function run_rust_code(code_block) {
|
||||
var result_block = code_block.querySelector(".result");
|
||||
if (!result_block) {
|
||||
result_block = document.createElement('code');
|
||||
result_block.className = 'result hljs language-bash';
|
||||
|
||||
code_block.append(result_block);
|
||||
}
|
||||
|
||||
let text = playground_text(code_block);
|
||||
let classes = code_block.querySelector('code').classList;
|
||||
let edition = "2015";
|
||||
if(classes.contains("edition2018")) {
|
||||
edition = "2018";
|
||||
} else if(classes.contains("edition2021")) {
|
||||
edition = "2021";
|
||||
}
|
||||
var params = {
|
||||
version: "stable",
|
||||
optimize: "0",
|
||||
code: text,
|
||||
edition: edition
|
||||
};
|
||||
|
||||
if (text.indexOf("#![feature") !== -1) {
|
||||
params.version = "nightly";
|
||||
}
|
||||
|
||||
result_block.innerText = "Running...";
|
||||
|
||||
fetch_with_timeout("https://play.rust-lang.org/evaluate.json", {
|
||||
headers: {
|
||||
'Content-Type': "application/json",
|
||||
},
|
||||
method: 'POST',
|
||||
mode: 'cors',
|
||||
body: JSON.stringify(params)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(response => {
|
||||
if (response.result.trim() === '') {
|
||||
result_block.innerText = "No output";
|
||||
result_block.classList.add("result-no-output");
|
||||
} else {
|
||||
result_block.innerText = response.result;
|
||||
result_block.classList.remove("result-no-output");
|
||||
}
|
||||
})
|
||||
.catch(error => result_block.innerText = "Playground Communication: " + error.message);
|
||||
}
|
||||
|
||||
// Syntax highlighting Configuration
|
||||
hljs.configure({
|
||||
tabReplace: ' ', // 4 spaces
|
||||
languages: [], // Languages used for auto-detection
|
||||
});
|
||||
|
||||
let code_nodes = Array
|
||||
.from(document.querySelectorAll('code'))
|
||||
// Don't highlight `inline code` blocks in headers.
|
||||
.filter(function (node) {return !node.parentElement.classList.contains("header"); });
|
||||
|
||||
if (window.ace) {
|
||||
// language-rust class needs to be removed for editable
|
||||
// blocks or highlightjs will capture events
|
||||
code_nodes
|
||||
.filter(function (node) {return node.classList.contains("editable"); })
|
||||
.forEach(function (block) { block.classList.remove('language-rust'); });
|
||||
|
||||
code_nodes
|
||||
.filter(function (node) {return !node.classList.contains("editable"); })
|
||||
.forEach(function (block) { hljs.highlightBlock(block); });
|
||||
} else {
|
||||
code_nodes.forEach(function (block) { hljs.highlightBlock(block); });
|
||||
}
|
||||
|
||||
// Adding the hljs class gives code blocks the color css
|
||||
// even if highlighting doesn't apply
|
||||
code_nodes.forEach(function (block) { block.classList.add('hljs'); });
|
||||
|
||||
Array.from(document.querySelectorAll("code.hljs")).forEach(function (block) {
|
||||
|
||||
var lines = Array.from(block.querySelectorAll('.boring'));
|
||||
// If no lines were hidden, return
|
||||
if (!lines.length) { return; }
|
||||
block.classList.add("hide-boring");
|
||||
|
||||
var buttons = document.createElement('div');
|
||||
buttons.className = 'buttons';
|
||||
buttons.innerHTML = "<button class=\"fa fa-eye\" title=\"Show hidden lines\" aria-label=\"Show hidden lines\"></button>";
|
||||
|
||||
// add expand button
|
||||
var pre_block = block.parentNode;
|
||||
pre_block.insertBefore(buttons, pre_block.firstChild);
|
||||
|
||||
pre_block.querySelector('.buttons').addEventListener('click', function (e) {
|
||||
if (e.target.classList.contains('fa-eye')) {
|
||||
e.target.classList.remove('fa-eye');
|
||||
e.target.classList.add('fa-eye-slash');
|
||||
e.target.title = 'Hide lines';
|
||||
e.target.setAttribute('aria-label', e.target.title);
|
||||
|
||||
block.classList.remove('hide-boring');
|
||||
} else if (e.target.classList.contains('fa-eye-slash')) {
|
||||
e.target.classList.remove('fa-eye-slash');
|
||||
e.target.classList.add('fa-eye');
|
||||
e.target.title = 'Show hidden lines';
|
||||
e.target.setAttribute('aria-label', e.target.title);
|
||||
|
||||
block.classList.add('hide-boring');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (window.playground_copyable) {
|
||||
Array.from(document.querySelectorAll('pre code')).forEach(function (block) {
|
||||
var pre_block = block.parentNode;
|
||||
if (!pre_block.classList.contains('playground')) {
|
||||
var buttons = pre_block.querySelector(".buttons");
|
||||
if (!buttons) {
|
||||
buttons = document.createElement('div');
|
||||
buttons.className = 'buttons';
|
||||
pre_block.insertBefore(buttons, pre_block.firstChild);
|
||||
}
|
||||
|
||||
var clipButton = document.createElement('button');
|
||||
clipButton.className = 'fa fa-copy clip-button';
|
||||
clipButton.title = 'Copy to clipboard';
|
||||
clipButton.setAttribute('aria-label', clipButton.title);
|
||||
clipButton.innerHTML = '<i class=\"tooltiptext\"></i>';
|
||||
|
||||
buttons.insertBefore(clipButton, buttons.firstChild);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Process playground code blocks
|
||||
Array.from(document.querySelectorAll(".playground")).forEach(function (pre_block) {
|
||||
// Add play button
|
||||
var buttons = pre_block.querySelector(".buttons");
|
||||
if (!buttons) {
|
||||
buttons = document.createElement('div');
|
||||
buttons.className = 'buttons';
|
||||
pre_block.insertBefore(buttons, pre_block.firstChild);
|
||||
}
|
||||
|
||||
var runCodeButton = document.createElement('button');
|
||||
runCodeButton.className = 'fa fa-play play-button';
|
||||
runCodeButton.hidden = true;
|
||||
runCodeButton.title = 'Run this code';
|
||||
runCodeButton.setAttribute('aria-label', runCodeButton.title);
|
||||
|
||||
buttons.insertBefore(runCodeButton, buttons.firstChild);
|
||||
runCodeButton.addEventListener('click', function (e) {
|
||||
run_rust_code(pre_block);
|
||||
});
|
||||
|
||||
if (window.playground_copyable) {
|
||||
var copyCodeClipboardButton = document.createElement('button');
|
||||
copyCodeClipboardButton.className = 'fa fa-copy clip-button';
|
||||
copyCodeClipboardButton.innerHTML = '<i class="tooltiptext"></i>';
|
||||
copyCodeClipboardButton.title = 'Copy to clipboard';
|
||||
copyCodeClipboardButton.setAttribute('aria-label', copyCodeClipboardButton.title);
|
||||
|
||||
buttons.insertBefore(copyCodeClipboardButton, buttons.firstChild);
|
||||
}
|
||||
|
||||
let code_block = pre_block.querySelector("code");
|
||||
if (window.ace && code_block.classList.contains("editable")) {
|
||||
var undoChangesButton = document.createElement('button');
|
||||
undoChangesButton.className = 'fa fa-history reset-button';
|
||||
undoChangesButton.title = 'Undo changes';
|
||||
undoChangesButton.setAttribute('aria-label', undoChangesButton.title);
|
||||
|
||||
buttons.insertBefore(undoChangesButton, buttons.firstChild);
|
||||
|
||||
undoChangesButton.addEventListener('click', function () {
|
||||
let editor = window.ace.edit(code_block);
|
||||
editor.setValue(editor.originalCode);
|
||||
editor.clearSelection();
|
||||
});
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
(function themes() {
|
||||
var html = document.querySelector('html');
|
||||
var themeToggleButton = document.getElementById('theme-toggle');
|
||||
var themePopup = document.getElementById('theme-list');
|
||||
var themeColorMetaTag = document.querySelector('meta[name="theme-color"]');
|
||||
var stylesheets = {
|
||||
ayuHighlight: document.querySelector("[href$='ayu-highlight.css']"),
|
||||
tomorrowNight: document.querySelector("[href$='tomorrow-night.css']"),
|
||||
highlight: document.querySelector("[href$='highlight.css']"),
|
||||
};
|
||||
|
||||
function showThemes() {
|
||||
themePopup.style.display = 'block';
|
||||
themeToggleButton.setAttribute('aria-expanded', true);
|
||||
themePopup.querySelector("button#" + get_theme()).focus();
|
||||
}
|
||||
|
||||
function updateThemeSelected() {
|
||||
themePopup.querySelectorAll('.theme-selected').forEach(function (el) {
|
||||
el.classList.remove('theme-selected');
|
||||
});
|
||||
themePopup.querySelector("button#" + get_theme()).classList.add('theme-selected');
|
||||
}
|
||||
|
||||
function hideThemes() {
|
||||
themePopup.style.display = 'none';
|
||||
themeToggleButton.setAttribute('aria-expanded', false);
|
||||
themeToggleButton.focus();
|
||||
}
|
||||
|
||||
function get_theme() {
|
||||
var theme;
|
||||
try { theme = localStorage.getItem('mdbook-theme'); } catch (e) { }
|
||||
if (theme === null || theme === undefined) {
|
||||
return default_theme;
|
||||
} else {
|
||||
return theme;
|
||||
}
|
||||
}
|
||||
|
||||
function set_theme(theme, store = true) {
|
||||
let ace_theme;
|
||||
|
||||
if (theme == 'coal' || theme == 'navy') {
|
||||
stylesheets.ayuHighlight.disabled = true;
|
||||
stylesheets.tomorrowNight.disabled = false;
|
||||
stylesheets.highlight.disabled = true;
|
||||
|
||||
ace_theme = "ace/theme/tomorrow_night";
|
||||
} else if (theme == 'ayu') {
|
||||
stylesheets.ayuHighlight.disabled = false;
|
||||
stylesheets.tomorrowNight.disabled = true;
|
||||
stylesheets.highlight.disabled = true;
|
||||
ace_theme = "ace/theme/tomorrow_night";
|
||||
} else {
|
||||
stylesheets.ayuHighlight.disabled = true;
|
||||
stylesheets.tomorrowNight.disabled = true;
|
||||
stylesheets.highlight.disabled = false;
|
||||
ace_theme = "ace/theme/dawn";
|
||||
}
|
||||
|
||||
setTimeout(function () {
|
||||
themeColorMetaTag.content = getComputedStyle(document.documentElement).backgroundColor;
|
||||
}, 1);
|
||||
|
||||
if (window.ace && window.editors) {
|
||||
window.editors.forEach(function (editor) {
|
||||
editor.setTheme(ace_theme);
|
||||
});
|
||||
}
|
||||
|
||||
var previousTheme = get_theme();
|
||||
|
||||
if (store) {
|
||||
try { localStorage.setItem('mdbook-theme', theme); } catch (e) { }
|
||||
}
|
||||
|
||||
html.classList.remove(previousTheme);
|
||||
html.classList.add(theme);
|
||||
updateThemeSelected();
|
||||
}
|
||||
|
||||
// Set theme
|
||||
var theme = get_theme();
|
||||
|
||||
set_theme(theme, false);
|
||||
|
||||
themeToggleButton.addEventListener('click', function () {
|
||||
if (themePopup.style.display === 'block') {
|
||||
hideThemes();
|
||||
} else {
|
||||
showThemes();
|
||||
}
|
||||
});
|
||||
|
||||
themePopup.addEventListener('click', function (e) {
|
||||
var theme;
|
||||
if (e.target.className === "theme") {
|
||||
theme = e.target.id;
|
||||
} else if (e.target.parentElement.className === "theme") {
|
||||
theme = e.target.parentElement.id;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
set_theme(theme);
|
||||
});
|
||||
|
||||
themePopup.addEventListener('focusout', function(e) {
|
||||
// e.relatedTarget is null in Safari and Firefox on macOS (see workaround below)
|
||||
if (!!e.relatedTarget && !themeToggleButton.contains(e.relatedTarget) && !themePopup.contains(e.relatedTarget)) {
|
||||
hideThemes();
|
||||
}
|
||||
});
|
||||
|
||||
// Should not be needed, but it works around an issue on macOS & iOS: https://github.com/rust-lang/mdBook/issues/628
|
||||
document.addEventListener('click', function(e) {
|
||||
if (themePopup.style.display === 'block' && !themeToggleButton.contains(e.target) && !themePopup.contains(e.target)) {
|
||||
hideThemes();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', function (e) {
|
||||
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; }
|
||||
if (!themePopup.contains(e.target)) { return; }
|
||||
|
||||
switch (e.key) {
|
||||
case 'Escape':
|
||||
e.preventDefault();
|
||||
hideThemes();
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
e.preventDefault();
|
||||
var li = document.activeElement.parentElement;
|
||||
if (li && li.previousElementSibling) {
|
||||
li.previousElementSibling.querySelector('button').focus();
|
||||
}
|
||||
break;
|
||||
case 'ArrowDown':
|
||||
e.preventDefault();
|
||||
var li = document.activeElement.parentElement;
|
||||
if (li && li.nextElementSibling) {
|
||||
li.nextElementSibling.querySelector('button').focus();
|
||||
}
|
||||
break;
|
||||
case 'Home':
|
||||
e.preventDefault();
|
||||
themePopup.querySelector('li:first-child button').focus();
|
||||
break;
|
||||
case 'End':
|
||||
e.preventDefault();
|
||||
themePopup.querySelector('li:last-child button').focus();
|
||||
break;
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
(function sidebar() {
|
||||
var body = document.querySelector("body");
|
||||
var sidebar = document.getElementById("sidebar");
|
||||
var sidebarLinks = document.querySelectorAll('#sidebar a');
|
||||
var sidebarToggleButton = document.getElementById("sidebar-toggle");
|
||||
var sidebarResizeHandle = document.getElementById("sidebar-resize-handle");
|
||||
var firstContact = null;
|
||||
|
||||
function showSidebar() {
|
||||
body.classList.remove('sidebar-hidden')
|
||||
body.classList.add('sidebar-visible');
|
||||
Array.from(sidebarLinks).forEach(function (link) {
|
||||
link.setAttribute('tabIndex', 0);
|
||||
});
|
||||
sidebarToggleButton.setAttribute('aria-expanded', true);
|
||||
sidebar.setAttribute('aria-hidden', false);
|
||||
try { localStorage.setItem('mdbook-sidebar', 'visible'); } catch (e) { }
|
||||
}
|
||||
|
||||
|
||||
var sidebarAnchorToggles = document.querySelectorAll('#sidebar a.toggle');
|
||||
|
||||
function toggleSection(ev) {
|
||||
ev.currentTarget.parentElement.classList.toggle('expanded');
|
||||
}
|
||||
|
||||
Array.from(sidebarAnchorToggles).forEach(function (el) {
|
||||
el.addEventListener('click', toggleSection);
|
||||
});
|
||||
|
||||
function hideSidebar() {
|
||||
body.classList.remove('sidebar-visible')
|
||||
body.classList.add('sidebar-hidden');
|
||||
Array.from(sidebarLinks).forEach(function (link) {
|
||||
link.setAttribute('tabIndex', -1);
|
||||
});
|
||||
sidebarToggleButton.setAttribute('aria-expanded', false);
|
||||
sidebar.setAttribute('aria-hidden', true);
|
||||
try { localStorage.setItem('mdbook-sidebar', 'hidden'); } catch (e) { }
|
||||
}
|
||||
|
||||
// Toggle sidebar
|
||||
sidebarToggleButton.addEventListener('click', function sidebarToggle() {
|
||||
if (body.classList.contains("sidebar-hidden")) {
|
||||
var current_width = parseInt(
|
||||
document.documentElement.style.getPropertyValue('--sidebar-width'), 10);
|
||||
if (current_width < 150) {
|
||||
document.documentElement.style.setProperty('--sidebar-width', '150px');
|
||||
}
|
||||
showSidebar();
|
||||
} else if (body.classList.contains("sidebar-visible")) {
|
||||
hideSidebar();
|
||||
} else {
|
||||
if (getComputedStyle(sidebar)['transform'] === 'none') {
|
||||
hideSidebar();
|
||||
} else {
|
||||
showSidebar();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
sidebarResizeHandle.addEventListener('mousedown', initResize, false);
|
||||
|
||||
function initResize(e) {
|
||||
window.addEventListener('mousemove', resize, false);
|
||||
window.addEventListener('mouseup', stopResize, false);
|
||||
body.classList.add('sidebar-resizing');
|
||||
}
|
||||
function resize(e) {
|
||||
var pos = (e.clientX - sidebar.offsetLeft);
|
||||
if (pos < 20) {
|
||||
hideSidebar();
|
||||
} else {
|
||||
if (body.classList.contains("sidebar-hidden")) {
|
||||
showSidebar();
|
||||
}
|
||||
pos = Math.min(pos, window.innerWidth - 100);
|
||||
document.documentElement.style.setProperty('--sidebar-width', pos + 'px');
|
||||
}
|
||||
}
|
||||
//on mouseup remove windows functions mousemove & mouseup
|
||||
function stopResize(e) {
|
||||
body.classList.remove('sidebar-resizing');
|
||||
window.removeEventListener('mousemove', resize, false);
|
||||
window.removeEventListener('mouseup', stopResize, false);
|
||||
}
|
||||
|
||||
document.addEventListener('touchstart', function (e) {
|
||||
firstContact = {
|
||||
x: e.touches[0].clientX,
|
||||
time: Date.now()
|
||||
};
|
||||
}, { passive: true });
|
||||
|
||||
document.addEventListener('touchmove', function (e) {
|
||||
if (!firstContact)
|
||||
return;
|
||||
|
||||
var curX = e.touches[0].clientX;
|
||||
var xDiff = curX - firstContact.x,
|
||||
tDiff = Date.now() - firstContact.time;
|
||||
|
||||
if (tDiff < 250 && Math.abs(xDiff) >= 150) {
|
||||
if (xDiff >= 0 && firstContact.x < Math.min(document.body.clientWidth * 0.25, 300))
|
||||
showSidebar();
|
||||
else if (xDiff < 0 && curX < 300)
|
||||
hideSidebar();
|
||||
|
||||
firstContact = null;
|
||||
}
|
||||
}, { passive: true });
|
||||
})();
|
||||
|
||||
(function chapterNavigation() {
|
||||
document.addEventListener('keydown', function (e) {
|
||||
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; }
|
||||
if (window.search && window.search.hasFocus()) { return; }
|
||||
var html = document.querySelector('html');
|
||||
|
||||
function next() {
|
||||
var nextButton = document.querySelector('.nav-chapters.next');
|
||||
if (nextButton) {
|
||||
window.location.href = nextButton.href;
|
||||
}
|
||||
}
|
||||
function prev() {
|
||||
var previousButton = document.querySelector('.nav-chapters.previous');
|
||||
if (previousButton) {
|
||||
window.location.href = previousButton.href;
|
||||
}
|
||||
}
|
||||
switch (e.key) {
|
||||
case 'ArrowRight':
|
||||
e.preventDefault();
|
||||
if (html.dir == 'rtl') {
|
||||
prev();
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
break;
|
||||
case 'ArrowLeft':
|
||||
e.preventDefault();
|
||||
if (html.dir == 'rtl') {
|
||||
next();
|
||||
} else {
|
||||
prev();
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
(function clipboard() {
|
||||
var clipButtons = document.querySelectorAll('.clip-button');
|
||||
|
||||
function hideTooltip(elem) {
|
||||
elem.firstChild.innerText = "";
|
||||
elem.className = 'fa fa-copy clip-button';
|
||||
}
|
||||
|
||||
function showTooltip(elem, msg) {
|
||||
elem.firstChild.innerText = msg;
|
||||
elem.className = 'fa fa-copy tooltipped';
|
||||
}
|
||||
|
||||
var clipboardSnippets = new ClipboardJS('.clip-button', {
|
||||
text: function (trigger) {
|
||||
hideTooltip(trigger);
|
||||
let playground = trigger.closest("pre");
|
||||
return playground_text(playground, false);
|
||||
}
|
||||
});
|
||||
|
||||
Array.from(clipButtons).forEach(function (clipButton) {
|
||||
clipButton.addEventListener('mouseout', function (e) {
|
||||
hideTooltip(e.currentTarget);
|
||||
});
|
||||
});
|
||||
|
||||
clipboardSnippets.on('success', function (e) {
|
||||
e.clearSelection();
|
||||
showTooltip(e.trigger, "Copied!");
|
||||
});
|
||||
|
||||
clipboardSnippets.on('error', function (e) {
|
||||
showTooltip(e.trigger, "Clipboard error!");
|
||||
});
|
||||
})();
|
||||
|
||||
(function scrollToTop () {
|
||||
var menuTitle = document.querySelector('.menu-title');
|
||||
|
||||
menuTitle.addEventListener('click', function () {
|
||||
document.scrollingElement.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
});
|
||||
})();
|
||||
|
||||
(function controllMenu() {
|
||||
var menu = document.getElementById('menu-bar');
|
||||
|
||||
(function controllPosition() {
|
||||
var scrollTop = document.scrollingElement.scrollTop;
|
||||
var prevScrollTop = scrollTop;
|
||||
var minMenuY = -menu.clientHeight - 50;
|
||||
// When the script loads, the page can be at any scroll (e.g. if you reforesh it).
|
||||
menu.style.top = scrollTop + 'px';
|
||||
// Same as parseInt(menu.style.top.slice(0, -2), but faster
|
||||
var topCache = menu.style.top.slice(0, -2);
|
||||
menu.classList.remove('sticky');
|
||||
var stickyCache = false; // Same as menu.classList.contains('sticky'), but faster
|
||||
document.addEventListener('scroll', function () {
|
||||
scrollTop = Math.max(document.scrollingElement.scrollTop, 0);
|
||||
// `null` means that it doesn't need to be updated
|
||||
var nextSticky = null;
|
||||
var nextTop = null;
|
||||
var scrollDown = scrollTop > prevScrollTop;
|
||||
var menuPosAbsoluteY = topCache - scrollTop;
|
||||
if (scrollDown) {
|
||||
nextSticky = false;
|
||||
if (menuPosAbsoluteY > 0) {
|
||||
nextTop = prevScrollTop;
|
||||
}
|
||||
} else {
|
||||
if (menuPosAbsoluteY > 0) {
|
||||
nextSticky = true;
|
||||
} else if (menuPosAbsoluteY < minMenuY) {
|
||||
nextTop = prevScrollTop + minMenuY;
|
||||
}
|
||||
}
|
||||
if (nextSticky === true && stickyCache === false) {
|
||||
menu.classList.add('sticky');
|
||||
stickyCache = true;
|
||||
} else if (nextSticky === false && stickyCache === true) {
|
||||
menu.classList.remove('sticky');
|
||||
stickyCache = false;
|
||||
}
|
||||
if (nextTop !== null) {
|
||||
menu.style.top = nextTop + 'px';
|
||||
topCache = nextTop;
|
||||
}
|
||||
prevScrollTop = scrollTop;
|
||||
}, { passive: true });
|
||||
})();
|
||||
(function controllBorder() {
|
||||
function updateBorder() {
|
||||
if (menu.offsetTop === 0) {
|
||||
menu.classList.remove('bordered');
|
||||
} else {
|
||||
menu.classList.add('bordered');
|
||||
}
|
||||
}
|
||||
updateBorder();
|
||||
document.addEventListener('scroll', updateBorder, { passive: true });
|
||||
})();
|
||||
})();
|
604
theme/css/chrome.css
Normal file
604
theme/css/chrome.css
Normal file
|
@ -0,0 +1,604 @@
|
|||
/* CSS for UI elements (a.k.a. chrome) */
|
||||
|
||||
html {
|
||||
scrollbar-color: var(--scrollbar) var(--bg);
|
||||
}
|
||||
#searchresults a,
|
||||
.content a:link,
|
||||
a:visited,
|
||||
a > .hljs {
|
||||
color: var(--links);
|
||||
}
|
||||
|
||||
/*
|
||||
body-container is necessary because mobile browsers don't seem to like
|
||||
overflow-x on the body tag when there is a <meta name="viewport"> tag.
|
||||
*/
|
||||
#body-container {
|
||||
/*
|
||||
This is used when the sidebar pushes the body content off the side of
|
||||
the screen on small screens. Without it, dragging on mobile Safari
|
||||
will want to reposition the viewport in a weird way.
|
||||
*/
|
||||
overflow-x: clip;
|
||||
}
|
||||
|
||||
/* Menu Bar */
|
||||
|
||||
#menu-bar,
|
||||
#menu-bar-hover-placeholder {
|
||||
z-index: 101;
|
||||
margin: auto calc(0px - var(--page-padding));
|
||||
}
|
||||
#menu-bar {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
background-color: var(--bg);
|
||||
border-block-end-color: var(--bg);
|
||||
border-block-end-width: 1px;
|
||||
border-block-end-style: solid;
|
||||
}
|
||||
#menu-bar.sticky,
|
||||
.js #menu-bar-hover-placeholder:hover + #menu-bar,
|
||||
.js #menu-bar:hover,
|
||||
.js.sidebar-visible #menu-bar {
|
||||
position: -webkit-sticky;
|
||||
position: sticky;
|
||||
top: 0 !important;
|
||||
}
|
||||
#menu-bar-hover-placeholder {
|
||||
position: sticky;
|
||||
position: -webkit-sticky;
|
||||
top: 0;
|
||||
height: var(--menu-bar-height);
|
||||
}
|
||||
#menu-bar.bordered {
|
||||
border-block-end-color: var(--table-border-color);
|
||||
}
|
||||
#menu-bar i, #menu-bar .icon-button {
|
||||
position: relative;
|
||||
padding: 0 8px;
|
||||
z-index: 10;
|
||||
line-height: var(--menu-bar-height);
|
||||
cursor: pointer;
|
||||
transition: color 0.5s;
|
||||
}
|
||||
@media only screen and (max-width: 420px) {
|
||||
#menu-bar i, #menu-bar .icon-button {
|
||||
padding: 0 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-button {
|
||||
border: none;
|
||||
background: none;
|
||||
padding: 0;
|
||||
color: inherit;
|
||||
}
|
||||
.icon-button i {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.right-buttons {
|
||||
margin: 0 15px;
|
||||
}
|
||||
.right-buttons a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.left-buttons {
|
||||
display: flex;
|
||||
margin: 0 5px;
|
||||
}
|
||||
.no-js .left-buttons button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.menu-title {
|
||||
display: inline-block;
|
||||
font-weight: 200;
|
||||
font-size: 2.4rem;
|
||||
line-height: var(--menu-bar-height);
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.js .menu-title {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.menu-bar,
|
||||
.menu-bar:visited,
|
||||
.nav-chapters,
|
||||
.nav-chapters:visited,
|
||||
.mobile-nav-chapters,
|
||||
.mobile-nav-chapters:visited,
|
||||
.menu-bar .icon-button,
|
||||
.menu-bar a i {
|
||||
color: var(--icons);
|
||||
}
|
||||
|
||||
.menu-bar i:hover,
|
||||
.menu-bar .icon-button:hover,
|
||||
.nav-chapters:hover,
|
||||
.mobile-nav-chapters i:hover {
|
||||
color: var(--icons-hover);
|
||||
}
|
||||
|
||||
/* Nav Icons */
|
||||
|
||||
.nav-chapters {
|
||||
font-size: 2.5em;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
margin: 0;
|
||||
max-width: 150px;
|
||||
min-width: 90px;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
flex-direction: column;
|
||||
|
||||
transition: color 0.5s, background-color 0.5s;
|
||||
}
|
||||
|
||||
.nav-chapters:hover {
|
||||
text-decoration: none;
|
||||
background-color: var(--theme-hover);
|
||||
transition: background-color 0.15s, color 0.15s;
|
||||
}
|
||||
|
||||
.nav-wrapper {
|
||||
margin-block-start: 50px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mobile-nav-chapters {
|
||||
font-size: 2.5em;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
width: 90px;
|
||||
border-radius: 5px;
|
||||
background-color: var(--sidebar-bg);
|
||||
}
|
||||
|
||||
/* Only Firefox supports flow-relative values */
|
||||
.previous { float: left; }
|
||||
[dir=rtl] .previous { float: right; }
|
||||
|
||||
/* Only Firefox supports flow-relative values */
|
||||
.next {
|
||||
float: right;
|
||||
right: var(--page-padding);
|
||||
}
|
||||
[dir=rtl] .next {
|
||||
float: left;
|
||||
right: unset;
|
||||
left: var(--page-padding);
|
||||
}
|
||||
|
||||
/* Use the correct buttons for RTL layouts*/
|
||||
[dir=rtl] .previous i.fa-angle-left:before {content:"\f105";}
|
||||
[dir=rtl] .next i.fa-angle-right:before { content:"\f104"; }
|
||||
|
||||
@media only screen and (max-width: 1080px) {
|
||||
.nav-wide-wrapper { display: none; }
|
||||
.nav-wrapper { display: block; }
|
||||
}
|
||||
|
||||
/* sidebar-visible */
|
||||
@media only screen and (max-width: 1380px) {
|
||||
#sidebar-toggle-anchor:checked ~ .page-wrapper .nav-wide-wrapper { display: none; }
|
||||
#sidebar-toggle-anchor:checked ~ .page-wrapper .nav-wrapper { display: block; }
|
||||
}
|
||||
|
||||
/* Inline code */
|
||||
|
||||
:not(pre) > .hljs {
|
||||
display: inline;
|
||||
padding: 0.1em 0.3em;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
:not(pre):not(a) > .hljs {
|
||||
color: var(--inline-code-color);
|
||||
overflow-x: initial;
|
||||
}
|
||||
|
||||
a:hover > .hljs {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
pre {
|
||||
position: relative;
|
||||
}
|
||||
pre > .buttons {
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
right: 0px;
|
||||
top: 2px;
|
||||
margin: 0px;
|
||||
padding: 2px 0px;
|
||||
|
||||
color: var(--sidebar-fg);
|
||||
cursor: pointer;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transition: visibility 0.1s linear, opacity 0.1s linear;
|
||||
}
|
||||
pre:hover > .buttons {
|
||||
visibility: visible;
|
||||
opacity: 1
|
||||
}
|
||||
pre > .buttons :hover {
|
||||
color: var(--sidebar-active);
|
||||
border-color: var(--icons-hover);
|
||||
background-color: var(--theme-hover);
|
||||
}
|
||||
pre > .buttons i {
|
||||
margin-inline-start: 8px;
|
||||
}
|
||||
pre > .buttons button {
|
||||
cursor: inherit;
|
||||
margin: 0px 5px;
|
||||
padding: 3px 5px;
|
||||
font-size: 14px;
|
||||
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-radius: 4px;
|
||||
border-color: var(--icons);
|
||||
background-color: var(--theme-popup-bg);
|
||||
transition: 100ms;
|
||||
transition-property: color,border-color,background-color;
|
||||
color: var(--icons);
|
||||
}
|
||||
@media (pointer: coarse) {
|
||||
pre > .buttons button {
|
||||
/* On mobile, make it easier to tap buttons. */
|
||||
padding: 0.3rem 1rem;
|
||||
}
|
||||
|
||||
.sidebar-resize-indicator {
|
||||
/* Hide resize indicator on devices with limited accuracy */
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
pre > code {
|
||||
display: block;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
/* FIXME: ACE editors overlap their buttons because ACE does absolute
|
||||
positioning within the code block which breaks padding. The only solution I
|
||||
can think of is to move the padding to the outer pre tag (or insert a div
|
||||
wrapper), but that would require fixing a whole bunch of CSS rules.
|
||||
*/
|
||||
.hljs.ace_editor {
|
||||
padding: 0rem 0rem;
|
||||
}
|
||||
|
||||
pre > .result {
|
||||
margin-block-start: 10px;
|
||||
}
|
||||
|
||||
/* Search */
|
||||
|
||||
#searchresults a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
mark {
|
||||
border-radius: 2px;
|
||||
padding-block-start: 0;
|
||||
padding-block-end: 1px;
|
||||
padding-inline-start: 3px;
|
||||
padding-inline-end: 3px;
|
||||
margin-block-start: 0;
|
||||
margin-block-end: -1px;
|
||||
margin-inline-start: -3px;
|
||||
margin-inline-end: -3px;
|
||||
background-color: var(--search-mark-bg);
|
||||
transition: background-color 300ms linear;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
mark.fade-out {
|
||||
background-color: rgba(0,0,0,0) !important;
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
.searchbar-outer {
|
||||
margin-inline-start: auto;
|
||||
margin-inline-end: auto;
|
||||
max-width: var(--content-max-width);
|
||||
}
|
||||
|
||||
#searchbar {
|
||||
width: 100%;
|
||||
margin-block-start: 5px;
|
||||
margin-block-end: 0;
|
||||
margin-inline-start: auto;
|
||||
margin-inline-end: auto;
|
||||
padding: 10px 16px;
|
||||
transition: box-shadow 300ms ease-in-out;
|
||||
border: 1px solid var(--searchbar-border-color);
|
||||
border-radius: 3px;
|
||||
background-color: var(--searchbar-bg);
|
||||
color: var(--searchbar-fg);
|
||||
}
|
||||
#searchbar:focus,
|
||||
#searchbar.active {
|
||||
box-shadow: 0 0 3px var(--searchbar-shadow-color);
|
||||
}
|
||||
|
||||
.searchresults-header {
|
||||
font-weight: bold;
|
||||
font-size: 1em;
|
||||
padding-block-start: 18px;
|
||||
padding-block-end: 0;
|
||||
padding-inline-start: 5px;
|
||||
padding-inline-end: 0;
|
||||
color: var(--searchresults-header-fg);
|
||||
}
|
||||
|
||||
.searchresults-outer {
|
||||
margin-inline-start: auto;
|
||||
margin-inline-end: auto;
|
||||
max-width: var(--content-max-width);
|
||||
border-block-end: 1px dashed var(--searchresults-border-color);
|
||||
}
|
||||
|
||||
ul#searchresults {
|
||||
list-style: none;
|
||||
padding-inline-start: 20px;
|
||||
}
|
||||
ul#searchresults li {
|
||||
margin: 10px 0px;
|
||||
padding: 2px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
ul#searchresults li.focus {
|
||||
background-color: var(--searchresults-li-bg);
|
||||
}
|
||||
ul#searchresults span.teaser {
|
||||
display: block;
|
||||
clear: both;
|
||||
margin-block-start: 5px;
|
||||
margin-block-end: 0;
|
||||
margin-inline-start: 20px;
|
||||
margin-inline-end: 0;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
ul#searchresults span.teaser em {
|
||||
font-weight: bold;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
/* Sidebar */
|
||||
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: var(--sidebar-width);
|
||||
font-size: 0.875em;
|
||||
box-sizing: border-box;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
overscroll-behavior-y: contain;
|
||||
background-color: var(--sidebar-bg);
|
||||
color: var(--sidebar-fg);
|
||||
}
|
||||
[dir=rtl] .sidebar { left: unset; right: 0; }
|
||||
.sidebar-resizing {
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
.no-js .sidebar,
|
||||
.js:not(.sidebar-resizing) .sidebar {
|
||||
transition: transform 0.3s; /* Animation: slide away */
|
||||
}
|
||||
.sidebar code {
|
||||
line-height: 2em;
|
||||
}
|
||||
.sidebar .sidebar-scrollbox {
|
||||
overflow-y: auto;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 10px 10px;
|
||||
}
|
||||
.sidebar .sidebar-resize-handle {
|
||||
position: absolute;
|
||||
cursor: col-resize;
|
||||
width: 0;
|
||||
right: calc(var(--sidebar-resize-indicator-width) * -1);
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.sidebar-resize-handle .sidebar-resize-indicator {
|
||||
width: 100%;
|
||||
height: 12px;
|
||||
background-color: var(--icons);
|
||||
margin-inline-start: var(--sidebar-resize-indicator-space);
|
||||
}
|
||||
|
||||
[dir=rtl] .sidebar .sidebar-resize-handle {
|
||||
left: calc(var(--sidebar-resize-indicator-width) * -1);
|
||||
right: unset;
|
||||
}
|
||||
.js .sidebar .sidebar-resize-handle {
|
||||
cursor: col-resize;
|
||||
width: calc(var(--sidebar-resize-indicator-width) - var(--sidebar-resize-indicator-space));
|
||||
}
|
||||
/* sidebar-hidden */
|
||||
#sidebar-toggle-anchor:not(:checked) ~ .sidebar {
|
||||
transform: translateX(calc(0px - var(--sidebar-width) - var(--sidebar-resize-indicator-width)));
|
||||
z-index: -1;
|
||||
}
|
||||
[dir=rtl] #sidebar-toggle-anchor:not(:checked) ~ .sidebar {
|
||||
transform: translateX(calc(var(--sidebar-width) + var(--sidebar-resize-indicator-width)));
|
||||
}
|
||||
.sidebar::-webkit-scrollbar {
|
||||
background: var(--sidebar-bg);
|
||||
}
|
||||
.sidebar::-webkit-scrollbar-thumb {
|
||||
background: var(--scrollbar);
|
||||
}
|
||||
|
||||
/* sidebar-visible */
|
||||
#sidebar-toggle-anchor:checked ~ .page-wrapper {
|
||||
transform: translateX(calc(var(--sidebar-width) + var(--sidebar-resize-indicator-width)));
|
||||
}
|
||||
[dir=rtl] #sidebar-toggle-anchor:checked ~ .page-wrapper {
|
||||
transform: translateX(calc(0px - var(--sidebar-width) - var(--sidebar-resize-indicator-width)));
|
||||
}
|
||||
@media only screen and (min-width: 620px) {
|
||||
#sidebar-toggle-anchor:checked ~ .page-wrapper {
|
||||
transform: none;
|
||||
margin-inline-start: calc(var(--sidebar-width) + var(--sidebar-resize-indicator-width));
|
||||
}
|
||||
[dir=rtl] #sidebar-toggle-anchor:checked ~ .page-wrapper {
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
.chapter {
|
||||
list-style: none outside none;
|
||||
padding-inline-start: 0;
|
||||
line-height: 2.2em;
|
||||
}
|
||||
|
||||
.chapter ol {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chapter li {
|
||||
display: flex;
|
||||
color: var(--sidebar-non-existant);
|
||||
}
|
||||
.chapter li a {
|
||||
display: block;
|
||||
padding: 0;
|
||||
text-decoration: none;
|
||||
color: var(--sidebar-fg);
|
||||
}
|
||||
|
||||
.chapter li a:hover {
|
||||
color: var(--sidebar-active);
|
||||
}
|
||||
|
||||
.chapter li a.active {
|
||||
color: var(--sidebar-active);
|
||||
}
|
||||
|
||||
.chapter li > a.toggle {
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
margin-inline-start: auto;
|
||||
padding: 0 10px;
|
||||
user-select: none;
|
||||
opacity: 0.68;
|
||||
}
|
||||
|
||||
.chapter li > a.toggle div {
|
||||
transition: transform 0.5s;
|
||||
}
|
||||
|
||||
/* collapse the section */
|
||||
.chapter li:not(.expanded) + li > ol {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.chapter li.chapter-item {
|
||||
line-height: 1.5em;
|
||||
margin-block-start: 0.6em;
|
||||
}
|
||||
|
||||
.chapter li.expanded > a.toggle div {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.spacer {
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
margin: 5px 0px;
|
||||
}
|
||||
.chapter .spacer {
|
||||
background-color: var(--sidebar-spacer);
|
||||
}
|
||||
|
||||
@media (-moz-touch-enabled: 1), (pointer: coarse) {
|
||||
.chapter li a { padding: 5px 0; }
|
||||
.spacer { margin: 10px 0; }
|
||||
}
|
||||
|
||||
.section {
|
||||
list-style: none outside none;
|
||||
padding-inline-start: 20px;
|
||||
line-height: 1.9em;
|
||||
}
|
||||
|
||||
/* Theme Menu Popup */
|
||||
|
||||
.theme-popup {
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
top: var(--menu-bar-height);
|
||||
z-index: 1000;
|
||||
border-radius: 4px;
|
||||
font-size: 0.7em;
|
||||
color: var(--fg);
|
||||
background: var(--theme-popup-bg);
|
||||
border: 1px solid var(--theme-popup-border);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
display: none;
|
||||
/* Don't let the children's background extend past the rounded corners. */
|
||||
overflow: hidden;
|
||||
}
|
||||
[dir=rtl] .theme-popup { left: unset; right: 10px; }
|
||||
.theme-popup .default {
|
||||
color: var(--icons);
|
||||
}
|
||||
.theme-popup .theme {
|
||||
width: 100%;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 2px 20px;
|
||||
line-height: 25px;
|
||||
white-space: nowrap;
|
||||
text-align: start;
|
||||
cursor: pointer;
|
||||
color: inherit;
|
||||
background: inherit;
|
||||
font-size: inherit;
|
||||
}
|
||||
.theme-popup .theme:hover {
|
||||
background-color: var(--theme-hover);
|
||||
}
|
||||
|
||||
.theme-selected::before {
|
||||
display: inline-block;
|
||||
content: "✓";
|
||||
margin-inline-start: -14px;
|
||||
width: 14px;
|
||||
}
|
232
theme/css/general.css
Normal file
232
theme/css/general.css
Normal file
|
@ -0,0 +1,232 @@
|
|||
/* Base styles and content styles */
|
||||
|
||||
:root {
|
||||
/* Browser default font-size is 16px, this way 1 rem = 10px */
|
||||
font-size: 62.5%;
|
||||
color-scheme: var(--color-scheme);
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: "Open Sans", sans-serif;
|
||||
color: var(--fg);
|
||||
background-color: var(--bg);
|
||||
text-size-adjust: none;
|
||||
-webkit-text-size-adjust: none;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-size: 1.6rem;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: var(--mono-font) !important;
|
||||
font-size: var(--code-font-size);
|
||||
direction: ltr !important;
|
||||
}
|
||||
|
||||
/* make long words/inline code not x overflow */
|
||||
main {
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
/* make wide tables scroll if they overflow */
|
||||
.table-wrapper {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
/* Don't change font size in headers. */
|
||||
h1 code, h2 code, h3 code, h4 code, h5 code, h6 code {
|
||||
font-size: unset;
|
||||
}
|
||||
|
||||
.left { float: left; }
|
||||
.right { float: right; }
|
||||
.boring { opacity: 0.6; }
|
||||
.hide-boring .boring { display: none; }
|
||||
.hidden { display: none !important; }
|
||||
|
||||
h2, h3 { margin-block-start: 2.5em; }
|
||||
h4, h5 { margin-block-start: 2em; }
|
||||
|
||||
.header + .header h3,
|
||||
.header + .header h4,
|
||||
.header + .header h5 {
|
||||
margin-block-start: 1em;
|
||||
}
|
||||
|
||||
h1:target::before,
|
||||
h2:target::before,
|
||||
h3:target::before,
|
||||
h4:target::before,
|
||||
h5:target::before,
|
||||
h6:target::before {
|
||||
display: inline-block;
|
||||
content: "»";
|
||||
margin-inline-start: -30px;
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
/* This is broken on Safari as of version 14, but is fixed
|
||||
in Safari Technology Preview 117 which I think will be Safari 14.2.
|
||||
https://bugs.webkit.org/show_bug.cgi?id=218076
|
||||
*/
|
||||
:target {
|
||||
/* Safari does not support logical properties */
|
||||
scroll-margin-top: calc(var(--menu-bar-height) + 0.5em);
|
||||
}
|
||||
|
||||
.page {
|
||||
outline: 0;
|
||||
padding: 0 var(--page-padding);
|
||||
margin-block-start: calc(0px - var(--menu-bar-height)); /* Compensate for the #menu-bar-hover-placeholder */
|
||||
}
|
||||
.page-wrapper {
|
||||
box-sizing: border-box;
|
||||
background-color: var(--bg);
|
||||
}
|
||||
.no-js .page-wrapper,
|
||||
.js:not(.sidebar-resizing) .page-wrapper {
|
||||
transition: margin-left 0.3s ease, transform 0.3s ease; /* Animation: slide away */
|
||||
}
|
||||
[dir=rtl] .js:not(.sidebar-resizing) .page-wrapper {
|
||||
transition: margin-right 0.3s ease, transform 0.3s ease; /* Animation: slide away */
|
||||
}
|
||||
|
||||
.content {
|
||||
overflow-y: auto;
|
||||
padding: 0 5px 50px 5px;
|
||||
}
|
||||
.content main {
|
||||
margin-inline-start: auto;
|
||||
margin-inline-end: auto;
|
||||
max-width: var(--content-max-width);
|
||||
}
|
||||
.content p { line-height: 1.45em; }
|
||||
.content ol { line-height: 1.45em; }
|
||||
.content ul { line-height: 1.45em; }
|
||||
.content a { text-decoration: none; }
|
||||
.content a:hover { text-decoration: underline; }
|
||||
.content img, .content video { max-width: 100%; }
|
||||
.content .header:link,
|
||||
.content .header:visited {
|
||||
color: var(--fg);
|
||||
}
|
||||
.content .header:link,
|
||||
.content .header:visited:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
table {
|
||||
margin: 0 auto;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
table td {
|
||||
padding: 3px 20px;
|
||||
border: 1px var(--table-border-color) solid;
|
||||
}
|
||||
table thead {
|
||||
background: var(--table-header-bg);
|
||||
}
|
||||
table thead td {
|
||||
font-weight: 700;
|
||||
border: none;
|
||||
}
|
||||
table thead th {
|
||||
padding: 3px 20px;
|
||||
}
|
||||
table thead tr {
|
||||
border: 1px var(--table-header-bg) solid;
|
||||
}
|
||||
/* Alternate background colors for rows */
|
||||
table tbody tr:nth-child(2n) {
|
||||
background: var(--table-alternate-bg);
|
||||
}
|
||||
|
||||
|
||||
blockquote {
|
||||
margin: 20px 0;
|
||||
padding: 0 20px;
|
||||
color: var(--fg);
|
||||
background-color: var(--quote-bg);
|
||||
border-block-start: .1em solid var(--quote-border);
|
||||
border-block-end: .1em solid var(--quote-border);
|
||||
}
|
||||
|
||||
.warning {
|
||||
margin: 20px;
|
||||
padding: 0 20px;
|
||||
border-inline-start: 2px solid var(--warning-border);
|
||||
}
|
||||
|
||||
.warning:before {
|
||||
position: absolute;
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
margin-inline-start: calc(-1.5rem - 21px);
|
||||
content: "ⓘ";
|
||||
text-align: center;
|
||||
background-color: var(--bg);
|
||||
color: var(--warning-border);
|
||||
font-weight: bold;
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
blockquote .warning:before {
|
||||
background-color: var(--quote-bg);
|
||||
}
|
||||
|
||||
kbd {
|
||||
background-color: var(--table-border-color);
|
||||
border-radius: 4px;
|
||||
border: solid 1px var(--theme-popup-border);
|
||||
box-shadow: inset 0 -1px 0 var(--theme-hover);
|
||||
display: inline-block;
|
||||
font-size: var(--code-font-size);
|
||||
font-family: var(--mono-font);
|
||||
line-height: 10px;
|
||||
padding: 4px 5px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
:not(.footnote-definition) + .footnote-definition,
|
||||
.footnote-definition + :not(.footnote-definition) {
|
||||
margin-block-start: 2em;
|
||||
}
|
||||
.footnote-definition {
|
||||
font-size: 0.9em;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.footnote-definition p {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.tooltiptext {
|
||||
position: absolute;
|
||||
visibility: hidden;
|
||||
color: #fff;
|
||||
background-color: #333;
|
||||
transform: translateX(-50%); /* Center by moving tooltip 50% of its width left */
|
||||
left: -8px; /* Half of the width of the icon */
|
||||
top: -35px;
|
||||
font-size: 0.8em;
|
||||
text-align: center;
|
||||
border-radius: 6px;
|
||||
padding: 5px 8px;
|
||||
margin: 5px;
|
||||
z-index: 1000;
|
||||
}
|
||||
.tooltipped .tooltiptext {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.chapter li.part-title {
|
||||
color: var(--sidebar-fg);
|
||||
margin: 5px 0px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.result-no-output {
|
||||
font-style: italic;
|
||||
}
|
50
theme/css/print.css
Normal file
50
theme/css/print.css
Normal file
|
@ -0,0 +1,50 @@
|
|||
|
||||
#sidebar,
|
||||
#menu-bar,
|
||||
.nav-chapters,
|
||||
.mobile-nav-chapters {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#page-wrapper.page-wrapper {
|
||||
transform: none !important;
|
||||
margin-inline-start: 0px;
|
||||
overflow-y: initial;
|
||||
}
|
||||
|
||||
#content {
|
||||
max-width: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.page {
|
||||
overflow-y: initial;
|
||||
}
|
||||
|
||||
code {
|
||||
direction: ltr !important;
|
||||
}
|
||||
|
||||
pre > .buttons {
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
a, a:visited, a:active, a:hover {
|
||||
color: #4183c4;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
page-break-inside: avoid;
|
||||
page-break-after: avoid;
|
||||
}
|
||||
|
||||
pre, code {
|
||||
page-break-inside: avoid;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.fa {
|
||||
display: none !important;
|
||||
}
|
279
theme/css/variables.css
Normal file
279
theme/css/variables.css
Normal file
|
@ -0,0 +1,279 @@
|
|||
|
||||
/* Globals */
|
||||
|
||||
:root {
|
||||
--sidebar-width: 300px;
|
||||
--sidebar-resize-indicator-width: 8px;
|
||||
--sidebar-resize-indicator-space: 2px;
|
||||
--page-padding: 15px;
|
||||
--content-max-width: 750px;
|
||||
--menu-bar-height: 50px;
|
||||
--mono-font: "Source Code Pro", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace, monospace;
|
||||
--code-font-size: 0.875em /* please adjust the ace font size accordingly in editor.js */
|
||||
}
|
||||
|
||||
/* Themes */
|
||||
|
||||
.ayu {
|
||||
--bg: hsl(210, 25%, 8%);
|
||||
--fg: #c5c5c5;
|
||||
|
||||
--sidebar-bg: #14191f;
|
||||
--sidebar-fg: #c8c9db;
|
||||
--sidebar-non-existant: #5c6773;
|
||||
--sidebar-active: #ffb454;
|
||||
--sidebar-spacer: #2d334f;
|
||||
|
||||
--scrollbar: var(--sidebar-fg);
|
||||
|
||||
--icons: #737480;
|
||||
--icons-hover: #b7b9cc;
|
||||
|
||||
--links: #0096cf;
|
||||
|
||||
--inline-code-color: #ffb454;
|
||||
|
||||
--theme-popup-bg: #14191f;
|
||||
--theme-popup-border: #5c6773;
|
||||
--theme-hover: #191f26;
|
||||
|
||||
--quote-bg: hsl(226, 15%, 17%);
|
||||
--quote-border: hsl(226, 15%, 22%);
|
||||
|
||||
--warning-border: #ff8e00;
|
||||
|
||||
--table-border-color: hsl(210, 25%, 13%);
|
||||
--table-header-bg: hsl(210, 25%, 28%);
|
||||
--table-alternate-bg: hsl(210, 25%, 11%);
|
||||
|
||||
--searchbar-border-color: #848484;
|
||||
--searchbar-bg: #424242;
|
||||
--searchbar-fg: #fff;
|
||||
--searchbar-shadow-color: #d4c89f;
|
||||
--searchresults-header-fg: #666;
|
||||
--searchresults-border-color: #888;
|
||||
--searchresults-li-bg: #252932;
|
||||
--search-mark-bg: #e3b171;
|
||||
|
||||
--color-scheme: dark;
|
||||
}
|
||||
|
||||
.coal {
|
||||
--bg: hsl(200, 7%, 8%);
|
||||
--fg: #98a3ad;
|
||||
|
||||
--sidebar-bg: #292c2f;
|
||||
--sidebar-fg: #a1adb8;
|
||||
--sidebar-non-existant: #505254;
|
||||
--sidebar-active: #3473ad;
|
||||
--sidebar-spacer: #393939;
|
||||
|
||||
--scrollbar: var(--sidebar-fg);
|
||||
|
||||
--icons: #43484d;
|
||||
--icons-hover: #b3c0cc;
|
||||
|
||||
--links: #2b79a2;
|
||||
|
||||
--inline-code-color: #c5c8c6;
|
||||
|
||||
--theme-popup-bg: #141617;
|
||||
--theme-popup-border: #43484d;
|
||||
--theme-hover: #1f2124;
|
||||
|
||||
--quote-bg: hsl(234, 21%, 18%);
|
||||
--quote-border: hsl(234, 21%, 23%);
|
||||
|
||||
--warning-border: #ff8e00;
|
||||
|
||||
--table-border-color: hsl(200, 7%, 13%);
|
||||
--table-header-bg: hsl(200, 7%, 28%);
|
||||
--table-alternate-bg: hsl(200, 7%, 11%);
|
||||
|
||||
--searchbar-border-color: #aaa;
|
||||
--searchbar-bg: #b7b7b7;
|
||||
--searchbar-fg: #000;
|
||||
--searchbar-shadow-color: #aaa;
|
||||
--searchresults-header-fg: #666;
|
||||
--searchresults-border-color: #98a3ad;
|
||||
--searchresults-li-bg: #2b2b2f;
|
||||
--search-mark-bg: #355c7d;
|
||||
|
||||
--color-scheme: dark;
|
||||
}
|
||||
|
||||
.light {
|
||||
--bg: hsl(0, 0%, 100%);
|
||||
--fg: hsl(0, 0%, 0%);
|
||||
|
||||
--sidebar-bg: #fafafa;
|
||||
--sidebar-fg: hsl(0, 0%, 0%);
|
||||
--sidebar-non-existant: #aaaaaa;
|
||||
--sidebar-active: #1f1fff;
|
||||
--sidebar-spacer: #f4f4f4;
|
||||
|
||||
--scrollbar: #8F8F8F;
|
||||
|
||||
--icons: #747474;
|
||||
--icons-hover: #000000;
|
||||
|
||||
--links: #20609f;
|
||||
|
||||
--inline-code-color: #301900;
|
||||
|
||||
--theme-popup-bg: #fafafa;
|
||||
--theme-popup-border: #cccccc;
|
||||
--theme-hover: #e6e6e6;
|
||||
|
||||
--quote-bg: hsl(197, 37%, 96%);
|
||||
--quote-border: hsl(197, 37%, 91%);
|
||||
|
||||
--warning-border: #ff8e00;
|
||||
|
||||
--table-border-color: hsl(0, 0%, 95%);
|
||||
--table-header-bg: hsl(0, 0%, 80%);
|
||||
--table-alternate-bg: hsl(0, 0%, 97%);
|
||||
|
||||
--searchbar-border-color: #aaa;
|
||||
--searchbar-bg: #fafafa;
|
||||
--searchbar-fg: #000;
|
||||
--searchbar-shadow-color: #aaa;
|
||||
--searchresults-header-fg: #666;
|
||||
--searchresults-border-color: #888;
|
||||
--searchresults-li-bg: #e4f2fe;
|
||||
--search-mark-bg: #a2cff5;
|
||||
|
||||
--color-scheme: light;
|
||||
}
|
||||
|
||||
.navy {
|
||||
--bg: hsl(226, 23%, 11%);
|
||||
--fg: #bcbdd0;
|
||||
|
||||
--sidebar-bg: #282d3f;
|
||||
--sidebar-fg: #c8c9db;
|
||||
--sidebar-non-existant: #505274;
|
||||
--sidebar-active: #2b79a2;
|
||||
--sidebar-spacer: #2d334f;
|
||||
|
||||
--scrollbar: var(--sidebar-fg);
|
||||
|
||||
--icons: #737480;
|
||||
--icons-hover: #b7b9cc;
|
||||
|
||||
--links: #2b79a2;
|
||||
|
||||
--inline-code-color: #c5c8c6;
|
||||
|
||||
--theme-popup-bg: #161923;
|
||||
--theme-popup-border: #737480;
|
||||
--theme-hover: #282e40;
|
||||
|
||||
--quote-bg: hsl(226, 15%, 17%);
|
||||
--quote-border: hsl(226, 15%, 22%);
|
||||
|
||||
--warning-border: #ff8e00;
|
||||
|
||||
--table-border-color: hsl(226, 23%, 16%);
|
||||
--table-header-bg: hsl(226, 23%, 31%);
|
||||
--table-alternate-bg: hsl(226, 23%, 14%);
|
||||
|
||||
--searchbar-border-color: #aaa;
|
||||
--searchbar-bg: #aeaec6;
|
||||
--searchbar-fg: #000;
|
||||
--searchbar-shadow-color: #aaa;
|
||||
--searchresults-header-fg: #5f5f71;
|
||||
--searchresults-border-color: #5c5c68;
|
||||
--searchresults-li-bg: #242430;
|
||||
--search-mark-bg: #a2cff5;
|
||||
|
||||
--color-scheme: dark;
|
||||
}
|
||||
|
||||
.rust {
|
||||
--bg: hsl(60, 9%, 87%);
|
||||
--fg: #262625;
|
||||
|
||||
--sidebar-bg: #3b2e2a;
|
||||
--sidebar-fg: #c8c9db;
|
||||
--sidebar-non-existant: #505254;
|
||||
--sidebar-active: #e69f67;
|
||||
--sidebar-spacer: #45373a;
|
||||
|
||||
--scrollbar: var(--sidebar-fg);
|
||||
|
||||
--icons: #737480;
|
||||
--icons-hover: #262625;
|
||||
|
||||
--links: #2b79a2;
|
||||
|
||||
--inline-code-color: #6e6b5e;
|
||||
|
||||
--theme-popup-bg: #e1e1db;
|
||||
--theme-popup-border: #b38f6b;
|
||||
--theme-hover: #99908a;
|
||||
|
||||
--quote-bg: hsl(60, 5%, 75%);
|
||||
--quote-border: hsl(60, 5%, 70%);
|
||||
|
||||
--warning-border: #ff8e00;
|
||||
|
||||
--table-border-color: hsl(60, 9%, 82%);
|
||||
--table-header-bg: #b3a497;
|
||||
--table-alternate-bg: hsl(60, 9%, 84%);
|
||||
|
||||
--searchbar-border-color: #aaa;
|
||||
--searchbar-bg: #fafafa;
|
||||
--searchbar-fg: #000;
|
||||
--searchbar-shadow-color: #aaa;
|
||||
--searchresults-header-fg: #666;
|
||||
--searchresults-border-color: #888;
|
||||
--searchresults-li-bg: #dec2a2;
|
||||
--search-mark-bg: #e69f67;
|
||||
|
||||
--color-scheme: light;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.light.no-js {
|
||||
--bg: hsl(200, 7%, 8%);
|
||||
--fg: #98a3ad;
|
||||
|
||||
--sidebar-bg: #292c2f;
|
||||
--sidebar-fg: #a1adb8;
|
||||
--sidebar-non-existant: #505254;
|
||||
--sidebar-active: #3473ad;
|
||||
--sidebar-spacer: #393939;
|
||||
|
||||
--scrollbar: var(--sidebar-fg);
|
||||
|
||||
--icons: #43484d;
|
||||
--icons-hover: #b3c0cc;
|
||||
|
||||
--links: #2b79a2;
|
||||
|
||||
--inline-code-color: #c5c8c6;
|
||||
|
||||
--theme-popup-bg: #141617;
|
||||
--theme-popup-border: #43484d;
|
||||
--theme-hover: #1f2124;
|
||||
|
||||
--quote-bg: hsl(234, 21%, 18%);
|
||||
--quote-border: hsl(234, 21%, 23%);
|
||||
|
||||
--warning-border: #ff8e00;
|
||||
|
||||
--table-border-color: hsl(200, 7%, 13%);
|
||||
--table-header-bg: hsl(200, 7%, 28%);
|
||||
--table-alternate-bg: hsl(200, 7%, 11%);
|
||||
|
||||
--searchbar-border-color: #aaa;
|
||||
--searchbar-bg: #b7b7b7;
|
||||
--searchbar-fg: #000;
|
||||
--searchbar-shadow-color: #aaa;
|
||||
--searchresults-header-fg: #666;
|
||||
--searchresults-border-color: #98a3ad;
|
||||
--searchresults-li-bg: #2b2b2f;
|
||||
--search-mark-bg: #355c7d;
|
||||
}
|
||||
}
|
BIN
theme/favicon.png
Normal file
BIN
theme/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.5 KiB |
22
theme/favicon.svg
Normal file
22
theme/favicon.svg
Normal file
|
@ -0,0 +1,22 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 199.7 184.2">
|
||||
<style>
|
||||
@media (prefers-color-scheme: dark) {
|
||||
svg { fill: white; }
|
||||
}
|
||||
</style>
|
||||
<path d="M189.5,36.8c0.2,2.8,0,5.1-0.6,6.8L153,162c-0.6,2.1-2,3.7-4.2,5c-2.2,1.2-4.4,1.9-6.7,1.9H31.4c-9.6,0-15.3-2.8-17.3-8.4
|
||||
c-0.8-2.2-0.8-3.9,0.1-5.2c0.9-1.2,2.4-1.8,4.6-1.8H123c7.4,0,12.6-1.4,15.4-4.1s5.7-8.9,8.6-18.4l32.9-108.6
|
||||
c1.8-5.9,1-11.1-2.2-15.6S169.9,0,164,0H72.7c-1,0-3.1,0.4-6.1,1.1l0.1-0.4C64.5,0.2,62.6,0,61,0.1s-3,0.5-4.3,1.4
|
||||
c-1.3,0.9-2.4,1.8-3.2,2.8S52,6.5,51.2,8.1c-0.8,1.6-1.4,3-1.9,4.3s-1.1,2.7-1.8,4.2c-0.7,1.5-1.3,2.7-2,3.7c-0.5,0.6-1.2,1.5-2,2.5
|
||||
s-1.6,2-2.2,2.8s-0.9,1.5-1.1,2.2c-0.2,0.7-0.1,1.8,0.2,3.2c0.3,1.4,0.4,2.4,0.4,3.1c-0.3,3-1.4,6.9-3.3,11.6
|
||||
c-1.9,4.7-3.6,8.1-5.1,10.1c-0.3,0.4-1.2,1.3-2.6,2.7c-1.4,1.4-2.3,2.6-2.6,3.7c-0.3,0.4-0.3,1.5-0.1,3.4c0.3,1.8,0.4,3.1,0.3,3.8
|
||||
c-0.3,2.7-1.3,6.3-3,10.8c-1.7,4.5-3.4,8.2-5,11c-0.2,0.5-0.9,1.4-2,2.8c-1.1,1.4-1.8,2.5-2,3.4c-0.2,0.6-0.1,1.8,0.1,3.4
|
||||
c0.2,1.6,0.2,2.8-0.1,3.6c-0.6,3-1.8,6.7-3.6,11c-1.8,4.3-3.6,7.9-5.4,11c-0.5,0.8-1.1,1.7-2,2.8c-0.8,1.1-1.5,2-2,2.8
|
||||
s-0.8,1.6-1,2.5c-0.1,0.5,0,1.3,0.4,2.3c0.3,1.1,0.4,1.9,0.4,2.6c-0.1,1.1-0.2,2.6-0.5,4.4c-0.2,1.8-0.4,2.9-0.4,3.2
|
||||
c-1.8,4.8-1.7,9.9,0.2,15.2c2.2,6.2,6.2,11.5,11.9,15.8c5.7,4.3,11.7,6.4,17.8,6.4h110.7c5.2,0,10.1-1.7,14.7-5.2s7.7-7.8,9.2-12.9
|
||||
l33-108.6c1.8-5.8,1-10.9-2.2-15.5C194.9,39.7,192.6,38,189.5,36.8z M59.6,122.8L73.8,80c0,0,7,0,10.8,0s28.8-1.7,25.4,17.5
|
||||
c-3.4,19.2-18.8,25.2-36.8,25.4S59.6,122.8,59.6,122.8z M78.6,116.8c4.7-0.1,18.9-2.9,22.1-17.1S89.2,86.3,89.2,86.3l-8.9,0
|
||||
l-10.2,30.5C70.2,116.9,74,116.9,78.6,116.8z M75.3,68.7L89,26.2h9.8l0.8,34l23.6-34h9.9l-13.6,42.5h-7.1l12.5-35.4l-24.5,35.4h-6.8
|
||||
l-0.8-35L82,68.7H75.3z"/>
|
||||
</svg>
|
||||
<!-- Original image Copyright Dave Gandy — CC BY 4.0 License -->
|
After Width: | Height: | Size: 1.8 KiB |
202
theme/fonts/OPEN-SANS-LICENSE.txt
Normal file
202
theme/fonts/OPEN-SANS-LICENSE.txt
Normal file
|
@ -0,0 +1,202 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
93
theme/fonts/SOURCE-CODE-PRO-LICENSE.txt
Normal file
93
theme/fonts/SOURCE-CODE-PRO-LICENSE.txt
Normal file
|
@ -0,0 +1,93 @@
|
|||
Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries.
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
100
theme/fonts/fonts.css
Normal file
100
theme/fonts/fonts.css
Normal file
|
@ -0,0 +1,100 @@
|
|||
/* Open Sans is licensed under the Apache License, Version 2.0. See http://www.apache.org/licenses/LICENSE-2.0 */
|
||||
/* Source Code Pro is under the Open Font License. See https://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL */
|
||||
|
||||
/* open-sans-300 - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: local('Open Sans Light'), local('OpenSans-Light'),
|
||||
url('open-sans-v17-all-charsets-300.woff2') format('woff2');
|
||||
}
|
||||
|
||||
/* open-sans-300italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
src: local('Open Sans Light Italic'), local('OpenSans-LightItalic'),
|
||||
url('open-sans-v17-all-charsets-300italic.woff2') format('woff2');
|
||||
}
|
||||
|
||||
/* open-sans-regular - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Open Sans Regular'), local('OpenSans-Regular'),
|
||||
url('open-sans-v17-all-charsets-regular.woff2') format('woff2');
|
||||
}
|
||||
|
||||
/* open-sans-italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
src: local('Open Sans Italic'), local('OpenSans-Italic'),
|
||||
url('open-sans-v17-all-charsets-italic.woff2') format('woff2');
|
||||
}
|
||||
|
||||
/* open-sans-600 - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
src: local('Open Sans SemiBold'), local('OpenSans-SemiBold'),
|
||||
url('open-sans-v17-all-charsets-600.woff2') format('woff2');
|
||||
}
|
||||
|
||||
/* open-sans-600italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
src: local('Open Sans SemiBold Italic'), local('OpenSans-SemiBoldItalic'),
|
||||
url('open-sans-v17-all-charsets-600italic.woff2') format('woff2');
|
||||
}
|
||||
|
||||
/* open-sans-700 - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local('Open Sans Bold'), local('OpenSans-Bold'),
|
||||
url('open-sans-v17-all-charsets-700.woff2') format('woff2');
|
||||
}
|
||||
|
||||
/* open-sans-700italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
src: local('Open Sans Bold Italic'), local('OpenSans-BoldItalic'),
|
||||
url('open-sans-v17-all-charsets-700italic.woff2') format('woff2');
|
||||
}
|
||||
|
||||
/* open-sans-800 - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
src: local('Open Sans ExtraBold'), local('OpenSans-ExtraBold'),
|
||||
url('open-sans-v17-all-charsets-800.woff2') format('woff2');
|
||||
}
|
||||
|
||||
/* open-sans-800italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: 800;
|
||||
src: local('Open Sans ExtraBold Italic'), local('OpenSans-ExtraBoldItalic'),
|
||||
url('open-sans-v17-all-charsets-800italic.woff2') format('woff2');
|
||||
}
|
||||
|
||||
/* source-code-pro-500 - latin_vietnamese_latin-ext_greek_cyrillic-ext_cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Source Code Pro';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: url('source-code-pro-v11-all-charsets-500.woff2') format('woff2');
|
||||
}
|
BIN
theme/fonts/open-sans-v17-all-charsets-300.woff2
Normal file
BIN
theme/fonts/open-sans-v17-all-charsets-300.woff2
Normal file
Binary file not shown.
BIN
theme/fonts/open-sans-v17-all-charsets-300italic.woff2
Normal file
BIN
theme/fonts/open-sans-v17-all-charsets-300italic.woff2
Normal file
Binary file not shown.
BIN
theme/fonts/open-sans-v17-all-charsets-600.woff2
Normal file
BIN
theme/fonts/open-sans-v17-all-charsets-600.woff2
Normal file
Binary file not shown.
BIN
theme/fonts/open-sans-v17-all-charsets-600italic.woff2
Normal file
BIN
theme/fonts/open-sans-v17-all-charsets-600italic.woff2
Normal file
Binary file not shown.
BIN
theme/fonts/open-sans-v17-all-charsets-700.woff2
Normal file
BIN
theme/fonts/open-sans-v17-all-charsets-700.woff2
Normal file
Binary file not shown.
BIN
theme/fonts/open-sans-v17-all-charsets-700italic.woff2
Normal file
BIN
theme/fonts/open-sans-v17-all-charsets-700italic.woff2
Normal file
Binary file not shown.
BIN
theme/fonts/open-sans-v17-all-charsets-800.woff2
Normal file
BIN
theme/fonts/open-sans-v17-all-charsets-800.woff2
Normal file
Binary file not shown.
BIN
theme/fonts/open-sans-v17-all-charsets-800italic.woff2
Normal file
BIN
theme/fonts/open-sans-v17-all-charsets-800italic.woff2
Normal file
Binary file not shown.
BIN
theme/fonts/open-sans-v17-all-charsets-italic.woff2
Normal file
BIN
theme/fonts/open-sans-v17-all-charsets-italic.woff2
Normal file
Binary file not shown.
BIN
theme/fonts/open-sans-v17-all-charsets-regular.woff2
Normal file
BIN
theme/fonts/open-sans-v17-all-charsets-regular.woff2
Normal file
Binary file not shown.
BIN
theme/fonts/source-code-pro-v11-all-charsets-500.woff2
Normal file
BIN
theme/fonts/source-code-pro-v11-all-charsets-500.woff2
Normal file
Binary file not shown.
82
theme/highlight.css
Normal file
82
theme/highlight.css
Normal file
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* An increased contrast highlighting scheme loosely based on the
|
||||
* "Base16 Atelier Dune Light" theme by Bram de Haan
|
||||
* (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/dune)
|
||||
* Original Base16 color scheme by Chris Kempson
|
||||
* (https://github.com/chriskempson/base16)
|
||||
*/
|
||||
|
||||
/* Comment */
|
||||
.hljs-comment,
|
||||
.hljs-quote {
|
||||
color: #575757;
|
||||
}
|
||||
|
||||
/* Red */
|
||||
.hljs-variable,
|
||||
.hljs-template-variable,
|
||||
.hljs-attribute,
|
||||
.hljs-tag,
|
||||
.hljs-name,
|
||||
.hljs-regexp,
|
||||
.hljs-link,
|
||||
.hljs-name,
|
||||
.hljs-selector-id,
|
||||
.hljs-selector-class {
|
||||
color: #d70025;
|
||||
}
|
||||
|
||||
/* Orange */
|
||||
.hljs-number,
|
||||
.hljs-meta,
|
||||
.hljs-built_in,
|
||||
.hljs-builtin-name,
|
||||
.hljs-literal,
|
||||
.hljs-type,
|
||||
.hljs-params {
|
||||
color: #b21e00;
|
||||
}
|
||||
|
||||
/* Green */
|
||||
.hljs-string,
|
||||
.hljs-symbol,
|
||||
.hljs-bullet {
|
||||
color: #008200;
|
||||
}
|
||||
|
||||
/* Blue */
|
||||
.hljs-title,
|
||||
.hljs-section {
|
||||
color: #0030f2;
|
||||
}
|
||||
|
||||
/* Purple */
|
||||
.hljs-keyword,
|
||||
.hljs-selector-tag {
|
||||
color: #9d00ec;
|
||||
}
|
||||
|
||||
.hljs {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
background: #f6f7f6;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.hljs-emphasis {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.hljs-strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.hljs-addition {
|
||||
color: #22863a;
|
||||
background-color: #f0fff4;
|
||||
}
|
||||
|
||||
.hljs-deletion {
|
||||
color: #b31d28;
|
||||
background-color: #ffeef0;
|
||||
}
|
54
theme/highlight.js
Normal file
54
theme/highlight.js
Normal file
File diff suppressed because one or more lines are too long
346
theme/index.hbs
Normal file
346
theme/index.hbs
Normal file
|
@ -0,0 +1,346 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html lang="{{ language }}" class="{{ default_theme }}" dir="{{ text_direction }}">
|
||||
<head>
|
||||
<!-- Book generated using mdBook -->
|
||||
<meta charset="UTF-8">
|
||||
<title>{{ title }}</title>
|
||||
{{#if is_print }}
|
||||
<meta name="robots" content="noindex">
|
||||
{{/if}}
|
||||
{{#if base_url}}
|
||||
<base href="{{ base_url }}">
|
||||
{{/if}}
|
||||
|
||||
|
||||
<!-- Custom HTML head -->
|
||||
{{> head}}
|
||||
|
||||
<meta name="description" content="{{ description }}">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
|
||||
{{#if favicon_svg}}
|
||||
<link rel="icon" href="{{ path_to_root }}favicon.svg">
|
||||
{{/if}}
|
||||
{{#if favicon_png}}
|
||||
<link rel="shortcut icon" href="{{ path_to_root }}favicon.png">
|
||||
{{/if}}
|
||||
<link rel="stylesheet" href="{{ path_to_root }}css/variables.css">
|
||||
<link rel="stylesheet" href="{{ path_to_root }}css/general.css">
|
||||
<link rel="stylesheet" href="{{ path_to_root }}css/chrome.css">
|
||||
{{#if print_enable}}
|
||||
<link rel="stylesheet" href="{{ path_to_root }}css/print.css" media="print">
|
||||
{{/if}}
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="stylesheet" href="{{ path_to_root }}FontAwesome/css/font-awesome.css">
|
||||
{{#if copy_fonts}}
|
||||
<link rel="stylesheet" href="{{ path_to_root }}fonts/fonts.css">
|
||||
{{/if}}
|
||||
|
||||
<!-- Highlight.js Stylesheets -->
|
||||
<link rel="stylesheet" href="{{ path_to_root }}highlight.css">
|
||||
<link rel="stylesheet" href="{{ path_to_root }}tomorrow-night.css">
|
||||
<link rel="stylesheet" href="{{ path_to_root }}ayu-highlight.css">
|
||||
|
||||
<!-- Custom theme stylesheets -->
|
||||
{{#each additional_css}}
|
||||
<link rel="stylesheet" href="{{ ../path_to_root }}{{ this }}">
|
||||
{{/each}}
|
||||
|
||||
{{#if mathjax_support}}
|
||||
<!-- MathJax -->
|
||||
<script async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
|
||||
{{/if}}
|
||||
</head>
|
||||
<body class="sidebar-visible no-js">
|
||||
<div id="body-container">
|
||||
<!-- Provide site root to javascript -->
|
||||
<script>
|
||||
var path_to_root = "{{ path_to_root }}";
|
||||
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "{{ preferred_dark_theme }}" : "{{ default_theme }}";
|
||||
</script>
|
||||
|
||||
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
||||
<script>
|
||||
try {
|
||||
var theme = localStorage.getItem('mdbook-theme');
|
||||
var sidebar = localStorage.getItem('mdbook-sidebar');
|
||||
|
||||
if (theme.startsWith('"') && theme.endsWith('"')) {
|
||||
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
|
||||
}
|
||||
|
||||
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
|
||||
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
|
||||
}
|
||||
} catch (e) { }
|
||||
</script>
|
||||
|
||||
<!-- Set the theme before any content is loaded, prevents flash -->
|
||||
<script>
|
||||
var theme;
|
||||
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
||||
if (theme === null || theme === undefined) { theme = default_theme; }
|
||||
var html = document.querySelector('html');
|
||||
html.classList.remove('{{ default_theme }}')
|
||||
html.classList.add(theme);
|
||||
var body = document.querySelector('body');
|
||||
body.classList.remove('no-js')
|
||||
body.classList.add('js');
|
||||
</script>
|
||||
|
||||
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
|
||||
|
||||
<!-- Hide / unhide sidebar before it is displayed -->
|
||||
<script>
|
||||
var body = document.querySelector('body');
|
||||
var sidebar = null;
|
||||
var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
|
||||
if (document.body.clientWidth >= 1080) {
|
||||
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
|
||||
sidebar = sidebar || 'visible';
|
||||
} else {
|
||||
sidebar = 'hidden';
|
||||
}
|
||||
sidebar_toggle.checked = sidebar === 'visible';
|
||||
body.classList.remove('sidebar-visible');
|
||||
body.classList.add("sidebar-" + sidebar);
|
||||
</script>
|
||||
|
||||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||||
<div class="sidebar-scrollbox">
|
||||
{{#toc}}{{/toc}}
|
||||
</div>
|
||||
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
|
||||
<div class="sidebar-resize-indicator"></div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Track and set sidebar scroll position -->
|
||||
<script>
|
||||
var sidebarScrollbox = document.querySelector('#sidebar .sidebar-scrollbox');
|
||||
sidebarScrollbox.addEventListener('click', function(e) {
|
||||
if (e.target.tagName === 'A') {
|
||||
sessionStorage.setItem('sidebar-scroll', sidebarScrollbox.scrollTop);
|
||||
}
|
||||
}, { passive: true });
|
||||
var sidebarScrollTop = sessionStorage.getItem('sidebar-scroll');
|
||||
sessionStorage.removeItem('sidebar-scroll');
|
||||
if (sidebarScrollTop) {
|
||||
// preserve sidebar scroll position when navigating via links within sidebar
|
||||
sidebarScrollbox.scrollTop = sidebarScrollTop;
|
||||
} else {
|
||||
// scroll sidebar to current active section when navigating via "next/previous chapter" buttons
|
||||
var activeSection = document.querySelector('#sidebar .active');
|
||||
if (activeSection) {
|
||||
activeSection.scrollIntoView({ block: 'center' });
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div id="page-wrapper" class="page-wrapper">
|
||||
|
||||
<div class="page">
|
||||
{{> header}}
|
||||
<div id="menu-bar-hover-placeholder"></div>
|
||||
<div id="menu-bar" class="menu-bar sticky">
|
||||
<div class="left-buttons">
|
||||
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
|
||||
<i class="fa fa-bars"></i>
|
||||
</label>
|
||||
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
|
||||
<i class="fa fa-paint-brush"></i>
|
||||
</button>
|
||||
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
|
||||
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
|
||||
</ul>
|
||||
{{#if search_enabled}}
|
||||
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
|
||||
<i class="fa fa-search"></i>
|
||||
</button>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<h1 class="menu-title">{{ book_title }}</h1>
|
||||
|
||||
<div class="right-buttons">
|
||||
{{#if print_enable}}
|
||||
<a href="{{ path_to_root }}print.html" title="Print this book" aria-label="Print this book">
|
||||
<i id="print-button" class="fa fa-print"></i>
|
||||
</a>
|
||||
{{/if}}
|
||||
{{#if git_repository_url}}
|
||||
<a href="{{git_repository_url}}" title="Git repository" aria-label="Git repository">
|
||||
<i id="git-repository-button" class="fa {{git_repository_icon}}"></i>
|
||||
</a>
|
||||
{{/if}}
|
||||
{{#if git_repository_edit_url}}
|
||||
<a href="{{git_repository_edit_url}}" title="Suggest an edit" aria-label="Suggest an edit">
|
||||
<i id="git-edit-button" class="fa fa-edit"></i>
|
||||
</a>
|
||||
{{/if}}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if search_enabled}}
|
||||
<div id="search-wrapper" class="hidden">
|
||||
<form id="searchbar-outer" class="searchbar-outer">
|
||||
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
|
||||
</form>
|
||||
<div id="searchresults-outer" class="searchresults-outer hidden">
|
||||
<div id="searchresults-header" class="searchresults-header"></div>
|
||||
<ul id="searchresults">
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
|
||||
<script>
|
||||
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
|
||||
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
|
||||
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
|
||||
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div id="content" class="content">
|
||||
<main>
|
||||
{{{ content }}}
|
||||
</main>
|
||||
|
||||
<nav class="nav-wrapper" aria-label="Page navigation">
|
||||
<!-- Mobile navigation buttons -->
|
||||
{{#previous}}
|
||||
<a rel="prev" href="{{ path_to_root }}{{link}}" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
{{/previous}}
|
||||
|
||||
{{#next}}
|
||||
<a rel="next prefetch" href="{{ path_to_root }}{{link}}" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
{{/next}}
|
||||
|
||||
<div style="clear: both"></div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
||||
{{#previous}}
|
||||
<a rel="prev" href="{{ path_to_root }}{{link}}" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
{{/previous}}
|
||||
|
||||
{{#next}}
|
||||
<a rel="next prefetch" href="{{ path_to_root }}{{link}}" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
{{/next}}
|
||||
</nav>
|
||||
|
||||
</div>
|
||||
|
||||
{{#if live_reload_endpoint}}
|
||||
<!-- Livereload script (if served using the cli tool) -->
|
||||
<script>
|
||||
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const wsAddress = wsProtocol + "//" + location.host + "/" + "{{{live_reload_endpoint}}}";
|
||||
const socket = new WebSocket(wsAddress);
|
||||
socket.onmessage = function (event) {
|
||||
if (event.data === "reload") {
|
||||
socket.close();
|
||||
location.reload();
|
||||
}
|
||||
};
|
||||
|
||||
window.onbeforeunload = function() {
|
||||
socket.close();
|
||||
}
|
||||
</script>
|
||||
{{/if}}
|
||||
|
||||
{{#if google_analytics}}
|
||||
<!-- Google Analytics Tag -->
|
||||
<script>
|
||||
var localAddrs = ["localhost", "127.0.0.1", ""];
|
||||
|
||||
// make sure we don't activate google analytics if the developer is
|
||||
// inspecting the book locally...
|
||||
if (localAddrs.indexOf(document.location.hostname) === -1) {
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
||||
|
||||
ga('create', '{{google_analytics}}', 'auto');
|
||||
ga('send', 'pageview');
|
||||
}
|
||||
</script>
|
||||
{{/if}}
|
||||
|
||||
{{#if playground_line_numbers}}
|
||||
<script>
|
||||
window.playground_line_numbers = true;
|
||||
</script>
|
||||
{{/if}}
|
||||
|
||||
{{#if playground_copyable}}
|
||||
<script>
|
||||
window.playground_copyable = true;
|
||||
</script>
|
||||
{{/if}}
|
||||
|
||||
{{#if playground_js}}
|
||||
<script src="{{ path_to_root }}ace.js"></script>
|
||||
<script src="{{ path_to_root }}editor.js"></script>
|
||||
<script src="{{ path_to_root }}mode-rust.js"></script>
|
||||
<script src="{{ path_to_root }}theme-dawn.js"></script>
|
||||
<script src="{{ path_to_root }}theme-tomorrow_night.js"></script>
|
||||
{{/if}}
|
||||
|
||||
{{#if search_js}}
|
||||
<script src="{{ path_to_root }}elasticlunr.min.js"></script>
|
||||
<script src="{{ path_to_root }}mark.min.js"></script>
|
||||
<script src="{{ path_to_root }}searcher.js"></script>
|
||||
{{/if}}
|
||||
|
||||
<script src="{{ path_to_root }}clipboard.min.js"></script>
|
||||
<script src="{{ path_to_root }}highlight.js"></script>
|
||||
<script src="{{ path_to_root }}book.js"></script>
|
||||
|
||||
<!-- Custom JS scripts -->
|
||||
{{#each additional_js}}
|
||||
<script src="{{ ../path_to_root }}{{this}}"></script>
|
||||
{{/each}}
|
||||
|
||||
{{#if is_print}}
|
||||
{{#if mathjax_support}}
|
||||
<script>
|
||||
window.addEventListener('load', function() {
|
||||
MathJax.Hub.Register.StartupHook('End', function() {
|
||||
window.setTimeout(window.print, 100);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{{else}}
|
||||
<script>
|
||||
window.addEventListener('load', function() {
|
||||
window.setTimeout(window.print, 100);
|
||||
});
|
||||
</script>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
Loading…
Add table
Reference in a new issue