Merge pull request from lfairy/new-docs

Redesign the book
This commit is contained in:
Chris Wong 2019-03-25 19:53:49 +13:00 committed by GitHub
commit be10b9837e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 1980 additions and 2 deletions

3
.gitignore vendored
View file

@ -1,2 +1,3 @@
target
Cargo.lock
/Cargo.lock
docs/site

View file

@ -7,4 +7,5 @@ members = [
]
exclude = [
"benchmarks",
"docs",
]

View file

@ -12,7 +12,7 @@ Note that Maud depends on the unstable [procedural macro API][rustissue], and so
For more info on Maud, see the [official book][book].
[book]: https://maud.lambda.xyz/
[booksrc]: https://github.com/lfairy/maud-book
[booksrc]: https://github.com/lfairy/maud/tree/master/docs
[apiref]: https://docs.rs/maud/
[changelog]: https://github.com/lfairy/maud/blob/master/CHANGELOG.md
[rustissue]: https://github.com/rust-lang/rust/issues/38356

743
docs/Cargo.lock generated Normal file
View file

@ -0,0 +1,743 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "adler32"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "aho-corasick"
version = "0.6.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ansi_term"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "arrayref"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "atty"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
"termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "autocfg"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "base64"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "bincode"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "bitflags"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "block-buffer"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "build_const"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "byte-tools"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "byteorder"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "cc"
version = "1.0.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "cfg-if"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "clap"
version = "2.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "comrak"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)",
"entities 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"pest 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"pest_derive 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"twoway 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"typed-arena 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode_categories 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "crc"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"build_const 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "crc32fast"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "digest"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "docs"
version = "0.0.0"
dependencies = [
"comrak 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
"maud 0.20.0",
"serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)",
"syntect 3.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "entities"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "fake-simd"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "flate2"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
"miniz-sys 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
"miniz_oxide_c_api 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "fnv"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "generic-array"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "humantime"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "itoa"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "lazy_static"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "lazycell"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "libc"
version = "0.2.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "line-wrap"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"safemem 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "linked-hash-map"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "literalext"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "maplit"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "matches"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "maud"
version = "0.20.0"
dependencies = [
"maud_htmlescape 0.17.0",
"maud_macros 0.20.0",
]
[[package]]
name = "maud_htmlescape"
version = "0.17.0"
[[package]]
name = "maud_macros"
version = "0.20.0"
dependencies = [
"literalext 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"maud_htmlescape 0.17.0",
]
[[package]]
name = "memchr"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "miniz-sys"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cc 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "miniz_oxide"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "miniz_oxide_c_api"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cc 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)",
"crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
"miniz_oxide 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "onig"
version = "4.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
"onig_sys 69.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "onig_sys"
version = "69.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cc 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)",
"pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "pest"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"ucd-trie 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "pest_derive"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"pest 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"pest_generator 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "pest_generator"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"pest 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"pest_meta 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "pest_meta"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"maplit 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"pest 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"sha-1 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "pkg-config"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "plist"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"line-wrap 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)",
"xml-rs 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "proc-macro2"
version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "quick-error"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "quote"
version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "redox_syscall"
version = "0.1.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "redox_termios"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "regex"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"aho-corasick 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-syntax 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
"thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "regex-syntax"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ryu"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "safemem"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "same-file"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "serde"
version = "1.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "serde_derive"
version = "1.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "serde_json"
version = "1.0.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
"ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "sha-1"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"block-buffer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)",
"fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "strsim"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "syn"
version = "0.15.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "syntect"
version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bincode 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"flate2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"onig 4.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"plist 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-syntax 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)",
"walkdir 2.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
"yaml-rust 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "termion"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "textwrap"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "thread_local"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "twoway"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "typed-arena"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "typenum"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "ucd-trie"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "ucd-util"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unicode-width"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unicode-xid"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unicode_categories"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "utf8-ranges"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "vec_map"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "walkdir"
version = "2.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"same-file 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "winapi"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi-util"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "xml-rs"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "yaml-rust"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"linked-hash-map 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[metadata]
"checksum adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7e522997b529f05601e05166c07ed17789691f562762c7f3b987263d2dedee5c"
"checksum aho-corasick 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "81ce3d38065e618af2d7b77e10c5ad9a069859b4be3c2250f674af3840d9c8a5"
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
"checksum arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee"
"checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652"
"checksum autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a6d640bee2da49f60a4068a7fae53acde8982514ab7bae8b8cea9e88cbcfd799"
"checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e"
"checksum bincode 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3efe0b4c8eaeed8600549c29f538a6a11bf422858d0ed435b1d70ec4ab101190"
"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12"
"checksum block-buffer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a076c298b9ecdb530ed9d967e74a6027d6a7478924520acddcddc24c1c8ab3ab"
"checksum build_const 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "39092a32794787acd8525ee150305ff051b0aa6cc2abaf193924f5ab05425f39"
"checksum byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40"
"checksum byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a019b10a2a7cdeb292db131fc8113e57ea2a908f6e7894b0c3c671893b65dbeb"
"checksum cc 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)" = "c9ce8bb087aacff865633f0bd5aeaed910fe2fe55b55f4739527f2e023a2e53d"
"checksum cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "11d43355396e872eefb45ce6342e4374ed7bc2b3a502d1b28e36d6e23c05d1f4"
"checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e"
"checksum comrak 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "6c6a70fb894be1bf51ca939f9e9008d93679908541793a6bf752bd313640a1d8"
"checksum crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d663548de7f5cca343f1e0a48d14dcfb0e9eb4e079ec58883b7251539fa10aeb"
"checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1"
"checksum digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "03b072242a8cbaf9c145665af9d250c59af3b958f83ed6824e13533cf76d5b90"
"checksum entities 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca"
"checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
"checksum flate2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f87e68aa82b2de08a6e037f1385455759df6e445a8df5e005b4297191dbf18aa"
"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3"
"checksum generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ef25c5683767570c2bbd7deba372926a55eaae9982d7726ee2a1050239d45b9d"
"checksum humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca7e5f2e110db35f93b837c81797f3714500b81d517bf20c431b16d3ca4f114"
"checksum itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b"
"checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14"
"checksum lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f"
"checksum libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)" = "aab692d7759f5cd8c859e169db98ae5b52c924add2af5fbbca11d12fefb567c1"
"checksum line-wrap 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f30344350a2a51da54c1d53be93fade8a237e545dbcc4bdbe635413f2117cab9"
"checksum linked-hash-map 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "70fb39025bc7cdd76305867c4eccf2f2dcf6e9a57f5b21a93e1c2d86cd03ec9e"
"checksum literalext 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2f42dd699527975a1e0d722e0707998671188a0125f2051d2d192fc201184a81"
"checksum maplit 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "08cbb6b4fef96b6d77bfc40ec491b1690c779e77b05cd9f07f787ed376fd4c43"
"checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
"checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39"
"checksum miniz-sys 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "0300eafb20369952951699b68243ab4334f4b10a88f411c221d444b36c40e649"
"checksum miniz_oxide 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c468f2369f07d651a5d0bb2c9079f8488a66d5466efe42d0c5c6466edcb7f71e"
"checksum miniz_oxide_c_api 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b7fe927a42e3807ef71defb191dc87d4e24479b221e67015fe38ae2b7b447bab"
"checksum onig 4.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a646989adad8a19f49be2090374712931c3a59835cb5277b4530f48b417f26e7"
"checksum onig_sys 69.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388410bf5fa341f10e58e6db3975f4bea1ac30247dd79d37a9e5ced3cb4cc3b0"
"checksum pest 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "54f0c72a98d8ab3c99560bfd16df8059cc10e1f9a8e83e6e3b97718dd766e9c3"
"checksum pest_derive 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0"
"checksum pest_generator 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "63120576c4efd69615b5537d3d052257328a4ca82876771d6944424ccfd9f646"
"checksum pest_meta 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f5a3492a4ed208ffc247adcdcc7ba2a95be3104f58877d0d02f0df39bf3efb5e"
"checksum pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "676e8eb2b1b4c9043511a9b7bea0915320d7e502b0a079fb03f9635a5252b18c"
"checksum plist 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f4739851c08dd9a62a78beff2edf1a438517268b2c563c42fc6d9d3139e42d2a"
"checksum proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)" = "4d317f9caece796be1980837fd5cb3dfec5613ebdb04ad0956deea83ce168915"
"checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0"
"checksum quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)" = "cdd8e04bd9c52e0342b406469d494fcb033be4bdbe5c606016defbb1681411e1"
"checksum redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)" = "423e376fffca3dfa06c9e9790a9ccd282fafb3cc6e6397d01dbf64f9bacc6b85"
"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
"checksum regex 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "53ee8cfdddb2e0291adfb9f13d31d3bbe0a03c9a402c01b1e24188d86c35b24f"
"checksum regex-syntax 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "8c2f35eedad5295fdf00a63d7d4b238135723f92b434ec06774dad15c7ab0861"
"checksum ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "eb9e9b8cde282a9fe6a42dd4681319bfb63f121b8a8ee9439c6f4107e58a46f7"
"checksum safemem 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8dca453248a96cb0749e36ccdfe2b0b4e54a61bfef89fb97ec621eb8e0a93dd9"
"checksum same-file 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8f20c4be53a8a1ff4c1f1b2bd14570d2f634628709752f0702ecdd2b3f9a5267"
"checksum serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)" = "92514fb95f900c9b5126e32d020f5c6d40564c27a5ea6d1d7d9f157a96623560"
"checksum serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)" = "bb6eabf4b5914e88e24eea240bb7c9f9a2cbc1bbbe8d961d381975ec3c6b806c"
"checksum serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)" = "5a23aa71d4a4d43fdbfaac00eff68ba8a06a51759a89ac3304323e800c4dd40d"
"checksum sha-1 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "51b9d1f3b5de8a167ab06834a7c883bd197f2191e1dda1a22d9ccfeedbf9aded"
"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550"
"checksum syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)" = "1825685f977249735d510a242a6727b46efe914bb67e38d30c071b1b72b1d5c2"
"checksum syntect 3.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e80b8831c5a543192ffc3727f01cf0e57579c6ac15558e3048bfb5708892167b"
"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096"
"checksum textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "307686869c93e71f94da64286f9a9524c0f308a9e1c87a583de8e9c9039ad3f6"
"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b"
"checksum twoway 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1"
"checksum typed-arena 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c6c06a92aef38bb4dc5b0df00d68496fc31307c5344c867bb61678c6e1671ec5"
"checksum typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "612d636f949607bdf9b123b4a6f6d966dedf3ff669f7f045890d3a4a73948169"
"checksum ucd-trie 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "71a9c5b1fe77426cf144cc30e49e955270f5086e31a6441dfa8b32efc09b9d77"
"checksum ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86"
"checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526"
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
"checksum unicode_categories 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
"checksum utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "796f7e48bef87609f7ade7e06495a87d5cd06c7866e6a5cbfceffc558a243737"
"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
"checksum walkdir 2.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "9d9d7ed3431229a144296213105a390676cc49c9b6a72bd19f3176c98e129fa1"
"checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0"
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
"checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9"
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
"checksum xml-rs 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "541b12c998c5b56aa2b4e6f18f03664eef9a4fd0a246a55594efae6cc2d964b5"
"checksum yaml-rust 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "65923dd1784f44da1d2c3dbbc5e822045628c590ba72123e1c73d3c230c4434d"

17
docs/Cargo.toml Normal file
View file

@ -0,0 +1,17 @@
[package]
name = "docs"
version = "0.0.0"
authors = ["Chris Wong <lambda.fairy@gmail.com>"]
license = "CC-BY-SA-4.0"
repository = "https://github.com/lfairy/maud"
description = "Documentation for Maud."
edition = "2018"
[dependencies]
comrak = "*"
maud = { path = "../maud" }
serde_json = "*"
syntect = "*"

38
docs/Makefile Normal file
View file

@ -0,0 +1,38 @@
slugs := index getting-started basic-syntax dynamic-content partials control-structures traits web-frameworks faq
slug_to_md = content/$(1).md
slug_to_html = site/$(1).html
builder := target/debug/docs
nav_json := site/nav.json
md_files := $(foreach slug,$(slugs),$(call slug_to_md,$(slug)))
html_files := $(foreach slug,$(slugs),$(call slug_to_html,$(slug)))
slugs_and_md_files := $(foreach slug,$(slugs),$(slug):$(call slug_to_md,$(slug)))
print_status = @ printf ' \e[1;35m♦ %s\e[0m\n' '$(1)'
.PHONY: all
all: $(html_files) site/styles.css
$(builder): $(wildcard src/*)
$(call print_status,Cargo)
@ cargo build
$(nav_json): $(md_files) $(builder)
$(call print_status,Table of contents)
@ $(builder) build-nav $@ $(slugs_and_md_files)
site/%.html: content/%.md $(nav_json) $(builder)
$(call print_status,Render $(*F))
@ $(builder) build-page $@ $(*F) $< $(nav_json)
site/styles.css: styles.css
$(call print_status,Copy stylesheet)
@ mkdir -p $(dir $@)
@ cp $^ $@
.PHONY: clean
clean:
$(call print_status,Clean)
@ cargo clean
@ rm -fr site

View file

@ -0,0 +1,149 @@
# Basic syntax
The next few sections will outline the syntax used by Maud templates.
## Literals `""`
Literal strings use the same syntax as Rust. Wrap them in double quotes, and use a backslash for escapes.
```rust
html! {
"Oatmeal, are you crazy?"
}
```
### Escaping and `PreEscaped`
By default, HTML special characters are escaped automatically. Wrap the string in `(PreEscaped())` to disable this escaping. (See the section on [dynamic content] to learn more about how this works.)
```rust
use maud::PreEscaped;
html! {
"<script>alert(\"XSS\")</script>" // &lt;script&gt;...
(PreEscaped("<script>alert(\"XSS\")</script>")) // <script>...
}
```
[dynamic content]: dynamic-content.md
If the string is long, or contains many special characters, then it may be worth using [raw strings] instead:
```rust
use maud::PreEscaped;
html! {
(PreEscaped(r#"
<script>
alert("Look ma, no backslashes!");
</script>
"#))
}
```
[raw strings]: https://doc.rust-lang.org/reference/tokens.html#raw-string-literals
If you want to add a `<!DOCTYPE html>` declaration to your page, you may use the `maud::DOCTYPE` constant instead of writing it out by hand:
```rust
use maud::DOCTYPE;
html! {
(DOCTYPE) // <!DOCTYPE html>
}
```
## Elements `p`
Write an element using curly braces: `p { ... }`.
Terminate a void element using a semicolon: `br;`. Note that the result will be rendered with HTML syntax `<br>` not `<br />`.
```rust
html! {
h1 { "Poem" }
p {
"Rock, you are a rock."
br;
"Gray, you are gray,"
br;
"Like a rock, which you are."
br;
"Rock."
}
}
```
Maud also supports ending a void element with a slash: `br /`. This syntax is [deprecated][#96] and should not be used in new code.
[#96]: https://github.com/lfairy/maud/pull/96
Before version 0.18, Maud allowed the curly braces to be omitted. This syntax was [removed][#137] and now causes an error instead.
[#137]: https://github.com/lfairy/maud/pull/137
## Non-empty attributes `id="yay"`
```rust
html! {
ul {
li {
a href="about:blank" { "Apple Bloom" }
}
li class="lower-middle" {
"Sweetie Belle"
}
li dir="rtl" {
"Scootaloo "
small { "(also a chicken)" }
}
}
}
```
Add attributes using the syntax: `attr="value"`. You can attach any number of attributes to an element. The values must be quoted: they are parsed as string literals.
## Empty attributes `checked?` `disabled?[foo]`
Declare an empty attribute using a `?` suffix: `checked?`.
```rust
html! {
form {
input type="checkbox" name="cupcakes" checked?;
" "
label for="cupcakes" { "Do you like cupcakes?" }
}
}
```
To toggle an attribute based on a boolean flag, use a `?[]` suffix instead: `checked?[foo]`. This will check the value of `foo` at runtime, inserting the attribute only if `foo` equals `true`.
```rust
let allow_editing = true;
html! {
p contenteditable?[allow_editing] {
"Edit me, I "
em { "dare" }
" you."
}
}
```
## Classes and IDs `.foo` `#bar`
Add classes and IDs to an element using `.foo` and `#bar` syntax. You can chain multiple classes and IDs together, and mix and match them with other attributes:
```rust
html! {
div.container#main {
input.big.scary.bright-red type="button" value="Launch Party Cannon";
}
}
```
To toggle a class based on a boolean flag, use a `[]` suffix: `.foo[is_foo]`. This will check the value of `is_foo` at runtime, inserting that class value `foo` in the class attribute only if `is_foo` is `true`.
```rust
let cuteness = 95;
html! {
p.cute[cuteness > 50] { "Squee!" }
}
```

View file

@ -0,0 +1,109 @@
# Control structures
Maud provides various control structures for adding dynamic elements to your templates.
## Branching with `@if` and `@else`
Use `@if` and `@else` to branch on a boolean expression. As with Rust, braces are mandatory and the `@else` clause is optional.
```rust
#[derive(PartialEq)]
enum Princess { Celestia, Luna, Cadance, TwilightSparkle }
let user = Princess::Celestia;
html! {
@if user == Princess::Luna {
h1 { "Super secret woona to-do list" }
ul {
li { "Nuke the Crystal Empire" }
li { "Kick a puppy" }
li { "Evil laugh" }
}
} @else if user == Princess::Celestia {
p { "Sister, please stop reading my diary. It's rude." }
} @else {
p { "Nothing to see here; move along." }
}
}
```
`@if let` is supported as well. It works as you'd expect:
```rust
let user = Some("Pinkie Pie");
html! {
p {
"Hello, "
@if let Some(name) = user {
(name)
} @else {
"stranger"
}
"!"
}
}
```
## Looping with `@for`
Use `@for .. in ..` to loop over the elements of an iterator.
```rust
let names = ["Applejack", "Rarity", "Fluttershy"];
html! {
p { "My favorite ponies are:" }
ol {
@for name in &names {
li { (name) }
}
}
}
```
## Declaring variables with `@let`
Declare a new variable within a template using `@let`. This can be useful when working with values in a for loop.
```rust
let names = ["Applejack", "Rarity", "Fluttershy"];
html! {
@for name in &names {
@let first_letter = name.chars().next().unwrap();
p {
"The first letter of "
b { (name) }
" is "
b { (first_letter) }
"."
}
}
}
```
## Matching with `@match`
Pattern matching is supported with `@match`.
```rust
enum Princess { Celestia, Luna, Cadance, TwilightSparkle }
let user = Princess::Celestia;
html! {
@match user {
Princess::Luna => {
h1 { "Super secret woona to-do list" }
ul {
li { "Nuke the Crystal Empire" }
li { "Kick a puppy" }
li { "Evil laugh" }
}
},
Princess::Celestia => {
p { "Sister, please stop reading my diary. It's rude." }
},
_ => p { "Nothing to see here; move along." }
}
}
```

View file

@ -0,0 +1,71 @@
# Dynamic content
Use `(foo)` syntax to splice in the value of `foo` at runtime. Any HTML special characters are escaped by default.
```rust
let best_pony = "Pinkie Pie";
let numbers = [1, 2, 3, 4];
html! {
p { "Hi, " (best_pony) "!" }
p {
"I have " (numbers.len()) " numbers, "
"and the first one is " (numbers[0])
}
}
```
Arbitrary Rust code can be included in a splice by using a [block](https://doc.rust-lang.org/reference.html#block-expressions). This can be helpful for complex expressions that would be difficult to read otherwise.
```rust
html! {
p {
({
let f: Foo = something_convertible_to_foo()?;
f.time().format("%H%Mh")
})
}
}
```
## Splices in attributes
Splices work in attributes as well.
```rust
let secret_message = "Surprise!";
html! {
p title=(secret_message) {
"Nothing to see here, move along."
}
}
```
To concatenate multiple values within an attribute, wrap the whole thing in braces. This syntax is useful for building URLs.
```rust
const GITHUB: &'static str = "https://github.com";
html! {
a href={ (GITHUB) "/lfairy/maud" } {
"Fork me on GitHub"
}
}
```
## What can be spliced?
You can splice any value that implements [`std::fmt::Display`][Display]. Most primitive types (such as `str` and `i32`) implement this trait, so they should work out of the box.
To change this behavior for some type, you can implement the [`Render`][Render] trait by hand. The [`PreEscaped`][PreEscaped] wrapper type, which outputs its argument without escaping, works this way. See the [traits](./traits.md) section for details.
```rust
use maud::PreEscaped;
let post = "<p>Pre-escaped</p>";
html! {
h1 { "My super duper blog post" }
(PreEscaped(post))
}
```
[Display]: http://doc.rust-lang.org/std/fmt/trait.Display.html
[Render]: https://docs.rs/maud/*/maud/trait.Render.html
[PreEscaped]: https://docs.rs/maud/*/maud/struct.PreEscaped.html

37
docs/content/faq.md Normal file
View file

@ -0,0 +1,37 @@
# Frequently asked questions
## What is the origin of the name "Maud"?
Maud is named after a [character](http://mlp.wikia.com/wiki/Maud_Pie) from *My Little Pony: Friendship is Magic*. It does not refer to the [poem](https://en.wikipedia.org/wiki/Maud_and_other_poems) by Alfred Tennyson, though other people have brought that up in the past.
Here are some reasons why I chose this name:
* "Maud" shares three letters with "markup";
* The library is efficient and austere, like the character;
* Google used to maintain a site called ["HTML5 Rocks"](https://techcrunch.com/2010/06/22/html5rocks-google/), and Maud (the character) is a geologist.
## Why does `html!` always allocate a `String`? Wouldn't it be more efficient if it wrote to a handle directly?
Good question! In fact, Maud did work this way in the past.
Sadly, that kind of thing didn't work out that well in practice. Having to pass the handle around made templates hard to compose, which is important in any non-trivial project. Furthermore, Iron (and other middleware frameworks) likes to take ownership of the response body, so we'd need to do some closure gymnastics to get everything to work together. To put the nail in the coffin, benchmarks showed that a `String` based solution is actually faster than one which avoids allocations.
For these reasons, I changed `html!` to return a `String` in version 0.11.
## Why is Maud written as a procedural macro? Can't it use `macro_rules!` instead?
This is certainly possible, and in fact the [Horrorshow](https://github.com/Stebalien/horrorshow-rs) library works this way.
I use procedural macros because they are more flexible. There are some syntax constructs in Maud that cannot be parsed with `macro_rules!`; better diagnostics are a bonus as well.
## Maud has had a lot of releases so far. When will it reach 1.0?
I plan to make a 1.0 release when the library can be used on stable Rust.
## Why doesn't Maud implement [context-aware escaping](https://security.googleblog.com/2009/03/reducing-xss-by-way-of-automatic.html)?
If a project follows best practices in separating HTML and CSS/JavaScript, then context-aware escaping is unnecessary.
Google uses context-aware escaping because it has a large, decades-old code base, much of it written before these best practices were well known. Any project that uses Maud is neither large nor decades-old, and so should not have the need for this feature.

View file

@ -0,0 +1,56 @@
# Getting started
## Install nightly Rust
Maud requires the nightly version of Rust.
If you're using `rustup`,
see the [documentation][rustup]
for how to install this version.
[rustup]: https://github.com/rust-lang/rustup.rs/blob/master/README.md#working-with-nightly-rust
## Add Maud to your project
Once Rust is set up,
create a new project with Cargo:
```sh
cargo new --bin pony-greeter
cd pony-greeter
```
Add `maud` to your `Cargo.toml`:
```toml
[dependencies]
maud = "*"
```
Then save the following to `src/main.rs`:
```rust
#![feature(proc_macro_hygiene)]
extern crate maud;
use maud::html;
fn main() {
let name = "Lyra";
let markup = html! {
p { "Hi, " (name) "!" }
};
println!("{}", markup.into_string());
}
```
`html!` takes a single argument: a template using Maud's custom syntax. This call expands to an expression of type [`Markup`][Markup], which can then be converted to a `String` using `.into_string()`.
[Markup]: https://docs.rs/maud/*/maud/type.Markup.html
Run this program with `cargo run`, and you should get the following:
```
<p>Hi, Lyra!</p>
```
Congrats you've written your first Maud program!

37
docs/content/index.md Normal file
View file

@ -0,0 +1,37 @@
<!-- Comment that prevents the title from getting picked up -->
# A macro for writing HTML
```rust
html! {
h1 { "Hello, world!" }
p.intro {
"This is an example of the "
a href="https://github.com/lfairy/maud" { "Maud" }
" template language."
}
}
```
Maud is an HTML [template engine] for Rust. It's implemented as a macro, `html!`, which compiles your markup to specialized Rust code. This unique approach makes Maud templates blazing fast, super type-safe, and easy to deploy.
[template engine]: https://www.simple-is-better.org/template/
## Tight integration with Rust
Since Maud is a Rust macro, it can borrow most of its features from the host language. Pattern matching and `for` loops work as they do in Rust. There is no need to derive JSON conversions, as your templates can work with Rust values directly.
## Type safety
Your templates are checked by the compiler, just like the code around them. Any typos will be caught at compile time, not after your app has already started.
## Minimal runtime
Since most of the work happens at compile time, the runtime footprint is small. The Maud runtime library, including integration with the [Rocket] and [Actix] web frameworks, is around 100 SLoC.
[Rocket]: https://rocket.rs/
[Actix]: https://actix.rs/
## Simple deployment
There is no need to track separate template files, since all relevant code is linked into the final executable.

59
docs/content/partials.md Normal file
View file

@ -0,0 +1,59 @@
# Partials
Maud does not have a built-in concept of partials or sub-templates. Instead, you can compose your markup with any function that returns `Markup`.
The following example uses a `header` and `footer` function that are used in the `page` function to return a final result.
```rust
extern crate maud;
use self::maud::{DOCTYPE, html, Markup};
/// A basic header with a dynamic `page_title`.
fn header(page_title: &str) -> Markup {
html! {
(DOCTYPE)
html {
meta charset="utf-8";
title { (page_title) }
}
}
}
/// A static footer.
fn footer() -> Markup {
html! {
footer {
span {
a href="rss.atom" { "RSS Feed" }
}
}
}
}
/// The final Markup, including `header` and `footer`.
///
/// Additionally takes a `greeting_box` that's `Markup`, not `&str`.
pub fn page(title: &str, greeting_box: Markup) -> Markup {
html! {
// Add the header markup to the page
(header(title))
body {
h1 { "Hello World" }
(greeting_box)
}
// Add the footer markup to the page
(footer())
}
}
```
Using the `page` function will return the markup for the whole page and looks like this:
```rust
fn main() {
page("Hello!", html! {
div { "Greetings, Maud." }
});
}
```

83
docs/content/traits.md Normal file
View file

@ -0,0 +1,83 @@
# The `Render` trait
By default, a `(splice)` is rendered using the [`std::fmt::Display`][Display] trait, with any HTML special characters escaped automatically.
To change this behavior, implement the [`Render`][Render] trait for your type. Then, when a value of this type is used in a template, Maud will call your custom code instead.
Below are some examples of using `Render`. Feel free to use these snippets in your own project! For more examples, take a look at the [`maud_extras`][maud_extras] crate.
## Example: a shorthand for including CSS stylesheets
When writing a web page, it can be annoying to write `link rel="stylesheet"` over and over again. This example provides a shorthand for linking to CSS stylesheets.
```rust
use maud::{Markup, Render};
/// Links to a CSS stylesheet at the given path.
struct Css(&'static str);
impl Render for Css {
fn render(&self) -> Markup {
html! {
link rel="stylesheet" type="text/css" href=(self.0);
}
}
}
```
## Example: a wrapper that calls `std::fmt::Debug`
When debugging an application, it can be useful to see its internal state. But these internal data types often don't implement `Display`. This wrapper lets us use the [`Debug`][Debug] trait instead.
To avoid extra allocation, we override the `.render_to()` method instead of `.render()`. This doesn't do any escaping by default, so we wrap the output in an `Escaper` as well.
```rust
use maud::{Render, Escaper};
use std::fmt;
/// Renders the given value using its `Debug` implementation.
struct Debug<T: fmt::Debug>(T);
impl<T: fmt::Debug> Render for Debug<T> {
fn render_to(&self, output: &mut String) {
let mut escaper = Escaper::new(output);
write!(escaper, "{:?}", self.0).unwrap();
}
}
```
## Example: rendering Markdown using `pulldown-cmark` and `ammonia`
[`pulldown-cmark`][pulldown-cmark] is a popular library for converting Markdown to HTML.
We also use the [`ammonia`][ammonia] library, which sanitizes the resulting markup.
```rust
extern crate ammonia;
extern crate pulldown_cmark;
use maud::{PreEscaped, Render};
use pulldown_cmark::{Parser, html};
/// Renders a block of Markdown using `pulldown-cmark`.
struct Markdown<T: AsRef<str>>(T);
impl<T: AsRef<str>> Render for Markdown<T> {
fn render(self, buffer: &mut String) {
// Generate raw HTML
let mut unsafe_html = String::new();
let parser = Parser::new(self.0.as_str());
html::push_html(&mut unsafe_html, parser);
// Sanitize it with ammonia
let safe_html = ammonia::clean(&unsafe_html);
PreEscaped(safe_html)
}
}
```
[maud_extras]: https://github.com/lfairy/maud/tree/master/maud_extras
[Debug]: https://doc.rust-lang.org/std/fmt/trait.Debug.html
[Display]: https://doc.rust-lang.org/std/fmt/trait.Display.html
[Render]: https://docs.rs/maud/*/maud/trait.Render.html
[pulldown-cmark]: https://docs.rs/pulldown-cmark/0.0.8/pulldown_cmark/index.html
[ammonia]: https://github.com/notriddle/ammonia

View file

@ -0,0 +1,143 @@
# Web framework integration
Maud includes support for these web frameworks: [Actix], [Iron], [Rocket], and [Rouille].
[Actix]: https://actix.rs/
[Iron]: http://ironframework.io
[Rocket]: https://rocket.rs/
[Rouille]: https://github.com/tomaka/rouille
# Actix
Actix support is available with the "actix-web" feature:
```toml
# ...
[dependencies]
maud = { version = "*", features = ["actix-web"] }
# ...
```
Actix request handlers can use a `Markup` that implements the `actix_web::Responder` trait.
```rust
#![feature(proc_macro_hygiene)]
use maud::{html, Markup};
use actix_web::{App, server, Path, http::Method};
fn index(params: Path<(String, u32)>) -> Markup {
html! {
h1 { "Hello " (params.0) " with id " (params.1) "!"}
}
}
fn main() {
let sys = actix::System::new("maud-example");
server::new(move || {
App::new()
.resource("/user/{name}/{id}", |r| {
r.method(Method::GET).with(index)
})
}).bind("127.0.0.1:8080")
.unwrap()
.start();
let _ = sys.run();
}
```
# Iron
Iron support is available with the "iron" feature:
```toml
# ...
[dependencies]
maud = { version = "*", features = ["iron"] }
# ...
```
With this feature enabled, you can then build a `Response` from a `Markup` object directly. Here's an example application using Iron and Maud:
```rust
#![feature(proc_macro_hygiene)]
use iron::prelude::*;
use iron::status;
use maud::html;
fn main() {
Iron::new(|r: &mut Request| {
let markup = html! {
h1 { "Hello, world!" }
p {
"You are viewing the page at " (r.url)
}
};
Ok(Response::with((status::Ok, markup)))
}).http("localhost:3000").unwrap();
}
```
`Markup` will set the content type of the response automatically, so you don't need to add it yourself.
# Rocket
Rocket works in a similar way, except using the `rocket` feature:
```toml
# ...
[dependencies]
maud = { version = "*", features = ["rocket"] }
# ...
```
This adds a `Responder` implementation for the `Markup` type, so you can return the result directly:
```rust
#![feature(decl_macro)]
#![feature(proc_macro_hygiene)]
use maud::{html, Markup};
use rocket::{get, routes};
use std::borrow::Cow;
#[get("/<name>")]
fn hello<'a>(name: Cow<'a, str>) -> Markup {
html! {
h1 { "Hello, " (name) "!" }
p { "Nice to meet you!" }
}
}
fn main() {
rocket::ignite().mount("/", routes![hello]).launch();
}
```
# Rouille
Unlike with the other frameworks, Rouille doesn't need any extra features at all! Calling `Response::html` on the rendered `Markup` will Just Work®.
```rust
#![feature(proc_macro_hygiene)]
use maud::html;
use rouille::{Response, router};
fn main() {
rouille::start_server("localhost:8000", move |request| {
router!(request,
(GET) (/{name: String}) => {
html! {
h1 { "Hello, " (name) "!" }
p { "Nice to meet you!" }
}
},
_ => Response::empty_404()
)
});
}
```

16
docs/deploy.sh Executable file
View file

@ -0,0 +1,16 @@
#!/bin/sh
set -e
nproc=$(nproc || echo 4)
make clean
make
cd site
git init
git add .
git commit -m 'Deploy'
git remote add github git@github.com:lfairy/maud.git
git push -f github master:gh-pages

208
docs/src/main.rs Normal file
View file

@ -0,0 +1,208 @@
#![feature(crate_visibility_modifier)]
#![feature(proc_macro_hygiene)]
use comrak::{self, Arena, ComrakOptions};
use comrak::nodes::{AstNode, NodeCodeBlock, NodeHeading, NodeHtmlBlock, NodeLink, NodeValue};
use serde_json;
use std::error::Error;
use std::env;
use std::fs::{self, File};
use std::io::{self, BufReader};
use std::mem;
use std::path::Path;
use std::string::FromUtf8Error;
use syntect::parsing::SyntaxSet;
use syntect::highlighting::{Color, ThemeSet};
use syntect::html::highlighted_html_for_string;
mod string_writer;
mod views;
fn main() -> Result<(), Box<dyn Error>> {
let args = env::args().collect::<Vec<_>>();
if args.len() >= 3 && &args[1] == "build-nav" && args[3..].iter().all(|arg| arg.contains(":")) {
let entries = args[3..].iter().map(|arg| {
let mut splits = arg.splitn(2, ":");
let slug = splits.next().unwrap();
let input_path = splits.next().unwrap();
(slug, input_path)
}).collect::<Vec<_>>();
build_nav(&entries, &args[2])
} else if args.len() == 6 && &args[1] == "build-page" {
build_page(&args[2], &args[3], &args[4], &args[5])
} else {
Err("invalid arguments".into())
}
}
fn build_nav(entries: &[(&str, &str)], nav_path: &str) -> Result<(), Box<dyn Error>> {
let arena = Arena::new();
let options = comrak_options();
let nav = entries.iter().map(|&(slug, input_path)| {
let title = load_page_title(&arena, &options, input_path)?;
Ok((slug, title))
}).collect::<io::Result<Vec<_>>>()?;
// Only write if different to avoid spurious rebuilds
let old_string = fs::read_to_string(nav_path).unwrap_or(String::new());
let new_string = serde_json::to_string_pretty(&nav)?;
if old_string != new_string {
fs::create_dir_all(Path::new(nav_path).parent().unwrap())?;
fs::write(nav_path, new_string)?;
}
Ok(())
}
fn build_page(
output_path: &str,
slug: &str,
input_path: &str,
nav_path: &str,
) -> Result<(), Box<dyn Error>> {
let nav: Vec<(String, Option<String>)> = {
let file = File::open(nav_path)?;
let reader = BufReader::new(file);
serde_json::from_reader(reader)?
};
let arena = Arena::new();
let options = comrak_options();
let nav = nav.iter().filter_map(|(slug, title)| {
title.as_ref().map(|title| {
let title = comrak::parse_document(&arena, title, &options);
(slug.as_str(), title)
})
}).collect::<Vec<_>>();
let page = load_page(&arena, &options, input_path)?;
let markup = views::main(&options, slug, page, &nav);
fs::create_dir_all(Path::new(output_path).parent().unwrap())?;
fs::write(output_path, markup.into_string())?;
Ok(())
}
struct Page<'a> {
title: Option<&'a AstNode<'a>>,
content: &'a AstNode<'a>,
}
fn load_page<'a>(
arena: &'a Arena<AstNode<'a>>,
options: &ComrakOptions,
path: impl AsRef<Path>,
) -> io::Result<Page<'a>> {
let page = load_page_raw(arena, options, path)?;
lower_headings(page.content);
rewrite_md_links(page.content)
.map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
highlight_code(page.content)
.map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
Ok(page)
}
fn load_page_title<'a>(
arena: &'a Arena<AstNode<'a>>,
options: &ComrakOptions,
path: impl AsRef<Path>,
) -> io::Result<Option<String>> {
let page = load_page_raw(arena, options, path)?;
let title = page.title.map(|title| {
let mut buffer = String::new();
comrak::format_commonmark(
title,
options,
&mut string_writer::StringWriter(&mut buffer),
).unwrap();
buffer
});
Ok(title)
}
fn load_page_raw<'a>(
arena: &'a Arena<AstNode<'a>>,
options: &ComrakOptions,
path: impl AsRef<Path>,
) -> io::Result<Page<'a>> {
let buffer = fs::read_to_string(path)?;
let content = comrak::parse_document(arena, &buffer, options);
let title = content
.first_child()
.filter(|node| {
let mut data = node.data.borrow_mut();
if let NodeValue::Heading(NodeHeading { level: 1, .. }) = data.value {
node.detach();
data.value = NodeValue::Document;
true
} else {
false
}
});
Ok(Page { title, content })
}
fn lower_headings<'a>(root: &'a AstNode<'a>) {
for node in root.descendants() {
let mut data = node.data.borrow_mut();
if let NodeValue::Heading(NodeHeading { level, .. }) = &mut data.value {
*level += 1;
}
}
}
fn rewrite_md_links<'a>(root: &'a AstNode<'a>) -> Result<(), FromUtf8Error> {
for node in root.descendants() {
let mut data = node.data.borrow_mut();
if let NodeValue::Link(NodeLink { url, .. }) = &mut data.value {
let mut url_string = String::from_utf8(mem::replace(url, Vec::new()))?;
if url_string.ends_with(".md") {
url_string.truncate(url_string.len() - ".md".len());
url_string.push_str(".html");
}
*url = url_string.into_bytes();
}
}
Ok(())
}
fn highlight_code<'a>(root: &'a AstNode<'a>) -> Result<(), FromUtf8Error> {
let ss = SyntaxSet::load_defaults_newlines();
let ts = ThemeSet::load_defaults();
let mut theme = ts.themes["InspiredGitHub"].clone();
theme.settings.background = Some(Color { r: 0xff, g: 0xee, b: 0xff, a: 0xff });
for node in root.descendants() {
let mut data = node.data.borrow_mut();
if let NodeValue::CodeBlock(NodeCodeBlock { info, literal, .. }) = &mut data.value {
let info = String::from_utf8(mem::replace(info, Vec::new()))?;
let syntax = ss.find_syntax_by_token(&info)
.unwrap_or_else(|| ss.find_syntax_plain_text());
let mut literal = String::from_utf8(mem::replace(literal, Vec::new()))?;
if !literal.ends_with('\n') {
// Syntect expects a trailing newline
literal.push('\n');
}
let html = highlighted_html_for_string(&literal, &ss, syntax, &theme);
data.value = NodeValue::HtmlBlock(NodeHtmlBlock {
block_type: 0,
literal: html.into_bytes(),
});
}
}
Ok(())
}
fn comrak_options() -> ComrakOptions {
ComrakOptions {
ext_header_ids: Some("".to_string()),
unsafe_: true,
..ComrakOptions::default()
}
}

19
docs/src/string_writer.rs Normal file
View file

@ -0,0 +1,19 @@
use std::io;
use std::str;
pub struct StringWriter<'a>(pub &'a mut String);
impl<'a> io::Write for StringWriter<'a> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
str::from_utf8(buf)
.map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))
.map(|s| {
self.0.push_str(s);
buf.len()
})
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}

104
docs/src/views.rs Normal file
View file

@ -0,0 +1,104 @@
use comrak::{self, ComrakOptions};
use comrak::nodes::AstNode;
use crate::Page;
use crate::string_writer::StringWriter;
use maud::{DOCTYPE, Markup, PreEscaped, Render, html};
use std::str;
struct Comrak<'a>(&'a AstNode<'a>, &'a ComrakOptions);
impl<'a> Render for Comrak<'a> {
fn render_to(&self, buffer: &mut String) {
comrak::format_html(self.0, self.1, &mut StringWriter(buffer)).unwrap();
}
}
/// Hack! Comrak wraps a single line of input in `<p>` tags, which is great in
/// general but not suitable for links in the navigation bar.
struct ComrakRemovePTags<'a>(&'a AstNode<'a>, &'a ComrakOptions);
impl<'a> Render for ComrakRemovePTags<'a> {
fn render(&self) -> Markup {
let mut buffer = String::new();
comrak::format_html(self.0, self.1, &mut StringWriter(&mut buffer)).unwrap();
assert!(buffer.starts_with("<p>") && buffer.ends_with("</p>\n"));
PreEscaped(buffer.trim_start_matches("<p>").trim_end_matches("</p>\n").to_string())
}
}
struct ComrakText<'a>(&'a AstNode<'a>, &'a ComrakOptions);
impl<'a> Render for ComrakText<'a> {
fn render_to(&self, buffer: &mut String) {
comrak::format_commonmark(self.0, self.1, &mut StringWriter(buffer)).unwrap();
}
}
crate fn main<'a>(
options: &'a ComrakOptions,
slug: &str,
page: Page<'a>,
nav: &[(&str, &'a AstNode<'a>)],
) -> Markup {
html! {
(DOCTYPE)
meta charset="utf-8";
title {
@if let Some(title) = page.title {
(ComrakText(title, options))
" \u{2013} "
}
"Maud, a macro for writing HTML"
}
link rel="stylesheet" href="styles.css";
meta name="theme-color" content="#808";
meta name="viewport" content="width=device-width";
header {
h1 {
a href="." {
"maud"
}
}
}
nav {
ul {
@for &(other_slug, other_title) in nav {
li {
@if other_slug == slug {
b {
(ComrakRemovePTags(other_title, options))
}
} @else {
a href={ (other_slug) ".html" } {
(ComrakRemovePTags(other_title, options))
}
}
}
}
}
ul {
li {
a href="https://docs.rs/maud/" {
"API documentation"
}
}
li {
a href="https://github.com/lfairy/maud" {
"GitHub"
}
}
}
}
main {
@if let Some(title) = page.title {
h2 {
(Comrak(title, options))
}
}
(Comrak(page.content, options))
}
}
}

73
docs/styles.css Normal file
View file

@ -0,0 +1,73 @@
html {
box-sizing: border-box;
font: 16px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif;
border-top: 0.75rem solid #808;
}
*, *::before, *::after {
box-sizing: inherit;
}
body {
margin: 0 auto;
padding: 0 2rem 2rem;
max-width: 40rem;
}
header h1 {
margin: 0 0 0 -0.25rem;
font-size: 5rem;
line-height: 0.75;
}
header h1 a:link, header h1 a:visited {
color: #808;
}
nav ul {
padding: 0;
list-style: none;
}
pre {
padding: 1rem;
font: 15px Consolas, monospace;
background: #fef;
}
a:link {
text-decoration: none;
}
a:link, a:visited {
color: #a0a;
}
a:focus, a:hover, a:active {
background: #fdd;
}
@media (min-width: 60rem) {
body {
max-width: 56rem; /* 15 + 1 + 40 */
display: grid;
grid-template-rows: auto minmax(0, 1fr);
grid-template-columns: 15rem auto;
grid-gap: 1rem;
}
header {
grid-column: 1;
grid-row: 1;
}
nav {
grid-column: 1;
grid-row: 2;
}
main {
grid-column: 2;
grid-row: 1 / 3;
}
}

14
docs/watch.sh Executable file
View file

@ -0,0 +1,14 @@
#!/bin/sh
set -e
python3 -m http.server -d site &
server_pid=$!
trap 'kill $server_pid' EXIT
nproc=$(nproc || echo 4)
while true
do
find . -name '*.rs' -o -name '*.md' -o -name '*.css' -not -path './site/*' | entr -d make -j$nproc
done