@@ -1,4 +1,3 @@ | |||||
target | target | ||||
.idea/ | .idea/ | ||||
site | |||||
theme | |||||
test_site/public |
@@ -3,14 +3,14 @@ name = "gutenberg" | |||||
version = "0.0.1" | version = "0.0.1" | ||||
dependencies = [ | dependencies = [ | ||||
"chrono 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", | "chrono 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"clap 2.20.5 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"clap 2.21.1 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"error-chain 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", | "error-chain 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", | "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"iron 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", | "iron 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"lazy_static 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", | "lazy_static 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"mount 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", | "mount 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"notify 4.0.0 (registry+https://github.com/rust-lang/crates.io-index)", | "notify 4.0.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"pulldown-cmark 0.0.8 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"pulldown-cmark 0.0.10 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", | "regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"serde 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)", | "serde 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"serde_derive 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)", | "serde_derive 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
@@ -18,7 +18,8 @@ dependencies = [ | |||||
"slug 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", | "slug 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"staticfile 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", | "staticfile 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"syntect 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", | "syntect 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"tera 0.8.0 (git+https://github.com/Keats/tera?branch=reload)", | |||||
"tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"tera 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"toml 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", | "toml 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", | "walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"ws 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", | "ws 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
@@ -26,7 +27,7 @@ dependencies = [ | |||||
[[package]] | [[package]] | ||||
name = "aho-corasick" | name = "aho-corasick" | ||||
version = "0.6.2" | |||||
version = "0.6.3" | |||||
source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
dependencies = [ | dependencies = [ | ||||
"memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", | "memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
@@ -37,6 +38,16 @@ name = "ansi_term" | |||||
version = "0.9.0" | version = "0.9.0" | ||||
source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
[[package]] | |||||
name = "atty" | |||||
version = "0.2.2" | |||||
source = "registry+https://github.com/rust-lang/crates.io-index" | |||||
dependencies = [ | |||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
] | |||||
[[package]] | [[package]] | ||||
name = "backtrace" | name = "backtrace" | ||||
version = "0.3.0" | version = "0.3.0" | ||||
@@ -56,7 +67,7 @@ name = "backtrace-sys" | |||||
version = "0.1.10" | version = "0.1.10" | ||||
source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
dependencies = [ | dependencies = [ | ||||
"gcc 0.3.43 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"gcc 0.3.45 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", | "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
] | ] | ||||
@@ -67,7 +78,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||||
dependencies = [ | dependencies = [ | ||||
"byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", | "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", | "num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"rustc-serialize 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"serde 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)", | "serde 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
] | ] | ||||
@@ -76,11 +87,6 @@ name = "bitflags" | |||||
version = "0.4.0" | version = "0.4.0" | ||||
source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
[[package]] | |||||
name = "bitflags" | |||||
version = "0.5.0" | |||||
source = "registry+https://github.com/rust-lang/crates.io-index" | |||||
[[package]] | [[package]] | ||||
name = "bitflags" | name = "bitflags" | ||||
version = "0.7.0" | version = "0.7.0" | ||||
@@ -131,17 +137,17 @@ dependencies = [ | |||||
[[package]] | [[package]] | ||||
name = "clap" | name = "clap" | ||||
version = "2.20.5" | |||||
version = "2.21.1" | |||||
source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
dependencies = [ | dependencies = [ | ||||
"ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", | "ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"atty 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"bitflags 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", | "strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"term_size 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", | "term_size 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"unicode-segmentation 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", | "unicode-segmentation 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", | "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"vec_map 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"vec_map 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
] | ] | ||||
[[package]] | [[package]] | ||||
@@ -149,7 +155,7 @@ name = "cmake" | |||||
version = "0.1.21" | version = "0.1.21" | ||||
source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
dependencies = [ | dependencies = [ | ||||
"gcc 0.3.43 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"gcc 0.3.45 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
] | ] | ||||
[[package]] | [[package]] | ||||
@@ -157,7 +163,7 @@ name = "conduit-mime-types" | |||||
version = "0.7.3" | version = "0.7.3" | ||||
source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
dependencies = [ | dependencies = [ | ||||
"rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"rustc-serialize 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
] | ] | ||||
[[package]] | [[package]] | ||||
@@ -233,7 +239,7 @@ dependencies = [ | |||||
[[package]] | [[package]] | ||||
name = "gcc" | name = "gcc" | ||||
version = "0.3.43" | |||||
version = "0.3.45" | |||||
source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
[[package]] | [[package]] | ||||
@@ -253,7 +259,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||||
[[package]] | [[package]] | ||||
name = "humansize" | name = "humansize" | ||||
version = "1.0.0" | |||||
version = "1.0.1" | |||||
source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
[[package]] | [[package]] | ||||
@@ -264,9 +270,9 @@ dependencies = [ | |||||
"httparse 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", | "httparse 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", | "language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", | "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"mime 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"mime 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"num_cpus 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", | "num_cpus 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"rustc-serialize 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", | "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"time 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)", | "time 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", | "traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
@@ -293,6 +299,15 @@ dependencies = [ | |||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", | "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
] | ] | ||||
[[package]] | |||||
name = "iovec" | |||||
version = "0.1.0" | |||||
source = "registry+https://github.com/rust-lang/crates.io-index" | |||||
dependencies = [ | |||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
] | |||||
[[package]] | [[package]] | ||||
name = "iron" | name = "iron" | ||||
version = "0.5.1" | version = "0.5.1" | ||||
@@ -364,7 +379,7 @@ dependencies = [ | |||||
[[package]] | [[package]] | ||||
name = "mime" | name = "mime" | ||||
version = "0.2.2" | |||||
version = "0.2.3" | |||||
source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
dependencies = [ | dependencies = [ | ||||
"log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", | "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
@@ -375,7 +390,7 @@ name = "miniz-sys" | |||||
version = "0.1.9" | version = "0.1.9" | ||||
source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
dependencies = [ | dependencies = [ | ||||
"gcc 0.3.43 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"gcc 0.3.45 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", | "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
] | ] | ||||
@@ -397,9 +412,10 @@ dependencies = [ | |||||
[[package]] | [[package]] | ||||
name = "mio" | name = "mio" | ||||
version = "0.6.4" | |||||
version = "0.6.5" | |||||
source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
dependencies = [ | dependencies = [ | ||||
"iovec 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"lazycell 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", | "lazycell 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", | "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
@@ -563,7 +579,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||||
dependencies = [ | dependencies = [ | ||||
"byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", | "byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"chrono 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)", | "chrono 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"rustc-serialize 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"serde 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)", | "serde 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"xml-rs 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", | "xml-rs 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
] | ] | ||||
@@ -578,10 +594,10 @@ dependencies = [ | |||||
[[package]] | [[package]] | ||||
name = "pulldown-cmark" | name = "pulldown-cmark" | ||||
version = "0.0.8" | |||||
version = "0.0.10" | |||||
source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
dependencies = [ | dependencies = [ | ||||
"bitflags 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"bitflags 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", | "getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
] | ] | ||||
@@ -608,7 +624,7 @@ name = "regex" | |||||
version = "0.2.1" | version = "0.2.1" | ||||
source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
dependencies = [ | dependencies = [ | ||||
"aho-corasick 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"aho-corasick 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", | "memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"regex-syntax 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", | "regex-syntax 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"thread_local 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", | "thread_local 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
@@ -627,7 +643,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||||
[[package]] | [[package]] | ||||
name = "rustc-serialize" | name = "rustc-serialize" | ||||
version = "0.3.22" | |||||
version = "0.3.23" | |||||
source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
[[package]] | [[package]] | ||||
@@ -766,20 +782,28 @@ dependencies = [ | |||||
"onig 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", | "onig 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"plist 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", | "plist 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"regex-syntax 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", | "regex-syntax 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"rustc-serialize 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", | "walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"yaml-rust 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", | "yaml-rust 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
] | ] | ||||
[[package]] | |||||
name = "tempdir" | |||||
version = "0.3.5" | |||||
source = "registry+https://github.com/rust-lang/crates.io-index" | |||||
dependencies = [ | |||||
"rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
] | |||||
[[package]] | [[package]] | ||||
name = "tera" | name = "tera" | ||||
version = "0.8.0" | |||||
source = "git+https://github.com/Keats/tera?branch=reload#ee038a6f3519ac35e1878ca9b29ec739a8f84a15" | |||||
version = "0.8.1" | |||||
source = "registry+https://github.com/rust-lang/crates.io-index" | |||||
dependencies = [ | dependencies = [ | ||||
"chrono 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", | "chrono 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"error-chain 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", | "error-chain 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", | "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"humansize 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"humansize 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"lazy_static 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", | "lazy_static 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"pest 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", | "pest 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", | "regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
@@ -927,7 +951,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||||
[[package]] | [[package]] | ||||
name = "vec_map" | name = "vec_map" | ||||
version = "0.6.0" | |||||
version = "0.7.0" | |||||
source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
[[package]] | [[package]] | ||||
@@ -972,7 +996,7 @@ dependencies = [ | |||||
"bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", | "bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"httparse 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", | "httparse 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", | "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"mio 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"mio 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", | "rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"sha1 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", | "sha1 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", | "slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
@@ -1002,13 +1026,13 @@ version = "0.3.5" | |||||
source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
[metadata] | [metadata] | ||||
"checksum aho-corasick 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0638fd549427caa90c499814196d1b9e3725eb4d15d7339d6de073a680ed0ca2" | |||||
"checksum aho-corasick 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "500909c4f87a9e52355b26626d890833e9e1d53ac566db76c36faa984b889699" | |||||
"checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6" | "checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6" | ||||
"checksum atty 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d912da0db7fa85514874458ca3651fe2cddace8d0b0505571dbdcd41ab490159" | |||||
"checksum backtrace 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f551bc2ddd53aea015d453ef0b635af89444afa5ed2405dd0b2062ad5d600d80" | "checksum backtrace 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f551bc2ddd53aea015d453ef0b635af89444afa5ed2405dd0b2062ad5d600d80" | ||||
"checksum backtrace-sys 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d192fd129132fbc97497c1f2ec2c2c5174e376b95f535199ef4fe0a293d33842" | "checksum backtrace-sys 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d192fd129132fbc97497c1f2ec2c2c5174e376b95f535199ef4fe0a293d33842" | ||||
"checksum bincode 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "55eb0b7fd108527b0c77860f75eca70214e11a8b4c6ef05148c54c05a25d48ad" | "checksum bincode 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "55eb0b7fd108527b0c77860f75eca70214e11a8b4c6ef05148c54c05a25d48ad" | ||||
"checksum bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8dead7461c1127cf637931a1e50934eb6eee8bff2f74433ac7909e9afcee04a3" | "checksum bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8dead7461c1127cf637931a1e50934eb6eee8bff2f74433ac7909e9afcee04a3" | ||||
"checksum bitflags 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4f67931368edf3a9a51d29886d245f1c3db2f1ef0dcc9e35ff70341b78c10d23" | |||||
"checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" | "checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" | ||||
"checksum bitflags 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e1ab483fc81a8143faa7203c4a3c02888ebd1a782e37e41fa34753ba9a162" | "checksum bitflags 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e1ab483fc81a8143faa7203c4a3c02888ebd1a782e37e41fa34753ba9a162" | ||||
"checksum byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0fc10e8cc6b2580fda3f36eb6dc5316657f812a3df879a44a66fc9f0fdbc4855" | "checksum byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0fc10e8cc6b2580fda3f36eb6dc5316657f812a3df879a44a66fc9f0fdbc4855" | ||||
@@ -1017,7 +1041,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||||
"checksum cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de1e760d7b6535af4241fca8bd8adf68e2e7edacc6b29f5d399050c5e48cf88c" | "checksum cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de1e760d7b6535af4241fca8bd8adf68e2e7edacc6b29f5d399050c5e48cf88c" | ||||
"checksum chrono 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)" = "9213f7cd7c27e95c2b57c49f0e69b1ea65b27138da84a170133fd21b07659c00" | "checksum chrono 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)" = "9213f7cd7c27e95c2b57c49f0e69b1ea65b27138da84a170133fd21b07659c00" | ||||
"checksum chrono 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "158b0bd7d75cbb6bf9c25967a48a2e9f77da95876b858eadfabaa99cd069de6e" | "checksum chrono 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "158b0bd7d75cbb6bf9c25967a48a2e9f77da95876b858eadfabaa99cd069de6e" | ||||
"checksum clap 2.20.5 (registry+https://github.com/rust-lang/crates.io-index)" = "7db281b0520e97fbd15cd615dcd8f8bcad0c26f5f7d5effe705f090f39e9a758" | |||||
"checksum clap 2.21.1 (registry+https://github.com/rust-lang/crates.io-index)" = "74a80f603221c9cd9aa27a28f52af452850051598537bb6b359c38a7d61e5cda" | |||||
"checksum cmake 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)" = "e1acc68a3f714627af38f9f5d09706a28584ba60dfe2cca68f40bf779f941b25" | "checksum cmake 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)" = "e1acc68a3f714627af38f9f5d09706a28584ba60dfe2cca68f40bf779f941b25" | ||||
"checksum conduit-mime-types 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "95ca30253581af809925ef68c2641cc140d6183f43e12e0af4992d53768bd7b8" | "checksum conduit-mime-types 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "95ca30253581af809925ef68c2641cc140d6183f43e12e0af4992d53768bd7b8" | ||||
"checksum dbghelp-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "97590ba53bcb8ac28279161ca943a924d1fd4a8fb3fa63302591647c4fc5b850" | "checksum dbghelp-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "97590ba53bcb8ac28279161ca943a924d1fd4a8fb3fa63302591647c4fc5b850" | ||||
@@ -1029,14 +1053,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||||
"checksum fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6cc484842f1e2884faf56f529f960cc12ad8c71ce96cc7abba0a067c98fee344" | "checksum fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6cc484842f1e2884faf56f529f960cc12ad8c71ce96cc7abba0a067c98fee344" | ||||
"checksum fsevent 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)" = "dfe593ebcfc76884138b25426999890b10da8e6a46d01b499d7c54c604672c38" | "checksum fsevent 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)" = "dfe593ebcfc76884138b25426999890b10da8e6a46d01b499d7c54c604672c38" | ||||
"checksum fsevent-sys 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "1a772d36c338d07a032d5375a36f15f9a7043bf0cb8ce7cee658e037c6032874" | "checksum fsevent-sys 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "1a772d36c338d07a032d5375a36f15f9a7043bf0cb8ce7cee658e037c6032874" | ||||
"checksum gcc 0.3.43 (registry+https://github.com/rust-lang/crates.io-index)" = "c07c758b972368e703a562686adb39125707cc1ef3399da8c019fc6c2498a75d" | |||||
"checksum gcc 0.3.45 (registry+https://github.com/rust-lang/crates.io-index)" = "40899336fb50db0c78710f53e87afc54d8c7266fb76262fecc78ca1a7f09deae" | |||||
"checksum getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9047cfbd08a437050b363d35ef160452c5fe8ea5187ae0a624708c91581d685" | "checksum getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9047cfbd08a437050b363d35ef160452c5fe8ea5187ae0a624708c91581d685" | ||||
"checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" | "checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" | ||||
"checksum httparse 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a6e7a63e511f9edffbab707141fbb8707d1a3098615fb2adbd5769cdfcc9b17d" | "checksum httparse 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a6e7a63e511f9edffbab707141fbb8707d1a3098615fb2adbd5769cdfcc9b17d" | ||||
"checksum humansize 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9b963e0c0a5149e12a9cab4d889404e4935e3484db7c4d9681e8bbdbcb9dfd80" | |||||
"checksum humansize 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "92d211e6e70b05749dce515b47684f29a3c8c38bbbb21c50b30aff9eca1b0bd3" | |||||
"checksum hyper 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)" = "43a15e3273b2133aaac0150478ab443fb89f15c3de41d8d93d8f3bb14bf560f6" | "checksum hyper 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)" = "43a15e3273b2133aaac0150478ab443fb89f15c3de41d8d93d8f3bb14bf560f6" | ||||
"checksum idna 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1053236e00ce4f668aeca4a769a09b3bf5a682d802abd6f3cb39374f6b162c11" | "checksum idna 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1053236e00ce4f668aeca4a769a09b3bf5a682d802abd6f3cb39374f6b162c11" | ||||
"checksum inotify 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "887fcc180136e77a85e6a6128579a719027b1bab9b1c38ea4444244fe262c20c" | "checksum inotify 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "887fcc180136e77a85e6a6128579a719027b1bab9b1c38ea4444244fe262c20c" | ||||
"checksum iovec 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "29d062ee61fccdf25be172e70f34c9f6efc597e1fb8f6526e8437b2046ab26be" | |||||
"checksum iron 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2440ae846e7a8c7f9b401db8f6e31b4ea5e7d3688b91761337da7e054520c75b" | "checksum iron 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2440ae846e7a8c7f9b401db8f6e31b4ea5e7d3688b91761337da7e054520c75b" | ||||
"checksum itoa 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eb2f404fbc66fd9aac13e998248505e7ecb2ad8e44ab6388684c5fb11c6c251c" | "checksum itoa 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eb2f404fbc66fd9aac13e998248505e7ecb2ad8e44ab6388684c5fb11c6c251c" | ||||
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" | "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" | ||||
@@ -1047,10 +1072,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||||
"checksum log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "5141eca02775a762cc6cd564d8d2c50f67c0ea3a372cbf1c51592b3e029e10ad" | "checksum log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "5141eca02775a762cc6cd564d8d2c50f67c0ea3a372cbf1c51592b3e029e10ad" | ||||
"checksum matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "efd7622e3022e1a6eaa602c4cea8912254e5582c9c692e9167714182244801b1" | "checksum matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "efd7622e3022e1a6eaa602c4cea8912254e5582c9c692e9167714182244801b1" | ||||
"checksum memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1dbccc0e46f1ea47b9f17e6d67c5a96bd27030519c519c9c91327e31275a47b4" | "checksum memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1dbccc0e46f1ea47b9f17e6d67c5a96bd27030519c519c9c91327e31275a47b4" | ||||
"checksum mime 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b5c93a4bd787ddc6e7833c519b73a50883deb5863d76d9b71eb8216fb7f94e66" | |||||
"checksum mime 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "5514f038123342d01ee5f95129e4ef1e0470c93bc29edf058a46f9ee3ba6737e" | |||||
"checksum miniz-sys 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "28eaee17666671fa872e567547e8428e83308ebe5808cdf6a0e28397dbe2c726" | "checksum miniz-sys 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "28eaee17666671fa872e567547e8428e83308ebe5808cdf6a0e28397dbe2c726" | ||||
"checksum mio 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a637d1ca14eacae06296a008fa7ad955347e34efcb5891cfd8ba05491a37907e" | "checksum mio 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a637d1ca14eacae06296a008fa7ad955347e34efcb5891cfd8ba05491a37907e" | ||||
"checksum mio 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "eecdbdd49a849336e77b453f021c89972a2cfb5b51931a0026ae0ac4602de681" | |||||
"checksum mio 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "aa30e3753079b08ce3d75cf3b44783e36fe0e1f64065f65c1d894d1688fb2580" | |||||
"checksum miow 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3e690c5df6b2f60acd45d56378981e827ff8295562fc8d34f573deb267a59cd1" | "checksum miow 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3e690c5df6b2f60acd45d56378981e827ff8295562fc8d34f573deb267a59cd1" | ||||
"checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" | "checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" | ||||
"checksum modifier 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "41f5c9112cb662acd3b204077e0de5bc66305fa8df65c8019d5adb10e9ab6e58" | "checksum modifier 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "41f5c9112cb662acd3b204077e0de5bc66305fa8df65c8019d5adb10e9ab6e58" | ||||
@@ -1069,14 +1094,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||||
"checksum pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "3a8b4c6b8165cd1a1cd4b9b120978131389f64bdaf456435caa41e630edba903" | "checksum pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "3a8b4c6b8165cd1a1cd4b9b120978131389f64bdaf456435caa41e630edba903" | ||||
"checksum plist 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6ab9bef2781bcdac1baf3e29eb297344cd24263e22fd9436d3a21215b7d8aa" | "checksum plist 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6ab9bef2781bcdac1baf3e29eb297344cd24263e22fd9436d3a21215b7d8aa" | ||||
"checksum plugin 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "1a6a0dc3910bc8db877ffed8e457763b317cf880df4ae19109b9f77d277cf6e0" | "checksum plugin 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "1a6a0dc3910bc8db877ffed8e457763b317cf880df4ae19109b9f77d277cf6e0" | ||||
"checksum pulldown-cmark 0.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "1058d7bb927ca067656537eec4e02c2b4b70eaaa129664c5b90c111e20326f41" | |||||
"checksum pulldown-cmark 0.0.10 (registry+https://github.com/rust-lang/crates.io-index)" = "b0b0f7b64fd9ff618da552df85b0d356a1487e5ef41df8b5727b0f73bd1215a1" | |||||
"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" | "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" | ||||
"checksum rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "022e0636ec2519ddae48154b028864bdce4eaf7d35226ab8e65c611be97b189d" | "checksum rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "022e0636ec2519ddae48154b028864bdce4eaf7d35226ab8e65c611be97b189d" | ||||
"checksum redox_syscall 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "8dd35cc9a8bdec562c757e3d43c1526b5c6d2653e23e2315065bc25556550753" | "checksum redox_syscall 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "8dd35cc9a8bdec562c757e3d43c1526b5c6d2653e23e2315065bc25556550753" | ||||
"checksum regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4278c17d0f6d62dfef0ab00028feb45bd7d2102843f80763474eeb1be8a10c01" | "checksum regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4278c17d0f6d62dfef0ab00028feb45bd7d2102843f80763474eeb1be8a10c01" | ||||
"checksum regex-syntax 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9191b1f57603095f105d317e375d19b1c9c5c3185ea9633a99a6dcbed04457" | "checksum regex-syntax 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9191b1f57603095f105d317e375d19b1c9c5c3185ea9633a99a6dcbed04457" | ||||
"checksum rustc-demangle 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3058a43ada2c2d0b92b3ae38007a2d0fa5e9db971be260e0171408a4ff471c95" | "checksum rustc-demangle 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3058a43ada2c2d0b92b3ae38007a2d0fa5e9db971be260e0171408a4ff471c95" | ||||
"checksum rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)" = "237546c689f20bb44980270c73c3b9edd0891c1be49cc1274406134a66d3957b" | |||||
"checksum rustc-serialize 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)" = "684ce48436d6465300c9ea783b6b14c4361d6b8dcbb1375b486a69cc19e2dfb0" | |||||
"checksum rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c5f5376ea5e30ce23c03eb77cbe4962b988deead10910c372b226388b594c084" | "checksum rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c5f5376ea5e30ce23c03eb77cbe4962b988deead10910c372b226388b594c084" | ||||
"checksum same-file 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d931a44fdaa43b8637009e7632a02adc4f2b2e0733c08caa4cf00e8da4a117a7" | "checksum same-file 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d931a44fdaa43b8637009e7632a02adc4f2b2e0733c08caa4cf00e8da4a117a7" | ||||
"checksum semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)" = "d4f410fedcf71af0345d7607d246e7ad15faaadd49d240ee3b24e5dc21a820ac" | "checksum semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)" = "d4f410fedcf71af0345d7607d246e7ad15faaadd49d240ee3b24e5dc21a820ac" | ||||
@@ -1095,7 +1120,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||||
"checksum syn 0.11.9 (registry+https://github.com/rust-lang/crates.io-index)" = "480c834701caba3548aa991e54677281be3a5414a9d09ddbdf4ed74a569a9d19" | "checksum syn 0.11.9 (registry+https://github.com/rust-lang/crates.io-index)" = "480c834701caba3548aa991e54677281be3a5414a9d09ddbdf4ed74a569a9d19" | ||||
"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" | "checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" | ||||
"checksum syntect 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6728e7e9bbd971751d17d39b0e38e3558c10b9fb32125441bb17c434a2754e7c" | "checksum syntect 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6728e7e9bbd971751d17d39b0e38e3558c10b9fb32125441bb17c434a2754e7c" | ||||
"checksum tera 0.8.0 (git+https://github.com/Keats/tera?branch=reload)" = "<none>" | |||||
"checksum tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "87974a6f5c1dfb344d733055601650059a3363de2a6104819293baff662132d6" | |||||
"checksum tera 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6f2ff83a1773a0482ddc961d0030b514f1848f592ae9612afb241e5eb455df75" | |||||
"checksum term_size 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "07b6c1ac5b3fffd75073276bca1ceed01f67a28537097a2a9539e116e50fb21a" | "checksum term_size 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "07b6c1ac5b3fffd75073276bca1ceed01f67a28537097a2a9539e116e50fb21a" | ||||
"checksum thread-id 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4437c97558c70d129e40629a5b385b3fb1ffac301e63941335e4d354081ec14a" | "checksum thread-id 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4437c97558c70d129e40629a5b385b3fb1ffac301e63941335e4d354081ec14a" | ||||
"checksum thread_local 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c85048c6260d17cf486ceae3282d9fb6b90be220bf5b28c400f5485ffc29f0c7" | "checksum thread_local 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c85048c6260d17cf486ceae3282d9fb6b90be220bf5b28c400f5485ffc29f0c7" | ||||
@@ -1115,7 +1141,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||||
"checksum unsafe-any 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b351086021ebc264aea3ab4f94d61d889d98e5e9ec2d985d993f50133537fd3a" | "checksum unsafe-any 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b351086021ebc264aea3ab4f94d61d889d98e5e9ec2d985d993f50133537fd3a" | ||||
"checksum url 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f5ba8a749fb4479b043733416c244fa9d1d3af3d7c23804944651c8a448cb87e" | "checksum url 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f5ba8a749fb4479b043733416c244fa9d1d3af3d7c23804944651c8a448cb87e" | ||||
"checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122" | "checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122" | ||||
"checksum vec_map 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cac5efe5cb0fa14ec2f84f83c701c562ee63f6dcc680861b21d65c682adfb05f" | |||||
"checksum vec_map 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8cdc8b93bd0198ed872357fb2e667f7125646b1762f16d60b2c96350d361897" | |||||
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" | "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" | ||||
"checksum walkdir 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "c66c0b9792f0a765345452775f3adbd28dde9d33f30d13e5dcc5ae17cf6f3780" | "checksum walkdir 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "c66c0b9792f0a765345452775f3adbd28dde9d33f30d13e5dcc5ae17cf6f3780" | ||||
"checksum walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "bb08f9e670fab86099470b97cd2b252d6527f0b3cc1401acdb595ffc9dd288ff" | "checksum walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "bb08f9e670fab86099470b97cd2b252d6527f0b3cc1401acdb595ffc9dd288ff" | ||||
@@ -9,6 +9,9 @@ homepage = "https://github.com/Keats/gutenberg" | |||||
repository = "https://github.com/Keats/gutenberg" | repository = "https://github.com/Keats/gutenberg" | ||||
keywords = ["static", "site", "generator", "blog"] | keywords = ["static", "site", "generator", "blog"] | ||||
[[bin]] | |||||
name = "gutenberg" | |||||
[dependencies] | [dependencies] | ||||
error-chain = "0.10" | error-chain = "0.10" | ||||
clap = "2.19" | clap = "2.19" | ||||
@@ -20,11 +23,13 @@ glob = "0.2" | |||||
serde = "0.9" | serde = "0.9" | ||||
serde_json = "0.9" | serde_json = "0.9" | ||||
serde_derive = "0.9" | serde_derive = "0.9" | ||||
tera = { git = "https://github.com/Keats/tera", branch = "reload" } | |||||
# tera = "0.8" | |||||
# tera = { path = "../tera" } | |||||
# tera = { git = "https://github.com/Keats/tera", branch = "reload" } | |||||
tera = "0.8" | |||||
slug = "0.1" | slug = "0.1" | ||||
syntect = "1" | syntect = "1" | ||||
chrono = "0.3" | chrono = "0.3" | ||||
toml = { version = "0.3", default-features = false, features = ["serde"]} | |||||
# Below is for the serve cmd | # Below is for the serve cmd | ||||
staticfile = "0.4" | staticfile = "0.4" | ||||
@@ -33,9 +38,5 @@ mount = "0.3" | |||||
notify = "4" | notify = "4" | ||||
ws = "0.6" | ws = "0.6" | ||||
[dependencies.toml] | |||||
version = "0.3" | |||||
default-features = false | |||||
features = ["serde"] | |||||
[dev-dependencies] | |||||
tempdir = "0.3" |
@@ -1,7 +1,9 @@ | |||||
use std::env; | |||||
use gutenberg::errors::Result; | use gutenberg::errors::Result; | ||||
use gutenberg::Site; | use gutenberg::Site; | ||||
pub fn build() -> Result<()> { | pub fn build() -> Result<()> { | ||||
Site::new(false)?.build() | |||||
Site::new(env::current_dir().unwrap())?.build() | |||||
} | } |
@@ -61,7 +61,8 @@ fn rebuild_done_handling(broadcaster: &Sender, res: Result<()>, reload_path: &st | |||||
pub fn serve(interface: &str, port: &str) -> Result<()> { | pub fn serve(interface: &str, port: &str) -> Result<()> { | ||||
println!("Building site..."); | println!("Building site..."); | ||||
let start = Instant::now(); | let start = Instant::now(); | ||||
let mut site = Site::new(true)?; | |||||
let mut site = Site::new(env::current_dir().unwrap())?; | |||||
site.enable_live_reload(); | |||||
site.build()?; | site.build()?; | ||||
report_elapsed_time(start); | report_elapsed_time(start); | ||||
@@ -81,8 +81,8 @@ impl Default for Config { | |||||
/// Get and parse the config. | /// Get and parse the config. | ||||
/// If it doesn't succeed, exit | /// If it doesn't succeed, exit | ||||
pub fn get_config() -> Config { | |||||
match Config::from_file("config.toml") { | |||||
pub fn get_config(path: &Path) -> Config { | |||||
match Config::from_file(path.join("config.toml")) { | |||||
Ok(c) => c, | Ok(c) => c, | ||||
Err(e) => { | Err(e) => { | ||||
println!("Failed to load config.toml"); | println!("Failed to load config.toml"); | ||||
@@ -2,8 +2,7 @@ use tera; | |||||
use toml; | use toml; | ||||
error_chain! { | error_chain! { | ||||
errors { | |||||
} | |||||
errors {} | |||||
links { | links { | ||||
Tera(tera::Error, tera::ErrorKind); | Tera(tera::Error, tera::ErrorKind); | ||||
@@ -1,12 +1,18 @@ | |||||
use std::collections::HashMap; | use std::collections::HashMap; | ||||
use std::path::Path; | |||||
use toml; | use toml; | ||||
use tera::Value; | use tera::Value; | ||||
use chrono::prelude::*; | use chrono::prelude::*; | ||||
use regex::Regex; | |||||
use errors::{Result, ResultExt}; | |||||
use errors::{Result}; | |||||
lazy_static! { | |||||
static ref PAGE_RE: Regex = Regex::new(r"^\n?\+\+\+\n((?s).*(?-s))\+\+\+\n?((?s).*(?-s))$").unwrap(); | |||||
} | |||||
/// The front matter of every page | /// The front matter of every page | ||||
@@ -85,157 +91,23 @@ impl FrontMatter { | |||||
} | } | ||||
#[cfg(test)] | |||||
mod tests { | |||||
use super::{FrontMatter}; | |||||
use tera::to_value; | |||||
#[test] | |||||
fn test_can_parse_a_valid_front_matter() { | |||||
let content = r#" | |||||
title = "Hello" | |||||
description = "hey there""#; | |||||
let res = FrontMatter::parse(content); | |||||
println!("{:?}", res); | |||||
assert!(res.is_ok()); | |||||
let res = res.unwrap(); | |||||
assert_eq!(res.title, "Hello".to_string()); | |||||
assert_eq!(res.description, "hey there".to_string()); | |||||
} | |||||
#[test] | |||||
fn test_can_parse_tags() { | |||||
let content = r#" | |||||
title = "Hello" | |||||
description = "hey there" | |||||
slug = "hello-world" | |||||
tags = ["rust", "html"]"#; | |||||
let res = FrontMatter::parse(content); | |||||
assert!(res.is_ok()); | |||||
let res = res.unwrap(); | |||||
assert_eq!(res.title, "Hello".to_string()); | |||||
assert_eq!(res.slug.unwrap(), "hello-world".to_string()); | |||||
assert_eq!(res.tags.unwrap(), ["rust".to_string(), "html".to_string()]); | |||||
} | |||||
#[test] | |||||
fn test_can_parse_extra_attributes_in_frontmatter() { | |||||
let content = r#" | |||||
title = "Hello" | |||||
description = "hey there" | |||||
slug = "hello-world" | |||||
[extra] | |||||
language = "en" | |||||
authors = ["Bob", "Alice"]"#; | |||||
let res = FrontMatter::parse(content); | |||||
assert!(res.is_ok()); | |||||
let res = res.unwrap(); | |||||
assert_eq!(res.title, "Hello".to_string()); | |||||
assert_eq!(res.slug.unwrap(), "hello-world".to_string()); | |||||
let extra = res.extra.unwrap(); | |||||
assert_eq!(extra.get("language").unwrap(), &to_value("en").unwrap()); | |||||
assert_eq!( | |||||
extra.get("authors").unwrap(), | |||||
&to_value(["Bob".to_string(), "Alice".to_string()]).unwrap() | |||||
); | |||||
/// Split a file between the front matter and its content | |||||
/// It will parse the front matter as well and returns any error encountered | |||||
/// TODO: add tests | |||||
pub fn split_content(file_path: &Path, content: &str) -> Result<(FrontMatter, String)> { | |||||
if !PAGE_RE.is_match(content) { | |||||
bail!("Couldn't find front matter in `{}`. Did you forget to add `+++`?", file_path.to_string_lossy()); | |||||
} | } | ||||
#[test] | |||||
fn test_is_ok_with_url_instead_of_slug() { | |||||
let content = r#" | |||||
title = "Hello" | |||||
description = "hey there" | |||||
url = "hello-world""#; | |||||
let res = FrontMatter::parse(content); | |||||
assert!(res.is_ok()); | |||||
let res = res.unwrap(); | |||||
assert!(res.slug.is_none()); | |||||
assert_eq!(res.url.unwrap(), "hello-world".to_string()); | |||||
} | |||||
// 2. extract the front matter and the content | |||||
let caps = PAGE_RE.captures(content).unwrap(); | |||||
// caps[0] is the full match | |||||
let front_matter = &caps[1]; | |||||
let content = &caps[2]; | |||||
#[test] | |||||
fn test_errors_with_empty_front_matter() { | |||||
let content = r#" "#; | |||||
let res = FrontMatter::parse(content); | |||||
assert!(res.is_err()); | |||||
} | |||||
#[test] | |||||
fn test_errors_with_invalid_front_matter() { | |||||
let content = r#"title = 1\n"#; | |||||
let res = FrontMatter::parse(content); | |||||
assert!(res.is_err()); | |||||
} | |||||
// 3. create our page, parse front matter and assign all of that | |||||
let meta = FrontMatter::parse(front_matter) | |||||
.chain_err(|| format!("Error when parsing front matter of file `{}`", file_path.to_string_lossy()))?; | |||||
#[test] | |||||
fn test_errors_with_missing_required_value_front_matter() { | |||||
let content = r#"title = """#; | |||||
let res = FrontMatter::parse(content); | |||||
assert!(res.is_err()); | |||||
} | |||||
#[test] | |||||
fn test_errors_on_non_string_tag() { | |||||
let content = r#" | |||||
title = "Hello" | |||||
description = "hey there" | |||||
slug = "hello-world" | |||||
tags = ["rust", 1]"#; | |||||
let res = FrontMatter::parse(content); | |||||
assert!(res.is_err()); | |||||
} | |||||
#[test] | |||||
fn test_errors_on_present_but_empty_slug() { | |||||
let content = r#" | |||||
title = "Hello" | |||||
description = "hey there" | |||||
slug = """#; | |||||
let res = FrontMatter::parse(content); | |||||
assert!(res.is_err()); | |||||
} | |||||
#[test] | |||||
fn test_errors_on_present_but_empty_url() { | |||||
let content = r#" | |||||
title = "Hello" | |||||
description = "hey there" | |||||
url = """#; | |||||
let res = FrontMatter::parse(content); | |||||
assert!(res.is_err()); | |||||
} | |||||
#[test] | |||||
fn test_parse_date_yyyy_mm_dd() { | |||||
let content = r#" | |||||
title = "Hello" | |||||
description = "hey there" | |||||
date = "2016-10-10""#; | |||||
let res = FrontMatter::parse(content).unwrap(); | |||||
assert!(res.parse_date().is_some()); | |||||
} | |||||
#[test] | |||||
fn test_parse_date_rfc3339() { | |||||
let content = r#" | |||||
title = "Hello" | |||||
description = "hey there" | |||||
date = "2002-10-02T15:00:00Z""#; | |||||
let res = FrontMatter::parse(content).unwrap(); | |||||
assert!(res.parse_date().is_some()); | |||||
} | |||||
#[test] | |||||
fn test_cant_parse_random_date_format() { | |||||
let content = r#" | |||||
title = "Hello" | |||||
description = "hey there" | |||||
date = "2002/10/12""#; | |||||
let res = FrontMatter::parse(content).unwrap(); | |||||
assert!(res.parse_date().is_none()); | |||||
} | |||||
Ok((meta, content.to_string())) | |||||
} | } |
@@ -14,6 +14,8 @@ extern crate glob; | |||||
extern crate syntect; | extern crate syntect; | ||||
extern crate slug; | extern crate slug; | ||||
extern crate chrono; | extern crate chrono; | ||||
#[cfg(test)] | |||||
extern crate tempdir; | |||||
mod utils; | mod utils; | ||||
mod config; | mod config; | ||||
@@ -22,9 +24,11 @@ mod page; | |||||
mod front_matter; | mod front_matter; | ||||
mod site; | mod site; | ||||
mod markdown; | mod markdown; | ||||
mod section; | |||||
pub use site::Site; | pub use site::Site; | ||||
pub use config::Config; | pub use config::Config; | ||||
pub use front_matter::FrontMatter; | |||||
pub use page::Page; | |||||
pub use front_matter::{FrontMatter, split_content}; | |||||
pub use page::{Page}; | |||||
pub use section::{Section}; | |||||
pub use utils::create_file; | pub use utils::create_file; |
@@ -1,32 +1,28 @@ | |||||
/// A page, can be a blog post or a basic page | /// A page, can be a blog post or a basic page | ||||
use std::cmp::Ordering; | use std::cmp::Ordering; | ||||
use std::fs::{File, read_dir}; | |||||
use std::io::prelude::*; | |||||
use std::fs::{read_dir}; | |||||
use std::path::{Path, PathBuf}; | use std::path::{Path, PathBuf}; | ||||
use std::result::Result as StdResult; | use std::result::Result as StdResult; | ||||
use regex::Regex; | |||||
use tera::{Tera, Context}; | use tera::{Tera, Context}; | ||||
use serde::ser::{SerializeStruct, self}; | use serde::ser::{SerializeStruct, self}; | ||||
use slug::slugify; | use slug::slugify; | ||||
use errors::{Result, ResultExt}; | use errors::{Result, ResultExt}; | ||||
use config::Config; | use config::Config; | ||||
use front_matter::{FrontMatter}; | |||||
use front_matter::{FrontMatter, split_content}; | |||||
use markdown::markdown_to_html; | use markdown::markdown_to_html; | ||||
use utils::{read_file, find_content_components}; | |||||
lazy_static! { | |||||
static ref PAGE_RE: Regex = Regex::new(r"^\n?\+\+\+\n((?s).*(?-s))\+\+\+\n((?s).*(?-s))$").unwrap(); | |||||
} | |||||
/// Looks into the current folder for the path and see if there's anything that is not a .md | /// Looks into the current folder for the path and see if there's anything that is not a .md | ||||
/// file. Those will be copied next to the rendered .html file | /// file. Those will be copied next to the rendered .html file | ||||
fn find_related_assets(path: &Path) -> Vec<PathBuf> { | fn find_related_assets(path: &Path) -> Vec<PathBuf> { | ||||
let mut assets = vec![]; | let mut assets = vec![]; | ||||
for entry in read_dir(path.parent().unwrap()).unwrap().filter_map(|e| e.ok()) { | |||||
for entry in read_dir(path).unwrap().filter_map(|e| e.ok()) { | |||||
let entry_path = entry.path(); | let entry_path = entry.path(); | ||||
if entry_path.is_file() { | if entry_path.is_file() { | ||||
match entry_path.extension() { | match entry_path.extension() { | ||||
@@ -43,24 +39,22 @@ fn find_related_assets(path: &Path) -> Vec<PathBuf> { | |||||
} | } | ||||
#[derive(Clone, Debug, PartialEq, Deserialize)] | |||||
#[derive(Clone, Debug, PartialEq)] | |||||
pub struct Page { | pub struct Page { | ||||
/// .md filepath, excluding the content/ bit | |||||
#[serde(skip_serializing)] | |||||
pub filepath: String, | |||||
/// The .md path | |||||
pub file_path: PathBuf, | |||||
/// The parent directory of the file. Is actually the grand parent directory | |||||
/// if it's an asset folder | |||||
pub parent_path: PathBuf, | |||||
/// The name of the .md file | /// The name of the .md file | ||||
#[serde(skip_serializing)] | |||||
pub filename: String, | |||||
/// The directories above our .md file are called sections | |||||
/// for example a file at content/kb/solutions/blabla.md will have 2 sections: | |||||
pub file_name: String, | |||||
/// The directories above our .md file | |||||
/// for example a file at content/kb/solutions/blabla.md will have 2 components: | |||||
/// `kb` and `solutions` | /// `kb` and `solutions` | ||||
#[serde(skip_serializing)] | |||||
pub sections: Vec<String>, | |||||
pub components: Vec<String>, | |||||
/// The actual content of the page, in markdown | /// The actual content of the page, in markdown | ||||
#[serde(skip_serializing)] | |||||
pub raw_content: String, | pub raw_content: String, | ||||
/// All the non-md files we found next to the .md file | /// All the non-md files we found next to the .md file | ||||
#[serde(skip_serializing)] | |||||
pub assets: Vec<PathBuf>, | pub assets: Vec<PathBuf>, | ||||
/// The HTML rendered of the page | /// The HTML rendered of the page | ||||
pub content: String, | pub content: String, | ||||
@@ -89,9 +83,10 @@ pub struct Page { | |||||
impl Page { | impl Page { | ||||
pub fn new(meta: FrontMatter) -> Page { | pub fn new(meta: FrontMatter) -> Page { | ||||
Page { | Page { | ||||
filepath: "".to_string(), | |||||
filename: "".to_string(), | |||||
sections: vec![], | |||||
file_path: PathBuf::new(), | |||||
parent_path: PathBuf::new(), | |||||
file_name: "".to_string(), | |||||
components: vec![], | |||||
raw_content: "".to_string(), | raw_content: "".to_string(), | ||||
assets: vec![], | assets: vec![], | ||||
content: "".to_string(), | content: "".to_string(), | ||||
@@ -118,25 +113,13 @@ impl Page { | |||||
/// Parse a page given the content of the .md file | /// Parse a page given the content of the .md file | ||||
/// Files without front matter or with invalid front matter are considered | /// Files without front matter or with invalid front matter are considered | ||||
/// erroneous | /// erroneous | ||||
pub fn parse(filepath: &str, content: &str, config: &Config) -> Result<Page> { | |||||
pub fn parse(file_path: &Path, content: &str, config: &Config) -> Result<Page> { | |||||
// 1. separate front matter from content | // 1. separate front matter from content | ||||
if !PAGE_RE.is_match(content) { | |||||
bail!("Couldn't find front matter in `{}`. Did you forget to add `+++`?", filepath); | |||||
} | |||||
// 2. extract the front matter and the content | |||||
let caps = PAGE_RE.captures(content).unwrap(); | |||||
// caps[0] is the full match | |||||
let front_matter = &caps[1]; | |||||
let content = &caps[2]; | |||||
// 3. create our page, parse front matter and assign all of that | |||||
let meta = FrontMatter::parse(front_matter) | |||||
.chain_err(|| format!("Error when parsing front matter of file `{}`", filepath))?; | |||||
let (meta, content) = split_content(file_path, content)?; | |||||
let mut page = Page::new(meta); | let mut page = Page::new(meta); | ||||
page.filepath = filepath.to_string(); | |||||
page.raw_content = content.to_string(); | |||||
page.file_path = file_path.to_path_buf(); | |||||
page.parent_path = page.file_path.parent().unwrap().to_path_buf(); | |||||
page.raw_content = content; | |||||
// We try to be smart about highlighting code as it can be time-consuming | // We try to be smart about highlighting code as it can be time-consuming | ||||
// If the global config disables it, then we do nothing. However, | // If the global config disables it, then we do nothing. However, | ||||
@@ -158,33 +141,38 @@ impl Page { | |||||
} | } | ||||
} | } | ||||
let path = Path::new(filepath); | |||||
page.filename = path.file_stem().expect("Couldn't get filename").to_string_lossy().to_string(); | |||||
let path = Path::new(file_path); | |||||
page.file_name = path.file_stem().unwrap().to_string_lossy().to_string(); | |||||
page.slug = { | page.slug = { | ||||
if let Some(ref slug) = page.meta.slug { | if let Some(ref slug) = page.meta.slug { | ||||
slug.trim().to_string() | slug.trim().to_string() | ||||
} else { | } else { | ||||
slugify(page.filename.clone()) | |||||
slugify(page.file_name.clone()) | |||||
} | } | ||||
}; | }; | ||||
// 4. Find sections | // 4. Find sections | ||||
// Pages with custom urls exists outside of sections | // Pages with custom urls exists outside of sections | ||||
if let Some(ref u) = page.meta.url { | if let Some(ref u) = page.meta.url { | ||||
page.url = u.trim().to_string(); | page.url = u.trim().to_string(); | ||||
} else { | } else { | ||||
// find out if we have sections | |||||
for section in path.parent().unwrap().components() { | |||||
page.sections.push(section.as_ref().to_string_lossy().to_string()); | |||||
} | |||||
if !page.sections.is_empty() { | |||||
page.url = format!("{}/{}", page.sections.join("/"), page.slug); | |||||
page.components = find_content_components(&page.file_path); | |||||
if !page.components.is_empty() { | |||||
// If we have a folder with an asset, don't consider it as a component | |||||
if page.file_name == "index" { | |||||
page.components.pop(); | |||||
// also set parent_path to grandparent instead | |||||
page.parent_path = page.parent_path.parent().unwrap().to_path_buf(); | |||||
} | |||||
// Don't add a trailing slash to sections | |||||
page.url = format!("{}/{}", page.components.join("/"), page.slug); | |||||
} else { | } else { | ||||
page.url = page.slug.clone(); | page.url = page.slug.clone(); | ||||
} | } | ||||
} | } | ||||
page.permalink = if config.base_url.ends_with('/') { | page.permalink = if config.base_url.ends_with('/') { | ||||
format!("{}{}", config.base_url, page.url) | format!("{}{}", config.base_url, page.url) | ||||
} else { | } else { | ||||
@@ -197,15 +185,14 @@ impl Page { | |||||
/// Read and parse a .md file into a Page struct | /// Read and parse a .md file into a Page struct | ||||
pub fn from_file<P: AsRef<Path>>(path: P, config: &Config) -> Result<Page> { | pub fn from_file<P: AsRef<Path>>(path: P, config: &Config) -> Result<Page> { | ||||
let path = path.as_ref(); | let path = path.as_ref(); | ||||
let content = read_file(path)?; | |||||
let mut page = Page::parse(path, &content, config)?; | |||||
page.assets = find_related_assets(&path.parent().unwrap()); | |||||
let mut content = String::new(); | |||||
File::open(path) | |||||
.chain_err(|| format!("Failed to open '{:?}'", path.display()))? | |||||
.read_to_string(&mut content)?; | |||||
if !page.assets.is_empty() && page.file_name != "index" { | |||||
bail!("Page `{}` has assets but is not named index.md", path.display()); | |||||
} | |||||
// Remove the content string from name | |||||
let mut page = Page::parse(&path.strip_prefix("content").unwrap().to_string_lossy(), &content, config)?; | |||||
page.assets = find_related_assets(&path); | |||||
Ok(page) | Ok(page) | ||||
} | } | ||||
@@ -223,7 +210,7 @@ impl Page { | |||||
context.add("page", self); | context.add("page", self); | ||||
tera.render(&tpl_name, &context) | tera.render(&tpl_name, &context) | ||||
.chain_err(|| format!("Failed to render page '{}'", self.filename)) | |||||
.chain_err(|| format!("Failed to render page '{}'", self.file_name)) | |||||
} | } | ||||
} | } | ||||
@@ -275,235 +262,25 @@ impl PartialOrd for Page { | |||||
#[cfg(test)] | #[cfg(test)] | ||||
mod tests { | mod tests { | ||||
use super::{Page}; | |||||
use config::Config; | |||||
#[test] | |||||
fn test_can_parse_a_valid_page() { | |||||
let content = r#" | |||||
+++ | |||||
title = "Hello" | |||||
description = "hey there" | |||||
slug = "hello-world" | |||||
+++ | |||||
Hello world"#; | |||||
let res = Page::parse("post.md", content, &Config::default()); | |||||
assert!(res.is_ok()); | |||||
let page = res.unwrap(); | |||||
assert_eq!(page.meta.title, "Hello".to_string()); | |||||
assert_eq!(page.meta.slug.unwrap(), "hello-world".to_string()); | |||||
assert_eq!(page.raw_content, "Hello world".to_string()); | |||||
assert_eq!(page.content, "<p>Hello world</p>\n".to_string()); | |||||
} | |||||
use tempdir::TempDir; | |||||
#[test] | |||||
fn test_can_find_one_parent_directory() { | |||||
let content = r#" | |||||
+++ | |||||
title = "Hello" | |||||
description = "hey there" | |||||
slug = "hello-world" | |||||
+++ | |||||
Hello world"#; | |||||
let res = Page::parse("posts/intro.md", content, &Config::default()); | |||||
assert!(res.is_ok()); | |||||
let page = res.unwrap(); | |||||
assert_eq!(page.sections, vec!["posts".to_string()]); | |||||
} | |||||
use std::fs::File; | |||||
#[test] | |||||
fn test_can_find_multiple_parent_directories() { | |||||
let content = r#" | |||||
+++ | |||||
title = "Hello" | |||||
description = "hey there" | |||||
slug = "hello-world" | |||||
+++ | |||||
Hello world"#; | |||||
let res = Page::parse("posts/intro/start.md", content, &Config::default()); | |||||
assert!(res.is_ok()); | |||||
let page = res.unwrap(); | |||||
assert_eq!(page.sections, vec!["posts".to_string(), "intro".to_string()]); | |||||
} | |||||
use super::{find_related_assets}; | |||||
#[test] | #[test] | ||||
fn test_can_make_url_from_sections_and_slug() { | |||||
let content = r#" | |||||
+++ | |||||
title = "Hello" | |||||
description = "hey there" | |||||
slug = "hello-world" | |||||
+++ | |||||
Hello world"#; | |||||
let mut conf = Config::default(); | |||||
conf.base_url = "http://hello.com/".to_string(); | |||||
let res = Page::parse("posts/intro/start.md", content, &conf); | |||||
assert!(res.is_ok()); | |||||
let page = res.unwrap(); | |||||
assert_eq!(page.url, "posts/intro/hello-world"); | |||||
assert_eq!(page.permalink, "http://hello.com/posts/intro/hello-world"); | |||||
} | |||||
#[test] | |||||
fn test_can_make_permalink_with_non_trailing_slash_base_url() { | |||||
let content = r#" | |||||
+++ | |||||
title = "Hello" | |||||
description = "hey there" | |||||
slug = "hello-world" | |||||
+++ | |||||
Hello world"#; | |||||
let mut conf = Config::default(); | |||||
conf.base_url = "http://hello.com".to_string(); | |||||
let res = Page::parse("posts/intro/start.md", content, &conf); | |||||
assert!(res.is_ok()); | |||||
let page = res.unwrap(); | |||||
assert_eq!(page.url, "posts/intro/hello-world"); | |||||
assert_eq!(page.permalink, format!("{}{}", conf.base_url, "/posts/intro/hello-world")); | |||||
} | |||||
#[test] | |||||
fn test_can_make_url_from_slug_only() { | |||||
let content = r#" | |||||
+++ | |||||
title = "Hello" | |||||
description = "hey there" | |||||
slug = "hello-world" | |||||
+++ | |||||
Hello world"#; | |||||
let res = Page::parse("start.md", content, &Config::default()); | |||||
assert!(res.is_ok()); | |||||
let page = res.unwrap(); | |||||
assert_eq!(page.url, "hello-world"); | |||||
assert_eq!(page.permalink, format!("{}{}", Config::default().base_url, "hello-world")); | |||||
} | |||||
#[test] | |||||
fn test_errors_on_invalid_front_matter_format() { | |||||
let content = r#" | |||||
title = "Hello" | |||||
description = "hey there" | |||||
slug = "hello-world" | |||||
+++ | |||||
Hello world"#; | |||||
let res = Page::parse("start.md", content, &Config::default()); | |||||
assert!(res.is_err()); | |||||
} | |||||
#[test] | |||||
fn test_can_make_slug_from_non_slug_filename() { | |||||
let content = r#" | |||||
+++ | |||||
title = "Hello" | |||||
description = "hey there" | |||||
+++ | |||||
Hello world"#; | |||||
let res = Page::parse("file with space.md", content, &Config::default()); | |||||
assert!(res.is_ok()); | |||||
let page = res.unwrap(); | |||||
assert_eq!(page.slug, "file-with-space"); | |||||
assert_eq!(page.permalink, format!("{}{}", Config::default().base_url, "file-with-space")); | |||||
} | |||||
#[test] | |||||
fn test_trim_slug_if_needed() { | |||||
let content = r#" | |||||
+++ | |||||
title = "Hello" | |||||
description = "hey there" | |||||
+++ | |||||
Hello world"#; | |||||
let res = Page::parse(" file with space.md", content, &Config::default()); | |||||
assert!(res.is_ok()); | |||||
let page = res.unwrap(); | |||||
assert_eq!(page.slug, "file-with-space"); | |||||
assert_eq!(page.permalink, format!("{}{}", Config::default().base_url, "file-with-space")); | |||||
} | |||||
#[test] | |||||
fn test_reading_analytics_short() { | |||||
let content = r#" | |||||
+++ | |||||
title = "Hello" | |||||
description = "hey there" | |||||
+++ | |||||
Hello world"#; | |||||
let res = Page::parse("file with space.md", content, &Config::default()); | |||||
assert!(res.is_ok()); | |||||
let page = res.unwrap(); | |||||
let (word_count, reading_time) = page.get_reading_analytics(); | |||||
assert_eq!(word_count, 2); | |||||
assert_eq!(reading_time, 0); | |||||
} | |||||
#[test] | |||||
fn test_reading_analytics_long() { | |||||
let mut content = r#" | |||||
+++ | |||||
title = "Hello" | |||||
description = "hey there" | |||||
+++ | |||||
Hello world"#.to_string(); | |||||
for _ in 0..1000 { | |||||
content.push_str(" Hello world"); | |||||
} | |||||
let res = Page::parse("hello.md", &content, &Config::default()); | |||||
assert!(res.is_ok()); | |||||
let page = res.unwrap(); | |||||
let (word_count, reading_time) = page.get_reading_analytics(); | |||||
assert_eq!(word_count, 2002); | |||||
assert_eq!(reading_time, 10); | |||||
} | |||||
#[test] | |||||
fn test_automatic_summary_is_empty_string() { | |||||
let content = r#" | |||||
+++ | |||||
title = "Hello" | |||||
description = "hey there" | |||||
+++ | |||||
Hello world"#.to_string(); | |||||
let res = Page::parse("hello.md", &content, &Config::default()); | |||||
assert!(res.is_ok()); | |||||
let page = res.unwrap(); | |||||
assert_eq!(page.summary, ""); | |||||
} | |||||
#[test] | |||||
fn test_can_specify_summary() { | |||||
let content = r#" | |||||
+++ | |||||
title = "Hello" | |||||
description = "hey there" | |||||
+++ | |||||
Hello world | |||||
<!-- more --> | |||||
"#.to_string(); | |||||
let res = Page::parse("hello.md", &content, &Config::default()); | |||||
assert!(res.is_ok()); | |||||
let page = res.unwrap(); | |||||
assert_eq!(page.summary, "<p>Hello world</p>\n"); | |||||
} | |||||
#[test] | |||||
fn test_can_auto_detect_when_highlighting_needed() { | |||||
let content = r#" | |||||
+++ | |||||
title = "Hello" | |||||
description = "hey there" | |||||
+++ | |||||
``` | |||||
Hey there | |||||
``` | |||||
"#.to_string(); | |||||
let mut config = Config::default(); | |||||
config.highlight_code = Some(true); | |||||
let res = Page::parse("hello.md", &content, &config); | |||||
assert!(res.is_ok()); | |||||
let page = res.unwrap(); | |||||
assert!(page.content.starts_with("<pre")); | |||||
fn test_find_related_assets() { | |||||
let tmp_dir = TempDir::new("example").expect("create temp dir"); | |||||
File::create(tmp_dir.path().join("index.md")).unwrap(); | |||||
File::create(tmp_dir.path().join("example.js")).unwrap(); | |||||
File::create(tmp_dir.path().join("graph.jpg")).unwrap(); | |||||
File::create(tmp_dir.path().join("fail.png")).unwrap(); | |||||
let assets = find_related_assets(tmp_dir.path()); | |||||
assert_eq!(assets.len(), 3); | |||||
assert_eq!(assets.iter().filter(|p| p.extension().unwrap() != "md").count(), 3); | |||||
assert_eq!(assets.iter().filter(|p| p.file_name().unwrap() == "example.js").count(), 1); | |||||
assert_eq!(assets.iter().filter(|p| p.file_name().unwrap() == "graph.jpg").count(), 1); | |||||
assert_eq!(assets.iter().filter(|p| p.file_name().unwrap() == "fail.png").count(), 1); | |||||
} | } | ||||
} | } |
@@ -0,0 +1,101 @@ | |||||
use std::path::{Path, PathBuf}; | |||||
use std::result::Result as StdResult; | |||||
use tera::{Tera, Context}; | |||||
use serde::ser::{SerializeStruct, self}; | |||||
use config::Config; | |||||
use front_matter::{FrontMatter, split_content}; | |||||
use errors::{Result, ResultExt}; | |||||
use utils::{read_file, find_content_components}; | |||||
use page::Page; | |||||
#[derive(Clone, Debug, PartialEq)] | |||||
pub struct Section { | |||||
/// The _index.md full path | |||||
pub file_path: PathBuf, | |||||
/// Path of the directory containing the _index.md file | |||||
pub parent_path: PathBuf, | |||||
/// The folder names from `content` to this section file | |||||
pub components: Vec<String>, | |||||
/// The relative URL of the page | |||||
pub url: String, | |||||
/// The full URL for that page | |||||
pub permalink: String, | |||||
/// The front matter meta-data | |||||
pub meta: FrontMatter, | |||||
/// All direct pages of that section | |||||
pub pages: Vec<Page>, | |||||
/// All direct subsections | |||||
pub subsections: Vec<Section>, | |||||
} | |||||
impl Section { | |||||
pub fn new(file_path: &Path, meta: FrontMatter) -> Section { | |||||
Section { | |||||
file_path: file_path.to_path_buf(), | |||||
parent_path: file_path.parent().unwrap().to_path_buf(), | |||||
components: vec![], | |||||
url: "".to_string(), | |||||
permalink: "".to_string(), | |||||
meta: meta, | |||||
pages: vec![], | |||||
subsections: vec![], | |||||
} | |||||
} | |||||
pub fn parse(file_path: &Path, content: &str, config: &Config) -> Result<Section> { | |||||
let (meta, _) = split_content(file_path, content)?; | |||||
let mut section = Section::new(file_path, meta); | |||||
section.components = find_content_components(§ion.file_path); | |||||
section.url = section.components.join("/"); | |||||
section.permalink = section.components.join("/"); | |||||
section.permalink = if config.base_url.ends_with('/') { | |||||
format!("{}{}", config.base_url, section.url) | |||||
} else { | |||||
format!("{}/{}", config.base_url, section.url) | |||||
}; | |||||
Ok(section) | |||||
} | |||||
/// Read and parse a .md file into a Page struct | |||||
pub fn from_file<P: AsRef<Path>>(path: P, config: &Config) -> Result<Section> { | |||||
let path = path.as_ref(); | |||||
let content = read_file(path)?; | |||||
Section::parse(path, &content, config) | |||||
} | |||||
/// Renders the page using the default layout, unless specified in front-matter | |||||
pub fn render_html(&self, tera: &Tera, config: &Config) -> Result<String> { | |||||
let tpl_name = match self.meta.template { | |||||
Some(ref l) => l.to_string(), | |||||
None => "section.html".to_string() | |||||
}; | |||||
// TODO: create a helper to create context to ensure all contexts | |||||
// have the same names | |||||
let mut context = Context::new(); | |||||
context.add("config", config); | |||||
context.add("section", self); | |||||
tera.render(&tpl_name, &context) | |||||
.chain_err(|| format!("Failed to render section '{}'", self.file_path.display())) | |||||
} | |||||
} | |||||
impl ser::Serialize for Section { | |||||
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error> where S: ser::Serializer { | |||||
let mut state = serializer.serialize_struct("section", 6)?; | |||||
state.serialize_field("title", &self.meta.title)?; | |||||
state.serialize_field("description", &self.meta.description)?; | |||||
state.serialize_field("url", &format!("/{}", self.url))?; | |||||
state.serialize_field("permalink", &self.permalink)?; | |||||
state.serialize_field("pages", &self.pages)?; | |||||
state.serialize_field("subsections", &self.subsections)?; | |||||
state.end() | |||||
} | |||||
} |
@@ -1,7 +1,7 @@ | |||||
use std::collections::HashMap; | |||||
use std::collections::{BTreeMap, HashMap}; | |||||
use std::iter::FromIterator; | use std::iter::FromIterator; | ||||
use std::fs::{remove_dir_all, copy, remove_file}; | use std::fs::{remove_dir_all, copy, remove_file}; | ||||
use std::path::Path; | |||||
use std::path::{Path, PathBuf}; | |||||
use glob::glob; | use glob::glob; | ||||
use tera::{Tera, Context}; | use tera::{Tera, Context}; | ||||
@@ -10,8 +10,9 @@ use walkdir::WalkDir; | |||||
use errors::{Result, ResultExt}; | use errors::{Result, ResultExt}; | ||||
use config::{Config, get_config}; | use config::{Config, get_config}; | ||||
use page::Page; | |||||
use page::{Page}; | |||||
use utils::{create_file, create_directory}; | use utils::{create_file, create_directory}; | ||||
use section::{Section}; | |||||
lazy_static! { | lazy_static! { | ||||
@@ -50,53 +51,98 @@ impl ListItem { | |||||
} | } | ||||
} | } | ||||
#[derive(Debug)] | #[derive(Debug)] | ||||
pub struct Site { | pub struct Site { | ||||
config: Config, | |||||
pages: HashMap<String, Page>, | |||||
sections: HashMap<String, Vec<String>>, | |||||
templates: Tera, | |||||
pub base_path: PathBuf, | |||||
pub config: Config, | |||||
pub pages: HashMap<PathBuf, Page>, | |||||
pub sections: BTreeMap<PathBuf, Section>, | |||||
pub templates: Tera, | |||||
live_reload: bool, | live_reload: bool, | ||||
output_path: PathBuf, | |||||
} | } | ||||
impl Site { | impl Site { | ||||
pub fn new(livereload: bool) -> Result<Site> { | |||||
let mut tera = Tera::new("templates/**/*") | |||||
.chain_err(|| "Error parsing templates")?; | |||||
/// Parse a site at the given path. Defaults to the current dir | |||||
/// Passing in a path is only used in tests | |||||
pub fn new<P: AsRef<Path>>(path: P) -> Result<Site> { | |||||
let path = path.as_ref(); | |||||
let tpl_glob = format!("{}/{}", path.to_string_lossy().replace("\\", "/"), "templates/**/*"); | |||||
let mut tera = Tera::new(&tpl_glob).chain_err(|| "Error parsing templates")?; | |||||
tera.extend(&GUTENBERG_TERA)?; | tera.extend(&GUTENBERG_TERA)?; | ||||
let mut site = Site { | let mut site = Site { | ||||
config: get_config(), | |||||
base_path: path.to_path_buf(), | |||||
config: get_config(&path), | |||||
pages: HashMap::new(), | pages: HashMap::new(), | ||||
sections: HashMap::new(), | |||||
sections: BTreeMap::new(), | |||||
templates: tera, | templates: tera, | ||||
live_reload: livereload, | |||||
live_reload: false, | |||||
output_path: PathBuf::from("public"), | |||||
}; | }; | ||||
site.parse_site()?; | site.parse_site()?; | ||||
Ok(site) | Ok(site) | ||||
} | } | ||||
/// What the function name says | |||||
pub fn enable_live_reload(&mut self) { | |||||
self.live_reload = true; | |||||
} | |||||
/// Used by tests to change the output path to a tmp dir | |||||
#[doc(hidden)] | |||||
pub fn set_output_path<P: AsRef<Path>>(&mut self, path: P) { | |||||
self.output_path = path.as_ref().to_path_buf(); | |||||
} | |||||
/// Reads all .md files in the `content` directory and create pages | /// Reads all .md files in the `content` directory and create pages | ||||
/// out of them | /// out of them | ||||
fn parse_site(&mut self) -> Result<()> { | fn parse_site(&mut self) -> Result<()> { | ||||
// First step: do all the articles and group article by sections | |||||
// hardcoded pattern so can't error | |||||
for entry in glob("content/**/*.md").unwrap().filter_map(|e| e.ok()) { | |||||
let page = Page::from_file(&entry.as_path(), &self.config)?; | |||||
let path = self.base_path.to_string_lossy().replace("\\", "/"); | |||||
let content_glob = format!("{}/{}", path, "content/**/*.md"); | |||||
// parent_dir -> Section | |||||
let mut sections = BTreeMap::new(); | |||||
for section in &page.sections { | |||||
self.sections.entry(section.clone()).or_insert_with(|| vec![]).push(page.slug.clone()); | |||||
// Glob is giving us the result order so _index will show up first | |||||
// for each directory | |||||
for entry in glob(&content_glob).unwrap().filter_map(|e| e.ok()) { | |||||
let path = entry.as_path(); | |||||
if path.file_name().unwrap() == "_index.md" { | |||||
let section = Section::from_file(&path, &self.config)?; | |||||
sections.insert(section.parent_path.clone(), section); | |||||
} else { | |||||
let page = Page::from_file(&path, &self.config)?; | |||||
if sections.contains_key(&page.parent_path) { | |||||
sections.get_mut(&page.parent_path).unwrap().pages.push(page.clone()); | |||||
} | |||||
self.pages.insert(page.file_path.clone(), page); | |||||
} | } | ||||
} | |||||
// Find out the direct subsections of each subsection if there are some | |||||
let mut grandparent_paths = HashMap::new(); | |||||
for section in sections.values() { | |||||
let grand_parent = section.parent_path.parent().unwrap().to_path_buf(); | |||||
grandparent_paths.entry(grand_parent).or_insert_with(|| vec![]).push(section.clone()); | |||||
} | |||||
self.pages.insert(page.slug.clone(), page); | |||||
for (parent_path, section) in sections.iter_mut() { | |||||
match grandparent_paths.get(parent_path) { | |||||
Some(paths) => section.subsections.extend(paths.clone()), | |||||
None => continue, | |||||
}; | |||||
} | } | ||||
self.sections = sections; | |||||
Ok(()) | Ok(()) | ||||
} | } | ||||
// Inject live reload script tag if in live reload mode | |||||
/// Inject live reload script tag if in live reload mode | |||||
fn inject_livereload(&self, html: String) -> String { | fn inject_livereload(&self, html: String) -> String { | ||||
if self.live_reload { | if self.live_reload { | ||||
return html.replace( | return html.replace( | ||||
@@ -108,11 +154,10 @@ impl Site { | |||||
html | html | ||||
} | } | ||||
/// Copy the content of the `static` folder into the `public` folder | /// Copy the content of the `static` folder into the `public` folder | ||||
/// | /// | ||||
/// TODO: only copy one file if possible because that would be a waster | |||||
/// to do re-copy the whole thing | |||||
/// TODO: only copy one file if possible because that would be a waste | |||||
/// to do re-copy the whole thing. Benchmark first to see if it's a big difference | |||||
pub fn copy_static_directory(&self) -> Result<()> { | pub fn copy_static_directory(&self) -> Result<()> { | ||||
let from = Path::new("static"); | let from = Path::new("static"); | ||||
let target = Path::new("public"); | let target = Path::new("public"); | ||||
@@ -160,7 +205,7 @@ impl Site { | |||||
} | } | ||||
pub fn build_pages(&self) -> Result<()> { | pub fn build_pages(&self) -> Result<()> { | ||||
let public = Path::new("public"); | |||||
let public = self.output_path.clone(); | |||||
if !public.exists() { | if !public.exists() { | ||||
create_directory(&public)?; | create_directory(&public)?; | ||||
} | } | ||||
@@ -168,40 +213,39 @@ impl Site { | |||||
let mut pages = vec![]; | let mut pages = vec![]; | ||||
let mut category_pages: HashMap<String, Vec<&Page>> = HashMap::new(); | let mut category_pages: HashMap<String, Vec<&Page>> = HashMap::new(); | ||||
let mut tag_pages: HashMap<String, Vec<&Page>> = HashMap::new(); | let mut tag_pages: HashMap<String, Vec<&Page>> = HashMap::new(); | ||||
// First we render the pages themselves | // First we render the pages themselves | ||||
for page in self.pages.values() { | for page in self.pages.values() { | ||||
// Copy the nesting of the content directory if we have sections for that page | // Copy the nesting of the content directory if we have sections for that page | ||||
let mut current_path = public.to_path_buf(); | let mut current_path = public.to_path_buf(); | ||||
// This loop happens when the page doesn't have a set URL | |||||
for section in &page.sections { | |||||
current_path.push(section); | |||||
for component in page.url.split("/") { | |||||
current_path.push(component); | |||||
if !current_path.exists() { | if !current_path.exists() { | ||||
create_directory(¤t_path)?; | create_directory(¤t_path)?; | ||||
} | } | ||||
} | } | ||||
// if we have a url already set, use that as base | |||||
if let Some(ref url) = page.meta.url { | |||||
current_path.push(url); | |||||
} | |||||
// Make sure the folder exists | // Make sure the folder exists | ||||
create_directory(¤t_path)?; | create_directory(¤t_path)?; | ||||
// Finally, create a index.html file there with the page rendered | // Finally, create a index.html file there with the page rendered | ||||
let output = page.render_html(&self.templates, &self.config)?; | let output = page.render_html(&self.templates, &self.config)?; | ||||
create_file(current_path.join("index.html"), &self.inject_livereload(output))?; | create_file(current_path.join("index.html"), &self.inject_livereload(output))?; | ||||
// Copy any asset we found previously into the same directory as the index.html | // Copy any asset we found previously into the same directory as the index.html | ||||
for asset in &page.assets { | for asset in &page.assets { | ||||
let asset_path = asset.as_path(); | let asset_path = asset.as_path(); | ||||
copy(&asset_path, ¤t_path.join(asset_path.file_name().unwrap()))?; | copy(&asset_path, ¤t_path.join(asset_path.file_name().unwrap()))?; | ||||
} | } | ||||
pages.push(page); | pages.push(page); | ||||
if let Some(ref category) = page.meta.category { | if let Some(ref category) = page.meta.category { | ||||
category_pages.entry(category.to_string()).or_insert_with(|| vec![]).push(page); | category_pages.entry(category.to_string()).or_insert_with(|| vec![]).push(page); | ||||
} | } | ||||
if let Some(ref tags) = page.meta.tags { | if let Some(ref tags) = page.meta.tags { | ||||
for tag in tags { | for tag in tags { | ||||
tag_pages.entry(tag.to_string()).or_insert_with(|| vec![]).push(page); | tag_pages.entry(tag.to_string()).or_insert_with(|| vec![]).push(page); | ||||
@@ -232,6 +276,7 @@ impl Site { | |||||
if self.config.generate_rss.unwrap() { | if self.config.generate_rss.unwrap() { | ||||
self.render_rss_feed()?; | self.render_rss_feed()?; | ||||
} | } | ||||
self.render_sections()?; | |||||
self.copy_static_directory() | self.copy_static_directory() | ||||
} | } | ||||
@@ -247,7 +292,7 @@ impl Site { | |||||
("tags", "tags.html", "tag.html", "tag") | ("tags", "tags.html", "tag.html", "tag") | ||||
}; | }; | ||||
let public = Path::new("public"); | |||||
let public = self.output_path.clone(); | |||||
let mut output_path = public.to_path_buf(); | let mut output_path = public.to_path_buf(); | ||||
output_path.push(name); | output_path.push(name); | ||||
create_directory(&output_path)?; | create_directory(&output_path)?; | ||||
@@ -292,8 +337,7 @@ impl Site { | |||||
context.add("pages", &self.pages.values().collect::<Vec<&Page>>()); | context.add("pages", &self.pages.values().collect::<Vec<&Page>>()); | ||||
let sitemap = self.templates.render("sitemap.xml", &context)?; | let sitemap = self.templates.render("sitemap.xml", &context)?; | ||||
let public = Path::new("public"); | |||||
create_file(public.join("sitemap.xml"), &sitemap)?; | |||||
create_file(self.output_path.join("sitemap.xml"), &sitemap)?; | |||||
Ok(()) | Ok(()) | ||||
} | } | ||||
@@ -309,6 +353,7 @@ impl Site { | |||||
if pages.is_empty() { | if pages.is_empty() { | ||||
return Ok(()); | return Ok(()); | ||||
} | } | ||||
pages.sort_by(|a, b| a.partial_cmp(b).unwrap()); | pages.sort_by(|a, b| a.partial_cmp(b).unwrap()); | ||||
context.add("pages", &pages); | context.add("pages", &pages); | ||||
context.add("last_build_date", &pages[0].meta.date); | context.add("last_build_date", &pages[0].meta.date); | ||||
@@ -323,8 +368,27 @@ impl Site { | |||||
let sitemap = self.templates.render("rss.xml", &context)?; | let sitemap = self.templates.render("rss.xml", &context)?; | ||||
let public = Path::new("public"); | |||||
create_file(public.join("rss.xml"), &sitemap)?; | |||||
create_file(self.output_path.join("rss.xml"), &sitemap)?; | |||||
Ok(()) | |||||
} | |||||
fn render_sections(&self) -> Result<()> { | |||||
let public = self.output_path.clone(); | |||||
for section in self.sections.values() { | |||||
let mut output_path = public.to_path_buf(); | |||||
for component in §ion.components { | |||||
output_path.push(component); | |||||
if !output_path.exists() { | |||||
create_directory(&output_path)?; | |||||
} | |||||
} | |||||
let output = section.render_html(&self.templates, &self.config)?; | |||||
create_file(output_path.join("index.html"), &self.inject_livereload(output))?; | |||||
} | |||||
Ok(()) | Ok(()) | ||||
} | } | ||||
@@ -21,3 +21,52 @@ pub fn create_directory<P: AsRef<Path>>(path: P) -> Result<()> { | |||||
} | } | ||||
Ok(()) | Ok(()) | ||||
} | } | ||||
/// Return the content of a file, with error handling added | |||||
pub fn read_file<P: AsRef<Path>>(path: P) -> Result<String> { | |||||
let path = path.as_ref(); | |||||
let mut content = String::new(); | |||||
File::open(path) | |||||
.chain_err(|| format!("Failed to open '{:?}'", path.display()))? | |||||
.read_to_string(&mut content)?; | |||||
Ok(content) | |||||
} | |||||
/// Takes a full path to a .md and returns only the components after the `content` directory | |||||
/// Will not return the filename as last component | |||||
pub fn find_content_components<P: AsRef<Path>>(path: P) -> Vec<String> { | |||||
let path = path.as_ref(); | |||||
let mut is_in_content = false; | |||||
let mut components = vec![]; | |||||
for section in path.parent().unwrap().components() { | |||||
let component = section.as_ref().to_string_lossy(); | |||||
if is_in_content { | |||||
components.push(component.to_string()); | |||||
continue; | |||||
} | |||||
if component == "content" { | |||||
is_in_content = true; | |||||
} | |||||
} | |||||
components | |||||
} | |||||
#[cfg(test)] | |||||
mod tests { | |||||
use super::{find_content_components}; | |||||
#[test] | |||||
fn test_find_content_components() { | |||||
let res = find_content_components("/home/vincent/code/site/content/posts/tutorials/python.md"); | |||||
assert_eq!(res, ["posts".to_string(), "tutorials".to_string()]); | |||||
} | |||||
} |
@@ -0,0 +1,7 @@ | |||||
title = "My site" | |||||
base_url = "https://replace-this-with-your-url.com" | |||||
highlight_code = true | |||||
[extra.author] | |||||
name = "Vincent Prouillet" |
@@ -0,0 +1,4 @@ | |||||
+++ | |||||
title = "Posts" | |||||
description = "" | |||||
+++ |
@@ -0,0 +1,7 @@ | |||||
+++ | |||||
title = "Fixed slug" | |||||
description = "" | |||||
slug = "something-else" | |||||
+++ | |||||
A simple page with a slug defined |
@@ -0,0 +1,7 @@ | |||||
+++ | |||||
title = "Fixed URL" | |||||
description = "" | |||||
url = "a-fixed-url" | |||||
+++ | |||||
A simple page with fixed url |
@@ -0,0 +1,6 @@ | |||||
+++ | |||||
title = "Simple" | |||||
description = "" | |||||
+++ | |||||
A simple page |
@@ -0,0 +1,6 @@ | |||||
+++ | |||||
title = "Python in posts" | |||||
description = "" | |||||
+++ | |||||
Same filename but different path |
@@ -0,0 +1,6 @@ | |||||
+++ | |||||
title = "Simple" | |||||
description = "" | |||||
+++ | |||||
A simple page |
@@ -0,0 +1,4 @@ | |||||
+++ | |||||
title = "Tutorials" | |||||
description = "" | |||||
+++ |
@@ -0,0 +1,4 @@ | |||||
+++ | |||||
title = "DevOps" | |||||
description = "" | |||||
+++ |
@@ -0,0 +1,6 @@ | |||||
+++ | |||||
title = "Docker" | |||||
description = "" | |||||
+++ | |||||
A simple page |
@@ -0,0 +1,6 @@ | |||||
+++ | |||||
title = "Nix" | |||||
description = "" | |||||
+++ | |||||
A simple page |
@@ -0,0 +1,4 @@ | |||||
+++ | |||||
title = "Programming" | |||||
description = "" | |||||
+++ |
@@ -0,0 +1,6 @@ | |||||
+++ | |||||
title = "Python tutorial" | |||||
description = "" | |||||
+++ | |||||
A simple page |
@@ -0,0 +1,6 @@ | |||||
+++ | |||||
title = "Rust" | |||||
description = "" | |||||
+++ | |||||
A simple page |
@@ -0,0 +1,7 @@ | |||||
+++ | |||||
title = "With assets" | |||||
description = "hey there" | |||||
slug = "with-assets" | |||||
+++ | |||||
Hello world |
@@ -0,0 +1,3 @@ | |||||
body { | |||||
color: red; | |||||
} |
@@ -0,0 +1,3 @@ | |||||
{% for category in categories %} | |||||
{{ category.name }} {{ category.slug }} {{ category.count }} | |||||
{% endfor %} |
@@ -0,0 +1,8 @@ | |||||
Category: {{ category }} | |||||
{% for page in pages %} | |||||
<article> | |||||
<h3 class="post__title"><a href="{{ page.url }}">{{ page.title }}</a></h3> | |||||
</article> | |||||
{% endfor %} |
@@ -0,0 +1,27 @@ | |||||
<!DOCTYPE html> | |||||
<html lang="{{ config.language_code }}"> | |||||
<head> | |||||
<meta charset="UTF-8"> | |||||
<meta name="apple-mobile-web-app-capable" content="yes"> | |||||
<meta name="viewport" content="width=device-width, initial-scale=1"> | |||||
<meta name="description" content="{{ config.description }}"> | |||||
<meta name="author" content="{{ config.extra.author.name }}"> | |||||
<link href="https://fonts.googleapis.com/css?family=Fira+Mono|Fira+Sans|Merriweather" rel="stylesheet"> | |||||
<link href="site.css" rel="stylesheet"> | |||||
<title>{{ config.title }}</title> | |||||
</head> | |||||
<body> | |||||
<div class="content"> | |||||
{% block content %} | |||||
<div class="list-posts"> | |||||
{% for page in pages %} | |||||
<article> | |||||
<h3 class="post__title"><a href="{{ page.url }}">{{ page.title }}</a></h3> | |||||
</article> | |||||
{% endfor %} | |||||
</div> | |||||
{% endblock content %} | |||||
</div> | |||||
</body> | |||||
</html> |
@@ -0,0 +1,5 @@ | |||||
{% extends "index.html" %} | |||||
{% block content %} | |||||
{{ page.content | safe }} | |||||
{% endblock content %} |
@@ -0,0 +1,10 @@ | |||||
{% extends "index.html" %} | |||||
{% block content %} | |||||
{% for page in section.pages %} | |||||
{{page.title}} | |||||
{% endfor %} | |||||
{% for subsection in section.subsections %} | |||||
{{subsection.title}} | |||||
{% endfor %} | |||||
{% endblock content %} |
@@ -0,0 +1,7 @@ | |||||
Tag: {{ tag }} | |||||
{% for page in pages %} | |||||
<article> | |||||
<h3 class="post__title"><a href="{{ page.url }}">{{ page.title }}</a></h3> | |||||
</article> | |||||
{% endfor %} |
@@ -0,0 +1,3 @@ | |||||
{% for tag in tags %} | |||||
{{ tag.name }} {{ tag.slug }} {{ tag.count }} | |||||
{% endfor %} |
@@ -0,0 +1,197 @@ | |||||
extern crate gutenberg; | |||||
extern crate tera; | |||||
use std::path::Path; | |||||
use gutenberg::{FrontMatter, split_content}; | |||||
use tera::to_value; | |||||
#[test] | |||||
fn test_can_parse_a_valid_front_matter() { | |||||
let content = r#" | |||||
title = "Hello" | |||||
description = "hey there""#; | |||||
let res = FrontMatter::parse(content); | |||||
println!("{:?}", res); | |||||
assert!(res.is_ok()); | |||||
let res = res.unwrap(); | |||||
assert_eq!(res.title, "Hello".to_string()); | |||||
assert_eq!(res.description, "hey there".to_string()); | |||||
} | |||||
#[test] | |||||
fn test_can_parse_tags() { | |||||
let content = r#" | |||||
title = "Hello" | |||||
description = "hey there" | |||||
slug = "hello-world" | |||||
tags = ["rust", "html"]"#; | |||||
let res = FrontMatter::parse(content); | |||||
assert!(res.is_ok()); | |||||
let res = res.unwrap(); | |||||
assert_eq!(res.title, "Hello".to_string()); | |||||
assert_eq!(res.slug.unwrap(), "hello-world".to_string()); | |||||
assert_eq!(res.tags.unwrap(), ["rust".to_string(), "html".to_string()]); | |||||
} | |||||
#[test] | |||||
fn test_can_parse_extra_attributes_in_frontmatter() { | |||||
let content = r#" | |||||
title = "Hello" | |||||
description = "hey there" | |||||
slug = "hello-world" | |||||
[extra] | |||||
language = "en" | |||||
authors = ["Bob", "Alice"]"#; | |||||
let res = FrontMatter::parse(content); | |||||
assert!(res.is_ok()); | |||||
let res = res.unwrap(); | |||||
assert_eq!(res.title, "Hello".to_string()); | |||||
assert_eq!(res.slug.unwrap(), "hello-world".to_string()); | |||||
let extra = res.extra.unwrap(); | |||||
assert_eq!(extra.get("language").unwrap(), &to_value("en").unwrap()); | |||||
assert_eq!( | |||||
extra.get("authors").unwrap(), | |||||
&to_value(["Bob".to_string(), "Alice".to_string()]).unwrap() | |||||
); | |||||
} | |||||
#[test] | |||||
fn test_is_ok_with_url_instead_of_slug() { | |||||
let content = r#" | |||||
title = "Hello" | |||||
description = "hey there" | |||||
url = "hello-world""#; | |||||
let res = FrontMatter::parse(content); | |||||
assert!(res.is_ok()); | |||||
let res = res.unwrap(); | |||||
assert!(res.slug.is_none()); | |||||
assert_eq!(res.url.unwrap(), "hello-world".to_string()); | |||||
} | |||||
#[test] | |||||
fn test_errors_with_empty_front_matter() { | |||||
let content = r#" "#; | |||||
let res = FrontMatter::parse(content); | |||||
assert!(res.is_err()); | |||||
} | |||||
#[test] | |||||
fn test_errors_with_invalid_front_matter() { | |||||
let content = r#"title = 1\n"#; | |||||
let res = FrontMatter::parse(content); | |||||
assert!(res.is_err()); | |||||
} | |||||
#[test] | |||||
fn test_errors_with_missing_required_value_front_matter() { | |||||
let content = r#"title = """#; | |||||
let res = FrontMatter::parse(content); | |||||
assert!(res.is_err()); | |||||
} | |||||
#[test] | |||||
fn test_errors_on_non_string_tag() { | |||||
let content = r#" | |||||
title = "Hello" | |||||
description = "hey there" | |||||
slug = "hello-world" | |||||
tags = ["rust", 1]"#; | |||||
let res = FrontMatter::parse(content); | |||||
assert!(res.is_err()); | |||||
} | |||||
#[test] | |||||
fn test_errors_on_present_but_empty_slug() { | |||||
let content = r#" | |||||
title = "Hello" | |||||
description = "hey there" | |||||
slug = """#; | |||||
let res = FrontMatter::parse(content); | |||||
assert!(res.is_err()); | |||||
} | |||||
#[test] | |||||
fn test_errors_on_present_but_empty_url() { | |||||
let content = r#" | |||||
title = "Hello" | |||||
description = "hey there" | |||||
url = """#; | |||||
let res = FrontMatter::parse(content); | |||||
assert!(res.is_err()); | |||||
} | |||||
#[test] | |||||
fn test_parse_date_yyyy_mm_dd() { | |||||
let content = r#" | |||||
title = "Hello" | |||||
description = "hey there" | |||||
date = "2016-10-10""#; | |||||
let res = FrontMatter::parse(content).unwrap(); | |||||
assert!(res.parse_date().is_some()); | |||||
} | |||||
#[test] | |||||
fn test_parse_date_rfc3339() { | |||||
let content = r#" | |||||
title = "Hello" | |||||
description = "hey there" | |||||
date = "2002-10-02T15:00:00Z""#; | |||||
let res = FrontMatter::parse(content).unwrap(); | |||||
assert!(res.parse_date().is_some()); | |||||
} | |||||
#[test] | |||||
fn test_cant_parse_random_date_format() { | |||||
let content = r#" | |||||
title = "Hello" | |||||
description = "hey there" | |||||
date = "2002/10/12""#; | |||||
let res = FrontMatter::parse(content).unwrap(); | |||||
assert!(res.parse_date().is_none()); | |||||
} | |||||
#[test] | |||||
fn test_can_split_content_valid() { | |||||
let content = r#" | |||||
+++ | |||||
title = "Title" | |||||
description = "hey there" | |||||
date = "2002/10/12" | |||||
+++ | |||||
Hello | |||||
"#; | |||||
let (front_matter, content) = split_content(Path::new(""), content).unwrap(); | |||||
assert_eq!(content, "Hello\n"); | |||||
assert_eq!(front_matter.title, "Title"); | |||||
} | |||||
#[test] | |||||
fn test_can_split_content_with_only_frontmatter_valid() { | |||||
let content = r#" | |||||
+++ | |||||
title = "Title" | |||||
description = "hey there" | |||||
date = "2002/10/12" | |||||
+++"#; | |||||
let (front_matter, content) = split_content(Path::new(""), content).unwrap(); | |||||
assert_eq!(content, ""); | |||||
assert_eq!(front_matter.title, "Title"); | |||||
} | |||||
#[test] | |||||
fn test_error_if_cannot_locate_frontmatter() { | |||||
let content = r#" | |||||
+++ | |||||
title = "Title" | |||||
description = "hey there" | |||||
date = "2002/10/12" | |||||
"#; | |||||
let res = split_content(Path::new(""), content); | |||||
assert!(res.is_err()); | |||||
} |
@@ -0,0 +1,249 @@ | |||||
extern crate gutenberg; | |||||
extern crate tempdir; | |||||
use tempdir::TempDir; | |||||
use std::fs::File; | |||||
use std::path::Path; | |||||
use gutenberg::{Page, Config}; | |||||
#[test] | |||||
fn test_can_parse_a_valid_page() { | |||||
let content = r#" | |||||
+++ | |||||
title = "Hello" | |||||
description = "hey there" | |||||
slug = "hello-world" | |||||
+++ | |||||
Hello world"#; | |||||
let res = Page::parse(Path::new("post.md"), content, &Config::default()); | |||||
assert!(res.is_ok()); | |||||
let page = res.unwrap(); | |||||
assert_eq!(page.meta.title, "Hello".to_string()); | |||||
assert_eq!(page.meta.slug.unwrap(), "hello-world".to_string()); | |||||
assert_eq!(page.raw_content, "Hello world".to_string()); | |||||
assert_eq!(page.content, "<p>Hello world</p>\n".to_string()); | |||||
} | |||||
#[test] | |||||
fn test_can_find_one_parent_directory() { | |||||
let content = r#" | |||||
+++ | |||||
title = "Hello" | |||||
description = "hey there" | |||||
slug = "hello-world" | |||||
+++ | |||||
Hello world"#; | |||||
let res = Page::parse(Path::new("content/posts/intro.md"), content, &Config::default()); | |||||
assert!(res.is_ok()); | |||||
let page = res.unwrap(); | |||||
assert_eq!(page.components, vec!["posts".to_string()]); | |||||
} | |||||
#[test] | |||||
fn test_can_find_multiple_parent_directories() { | |||||
let content = r#" | |||||
+++ | |||||
title = "Hello" | |||||
description = "hey there" | |||||
slug = "hello-world" | |||||
+++ | |||||
Hello world"#; | |||||
let res = Page::parse(Path::new("content/posts/intro/start.md"), content, &Config::default()); | |||||
assert!(res.is_ok()); | |||||
let page = res.unwrap(); | |||||
assert_eq!(page.components, vec!["posts".to_string(), "intro".to_string()]); | |||||
} | |||||
#[test] | |||||
fn test_can_make_url_from_sections_and_slug() { | |||||
let content = r#" | |||||
+++ | |||||
title = "Hello" | |||||
description = "hey there" | |||||
slug = "hello-world" | |||||
+++ | |||||
Hello world"#; | |||||
let mut conf = Config::default(); | |||||
conf.base_url = "http://hello.com/".to_string(); | |||||
let res = Page::parse(Path::new("content/posts/intro/start.md"), content, &conf); | |||||
assert!(res.is_ok()); | |||||
let page = res.unwrap(); | |||||
assert_eq!(page.url, "posts/intro/hello-world"); | |||||
assert_eq!(page.permalink, "http://hello.com/posts/intro/hello-world"); | |||||
} | |||||
#[test] | |||||
fn test_can_make_permalink_with_non_trailing_slash_base_url() { | |||||
let content = r#" | |||||
+++ | |||||
title = "Hello" | |||||
description = "hey there" | |||||
slug = "hello-world" | |||||
+++ | |||||
Hello world"#; | |||||
let mut conf = Config::default(); | |||||
conf.base_url = "http://hello.com".to_string(); | |||||
let res = Page::parse(Path::new("content/posts/intro/hello-world.md"), content, &conf); | |||||
assert!(res.is_ok()); | |||||
let page = res.unwrap(); | |||||
assert_eq!(page.url, "posts/intro/hello-world"); | |||||
assert_eq!(page.permalink, format!("{}{}", conf.base_url, "/posts/intro/hello-world")); | |||||
} | |||||
#[test] | |||||
fn test_can_make_url_from_slug_only() { | |||||
let content = r#" | |||||
+++ | |||||
title = "Hello" | |||||
description = "hey there" | |||||
slug = "hello-world" | |||||
+++ | |||||
Hello world"#; | |||||
let res = Page::parse(Path::new("start.md"), content, &Config::default()); | |||||
assert!(res.is_ok()); | |||||
let page = res.unwrap(); | |||||
assert_eq!(page.url, "hello-world"); | |||||
assert_eq!(page.permalink, format!("{}{}", Config::default().base_url, "hello-world")); | |||||
} | |||||
#[test] | |||||
fn test_errors_on_invalid_front_matter_format() { | |||||
let content = r#" | |||||
title = "Hello" | |||||
description = "hey there" | |||||
slug = "hello-world" | |||||
+++ | |||||
Hello world"#; | |||||
let res = Page::parse(Path::new("start.md"), content, &Config::default()); | |||||
assert!(res.is_err()); | |||||
} | |||||
#[test] | |||||
fn test_can_make_slug_from_non_slug_filename() { | |||||
let content = r#" | |||||
+++ | |||||
title = "Hello" | |||||
description = "hey there" | |||||
+++ | |||||
Hello world"#; | |||||
let res = Page::parse(Path::new("file with space.md"), content, &Config::default()); | |||||
assert!(res.is_ok()); | |||||
let page = res.unwrap(); | |||||
assert_eq!(page.slug, "file-with-space"); | |||||
assert_eq!(page.permalink, format!("{}{}", Config::default().base_url, "file-with-space")); | |||||
} | |||||
#[test] | |||||
fn test_trim_slug_if_needed() { | |||||
let content = r#" | |||||
+++ | |||||
title = "Hello" | |||||
description = "hey there" | |||||
+++ | |||||
Hello world"#; | |||||
let res = Page::parse(Path::new(" file with space.md"), content, &Config::default()); | |||||
assert!(res.is_ok()); | |||||
let page = res.unwrap(); | |||||
assert_eq!(page.slug, "file-with-space"); | |||||
assert_eq!(page.permalink, format!("{}{}", Config::default().base_url, "file-with-space")); | |||||
} | |||||
#[test] | |||||
fn test_reading_analytics_short() { | |||||
let content = r#" | |||||
+++ | |||||
title = "Hello" | |||||
description = "hey there" | |||||
+++ | |||||
Hello world"#; | |||||
let res = Page::parse(Path::new("hello.md"), content, &Config::default()); | |||||
assert!(res.is_ok()); | |||||
let page = res.unwrap(); | |||||
let (word_count, reading_time) = page.get_reading_analytics(); | |||||
assert_eq!(word_count, 2); | |||||
assert_eq!(reading_time, 0); | |||||
} | |||||
#[test] | |||||
fn test_reading_analytics_long() { | |||||
let mut content = r#" | |||||
+++ | |||||
title = "Hello" | |||||
description = "hey there" | |||||
+++ | |||||
Hello world"#.to_string(); | |||||
for _ in 0..1000 { | |||||
content.push_str(" Hello world"); | |||||
} | |||||
let res = Page::parse(Path::new("hello.md"), &content, &Config::default()); | |||||
assert!(res.is_ok()); | |||||
let page = res.unwrap(); | |||||
let (word_count, reading_time) = page.get_reading_analytics(); | |||||
assert_eq!(word_count, 2002); | |||||
assert_eq!(reading_time, 10); | |||||
} | |||||
#[test] | |||||
fn test_automatic_summary_is_empty_string() { | |||||
let content = r#" | |||||
+++ | |||||
title = "Hello" | |||||
description = "hey there" | |||||
+++ | |||||
Hello world"#.to_string(); | |||||
let res = Page::parse(Path::new("hello.md"), &content, &Config::default()); | |||||
assert!(res.is_ok()); | |||||
let page = res.unwrap(); | |||||
assert_eq!(page.summary, ""); | |||||
} | |||||
#[test] | |||||
fn test_can_specify_summary() { | |||||
let content = r#" | |||||
+++ | |||||
title = "Hello" | |||||
description = "hey there" | |||||
+++ | |||||
Hello world | |||||
<!-- more --> | |||||
"#.to_string(); | |||||
let res = Page::parse(Path::new("hello.md"), &content, &Config::default()); | |||||
assert!(res.is_ok()); | |||||
let page = res.unwrap(); | |||||
assert_eq!(page.summary, "<p>Hello world</p>\n"); | |||||
} | |||||
#[test] | |||||
fn test_can_auto_detect_when_highlighting_needed() { | |||||
let content = r#" | |||||
+++ | |||||
title = "Hello" | |||||
description = "hey there" | |||||
+++ | |||||
``` | |||||
Hey there | |||||
``` | |||||
"#.to_string(); | |||||
let mut config = Config::default(); | |||||
config.highlight_code = Some(true); | |||||
let res = Page::parse(Path::new("hello.md"), &content, &config); | |||||
assert!(res.is_ok()); | |||||
let page = res.unwrap(); | |||||
assert!(page.content.starts_with("<pre")); | |||||
} | |||||
#[test] | |||||
fn test_file_not_named_index_with_assets() { | |||||
let tmp_dir = TempDir::new("example").expect("create temp dir"); | |||||
File::create(tmp_dir.path().join("something.md")).unwrap(); | |||||
File::create(tmp_dir.path().join("example.js")).unwrap(); | |||||
File::create(tmp_dir.path().join("graph.jpg")).unwrap(); | |||||
File::create(tmp_dir.path().join("fail.png")).unwrap(); | |||||
let page = Page::from_file(tmp_dir.path().join("something.md"), &Config::default()); | |||||
assert!(page.is_err()); | |||||
} |
@@ -0,0 +1,176 @@ | |||||
extern crate gutenberg; | |||||
extern crate tempdir; | |||||
extern crate glob; | |||||
use std::env; | |||||
use std::path::Path; | |||||
use std::fs::File; | |||||
use std::io::prelude::*; | |||||
// use glob::glob; | |||||
use tempdir::TempDir; | |||||
use gutenberg::{Site}; | |||||
#[test] | |||||
fn test_can_parse_site() { | |||||
let mut path = env::current_dir().unwrap().to_path_buf(); | |||||
path.push("test_site"); | |||||
let site = Site::new(&path).unwrap(); | |||||
// Correct number of pages (sections are pages too) | |||||
assert_eq!(site.pages.len(), 10); | |||||
let posts_path = path.join("content").join("posts"); | |||||
// Make sure we remove all the pwd + content from the sections | |||||
let basic = site.pages.get(&posts_path.join("simple.md")).unwrap(); | |||||
assert_eq!(basic.components, vec!["posts".to_string()]); | |||||
// Make sure the page with a url doesn't have any sections | |||||
let url_post = site.pages.get(&posts_path.join("fixed-url.md")).unwrap(); | |||||
assert!(url_post.components.is_empty()); | |||||
// Make sure the article in a folder with only asset doesn't get counted as a section | |||||
let asset_folder_post = site.pages.get(&posts_path.join("with-assets").join("index.md")).unwrap(); | |||||
assert_eq!(asset_folder_post.components, vec!["posts".to_string()]); | |||||
// That we have the right number of sections | |||||
assert_eq!(site.sections.len(), 4); | |||||
// And that the sections are correct | |||||
let posts_section = site.sections.get(&posts_path).unwrap(); | |||||
assert_eq!(posts_section.subsections.len(), 1); | |||||
assert_eq!(posts_section.pages.len(), 5); | |||||
let tutorials_section = site.sections.get(&posts_path.join("tutorials")).unwrap(); | |||||
assert_eq!(tutorials_section.subsections.len(), 2); | |||||
assert_eq!(tutorials_section.pages.len(), 0); | |||||
let devops_section = site.sections.get(&posts_path.join("tutorials").join("devops")).unwrap(); | |||||
assert_eq!(devops_section.subsections.len(), 0); | |||||
assert_eq!(devops_section.pages.len(), 2); | |||||
let prog_section = site.sections.get(&posts_path.join("tutorials").join("programming")).unwrap(); | |||||
assert_eq!(prog_section.subsections.len(), 0); | |||||
assert_eq!(prog_section.pages.len(), 2); | |||||
} | |||||
// 2 helper macros to make all the build testing more bearable | |||||
macro_rules! file_exists { | |||||
($root: expr, $path: expr) => { | |||||
{ | |||||
let mut path = $root.clone(); | |||||
for component in $path.split("/") { | |||||
path = path.join(component); | |||||
} | |||||
Path::new(&path).exists() | |||||
} | |||||
} | |||||
} | |||||
macro_rules! file_contains { | |||||
($root: expr, $path: expr, $text: expr) => { | |||||
{ | |||||
let mut path = $root.clone(); | |||||
for component in $path.split("/") { | |||||
path = path.join(component); | |||||
} | |||||
let mut file = File::open(&path).unwrap(); | |||||
let mut s = String::new(); | |||||
file.read_to_string(&mut s).unwrap(); | |||||
s.contains($text) | |||||
} | |||||
} | |||||
} | |||||
#[test] | |||||
fn test_can_build_site_without_live_reload() { | |||||
let mut path = env::current_dir().unwrap().to_path_buf(); | |||||
path.push("test_site"); | |||||
let mut site = Site::new(&path).unwrap(); | |||||
let tmp_dir = TempDir::new("example").expect("create temp dir"); | |||||
let public = &tmp_dir.path().join("public"); | |||||
site.set_output_path(&public); | |||||
site.build().unwrap(); | |||||
assert!(Path::new(&public).exists()); | |||||
assert!(file_exists!(public, "index.html")); | |||||
assert!(file_exists!(public, "sitemap.xml")); | |||||
assert!(file_exists!(public, "a-fixed-url/index.html")); | |||||
assert!(file_exists!(public, "posts/python/index.html")); | |||||
assert!(file_exists!(public, "posts/tutorials/devops/nix/index.html")); | |||||
assert!(file_exists!(public, "posts/with-assets/index.html")); | |||||
// Sections | |||||
assert!(file_exists!(public, "posts/index.html")); | |||||
assert!(file_exists!(public, "posts/tutorials/index.html")); | |||||
assert!(file_exists!(public, "posts/tutorials/devops/index.html")); | |||||
assert!(file_exists!(public, "posts/tutorials/programming/index.html")); | |||||
// TODO: add assertion for syntax highlighting | |||||
// No tags or categories | |||||
assert_eq!(file_exists!(public, "categories/index.html"), false); | |||||
assert_eq!(file_exists!(public, "tags/index.html"), false); | |||||
// no live reload code | |||||
assert_eq!(file_contains!(public, "index.html", "/livereload.js?port=1112&mindelay=10"), false); | |||||
} | |||||
#[test] | |||||
fn test_can_build_site_with_live_reload() { | |||||
let mut path = env::current_dir().unwrap().to_path_buf(); | |||||
path.push("test_site"); | |||||
let mut site = Site::new(&path).unwrap(); | |||||
let tmp_dir = TempDir::new("example").expect("create temp dir"); | |||||
let public = &tmp_dir.path().join("public"); | |||||
site.set_output_path(&public); | |||||
site.enable_live_reload(); | |||||
site.build().unwrap(); | |||||
assert!(Path::new(&public).exists()); | |||||
assert!(file_exists!(public, "index.html")); | |||||
assert!(file_exists!(public, "sitemap.xml")); | |||||
assert!(file_exists!(public, "a-fixed-url/index.html")); | |||||
assert!(file_exists!(public, "posts/python/index.html")); | |||||
assert!(file_exists!(public, "posts/tutorials/devops/nix/index.html")); | |||||
assert!(file_exists!(public, "posts/with-assets/index.html")); | |||||
// Sections | |||||
assert!(file_exists!(public, "posts/index.html")); | |||||
assert!(file_exists!(public, "posts/tutorials/index.html")); | |||||
assert!(file_exists!(public, "posts/tutorials/devops/index.html")); | |||||
assert!(file_exists!(public, "posts/tutorials/programming/index.html")); | |||||
// TODO: add assertion for syntax highlighting | |||||
// No tags or categories | |||||
assert_eq!(file_exists!(public, "categories/index.html"), false); | |||||
assert_eq!(file_exists!(public, "tags/index.html"), false); | |||||
// no live reload code | |||||
assert!(file_contains!(public, "index.html", "/livereload.js?port=1112&mindelay=10")); | |||||
} | |||||
#[test] | |||||
fn test_can_build_site_with_categories() { | |||||
let mut path = env::current_dir().unwrap().to_path_buf(); | |||||
path.push("test_site"); | |||||
let mut site = Site::new(&path).unwrap(); | |||||
let tmp_dir = TempDir::new("example").expect("create temp dir"); | |||||
site.set_output_path(&tmp_dir); | |||||
site.build().unwrap(); | |||||
} | |||||
#[test] | |||||
fn test_can_build_site_with_tags() { | |||||
let mut path = env::current_dir().unwrap().to_path_buf(); | |||||
path.push("test_site"); | |||||
let mut site = Site::new(&path).unwrap(); | |||||
let tmp_dir = TempDir::new("example").expect("create temp dir"); | |||||
site.set_output_path(&tmp_dir); | |||||
site.build().unwrap(); | |||||
} |