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
|
## Authors Note
|
||||||
|
|
||||||
This entire book is a single literate code base, the source code is available at
|
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
|
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
|
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
|
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
|
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:
|
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
|
## Index of non-day modules
|
||||||
|
|
||||||
|
@ -59,26 +59,6 @@ solution.
|
||||||
Provider wrappers over the standard library `IOArray` type to make them more
|
Provider wrappers over the standard library `IOArray` type to make them more
|
||||||
ergonomic to use.
|
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
|
## Index of years and days
|
||||||
|
|
||||||
- 2015
|
- 2015
|
||||||
|
|
|
@ -19,7 +19,6 @@ depends = base
|
||||||
, tailrec
|
, tailrec
|
||||||
, eff
|
, eff
|
||||||
, elab-util
|
, elab-util
|
||||||
, sop
|
|
||||||
, ansi
|
, ansi
|
||||||
, if-unsolved-implicit
|
, if-unsolved-implicit
|
||||||
, c-ffi
|
, c-ffi
|
||||||
|
@ -31,10 +30,6 @@ modules = Runner
|
||||||
, Util.Eff
|
, Util.Eff
|
||||||
, Util.Digits
|
, Util.Digits
|
||||||
, Array
|
, Array
|
||||||
, Parser
|
|
||||||
, Parser.Interface
|
|
||||||
, Parser.Numbers
|
|
||||||
, Parser.JSON
|
|
||||||
|
|
||||||
-- main file (i.e. file to load at REPL)
|
-- main file (i.e. file to load at REPL)
|
||||||
main = Main
|
main = Main
|
||||||
|
|
|
@ -8,6 +8,3 @@ title = "Idris 2 by Highly Contrived Example"
|
||||||
[build]
|
[build]
|
||||||
create-missing = false
|
create-missing = false
|
||||||
use-default-preprocessors = 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
|
# TODO: Post process them to set themeing correctly
|
||||||
$output ~~ s:g/'<style>' .* '</style>'//;
|
$output ~~ s:g/'<style>' .* '</style>'//;
|
||||||
$output ~~ s:g/'<br />'//;
|
$output ~~ s:g/'<br />'//;
|
||||||
$output ~~ s:g/'\\*'/*/;
|
|
||||||
$output ~~ s:g/'\\_'/_/;
|
|
||||||
$output ~~ s:g/'\\\\'/\\/;
|
|
||||||
$output ~~ s:g/'<code'/<pre><code/;
|
$output ~~ s:g/'<code'/<pre><code/;
|
||||||
$output ~~ s:g/'</code>'/<\/code><\/pre>/;
|
$output ~~ s:g/'</code>'/<\/code><\/pre>/;
|
||||||
$output ~~ s:g/'class="IdrisKeyword"'/class="hljs-keyword"/;
|
$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.Eff - Effects and Effect Accessories](Util/Eff.md)
|
||||||
- [Util.Digits - Pattern Matching Integers as Lists of Digits](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)
|
- [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
|
# Problems
|
||||||
|
|
||||||
|
|
|
@ -235,4 +235,4 @@ day11 = Both 11 part1 part2
|
||||||
|
|
||||||
## References
|
## 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