commit
5559dff996
10
CHANGELOG.md
10
CHANGELOG.md
|
@ -1,5 +1,15 @@
|
|||
# Changelog
|
||||
|
||||
## 0.0.6 (unreleased)
|
||||
|
||||
- Fix missing serialized data for sections
|
||||
- Change the single item template context for categories/tags
|
||||
- Add a `get_url` and a `get_section` global Tera function
|
||||
- Add a config option to control how many articles to show in RSS feed
|
||||
- Move `insert_anchor_links` from config to being a section option and it can
|
||||
now be insert left or right
|
||||
|
||||
|
||||
## 0.0.5 (2017-05-15)
|
||||
|
||||
- Fix XML templates overriding and reloading
|
||||
|
|
126
Cargo.lock
generated
126
Cargo.lock
generated
|
@ -4,7 +4,7 @@ version = "0.0.5"
|
|||
dependencies = [
|
||||
"base64 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"chrono 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"clap 2.24.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"clap 2.24.2 (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)",
|
||||
"iron 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -12,14 +12,14 @@ dependencies = [
|
|||
"mount 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"notify 4.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pulldown-cmark 0.0.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.7 (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)",
|
||||
"syntect 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tera 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tera 0.10.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"term-painter 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"toml 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -45,20 +45,20 @@ 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.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.0"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"backtrace-sys 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"dbghelp-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-demangle 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
@ -69,7 +69,7 @@ version = "0.1.10"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"gcc 0.3.46 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -155,7 +155,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "2.24.1"
|
||||
version = "2.24.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -163,9 +163,9 @@ dependencies = [
|
|||
"bitflags 0.8.2 (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.3.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-segmentation 1.2.0 (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.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -212,7 +212,7 @@ name = "error-chain"
|
|||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"backtrace 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"backtrace 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -220,7 +220,7 @@ name = "filetime"
|
|||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -228,7 +228,7 @@ name = "flate2"
|
|||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"miniz-sys 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
|
@ -244,7 +244,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
dependencies = [
|
||||
"bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fsevent-sys 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -252,7 +252,7 @@ name = "fsevent-sys"
|
|||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -301,11 +301,11 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-bidi 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-bidi 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-normalization 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
|
@ -314,7 +314,7 @@ name = "inotify"
|
|||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -322,7 +322,7 @@ name = "iovec"
|
|||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
|
@ -374,7 +374,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.22"
|
||||
version = "0.2.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
|
@ -392,7 +392,7 @@ name = "memchr"
|
|||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -409,7 +409,7 @@ version = "0.1.9"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"gcc 0.3.46 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -418,7 +418,7 @@ version = "0.5.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"miow 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"net2 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -436,7 +436,7 @@ 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)",
|
||||
"lazycell 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"net2 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -487,7 +487,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
dependencies = [
|
||||
"cfg-if 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)",
|
||||
"libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
@ -498,7 +498,7 @@ version = "0.5.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -512,7 +512,7 @@ dependencies = [
|
|||
"fsevent-sys 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"inotify 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"mio 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"walkdir 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -555,7 +555,7 @@ name = "num_cpus"
|
|||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -565,7 +565,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
dependencies = [
|
||||
"bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"onig_sys 61.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
|
@ -575,7 +575,7 @@ version = "61.3.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cmake 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
|
@ -628,7 +628,7 @@ name = "rand"
|
|||
version = "0.3.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -638,19 +638,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"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)",
|
||||
"regex-syntax 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex-syntax 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"thread_local 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.4.0"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
|
@ -702,12 +702,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.5"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.5"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -732,7 +732,7 @@ dependencies = [
|
|||
"dtoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"itoa 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -804,7 +804,7 @@ dependencies = [
|
|||
"lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"onig 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"plist 0.1.3 (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.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-serialize 0.3.24 (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)",
|
||||
|
@ -820,7 +820,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tera"
|
||||
version = "0.10.5"
|
||||
version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"chrono 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -829,8 +829,8 @@ dependencies = [
|
|||
"humansize 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 0.2.8 (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)",
|
||||
"serde 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"slug 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"url 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -859,7 +859,7 @@ version = "0.3.0"
|
|||
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.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
|
@ -869,7 +869,7 @@ version = "3.1.0"
|
|||
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.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -887,7 +887,7 @@ version = "0.1.37"
|
|||
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.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"redox_syscall 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
@ -897,7 +897,7 @@ name = "toml"
|
|||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"serde 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -928,7 +928,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.2.5"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -941,7 +941,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.1.0"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
|
@ -980,7 +980,7 @@ name = "url"
|
|||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"idna 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"idna 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
|
@ -991,7 +991,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
|
||||
[[package]]
|
||||
name = "vec_map"
|
||||
version = "0.7.0"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
|
@ -1070,7 +1070,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
"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 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.2 (registry+https://github.com/rust-lang/crates.io-index)" = "72f9b4182546f4b04ebc4ab7f84948953a118bd6021a1b6a6c909e3e94f6be76"
|
||||
"checksum backtrace-sys 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d192fd129132fbc97497c1f2ec2c2c5174e376b95f535199ef4fe0a293d33842"
|
||||
"checksum base64 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "30e93c03064e7590d0466209155251b90c22e37fab1daf2771582598b5827557"
|
||||
"checksum bincode 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "55eb0b7fd108527b0c77860f75eca70214e11a8b4c6ef05148c54c05a25d48ad"
|
||||
|
@ -1084,7 +1084,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 chrono 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)" = "9213f7cd7c27e95c2b57c49f0e69b1ea65b27138da84a170133fd21b07659c00"
|
||||
"checksum chrono 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d9123be86fd2a8f627836c235ecdf331fdd067ecf7ac05aa1a68fbcf2429f056"
|
||||
"checksum clap 2.24.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b7541069be0b8aec41030802abe8b5cdef0490070afaa55418adea93b1e431e0"
|
||||
"checksum clap 2.24.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6b8f69e518f967224e628896b54e41ff6acfb4dcfefc5076325c36525dac900f"
|
||||
"checksum cmake 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)" = "92278eb79412c8f75cfc89e707a1bb3a6490b68f7f2e78d15c774f30fe701122"
|
||||
"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"
|
||||
|
@ -1102,7 +1102,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
"checksum httparse 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77f756bed9ee3a83ce98774f4155b42a31b787029013f3a7d83eca714e500e21"
|
||||
"checksum humansize 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "92d211e6e70b05749dce515b47684f29a3c8c38bbbb21c50b30aff9eca1b0bd3"
|
||||
"checksum hyper 0.10.10 (registry+https://github.com/rust-lang/crates.io-index)" = "36e108e0b1fa2d17491cbaac4bc460dc0956029d10ccf83c913dd0e5db3e7f07"
|
||||
"checksum idna 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6ac85ec3f80c8e4e99d9325521337e14ec7555c458a14e377d189659a427f375"
|
||||
"checksum idna 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2233d4940b1f19f0418c158509cd7396b8d70a5db5705ce410914dc8fa603b37"
|
||||
"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"
|
||||
|
@ -1111,7 +1111,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
"checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a"
|
||||
"checksum lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3b37545ab726dd833ec6420aaba8231c5b320814b9029ad585555d2a03e94fbf"
|
||||
"checksum lazycell 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ce12306c4739d86ee97c23139f3a34ddf0387bbf181bc7929d287025a8c3ef6b"
|
||||
"checksum libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)" = "babb8281da88cba992fa1f4ddec7d63ed96280a1a53ec9b919fd37b53d71e502"
|
||||
"checksum libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)" = "e7eb6b826bfc1fdea7935d46556250d1799b7fe2d9f7951071f4291710665e3e"
|
||||
"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 memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1dbccc0e46f1ea47b9f17e6d67c5a96bd27030519c519c9c91327e31275a47b4"
|
||||
|
@ -1141,8 +1141,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
"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 redox_syscall 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)" = "29dbdfd4b9df8ab31dec47c6087b7b13cbf4a776f335e4de8efba8288dda075b"
|
||||
"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 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1731164734096285ec2a5ec7fea5248ae2f5485b3feeb0115af4fda2183b2d1b"
|
||||
"checksum regex-syntax 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ad890a5eef7953f55427c50575c680c42841653abd2b028b68cd223d157f62db"
|
||||
"checksum rustc-demangle 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3058a43ada2c2d0b92b3ae38007a2d0fa5e9db971be260e0171408a4ff471c95"
|
||||
"checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
|
||||
"checksum rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c5f5376ea5e30ce23c03eb77cbe4962b988deead10910c372b226388b594c084"
|
||||
|
@ -1151,8 +1151,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
"checksum sequence_trie 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c915714ca833b1d4d6b8f6a9d72a3ff632fe45b40a8d184ef79c81bec6327eed"
|
||||
"checksum serde 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)" = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8"
|
||||
"checksum serde 0.9.15 (registry+https://github.com/rust-lang/crates.io-index)" = "34b623917345a631dc9608d5194cc206b3fe6c3554cd1c75b937e55e285254af"
|
||||
"checksum serde 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e101024c846392aadc80d5d452f2ff011f9bff1a0441151f8575e8a23488ef95"
|
||||
"checksum serde_derive 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "7c85839e9a00a5b0c7bddb1e44b8c3907c7aba5fe234c7ec5ccef1c188eb41d9"
|
||||
"checksum serde 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c0c3d79316a6051231925504f6ef893d45088e8823c77a8331a3dcf427ee9087"
|
||||
"checksum serde_derive 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "0019cd5b9f0529a1a0e145a912e9a2d60c325c58f7f260fc36c71976e9d76aee"
|
||||
"checksum serde_derive_internals 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "021c338d22c7e30f957a6ab7e388cb6098499dda9fd4ba1661ee074ca7a180d1"
|
||||
"checksum serde_json 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "48b04779552e92037212c3615370f6bd57a40ebba7f20e554ff9f55e41a69a7b"
|
||||
"checksum sha1 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cc30b1e1e8c40c121ca33b86c23308a090d19974ef001b4bf6e61fd1a0fb095c"
|
||||
|
@ -1165,7 +1165,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6"
|
||||
"checksum syntect 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "24204b1f4bdd49f84e5f4b219d0bf1dc45ac2fd7fc46320ab6627b537d6d4b69"
|
||||
"checksum tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "87974a6f5c1dfb344d733055601650059a3363de2a6104819293baff662132d6"
|
||||
"checksum tera 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)" = "5ce5ea7e2239a92d2bb662b8a337d8a3c45b9e6d630d113b0ca18dd6e64fb05d"
|
||||
"checksum tera 0.10.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9c931ade2857155d5e55115375d4d2b8a441536e2b9e44643a8b67e235e09030"
|
||||
"checksum term 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d168af3930b369cfe245132550579d47dfd873d69470755a19c2c6568dbbd989"
|
||||
"checksum term-painter 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ab900bf2f05175932b13d4fc12f8ff09ef777715b04998791ab2c930841e496b"
|
||||
"checksum term_size 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2b6b55df3198cc93372e85dd2ed817f0e38ce8cc0f22eb32391bfad9c4bf209"
|
||||
|
@ -1177,9 +1177,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
"checksum typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887"
|
||||
"checksum typemap 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "653be63c80a3296da5551e1bfd2cca35227e13cdd08c6668903ae2f4f77aa1f6"
|
||||
"checksum unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "13a5906ca2b98c799f4b1ab4557b76367ebd6ae5ef14930ec841c74aed5f3764"
|
||||
"checksum unicode-bidi 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d3a078ebdd62c0e71a709c3d53d2af693fe09fe93fbff8344aebe289b78f9032"
|
||||
"checksum unicode-bidi 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c44d4e7ce691e2538b886bf33669fd6da1653a12d741b9390f351955c0949c03"
|
||||
"checksum unicode-normalization 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "e28fa37426fceeb5cf8f41ee273faa7c82c47dc8fba5853402841e665fcd86ff"
|
||||
"checksum unicode-segmentation 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "18127285758f0e2c6cf325bb3f3d138a12fee27de4f23e146cd6a179f26c2cf3"
|
||||
"checksum unicode-segmentation 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a8083c594e02b8ae1654ae26f0ade5158b119bd88ad0e8227a5d8fcd72407946"
|
||||
"checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f"
|
||||
"checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc"
|
||||
"checksum unidecode 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d2adb95ee07cd579ed18131f2d9e7a17c25a4b76022935c7f2460d2bfae89fd2"
|
||||
|
@ -1187,7 +1187,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 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 vec_map 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8cdc8b93bd0198ed872357fb2e667f7125646b1762f16d60b2c96350d361897"
|
||||
"checksum vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "887b5b631c2ad01628bbbaa7dd4c869f80d3186688f8d0b6f58774fbe324988c"
|
||||
"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 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "bb08f9e670fab86099470b97cd2b252d6527f0b3cc1401acdb595ffc9dd288ff"
|
||||
|
|
11
README.md
11
README.md
|
@ -169,10 +169,13 @@ to link to. The path to the file starts from the `content` directory.
|
|||
For example, linking to a file located at `content/pages/about.md` would be `[my link](./pages/about.md)`.
|
||||
|
||||
### Anchors
|
||||
Headers get an automatic id from their content in order to be able to add deep links. By default no links are actually created but
|
||||
the `insert_anchor_links` option in `config.toml` can be set to `true` to link tags. The default template is very ugly and will need
|
||||
CSS tweaks in your projet to look decent. The default template can also be easily overwritten by creating a `anchor-link.html` file in
|
||||
the `templates` directory.
|
||||
Headers get an automatic id from their content in order to be able to add deep links.
|
||||
You can also choose, at the section level, whether to automatically insert an anchor link next to it. It is turned off by default
|
||||
but can be turned on by setting `insert_anchor = "left"` or `insert_anchor = "right"` in the `_index.md` file. `left` will insert
|
||||
the anchor link before the title text and right will insert it after.
|
||||
|
||||
The default template is very basic and will need CSS tweaks in your projet to look decent.
|
||||
It can easily be overwritten by creating a `anchor-link.html` file in the `templates` directory.
|
||||
|
||||
### Shortcodes
|
||||
Gutenberg uses markdown for content but sometimes you want to insert some HTML, for example for a YouTube video.
|
||||
|
|
|
@ -16,8 +16,8 @@ base_url = "https://example.com"
|
|||
"#;
|
||||
|
||||
|
||||
pub fn create_new_project<P: AsRef<Path>>(name: P) -> Result<()> {
|
||||
let path = name.as_ref();
|
||||
pub fn create_new_project(name: &str) -> Result<()> {
|
||||
let path = Path::new(name);
|
||||
|
||||
// Better error message than the rust default
|
||||
if path.exists() && path.is_dir() {
|
||||
|
@ -26,7 +26,7 @@ pub fn create_new_project<P: AsRef<Path>>(name: P) -> Result<()> {
|
|||
|
||||
// main folder
|
||||
create_dir(path)?;
|
||||
create_file(path.join("config.toml"), CONFIG.trim_left())?;
|
||||
create_file(&path.join("config.toml"), CONFIG.trim_left())?;
|
||||
|
||||
// content folder
|
||||
create_dir(path.join("content"))?;
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -23,6 +23,10 @@ enum ChangeKind {
|
|||
StaticFiles,
|
||||
}
|
||||
|
||||
// Uglified using uglifyjs
|
||||
// Also, commenting out the lines 330-340 (containing `e instanceof ProtocolError`) was needed
|
||||
// as it seems their build didn't work well and didn't include ProtocolError so it would error on
|
||||
// errors
|
||||
const LIVE_RELOAD: &'static str = include_str!("livereload.js");
|
||||
|
||||
|
||||
|
@ -49,8 +53,6 @@ fn rebuild_done_handling(broadcaster: &Sender, res: Result<()>, reload_path: &st
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// Most of it taken from mdbook
|
||||
pub fn serve(interface: &str, port: &str, config_file: &str) -> Result<()> {
|
||||
let start = Instant::now();
|
||||
let mut site = Site::new(env::current_dir().unwrap(), config_file)?;
|
||||
|
@ -88,7 +90,8 @@ pub fn serve(interface: &str, port: &str, config_file: &str) -> Result<()> {
|
|||
mount.mount("/livereload.js", livereload_handler);
|
||||
// Starts with a _ to not trigger the unused lint
|
||||
// we need to assign to a variable otherwise it will block
|
||||
let _iron = Iron::new(mount).http(address.as_str()).unwrap();
|
||||
let _iron = Iron::new(mount).http(address.as_str())
|
||||
.chain_err(|| "Can't start the webserver")?;
|
||||
|
||||
// The websocket for livereload
|
||||
let ws_server = WebSocket::new(|output: Sender| {
|
||||
|
@ -119,8 +122,6 @@ pub fn serve(interface: &str, port: &str, config_file: &str) -> Result<()> {
|
|||
use notify::DebouncedEvent::*;
|
||||
|
||||
loop {
|
||||
// See https://github.com/spf13/hugo/blob/master/commands/hugo.go
|
||||
// for a more complete version of that
|
||||
match rx.recv() {
|
||||
Ok(event) => {
|
||||
match event {
|
||||
|
@ -162,7 +163,6 @@ pub fn serve(interface: &str, port: &str, config_file: &str) -> Result<()> {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/// Returns whether the path we received corresponds to a temp file created
|
||||
/// by an editor or the OS
|
||||
fn is_temp_file(path: &Path) -> bool {
|
||||
|
@ -191,7 +191,6 @@ fn is_temp_file(path: &Path) -> bool {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/// Detect what changed from the given path so we have an idea what needs
|
||||
/// to be reloaded
|
||||
fn detect_change_kind(pwd: &str, path: &Path) -> (ChangeKind, String) {
|
||||
|
@ -218,8 +217,8 @@ mod tests {
|
|||
use super::{is_temp_file, detect_change_kind, ChangeKind};
|
||||
|
||||
#[test]
|
||||
fn test_can_recognize_temp_files() {
|
||||
let testcases = vec![
|
||||
fn can_recognize_temp_files() {
|
||||
let test_cases = vec![
|
||||
Path::new("hello.swp"),
|
||||
Path::new("hello.swx"),
|
||||
Path::new(".DS_STORE"),
|
||||
|
@ -231,14 +230,14 @@ mod tests {
|
|||
Path::new("#hello.html"),
|
||||
];
|
||||
|
||||
for t in testcases {
|
||||
for t in test_cases {
|
||||
assert!(is_temp_file(&t));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_can_detect_kind_of_changes() {
|
||||
let testcases = vec![
|
||||
fn can_detect_kind_of_changes() {
|
||||
let test_cases = vec![
|
||||
(
|
||||
(ChangeKind::Templates, "/templates/hello.html".to_string()),
|
||||
"/home/vincent/site", Path::new("/home/vincent/site/templates/hello.html")
|
||||
|
@ -253,7 +252,7 @@ mod tests {
|
|||
),
|
||||
];
|
||||
|
||||
for (expected, pwd, path) in testcases {
|
||||
for (expected, pwd, path) in test_cases {
|
||||
assert_eq!(expected, detect_change_kind(&pwd, &path));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,13 +36,17 @@ pub fn notify_site_size(site: &Site) {
|
|||
|
||||
/// Display a warning in the console if there are ignored pages in the site
|
||||
pub fn warn_about_ignored_pages(site: &Site) {
|
||||
let ignored_pages = site.get_ignored_pages();
|
||||
let ignored_pages: Vec<_> = site.sections
|
||||
.values()
|
||||
.flat_map(|s| s.ignored_pages.iter().map(|p| p.file.path.clone()))
|
||||
.collect();
|
||||
|
||||
if !ignored_pages.is_empty() {
|
||||
warn(&format!(
|
||||
"{} page(s) ignored (missing date or order in a sorted section):",
|
||||
ignored_pages.len()
|
||||
));
|
||||
for path in site.get_ignored_pages() {
|
||||
for path in ignored_pages {
|
||||
warn(&format!("- {}", path.display()));
|
||||
}
|
||||
}
|
||||
|
@ -62,9 +66,11 @@ pub fn report_elapsed_time(instant: Instant) {
|
|||
|
||||
/// Display an error message and the actual error(s)
|
||||
pub fn unravel_errors(message: &str, error: &Error) {
|
||||
if !message.is_empty() {
|
||||
self::error(message);
|
||||
self::error(&format!("Error: {}", error));
|
||||
for e in error.iter().skip(1) {
|
||||
self::error(&format!("Reason: {}", e));
|
||||
}
|
||||
}
|
||||
self::error(&format!("Error: {}", error));
|
||||
for e in error.iter().skip(1) {
|
||||
self::error(&format!("Reason: {}", e));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -70,7 +70,7 @@ fn main() {
|
|||
match cmd::serve(interface, port, config_file) {
|
||||
Ok(()) => (),
|
||||
Err(e) => {
|
||||
console::unravel_errors("Failed to build the site", &e);
|
||||
console::unravel_errors("", &e);
|
||||
::std::process::exit(1);
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,8 +1,21 @@
|
|||
use std::path::Path;
|
||||
|
||||
use gutenberg::{Site, SectionFrontMatter, PageFrontMatter};
|
||||
use gutenberg::{Site, Page, Section, SectionFrontMatter, PageFrontMatter};
|
||||
use gutenberg::errors::Result;
|
||||
|
||||
|
||||
/// Finds the section that contains the page given if there is one
|
||||
pub fn find_parent_section<'a>(site: &'a Site, page: &Page) -> Option<&'a Section> {
|
||||
for section in site.sections.values() {
|
||||
if section.is_child_page(&page.file.path) {
|
||||
return Some(section)
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum PageChangesNeeded {
|
||||
/// Editing `tags`
|
||||
|
@ -22,7 +35,7 @@ enum SectionChangesNeeded {
|
|||
Sort,
|
||||
/// Editing `title`, `description`, `extra`, `template` or setting `render` to true
|
||||
Render,
|
||||
/// Editing `paginate_by` or `paginate_path`
|
||||
/// Editing `paginate_by`, `paginate_path` or `insert_anchor`
|
||||
RenderWithPages,
|
||||
/// Setting `render` to false
|
||||
Delete,
|
||||
|
@ -43,7 +56,9 @@ fn find_section_front_matter_changes(current: &SectionFrontMatter, other: &Secti
|
|||
return changes_needed;
|
||||
}
|
||||
|
||||
if current.paginate_by != other.paginate_by || current.paginate_path != other.paginate_path {
|
||||
if current.paginate_by != other.paginate_by
|
||||
|| current.paginate_path != other.paginate_path
|
||||
|| current.insert_anchor != other.insert_anchor {
|
||||
changes_needed.push(SectionChangesNeeded::RenderWithPages);
|
||||
// Nothing else we can do
|
||||
return changes_needed;
|
||||
|
@ -85,7 +100,7 @@ pub fn after_content_change(site: &mut Site, path: &Path) -> Result<()> {
|
|||
// A section was deleted, many things can be impacted:
|
||||
// - the pages of the section are becoming orphans
|
||||
// - any page that was referencing the section (index, etc)
|
||||
let relative_path = site.sections[path].relative_path.clone();
|
||||
let relative_path = site.sections[path].file.relative.clone();
|
||||
// Remove the link to it and the section itself from the Site
|
||||
site.permalinks.remove(&relative_path);
|
||||
site.sections.remove(path);
|
||||
|
@ -94,18 +109,20 @@ pub fn after_content_change(site: &mut Site, path: &Path) -> Result<()> {
|
|||
// A page was deleted, many things can be impacted:
|
||||
// - the section the page is in
|
||||
// - any page that was referencing the section (index, etc)
|
||||
let relative_path = site.pages[path].relative_path.clone();
|
||||
let relative_path = site.pages[path].file.relative.clone();
|
||||
site.permalinks.remove(&relative_path);
|
||||
if let Some(p) = site.pages.remove(path) {
|
||||
if p.meta.has_tags() || p.meta.category.is_some() {
|
||||
site.populate_tags_and_categories();
|
||||
}
|
||||
|
||||
if site.find_parent_section(&p).is_some() {
|
||||
if find_parent_section(site, &p).is_some() {
|
||||
site.populate_sections();
|
||||
}
|
||||
};
|
||||
}
|
||||
// Ensure we have our fn updated so it doesn't contain the permalinks deleted
|
||||
site.register_get_url_fn();
|
||||
// Deletion is something that doesn't happen all the time so we
|
||||
// don't need to optimise it too much
|
||||
return site.build();
|
||||
|
@ -140,6 +157,7 @@ pub fn after_content_change(site: &mut Site, path: &Path) -> Result<()> {
|
|||
return Ok(());
|
||||
},
|
||||
None => {
|
||||
site.register_get_url_fn();
|
||||
// New section, only render that one
|
||||
site.populate_sections();
|
||||
return site.render_section(&site.sections[path], true);
|
||||
|
@ -150,6 +168,7 @@ pub fn after_content_change(site: &mut Site, path: &Path) -> Result<()> {
|
|||
// A page was edited
|
||||
match site.add_page(path, true)? {
|
||||
Some(prev) => {
|
||||
site.register_get_url_fn();
|
||||
// Updating a page
|
||||
let current = site.pages[path].clone();
|
||||
// Front matter didn't change, only content did
|
||||
|
@ -171,8 +190,8 @@ pub fn after_content_change(site: &mut Site, path: &Path) -> Result<()> {
|
|||
site.render_categories()?;
|
||||
},
|
||||
PageChangesNeeded::Sort => {
|
||||
let section_path = match site.find_parent_section(&site.pages[path]) {
|
||||
Some(s) => s.file_path.clone(),
|
||||
let section_path = match find_parent_section(site, &site.pages[path]) {
|
||||
Some(s) => s.file.path.clone(),
|
||||
None => continue // Do nothing if it's an orphan page
|
||||
};
|
||||
site.populate_sections();
|
||||
|
@ -188,6 +207,7 @@ pub fn after_content_change(site: &mut Site, path: &Path) -> Result<()> {
|
|||
|
||||
},
|
||||
None => {
|
||||
site.register_get_url_fn();
|
||||
// It's a new page!
|
||||
site.populate_sections();
|
||||
site.populate_tags_and_categories();
|
||||
|
|
|
@ -6,7 +6,7 @@ use std::collections::HashMap;
|
|||
use toml::{Value as Toml, self};
|
||||
|
||||
use errors::{Result, ResultExt};
|
||||
use markdown::SETUP;
|
||||
use rendering::highlighting::THEME_SET;
|
||||
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
|
@ -24,8 +24,10 @@ pub struct Config {
|
|||
pub description: Option<String>,
|
||||
/// The language used in the site. Defaults to "en"
|
||||
pub language_code: Option<String>,
|
||||
/// Whether to generate RSS, defaults to false
|
||||
/// Whether to generate RSS. Defaults to false
|
||||
pub generate_rss: Option<bool>,
|
||||
/// The number of articles to include in the RSS feed. Defaults to unlimited
|
||||
pub rss_limit: Option<usize>,
|
||||
/// Whether to generate tags and individual tag pages if some pages have them. Defaults to true
|
||||
pub generate_tags_pages: Option<bool>,
|
||||
/// Whether to generate categories and individual tag categories if some pages have them. Defaults to true
|
||||
|
@ -59,13 +61,14 @@ impl Config {
|
|||
set_default!(config.language_code, "en".to_string());
|
||||
set_default!(config.highlight_code, false);
|
||||
set_default!(config.generate_rss, false);
|
||||
set_default!(config.rss_limit, <usize>::max_value());
|
||||
set_default!(config.generate_tags_pages, false);
|
||||
set_default!(config.generate_categories_pages, false);
|
||||
set_default!(config.insert_anchor_links, false);
|
||||
|
||||
match config.highlight_theme {
|
||||
Some(ref t) => {
|
||||
if !SETUP.theme_set.themes.contains_key(t) {
|
||||
if !THEME_SET.themes.contains_key(t) {
|
||||
bail!("Theme {} not available", t)
|
||||
}
|
||||
},
|
||||
|
@ -87,7 +90,9 @@ impl Config {
|
|||
|
||||
/// Makes a url, taking into account that the base url might have a trailing slash
|
||||
pub fn make_permalink(&self, path: &str) -> String {
|
||||
if self.base_url.ends_with('/') {
|
||||
if self.base_url.ends_with('/') && path.starts_with('/') {
|
||||
format!("{}{}", self.base_url, &path[1..])
|
||||
} else if self.base_url.ends_with('/') {
|
||||
format!("{}{}", self.base_url, path)
|
||||
} else {
|
||||
format!("{}/{}", self.base_url, path)
|
||||
|
@ -95,8 +100,9 @@ impl Config {
|
|||
}
|
||||
}
|
||||
|
||||
/// Exists only for testing purposes
|
||||
#[doc(hidden)]
|
||||
impl Default for Config {
|
||||
/// Exists for testing purposes
|
||||
fn default() -> Config {
|
||||
Config {
|
||||
title: "".to_string(),
|
||||
|
@ -106,6 +112,7 @@ impl Default for Config {
|
|||
description: None,
|
||||
language_code: Some("en".to_string()),
|
||||
generate_rss: Some(false),
|
||||
rss_limit: Some(10000),
|
||||
generate_tags_pages: Some(true),
|
||||
generate_categories_pages: Some(true),
|
||||
insert_anchor_links: Some(false),
|
||||
|
@ -181,4 +188,17 @@ hello = "world"
|
|||
assert_eq!(config.unwrap().extra.unwrap().get("hello").unwrap().as_str().unwrap(), "world");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_make_url_with_non_trailing_slash_base_url() {
|
||||
let mut config = Config::default();
|
||||
config.base_url = "http://vincent.is".to_string();
|
||||
assert_eq!(config.make_permalink("hello"), "http://vincent.is/hello");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_make_url_with_trailing_slash_path() {
|
||||
let mut config = Config::default();
|
||||
config.base_url = "http://vincent.is/".to_string();
|
||||
assert_eq!(config.make_permalink("/hello"), "http://vincent.is/hello");
|
||||
}
|
||||
}
|
||||
|
|
116
src/content/file_info.rs
Normal file
116
src/content/file_info.rs
Normal file
|
@ -0,0 +1,116 @@
|
|||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// Takes a full path to a file and returns only the components after the first `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
|
||||
}
|
||||
|
||||
/// Struct that contains all the information about the actual file
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct FileInfo {
|
||||
/// The full path to the .md file
|
||||
pub path: PathBuf,
|
||||
/// The name of the .md file without the extension, always `_index` for sections
|
||||
pub name: String,
|
||||
/// The .md path, starting from the content directory, with `/` slashes
|
||||
pub relative: String,
|
||||
/// Path of the directory containing the .md file
|
||||
pub parent: PathBuf,
|
||||
/// Path of the grand parent directory for that file. Only used in sections to find subsections.
|
||||
pub grand_parent: Option<PathBuf>,
|
||||
/// The folder names to this section file, starting from the `content` directory
|
||||
/// For example a file at content/kb/solutions/blabla.md will have 2 components:
|
||||
/// `kb` and `solutions`
|
||||
pub components: Vec<String>,
|
||||
}
|
||||
|
||||
impl FileInfo {
|
||||
pub fn new_page(path: &Path) -> FileInfo {
|
||||
let file_path = path.to_path_buf();
|
||||
let mut parent = file_path.parent().unwrap().to_path_buf();
|
||||
let name = path.file_stem().unwrap().to_string_lossy().to_string();
|
||||
let mut components = find_content_components(&file_path);
|
||||
let relative = format!("{}/{}.md", components.join("/"), name);
|
||||
|
||||
// If we have a folder with an asset, don't consider it as a component
|
||||
if !components.is_empty() && name == "index" {
|
||||
components.pop();
|
||||
// also set parent_path to grandparent instead
|
||||
parent = parent.parent().unwrap().to_path_buf();
|
||||
}
|
||||
|
||||
FileInfo {
|
||||
path: file_path,
|
||||
// We don't care about grand parent for pages
|
||||
grand_parent: None,
|
||||
parent,
|
||||
name,
|
||||
components,
|
||||
relative,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_section(path: &Path) -> FileInfo {
|
||||
let parent = path.parent().unwrap().to_path_buf();
|
||||
let components = find_content_components(path);
|
||||
let relative = if components.is_empty() {
|
||||
// the index one
|
||||
"_index.md".to_string()
|
||||
} else {
|
||||
format!("{}/_index.md", components.join("/"))
|
||||
};
|
||||
let grand_parent = parent.parent().map(|p| p.to_path_buf());
|
||||
|
||||
FileInfo {
|
||||
path: path.to_path_buf(),
|
||||
parent,
|
||||
grand_parent,
|
||||
name: "_index".to_string(),
|
||||
components,
|
||||
relative,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
impl Default for FileInfo {
|
||||
fn default() -> FileInfo {
|
||||
FileInfo {
|
||||
path: PathBuf::new(),
|
||||
parent: PathBuf::new(),
|
||||
grand_parent: None,
|
||||
name: String::new(),
|
||||
components: vec![],
|
||||
relative: String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::find_content_components;
|
||||
|
||||
#[test]
|
||||
fn can_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()]);
|
||||
}
|
||||
}
|
|
@ -1,14 +1,14 @@
|
|||
// TODO: move section/page and maybe pagination in this mod
|
||||
// Not sure where pagination stands if I add a render mod
|
||||
|
||||
mod page;
|
||||
mod pagination;
|
||||
mod section;
|
||||
mod sorting;
|
||||
mod utils;
|
||||
mod file_info;
|
||||
mod taxonomies;
|
||||
|
||||
pub use self::page::{Page};
|
||||
pub use self::section::{Section};
|
||||
pub use self::pagination::{Paginator, Pager};
|
||||
pub use self::sorting::{SortBy, sort_pages, populate_previous_and_next_pages};
|
||||
pub use self::taxonomies::{Taxonomy, TaxonomyItem};
|
||||
|
||||
|
|
|
@ -4,43 +4,32 @@ use std::path::{Path, PathBuf};
|
|||
use std::result::Result as StdResult;
|
||||
|
||||
|
||||
use tera::{Tera, Context};
|
||||
use tera::{Tera, Context as TeraContext};
|
||||
use serde::ser::{SerializeStruct, self};
|
||||
use slug::slugify;
|
||||
|
||||
use errors::{Result, ResultExt};
|
||||
use config::Config;
|
||||
use front_matter::{PageFrontMatter, split_page_content};
|
||||
use markdown::markdown_to_html;
|
||||
use utils::{read_file, find_content_components};
|
||||
use front_matter::{PageFrontMatter, InsertAnchor, split_page_content};
|
||||
use rendering::markdown::markdown_to_html;
|
||||
use rendering::context::Context;
|
||||
use fs::{read_file};
|
||||
use content::utils::{find_related_assets, get_reading_analytics};
|
||||
|
||||
use content::file_info::FileInfo;
|
||||
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Page {
|
||||
/// All info about the actual file
|
||||
pub file: FileInfo,
|
||||
/// The front matter meta-data
|
||||
pub meta: PageFrontMatter,
|
||||
/// The .md path
|
||||
pub file_path: PathBuf,
|
||||
/// The .md path, starting from the content directory, with / slashes
|
||||
pub relative_path: String,
|
||||
/// 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
|
||||
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`
|
||||
pub components: Vec<String>,
|
||||
/// The actual content of the page, in markdown
|
||||
pub raw_content: String,
|
||||
/// All the non-md files we found next to the .md file
|
||||
pub assets: Vec<PathBuf>,
|
||||
/// The HTML rendered of the page
|
||||
pub content: String,
|
||||
|
||||
/// The slug of that page.
|
||||
/// First tries to find the slug in the meta and defaults to filename otherwise
|
||||
pub slug: String,
|
||||
|
@ -52,7 +41,6 @@ pub struct Page {
|
|||
/// When <!-- more --> is found in the text, will take the content up to that part
|
||||
/// as summary
|
||||
pub summary: Option<String>,
|
||||
|
||||
/// The previous page, by whatever sorting is used for the index/section
|
||||
pub previous: Option<Box<Page>>,
|
||||
/// The next page, by whatever sorting is used for the index/section
|
||||
|
@ -61,14 +49,12 @@ pub struct Page {
|
|||
|
||||
|
||||
impl Page {
|
||||
pub fn new(meta: PageFrontMatter) -> Page {
|
||||
pub fn new<P: AsRef<Path>>(file_path: P, meta: PageFrontMatter) -> Page {
|
||||
let file_path = file_path.as_ref();
|
||||
|
||||
Page {
|
||||
file: FileInfo::new_page(file_path),
|
||||
meta: meta,
|
||||
file_path: PathBuf::new(),
|
||||
relative_path: String::new(),
|
||||
parent_path: PathBuf::new(),
|
||||
file_name: "".to_string(),
|
||||
components: vec![],
|
||||
raw_content: "".to_string(),
|
||||
assets: vec![],
|
||||
content: "".to_string(),
|
||||
|
@ -85,49 +71,26 @@ impl Page {
|
|||
/// Files without front matter or with invalid front matter are considered
|
||||
/// erroneous
|
||||
pub fn parse(file_path: &Path, content: &str, config: &Config) -> Result<Page> {
|
||||
// 1. separate front matter from content
|
||||
let (meta, content) = split_page_content(file_path, content)?;
|
||||
let mut page = Page::new(meta);
|
||||
page.file_path = file_path.to_path_buf();
|
||||
page.parent_path = page.file_path.parent().unwrap().to_path_buf();
|
||||
let mut page = Page::new(file_path, meta);
|
||||
page.raw_content = content;
|
||||
|
||||
let path = Path::new(file_path);
|
||||
page.file_name = path.file_stem().unwrap().to_string_lossy().to_string();
|
||||
|
||||
page.slug = {
|
||||
if let Some(ref slug) = page.meta.slug {
|
||||
slug.trim().to_string()
|
||||
} else {
|
||||
slugify(page.file_name.clone())
|
||||
slugify(page.file.name.clone())
|
||||
}
|
||||
};
|
||||
page.components = find_content_components(&page.file_path);
|
||||
page.relative_path = format!("{}/{}.md", page.components.join("/"), page.file_name);
|
||||
|
||||
// 4. Find sections
|
||||
// Pages with custom urls exists outside of sections
|
||||
let mut path_set = false;
|
||||
if let Some(ref u) = page.meta.url {
|
||||
page.path = u.trim().to_string();
|
||||
path_set = true;
|
||||
} else {
|
||||
page.path = if page.file.components.is_empty() {
|
||||
page.slug.clone()
|
||||
} else {
|
||||
format!("{}/{}", page.file.components.join("/"), page.slug)
|
||||
};
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
if !path_set {
|
||||
// Don't add a trailing slash to sections
|
||||
page.path = format!("{}/{}", page.components.join("/"), page.slug);
|
||||
}
|
||||
} else if !path_set {
|
||||
page.path = page.slug.clone();
|
||||
}
|
||||
|
||||
page.permalink = config.make_permalink(&page.path);
|
||||
|
||||
Ok(page)
|
||||
|
@ -140,7 +103,7 @@ impl Page {
|
|||
let mut page = Page::parse(path, &content, config)?;
|
||||
page.assets = find_related_assets(path.parent().unwrap());
|
||||
|
||||
if !page.assets.is_empty() && page.file_name != "index" {
|
||||
if !page.assets.is_empty() && page.file.name != "index" {
|
||||
bail!("Page `{}` has assets ({:?}) but is not named index.md", path.display(), page.assets);
|
||||
}
|
||||
|
||||
|
@ -150,13 +113,13 @@ impl Page {
|
|||
|
||||
/// We need access to all pages url to render links relative to content
|
||||
/// so that can't happen at the same time as parsing
|
||||
pub fn render_markdown(&mut self, permalinks: &HashMap<String, String>, tera: &Tera, config: &Config) -> Result<()> {
|
||||
self.content = markdown_to_html(&self.raw_content, permalinks, tera, config)?;
|
||||
|
||||
pub fn render_markdown(&mut self, permalinks: &HashMap<String, String>, tera: &Tera, config: &Config, anchor_insert: InsertAnchor) -> Result<()> {
|
||||
let context = Context::new(tera, config, permalinks, anchor_insert);
|
||||
self.content = markdown_to_html(&self.raw_content, &context)?;
|
||||
if self.raw_content.contains("<!-- more -->") {
|
||||
self.summary = Some({
|
||||
let summary = self.raw_content.splitn(2, "<!-- more -->").collect::<Vec<&str>>()[0];
|
||||
markdown_to_html(summary, permalinks, tera, config)?
|
||||
markdown_to_html(summary, &context)?
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -170,26 +133,22 @@ impl Page {
|
|||
None => "page.html".to_string()
|
||||
};
|
||||
|
||||
let mut context = Context::new();
|
||||
let mut context = TeraContext::new();
|
||||
context.add("config", config);
|
||||
context.add("page", self);
|
||||
context.add("current_url", &self.permalink);
|
||||
context.add("current_path", &self.path);
|
||||
|
||||
tera.render(&tpl_name, &context)
|
||||
.chain_err(|| format!("Failed to render page '{}'", self.file_path.display()))
|
||||
.chain_err(|| format!("Failed to render page '{}'", self.file.path.display()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Page {
|
||||
fn default() -> Page {
|
||||
Page {
|
||||
file: FileInfo::default(),
|
||||
meta: PageFrontMatter::default(),
|
||||
file_path: PathBuf::new(),
|
||||
relative_path: String::new(),
|
||||
parent_path: PathBuf::new(),
|
||||
file_name: "".to_string(),
|
||||
components: vec![],
|
||||
raw_content: "".to_string(),
|
||||
assets: vec![],
|
||||
content: "".to_string(),
|
||||
|
@ -205,7 +164,7 @@ impl Default for Page {
|
|||
|
||||
impl ser::Serialize for Page {
|
||||
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error> where S: ser::Serializer {
|
||||
let mut state = serializer.serialize_struct("page", 16)?;
|
||||
let mut state = serializer.serialize_struct("page", 15)?;
|
||||
state.serialize_field("content", &self.content)?;
|
||||
state.serialize_field("title", &self.meta.title)?;
|
||||
state.serialize_field("description", &self.meta.description)?;
|
||||
|
@ -215,7 +174,6 @@ impl ser::Serialize for Page {
|
|||
state.serialize_field("permalink", &self.permalink)?;
|
||||
state.serialize_field("summary", &self.summary)?;
|
||||
state.serialize_field("tags", &self.meta.tags)?;
|
||||
state.serialize_field("draft", &self.meta.draft)?;
|
||||
state.serialize_field("category", &self.meta.category)?;
|
||||
state.serialize_field("extra", &self.meta.extra)?;
|
||||
let (word_count, reading_time) = get_reading_analytics(&self.raw_content);
|
||||
|
@ -226,3 +184,142 @@ impl ser::Serialize for Page {
|
|||
state.end()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::HashMap;
|
||||
use std::fs::{File, create_dir};
|
||||
use std::path::Path;
|
||||
|
||||
use tera::Tera;
|
||||
use tempdir::TempDir;
|
||||
|
||||
use config::Config;
|
||||
use super::Page;
|
||||
use front_matter::InsertAnchor;
|
||||
|
||||
|
||||
#[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 mut page = res.unwrap();
|
||||
page.render_markdown(&HashMap::default(), &Tera::default(), &Config::default(), InsertAnchor::None).unwrap();
|
||||
|
||||
assert_eq!(page.meta.title.unwrap(), "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_make_url_from_sections_and_slug() {
|
||||
let content = r#"
|
||||
+++
|
||||
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.path, "posts/intro/hello-world");
|
||||
assert_eq!(page.permalink, "http://hello.com/posts/intro/hello-world");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_make_url_from_slug_only() {
|
||||
let content = r#"
|
||||
+++
|
||||
slug = "hello-world"
|
||||
+++
|
||||
Hello world"#;
|
||||
let config = Config::default();
|
||||
let res = Page::parse(Path::new("start.md"), content, &config);
|
||||
assert!(res.is_ok());
|
||||
let page = res.unwrap();
|
||||
assert_eq!(page.path, "hello-world");
|
||||
assert_eq!(page.permalink, config.make_permalink("hello-world"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn errors_on_invalid_front_matter_format() {
|
||||
// missing starting +++
|
||||
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 can_make_slug_from_non_slug_filename() {
|
||||
let config = Config::default();
|
||||
let res = Page::parse(Path::new(" file with space.md"), "+++\n+++", &config);
|
||||
assert!(res.is_ok());
|
||||
let page = res.unwrap();
|
||||
assert_eq!(page.slug, "file-with-space");
|
||||
assert_eq!(page.permalink, config.make_permalink(&page.slug));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_specify_summary() {
|
||||
let config = Config::default();
|
||||
let content = r#"
|
||||
+++
|
||||
+++
|
||||
Hello world
|
||||
<!-- more -->"#.to_string();
|
||||
let res = Page::parse(Path::new("hello.md"), &content, &config);
|
||||
assert!(res.is_ok());
|
||||
let mut page = res.unwrap();
|
||||
page.render_markdown(&HashMap::default(), &Tera::default(), &config, InsertAnchor::None).unwrap();
|
||||
assert_eq!(page.summary, Some("<p>Hello world</p>\n".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn page_with_assets_gets_right_parent_path() {
|
||||
let tmp_dir = TempDir::new("example").expect("create temp dir");
|
||||
let path = tmp_dir.path();
|
||||
create_dir(&path.join("content")).expect("create content temp dir");
|
||||
create_dir(&path.join("content").join("posts")).expect("create posts temp dir");
|
||||
let nested_path = path.join("content").join("posts").join("assets");
|
||||
create_dir(&nested_path).expect("create nested temp dir");
|
||||
File::create(nested_path.join("index.md")).unwrap();
|
||||
File::create(nested_path.join("example.js")).unwrap();
|
||||
File::create(nested_path.join("graph.jpg")).unwrap();
|
||||
File::create(nested_path.join("fail.png")).unwrap();
|
||||
|
||||
let res = Page::parse(
|
||||
nested_path.join("index.md").as_path(),
|
||||
"+++\nurl=\"hey\"+++\n",
|
||||
&Config::default()
|
||||
);
|
||||
assert!(res.is_ok());
|
||||
let page = res.unwrap();
|
||||
assert_eq!(page.file.parent, path.join("content").join("posts"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn errors_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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -145,7 +145,7 @@ impl<'a> Paginator<'a> {
|
|||
}
|
||||
|
||||
site.tera.render(&self.section.get_template_name(), &context)
|
||||
.chain_err(|| format!("Failed to render pager {} of section '{}'", pager.index, self.section.file_path.display()))
|
||||
.chain_err(|| format!("Failed to render pager {} of section '{}'", pager.index, self.section.file.path.display()))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -166,7 +166,7 @@ mod tests {
|
|||
if !is_index {
|
||||
s.path = "posts".to_string();
|
||||
s.permalink = "https://vincent.is/posts".to_string();
|
||||
s.components = vec!["posts".to_string()];
|
||||
s.file.components = vec!["posts".to_string()];
|
||||
} else {
|
||||
s.permalink = "https://vincent.is".to_string();
|
||||
}
|
||||
|
|
|
@ -2,29 +2,25 @@ use std::collections::HashMap;
|
|||
use std::path::{Path, PathBuf};
|
||||
use std::result::Result as StdResult;
|
||||
|
||||
use tera::{Tera, Context};
|
||||
use tera::{Tera, Context as TeraContext};
|
||||
use serde::ser::{SerializeStruct, self};
|
||||
|
||||
use config::Config;
|
||||
use front_matter::{SectionFrontMatter, split_section_content};
|
||||
use errors::{Result, ResultExt};
|
||||
use utils::{read_file, find_content_components};
|
||||
use markdown::markdown_to_html;
|
||||
use fs::{read_file};
|
||||
use rendering::markdown::markdown_to_html;
|
||||
use rendering::context::Context;
|
||||
use content::Page;
|
||||
use content::file_info::FileInfo;
|
||||
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Section {
|
||||
/// All info about the actual file
|
||||
pub file: FileInfo,
|
||||
/// The front matter meta-data
|
||||
pub meta: SectionFrontMatter,
|
||||
/// The _index.md full path
|
||||
pub file_path: PathBuf,
|
||||
/// The .md path, starting from the content directory, with / slashes
|
||||
pub relative_path: String,
|
||||
/// 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 URL path of the page
|
||||
pub path: String,
|
||||
/// The full URL for that page
|
||||
|
@ -46,11 +42,8 @@ impl Section {
|
|||
let file_path = file_path.as_ref();
|
||||
|
||||
Section {
|
||||
file: FileInfo::new_section(file_path),
|
||||
meta: meta,
|
||||
file_path: file_path.to_path_buf(),
|
||||
relative_path: "".to_string(),
|
||||
parent_path: file_path.parent().unwrap().to_path_buf(),
|
||||
components: vec![],
|
||||
path: "".to_string(),
|
||||
permalink: "".to_string(),
|
||||
raw_content: "".to_string(),
|
||||
|
@ -65,16 +58,8 @@ impl Section {
|
|||
let (meta, content) = split_section_content(file_path, content)?;
|
||||
let mut section = Section::new(file_path, meta);
|
||||
section.raw_content = content.clone();
|
||||
section.components = find_content_components(§ion.file_path);
|
||||
section.path = section.components.join("/");
|
||||
section.path = section.file.components.join("/");
|
||||
section.permalink = config.make_permalink(§ion.path);
|
||||
if section.components.is_empty() {
|
||||
// the index one
|
||||
section.relative_path = "_index.md".to_string();
|
||||
} else {
|
||||
section.relative_path = format!("{}/_index.md", section.components.join("/"));
|
||||
}
|
||||
|
||||
Ok(section)
|
||||
}
|
||||
|
||||
|
@ -101,7 +86,8 @@ impl Section {
|
|||
/// We need access to all pages url to render links relative to content
|
||||
/// so that can't happen at the same time as parsing
|
||||
pub fn render_markdown(&mut self, permalinks: &HashMap<String, String>, tera: &Tera, config: &Config) -> Result<()> {
|
||||
self.content = markdown_to_html(&self.raw_content, permalinks, tera, config)?;
|
||||
let context = Context::new(tera, config, permalinks, self.meta.insert_anchor.unwrap());
|
||||
self.content = markdown_to_html(&self.raw_content, &context)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -109,7 +95,7 @@ impl Section {
|
|||
pub fn render_html(&self, sections: HashMap<String, Section>, tera: &Tera, config: &Config) -> Result<String> {
|
||||
let tpl_name = self.get_template_name();
|
||||
|
||||
let mut context = Context::new();
|
||||
let mut context = TeraContext::new();
|
||||
context.add("config", config);
|
||||
context.add("section", self);
|
||||
context.add("current_url", &self.permalink);
|
||||
|
@ -119,46 +105,36 @@ impl Section {
|
|||
}
|
||||
|
||||
tera.render(&tpl_name, &context)
|
||||
.chain_err(|| format!("Failed to render section '{}'", self.file_path.display()))
|
||||
.chain_err(|| format!("Failed to render section '{}'", self.file.path.display()))
|
||||
}
|
||||
|
||||
/// Is this the index section?
|
||||
pub fn is_index(&self) -> bool {
|
||||
self.components.is_empty()
|
||||
self.file.components.is_empty()
|
||||
}
|
||||
|
||||
/// Returns all the paths for the pages belonging to that section
|
||||
/// Returns all the paths of the pages belonging to that section
|
||||
pub fn all_pages_path(&self) -> Vec<PathBuf> {
|
||||
let mut paths = vec![];
|
||||
paths.extend(self.pages.iter().map(|p| p.file_path.clone()));
|
||||
paths.extend(self.ignored_pages.iter().map(|p| p.file_path.clone()));
|
||||
paths.extend(self.pages.iter().map(|p| p.file.path.clone()));
|
||||
paths.extend(self.ignored_pages.iter().map(|p| p.file.path.clone()));
|
||||
paths
|
||||
}
|
||||
|
||||
/// Whether the page given belongs to that section
|
||||
pub fn is_child_page(&self, page: &Page) -> bool {
|
||||
for p in &self.pages {
|
||||
if p.file_path == page.file_path {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for p in &self.ignored_pages {
|
||||
if p.file_path == page.file_path {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
pub fn is_child_page(&self, path: &PathBuf) -> bool {
|
||||
self.all_pages_path().contains(path)
|
||||
}
|
||||
}
|
||||
|
||||
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", 7)?;
|
||||
let mut state = serializer.serialize_struct("section", 9)?;
|
||||
state.serialize_field("content", &self.content)?;
|
||||
state.serialize_field("permalink", &self.permalink)?;
|
||||
state.serialize_field("title", &self.meta.title)?;
|
||||
state.serialize_field("description", &self.meta.description)?;
|
||||
state.serialize_field("extra", &self.meta.extra)?;
|
||||
state.serialize_field("path", &format!("/{}", self.path))?;
|
||||
state.serialize_field("permalink", &self.permalink)?;
|
||||
state.serialize_field("pages", &self.pages)?;
|
||||
|
@ -167,15 +143,12 @@ impl ser::Serialize for Section {
|
|||
}
|
||||
}
|
||||
|
||||
/// Used to create a default index section if there is no _index.md in the root content directory
|
||||
impl Default for Section {
|
||||
/// Used to create a default index section if there is no _index.md in the root content directory
|
||||
fn default() -> Section {
|
||||
Section {
|
||||
file: FileInfo::default(),
|
||||
meta: SectionFrontMatter::default(),
|
||||
file_path: PathBuf::new(),
|
||||
relative_path: "".to_string(),
|
||||
parent_path: PathBuf::new(),
|
||||
components: vec![],
|
||||
path: "".to_string(),
|
||||
permalink: "".to_string(),
|
||||
raw_content: "".to_string(),
|
||||
|
|
|
@ -59,12 +59,20 @@ pub fn populate_previous_and_next_pages(input: &[Page]) -> Vec<Page> {
|
|||
|
||||
if i > 0 {
|
||||
let next = &pages[i - 1];
|
||||
new_page.next = Some(Box::new(next.clone()));
|
||||
let mut next_page = next.clone();
|
||||
// Remove prev/next otherwise we serialise the whole thing...
|
||||
next_page.previous = None;
|
||||
next_page.next = None;
|
||||
new_page.next = Some(Box::new(next_page));
|
||||
}
|
||||
|
||||
if i < input.len() - 1 {
|
||||
let previous = &pages[i + 1];
|
||||
new_page.previous = Some(Box::new(previous.clone()));
|
||||
// Remove prev/next otherwise we serialise the whole thing...
|
||||
let mut previous_page = previous.clone();
|
||||
previous_page.previous = None;
|
||||
previous_page.next = None;
|
||||
new_page.previous = Some(Box::new(previous_page));
|
||||
}
|
||||
res.push(new_page);
|
||||
}
|
||||
|
@ -81,13 +89,13 @@ mod tests {
|
|||
fn create_page_with_date(date: &str) -> Page {
|
||||
let mut front_matter = PageFrontMatter::default();
|
||||
front_matter.date = Some(date.to_string());
|
||||
Page::new(front_matter)
|
||||
Page::new("content/hello.md", front_matter)
|
||||
}
|
||||
|
||||
fn create_page_with_order(order: usize) -> Page {
|
||||
let mut front_matter = PageFrontMatter::default();
|
||||
front_matter.order = Some(order);
|
||||
Page::new(front_matter)
|
||||
Page::new("content/hello.md", front_matter)
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
135
src/content/taxonomies.rs
Normal file
135
src/content/taxonomies.rs
Normal file
|
@ -0,0 +1,135 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use slug::slugify;
|
||||
use tera::{Context, Tera};
|
||||
|
||||
use config::Config;
|
||||
use errors::{Result, ResultExt};
|
||||
use content::Page;
|
||||
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub enum TaxonomyKind {
|
||||
Tags,
|
||||
Categories,
|
||||
}
|
||||
|
||||
/// A tag or category
|
||||
#[derive(Debug, Clone, Serialize, PartialEq)]
|
||||
pub struct TaxonomyItem {
|
||||
pub name: String,
|
||||
pub slug: String,
|
||||
pub pages: Vec<Page>,
|
||||
}
|
||||
|
||||
impl TaxonomyItem {
|
||||
pub fn new(name: &str, pages: Vec<Page>) -> TaxonomyItem {
|
||||
TaxonomyItem {
|
||||
name: name.to_string(),
|
||||
slug: slugify(name),
|
||||
pages,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// All the tags or categories
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Taxonomy {
|
||||
pub kind: TaxonomyKind,
|
||||
// this vec is sorted by the count of item
|
||||
pub items: Vec<TaxonomyItem>,
|
||||
}
|
||||
|
||||
impl Taxonomy {
|
||||
// TODO: take a Vec<&'a Page> if it makes a difference in terms of perf for actual sites
|
||||
pub fn find_tags_and_categories(all_pages: Vec<Page>) -> (Taxonomy, Taxonomy) {
|
||||
let mut tags = HashMap::new();
|
||||
let mut categories = HashMap::new();
|
||||
|
||||
// Find all the tags/categories first
|
||||
for page in all_pages {
|
||||
if let Some(ref category) = page.meta.category {
|
||||
categories
|
||||
.entry(category.to_string())
|
||||
.or_insert_with(|| vec![])
|
||||
.push(page.clone());
|
||||
}
|
||||
|
||||
if let Some(ref t) = page.meta.tags {
|
||||
for tag in t {
|
||||
tags
|
||||
.entry(tag.to_string())
|
||||
.or_insert_with(|| vec![])
|
||||
.push(page.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Then make TaxonomyItem out of them, after sorting it
|
||||
let tags_taxonomy = Taxonomy::new(TaxonomyKind::Tags, tags);
|
||||
let categories_taxonomy = Taxonomy::new(TaxonomyKind::Categories, categories);
|
||||
|
||||
(tags_taxonomy, categories_taxonomy)
|
||||
}
|
||||
|
||||
fn new(kind: TaxonomyKind, items: HashMap<String, Vec<Page>>) -> Taxonomy {
|
||||
let mut sorted_items = vec![];
|
||||
for (name, pages) in &items {
|
||||
sorted_items.push(
|
||||
TaxonomyItem::new(name, pages.clone())
|
||||
);
|
||||
}
|
||||
sorted_items.sort_by(|a, b| b.pages.len().cmp(&a.pages.len()));
|
||||
|
||||
Taxonomy {
|
||||
kind,
|
||||
items: sorted_items,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.items.len()
|
||||
}
|
||||
|
||||
pub fn get_single_item_name(&self) -> String {
|
||||
match self.kind {
|
||||
TaxonomyKind::Tags => "tag".to_string(),
|
||||
TaxonomyKind::Categories => "category".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_list_name(&self) -> String {
|
||||
match self.kind {
|
||||
TaxonomyKind::Tags => "tags".to_string(),
|
||||
TaxonomyKind::Categories => "categories".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_single_item(&self, item: &TaxonomyItem, tera: &Tera, config: &Config) -> Result<String> {
|
||||
let name = self.get_single_item_name();
|
||||
let mut context = Context::new();
|
||||
context.add("config", config);
|
||||
// TODO: how to sort categories and tag content?
|
||||
// Have a setting in config.toml or a _category.md and _tag.md
|
||||
// The latter is more in line with the rest of Gutenberg but order ordering
|
||||
// doesn't really work across sections.
|
||||
context.add(&name, item);
|
||||
context.add("current_url", &config.make_permalink(&format!("{}/{}", name, item.slug)));
|
||||
context.add("current_path", &format!("/{}/{}", name, item.slug));
|
||||
|
||||
tera.render(&format!("{}.html", name), &context)
|
||||
.chain_err(|| format!("Failed to render {} page.", name))
|
||||
}
|
||||
|
||||
pub fn render_list(&self, tera: &Tera, config: &Config) -> Result<String> {
|
||||
let name = self.get_list_name();
|
||||
let mut context = Context::new();
|
||||
context.add("config", config);
|
||||
context.add(&name, &self.items);
|
||||
context.add("current_url", &config.make_permalink(&name));
|
||||
context.add("current_path", &name);
|
||||
|
||||
tera.render(&format!("{}.html", name), &context)
|
||||
.chain_err(|| format!("Failed to render {} page.", name))
|
||||
}
|
||||
}
|
|
@ -32,7 +32,6 @@ pub fn get_reading_analytics(content: &str) -> (usize, usize) {
|
|||
(word_count, (word_count / 200))
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::fs::File;
|
||||
|
|
|
@ -8,7 +8,7 @@ mod page;
|
|||
mod section;
|
||||
|
||||
pub use self::page::PageFrontMatter;
|
||||
pub use self::section::{SectionFrontMatter};
|
||||
pub use self::section::{SectionFrontMatter, InsertAnchor};
|
||||
|
||||
lazy_static! {
|
||||
static ref PAGE_RE: Regex = Regex::new(r"^[[:space:]]*\+\+\+\r?\n((?s).*?(?-s))\+\+\+\r?\n?((?s).*(?-s))$").unwrap();
|
||||
|
|
|
@ -24,8 +24,6 @@ pub struct PageFrontMatter {
|
|||
pub url: Option<String>,
|
||||
/// Tags, not to be confused with categories
|
||||
pub tags: Option<Vec<String>>,
|
||||
/// Whether this page is a draft and should be published or not
|
||||
pub draft: Option<bool>,
|
||||
/// Only one category allowed. Can't be an empty string if present
|
||||
pub category: Option<String>,
|
||||
/// Integer to use to order content. Lowest is at the bottom, highest first
|
||||
|
@ -100,7 +98,6 @@ impl Default for PageFrontMatter {
|
|||
slug: None,
|
||||
url: None,
|
||||
tags: None,
|
||||
draft: None,
|
||||
category: None,
|
||||
order: None,
|
||||
template: None,
|
||||
|
|
|
@ -9,6 +9,14 @@ use content::SortBy;
|
|||
static DEFAULT_PAGINATE_PATH: &'static str = "page";
|
||||
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum InsertAnchor {
|
||||
Left,
|
||||
Right,
|
||||
None,
|
||||
}
|
||||
|
||||
/// The front matter of every section
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct SectionFrontMatter {
|
||||
|
@ -28,6 +36,10 @@ pub struct SectionFrontMatter {
|
|||
/// Path to be used by pagination: the page number will be appended after it. Defaults to `page`.
|
||||
#[serde(skip_serializing)]
|
||||
pub paginate_path: Option<String>,
|
||||
/// Whether to insert a link for each header like in Github READMEs. Defaults to false
|
||||
/// The default template can be overridden by creating a `anchor-link.html` template and CSS will need to be
|
||||
/// written if you turn that on.
|
||||
pub insert_anchor: Option<InsertAnchor>,
|
||||
/// Whether to render that section or not. Defaults to `true`.
|
||||
/// Useful when the section is only there to organize things but is not meant
|
||||
/// to be used directly, like a posts section in a personal site
|
||||
|
@ -56,6 +68,10 @@ impl SectionFrontMatter {
|
|||
f.sort_by = Some(SortBy::None);
|
||||
}
|
||||
|
||||
if f.insert_anchor.is_none() {
|
||||
f.insert_anchor = Some(InsertAnchor::None);
|
||||
}
|
||||
|
||||
Ok(f)
|
||||
}
|
||||
|
||||
|
@ -87,6 +103,7 @@ impl Default for SectionFrontMatter {
|
|||
paginate_by: None,
|
||||
paginate_path: Some(DEFAULT_PAGINATE_PATH.to_string()),
|
||||
render: Some(true),
|
||||
insert_anchor: Some(InsertAnchor::None),
|
||||
extra: None,
|
||||
}
|
||||
}
|
||||
|
|
40
src/fs.rs
Normal file
40
src/fs.rs
Normal file
|
@ -0,0 +1,40 @@
|
|||
use std::io::prelude::*;
|
||||
use std::fs::{File, create_dir};
|
||||
use std::path::Path;
|
||||
|
||||
use errors::{Result, ResultExt};
|
||||
|
||||
/// Create a file with the content given
|
||||
pub fn create_file(path: &Path, content: &str) -> Result<()> {
|
||||
let mut file = File::create(&path)?;
|
||||
file.write_all(content.as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create a directory at the given path if it doesn't exist already
|
||||
pub fn ensure_directory_exists(path: &Path) -> Result<()> {
|
||||
if !path.exists() {
|
||||
create_directory(path)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Very similar to `create_dir` from the std except it checks if the folder
|
||||
/// exists before creating it
|
||||
pub fn create_directory(path: &Path) -> Result<()> {
|
||||
if !path.exists() {
|
||||
create_dir(path)
|
||||
.chain_err(|| format!("Was not able to create folder {}", path.display()))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Return the content of a file, with error handling added
|
||||
pub fn read_file(path: &Path) -> Result<String> {
|
||||
let mut content = String::new();
|
||||
File::open(path)
|
||||
.chain_err(|| format!("Failed to open '{:?}'", path.display()))?
|
||||
.read_to_string(&mut content)?;
|
||||
|
||||
Ok(content)
|
||||
}
|
|
@ -19,19 +19,18 @@ extern crate base64;
|
|||
#[cfg(test)]
|
||||
extern crate tempdir;
|
||||
|
||||
mod utils;
|
||||
mod fs;
|
||||
mod config;
|
||||
pub mod errors;
|
||||
mod front_matter;
|
||||
mod content;
|
||||
mod site;
|
||||
mod markdown;
|
||||
mod rendering;
|
||||
// Filters, Global Fns and default instance of Tera
|
||||
mod templates;
|
||||
|
||||
pub use site::{Site};
|
||||
pub use config::{Config, get_config};
|
||||
pub use front_matter::{PageFrontMatter, SectionFrontMatter, split_page_content, split_section_content};
|
||||
pub use front_matter::{PageFrontMatter, SectionFrontMatter, InsertAnchor, split_page_content, split_section_content};
|
||||
pub use content::{Page, Section, SortBy, sort_pages, populate_previous_and_next_pages};
|
||||
pub use utils::{create_file};
|
||||
pub use markdown::markdown_to_html;
|
||||
pub use fs::{create_file};
|
||||
|
|
33
src/rendering/context.rs
Normal file
33
src/rendering/context.rs
Normal file
|
@ -0,0 +1,33 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use tera::Tera;
|
||||
|
||||
use config::Config;
|
||||
use front_matter::InsertAnchor;
|
||||
|
||||
|
||||
/// All the information from the gutenberg site that is needed to render HTML from markdown
|
||||
#[derive(Debug)]
|
||||
pub struct Context<'a> {
|
||||
pub tera: &'a Tera,
|
||||
pub highlight_code: bool,
|
||||
pub highlight_theme: String,
|
||||
pub permalinks: &'a HashMap<String, String>,
|
||||
pub insert_anchor: InsertAnchor,
|
||||
}
|
||||
|
||||
impl<'a> Context<'a> {
|
||||
pub fn new(tera: &'a Tera, config: &'a Config, permalinks: &'a HashMap<String, String>, insert_anchor: InsertAnchor) -> Context<'a> {
|
||||
Context {
|
||||
tera,
|
||||
permalinks,
|
||||
insert_anchor,
|
||||
highlight_code: config.highlight_code.unwrap(),
|
||||
highlight_theme: config.highlight_theme.clone().unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn should_insert_anchor(&self) -> bool {
|
||||
self.insert_anchor != InsertAnchor::None
|
||||
}
|
||||
}
|
6
src/rendering/highlighting.rs
Normal file
6
src/rendering/highlighting.rs
Normal file
|
@ -0,0 +1,6 @@
|
|||
use syntect::dumps::from_binary;
|
||||
use syntect::highlighting::ThemeSet;
|
||||
|
||||
lazy_static!{
|
||||
pub static ref THEME_SET: ThemeSet = from_binary(include_bytes!("../../sublime_themes/all.themedump"));
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
use std::borrow::Cow::Owned;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use pulldown_cmark as cmark;
|
||||
use self::cmark::{Parser, Event, Tag, Options, OPTION_ENABLE_TABLES, OPTION_ENABLE_FOOTNOTES};
|
||||
|
@ -8,18 +7,19 @@ use slug::slugify;
|
|||
use syntect::dumps::from_binary;
|
||||
use syntect::easy::HighlightLines;
|
||||
use syntect::parsing::SyntaxSet;
|
||||
use syntect::highlighting::ThemeSet;
|
||||
use syntect::html::{start_coloured_html_snippet, styles_to_coloured_html, IncludeBackground};
|
||||
use tera::{Tera, Context};
|
||||
|
||||
use config::Config;
|
||||
use errors::{Result, ResultExt};
|
||||
use tera::{Context as TeraContext};
|
||||
|
||||
use errors::{Result};
|
||||
use site::resolve_internal_link;
|
||||
use front_matter::InsertAnchor;
|
||||
use rendering::context::Context;
|
||||
use rendering::highlighting::THEME_SET;
|
||||
use rendering::short_code::{ShortCode, parse_shortcode, render_simple_shortcode};
|
||||
|
||||
// We need to put those in a struct to impl Send and sync
|
||||
pub struct Setup {
|
||||
pub syntax_set: SyntaxSet,
|
||||
pub theme_set: ThemeSet,
|
||||
}
|
||||
|
||||
unsafe impl Send for Setup {}
|
||||
|
@ -29,87 +29,25 @@ lazy_static!{
|
|||
static ref SHORTCODE_RE: Regex = Regex::new(r#"\{(?:%|\{)\s+([[:alnum:]]+?)\(([[:alnum:]]+?="?.+?"?)\)\s+(?:%|\})\}"#).unwrap();
|
||||
pub static ref SETUP: Setup = Setup {
|
||||
syntax_set: {
|
||||
let mut ps: SyntaxSet = from_binary(include_bytes!("../sublime_syntaxes/newlines.packdump"));
|
||||
let mut ps: SyntaxSet = from_binary(include_bytes!("../../sublime_syntaxes/newlines.packdump"));
|
||||
ps.link_syntaxes();
|
||||
ps
|
||||
},
|
||||
theme_set: from_binary(include_bytes!("../sublime_themes/all.themedump"))
|
||||
};
|
||||
}
|
||||
|
||||
/// A shortcode that has a body
|
||||
/// Called by having some content like {% ... %} body {% end %}
|
||||
/// We need the struct to hold the data while we're processing the markdown
|
||||
#[derive(Debug)]
|
||||
struct ShortCode {
|
||||
name: String,
|
||||
args: HashMap<String, String>,
|
||||
body: String,
|
||||
}
|
||||
|
||||
impl ShortCode {
|
||||
pub fn new(name: &str, args: HashMap<String, String>) -> ShortCode {
|
||||
ShortCode {
|
||||
name: name.to_string(),
|
||||
args: args,
|
||||
body: String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn append(&mut self, text: &str) {
|
||||
self.body.push_str(text)
|
||||
}
|
||||
|
||||
pub fn render(&self, tera: &Tera) -> Result<String> {
|
||||
let mut context = Context::new();
|
||||
for (key, value) in &self.args {
|
||||
context.add(key, value);
|
||||
}
|
||||
context.add("body", &self.body);
|
||||
let tpl_name = format!("shortcodes/{}.html", self.name);
|
||||
tera.render(&tpl_name, &context)
|
||||
.chain_err(|| format!("Failed to render {} shortcode", self.name))
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a shortcode without a body
|
||||
fn parse_shortcode(input: &str) -> (String, HashMap<String, String>) {
|
||||
let mut args = HashMap::new();
|
||||
let caps = SHORTCODE_RE.captures(input).unwrap();
|
||||
// caps[0] is the full match
|
||||
let name = &caps[1];
|
||||
let arg_list = &caps[2];
|
||||
for arg in arg_list.split(',') {
|
||||
let bits = arg.split('=').collect::<Vec<_>>();
|
||||
args.insert(bits[0].trim().to_string(), bits[1].replace("\"", ""));
|
||||
}
|
||||
|
||||
(name.to_string(), args)
|
||||
}
|
||||
|
||||
/// Renders a shortcode or return an error
|
||||
fn render_simple_shortcode(tera: &Tera, name: &str, args: &HashMap<String, String>) -> Result<String> {
|
||||
let mut context = Context::new();
|
||||
for (key, value) in args.iter() {
|
||||
context.add(key, value);
|
||||
}
|
||||
let tpl_name = format!("shortcodes/{}.html", name);
|
||||
|
||||
tera.render(&tpl_name, &context).chain_err(|| format!("Failed to render {} shortcode", name))
|
||||
}
|
||||
|
||||
pub fn markdown_to_html(content: &str, permalinks: &HashMap<String, String>, tera: &Tera, config: &Config) -> Result<String> {
|
||||
pub fn markdown_to_html(content: &str, context: &Context) -> Result<String> {
|
||||
// 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 we see a code block in the content, we assume that this page needs
|
||||
// to be highlighted. It could potentially have false positive if the content
|
||||
// has ``` in it but that seems kind of unlikely
|
||||
let should_highlight = if config.highlight_code.unwrap() {
|
||||
let should_highlight = if context.highlight_code {
|
||||
content.contains("```")
|
||||
} else {
|
||||
false
|
||||
};
|
||||
let highlight_theme = config.highlight_theme.clone().unwrap();
|
||||
// Set while parsing
|
||||
let mut error = None;
|
||||
let mut highlighter: Option<HighlightLines> = None;
|
||||
|
@ -167,7 +105,7 @@ pub fn markdown_to_html(content: &str, permalinks: &HashMap<String, String>, ter
|
|||
if shortcode_block.is_none() && text.starts_with("{{") && text.ends_with("}}") && SHORTCODE_RE.is_match(&text) {
|
||||
let (name, args) = parse_shortcode(&text);
|
||||
added_shortcode = true;
|
||||
match render_simple_shortcode(tera, &name, &args) {
|
||||
match render_simple_shortcode(context.tera, &name, &args) {
|
||||
Ok(s) => return Event::Html(Owned(format!("</p>{}", s))),
|
||||
Err(e) => {
|
||||
error = Some(e);
|
||||
|
@ -193,7 +131,7 @@ pub fn markdown_to_html(content: &str, permalinks: &HashMap<String, String>, ter
|
|||
if let Some(ref mut shortcode) = shortcode_block {
|
||||
if text.trim() == "{% end %}" {
|
||||
added_shortcode = true;
|
||||
match shortcode.render(tera) {
|
||||
match shortcode.render(context.tera) {
|
||||
Ok(s) => return Event::Html(Owned(format!("</p>{}", s))),
|
||||
Err(e) => {
|
||||
error = Some(e);
|
||||
|
@ -213,15 +151,20 @@ pub fn markdown_to_html(content: &str, permalinks: &HashMap<String, String>, ter
|
|||
}
|
||||
let id = find_anchor(&anchors, slugify(&text), 0);
|
||||
anchors.push(id.clone());
|
||||
let anchor_link = if config.insert_anchor_links.unwrap() {
|
||||
let mut context = Context::new();
|
||||
context.add("id", &id);
|
||||
tera.render("anchor-link.html", &context).unwrap()
|
||||
let anchor_link = if context.should_insert_anchor() {
|
||||
let mut c = TeraContext::new();
|
||||
c.add("id", &id);
|
||||
context.tera.render("anchor-link.html", &c).unwrap()
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
header_already_inserted = true;
|
||||
return Event::Html(Owned(format!(r#"id="{}">{}{}"#, id, anchor_link, text)));
|
||||
let event = match context.insert_anchor {
|
||||
InsertAnchor::Left => Event::Html(Owned(format!(r#"id="{}">{}{}"#, id, anchor_link, text))),
|
||||
InsertAnchor::Right => Event::Html(Owned(format!(r#"id="{}">{}{}"#, id, text, anchor_link))),
|
||||
InsertAnchor::None => Event::Html(Owned(format!(r#"id="{}">{}"#, id, text)))
|
||||
};
|
||||
return event;
|
||||
}
|
||||
|
||||
// Business as usual
|
||||
|
@ -232,7 +175,7 @@ pub fn markdown_to_html(content: &str, permalinks: &HashMap<String, String>, ter
|
|||
if !should_highlight {
|
||||
return Event::Html(Owned("<pre><code>".to_owned()));
|
||||
}
|
||||
let theme = &SETUP.theme_set.themes[&highlight_theme];
|
||||
let theme = &THEME_SET.themes[&context.highlight_theme];
|
||||
let syntax = info
|
||||
.split(' ')
|
||||
.next()
|
||||
|
@ -257,21 +200,11 @@ pub fn markdown_to_html(content: &str, permalinks: &HashMap<String, String>, ter
|
|||
return Event::Html(Owned("".to_owned()));
|
||||
}
|
||||
if link.starts_with("./") {
|
||||
// First we remove the ./ since that's gutenberg specific
|
||||
let clean_link = link.replacen("./", "", 1);
|
||||
// Then we remove any potential anchor
|
||||
// parts[0] will be the file path and parts[1] the anchor if present
|
||||
let parts = clean_link.split('#').collect::<Vec<_>>();
|
||||
match permalinks.get(parts[0]) {
|
||||
Some(p) => {
|
||||
let url = if parts.len() > 1 {
|
||||
format!("{}#{}", p, parts[1])
|
||||
} else {
|
||||
p.to_string()
|
||||
};
|
||||
match resolve_internal_link(link, context.permalinks) {
|
||||
Ok(url) => {
|
||||
return Event::Start(Tag::Link(Owned(url), title.clone()));
|
||||
},
|
||||
None => {
|
||||
Err(_) => {
|
||||
error = Some(format!("Relative link {} not found.", link).into());
|
||||
return Event::Html(Owned("".to_string()));
|
||||
}
|
||||
|
@ -340,46 +273,33 @@ pub fn markdown_to_html(content: &str, permalinks: &HashMap<String, String>, ter
|
|||
mod tests {
|
||||
use std::collections::HashMap;
|
||||
|
||||
use templates::GUTENBERG_TERA;
|
||||
use tera::Tera;
|
||||
|
||||
use config::Config;
|
||||
use super::{markdown_to_html, parse_shortcode};
|
||||
use front_matter::InsertAnchor;
|
||||
use templates::GUTENBERG_TERA;
|
||||
use rendering::context::Context;
|
||||
|
||||
use super::markdown_to_html;
|
||||
|
||||
#[test]
|
||||
fn test_parse_simple_shortcode_one_arg() {
|
||||
let (name, args) = parse_shortcode(r#"{{ youtube(id="w7Ft2ymGmfc") }}"#);
|
||||
assert_eq!(name, "youtube");
|
||||
assert_eq!(args["id"], "w7Ft2ymGmfc");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_simple_shortcode_several_arg() {
|
||||
let (name, args) = parse_shortcode(r#"{{ youtube(id="w7Ft2ymGmfc", autoplay=true) }}"#);
|
||||
assert_eq!(name, "youtube");
|
||||
assert_eq!(args["id"], "w7Ft2ymGmfc");
|
||||
assert_eq!(args["autoplay"], "true");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_block_shortcode_several_arg() {
|
||||
let (name, args) = parse_shortcode(r#"{% youtube(id="w7Ft2ymGmfc", autoplay=true) %}"#);
|
||||
assert_eq!(name, "youtube");
|
||||
assert_eq!(args["id"], "w7Ft2ymGmfc");
|
||||
assert_eq!(args["autoplay"], "true");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_markdown_to_html_simple() {
|
||||
let res = markdown_to_html("hello", &HashMap::new(), &Tera::default(), &Config::default()).unwrap();
|
||||
fn can_do_markdown_to_html_simple() {
|
||||
let tera_ctx = Tera::default();
|
||||
let permalinks_ctx = HashMap::new();
|
||||
let config_ctx = Config::default();
|
||||
let context = Context::new(&tera_ctx, &config_ctx, &permalinks_ctx, InsertAnchor::None);
|
||||
let res = markdown_to_html("hello", &context).unwrap();
|
||||
assert_eq!(res, "<p>hello</p>\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_markdown_to_html_code_block_highlighting_off() {
|
||||
let mut config = Config::default();
|
||||
config.highlight_code = Some(false);
|
||||
let res = markdown_to_html("```\n$ gutenberg server\n```", &HashMap::new(), &Tera::default(), &config).unwrap();
|
||||
fn doesnt_highlight_code_block_with_highlighting_off() {
|
||||
let tera_ctx = Tera::default();
|
||||
let permalinks_ctx = HashMap::new();
|
||||
let config_ctx = Config::default();
|
||||
let mut context = Context::new(&tera_ctx, &config_ctx, &permalinks_ctx, InsertAnchor::None);
|
||||
context.highlight_code = false;
|
||||
let res = markdown_to_html("```\n$ gutenberg server\n```", &context).unwrap();
|
||||
assert_eq!(
|
||||
res,
|
||||
"<pre><code>$ gutenberg server\n</code></pre>\n"
|
||||
|
@ -387,8 +307,12 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_markdown_to_html_code_block_no_lang() {
|
||||
let res = markdown_to_html("```\n$ gutenberg server\n$ ping\n```", &HashMap::new(), &Tera::default(), &Config::default()).unwrap();
|
||||
fn can_highlight_code_block_no_lang() {
|
||||
let tera_ctx = Tera::default();
|
||||
let permalinks_ctx = HashMap::new();
|
||||
let config_ctx = Config::default();
|
||||
let context = Context::new(&tera_ctx, &config_ctx, &permalinks_ctx, InsertAnchor::None);
|
||||
let res = markdown_to_html("```\n$ gutenberg server\n$ ping\n```", &context).unwrap();
|
||||
assert_eq!(
|
||||
res,
|
||||
"<pre style=\"background-color:#2b303b\">\n<span style=\"background-color:#2b303b;color:#c0c5ce;\">$ gutenberg server\n</span><span style=\"background-color:#2b303b;color:#c0c5ce;\">$ ping\n</span></pre>"
|
||||
|
@ -396,8 +320,12 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_markdown_to_html_code_block_with_lang() {
|
||||
let res = markdown_to_html("```python\nlist.append(1)\n```", &HashMap::new(), &Tera::default(), &Config::default()).unwrap();
|
||||
fn can_highlight_code_block_with_lang() {
|
||||
let tera_ctx = Tera::default();
|
||||
let permalinks_ctx = HashMap::new();
|
||||
let config_ctx = Config::default();
|
||||
let context = Context::new(&tera_ctx, &config_ctx, &permalinks_ctx, InsertAnchor::None);
|
||||
let res = markdown_to_html("```python\nlist.append(1)\n```", &context).unwrap();
|
||||
assert_eq!(
|
||||
res,
|
||||
"<pre style=\"background-color:#2b303b\">\n<span style=\"background-color:#2b303b;color:#c0c5ce;\">list</span><span style=\"background-color:#2b303b;color:#c0c5ce;\">.</span><span style=\"background-color:#2b303b;color:#bf616a;\">append</span><span style=\"background-color:#2b303b;color:#c0c5ce;\">(</span><span style=\"background-color:#2b303b;color:#d08770;\">1</span><span style=\"background-color:#2b303b;color:#c0c5ce;\">)</span><span style=\"background-color:#2b303b;color:#c0c5ce;\">\n</span></pre>"
|
||||
|
@ -405,8 +333,12 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_markdown_to_html_code_block_with_unknown_lang() {
|
||||
let res = markdown_to_html("```yolo\nlist.append(1)\n```", &HashMap::new(), &Tera::default(), &Config::default()).unwrap();
|
||||
fn can_higlight_code_block_with_unknown_lang() {
|
||||
let tera_ctx = Tera::default();
|
||||
let permalinks_ctx = HashMap::new();
|
||||
let config_ctx = Config::default();
|
||||
let context = Context::new(&tera_ctx, &config_ctx, &permalinks_ctx, InsertAnchor::None);
|
||||
let res = markdown_to_html("```yolo\nlist.append(1)\n```", &context).unwrap();
|
||||
// defaults to plain text
|
||||
assert_eq!(
|
||||
res,
|
||||
|
@ -415,18 +347,24 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_markdown_to_html_with_shortcode() {
|
||||
fn can_render_shortcode() {
|
||||
let permalinks_ctx = HashMap::new();
|
||||
let config_ctx = Config::default();
|
||||
let context = Context::new(&GUTENBERG_TERA, &config_ctx, &permalinks_ctx, InsertAnchor::None);
|
||||
let res = markdown_to_html(r#"
|
||||
Hello
|
||||
|
||||
{{ youtube(id="ub36ffWAqgQ") }}
|
||||
"#, &HashMap::new(), &GUTENBERG_TERA, &Config::default()).unwrap();
|
||||
"#, &context).unwrap();
|
||||
assert!(res.contains("<p>Hello</p>\n<div >"));
|
||||
assert!(res.contains(r#"<iframe src="https://www.youtube.com/embed/ub36ffWAqgQ""#));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_markdown_to_html_with_several_shortcode_in_row() {
|
||||
fn can_render_several_shortcode_in_row() {
|
||||
let permalinks_ctx = HashMap::new();
|
||||
let config_ctx = Config::default();
|
||||
let context = Context::new(&GUTENBERG_TERA, &config_ctx, &permalinks_ctx, InsertAnchor::None);
|
||||
let res = markdown_to_html(r#"
|
||||
Hello
|
||||
|
||||
|
@ -438,7 +376,7 @@ Hello
|
|||
|
||||
{{ gist(url="https://gist.github.com/Keats/32d26f699dcc13ebd41b") }}
|
||||
|
||||
"#, &HashMap::new(), &GUTENBERG_TERA, &Config::default()).unwrap();
|
||||
"#, &context).unwrap();
|
||||
assert!(res.contains("<p>Hello</p>\n<div >"));
|
||||
assert!(res.contains(r#"<iframe src="https://www.youtube.com/embed/ub36ffWAqgQ""#));
|
||||
assert!(res.contains(r#"<iframe src="https://www.youtube.com/embed/ub36ffWAqgQ?autoplay=1""#));
|
||||
|
@ -446,40 +384,52 @@ Hello
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_markdown_to_html_shortcode_in_code_block() {
|
||||
let res = markdown_to_html(r#"```{{ youtube(id="w7Ft2ymGmfc") }}```"#, &HashMap::new(), &GUTENBERG_TERA, &Config::default()).unwrap();
|
||||
fn doesnt_render_shortcode_in_code_block() {
|
||||
let permalinks_ctx = HashMap::new();
|
||||
let config_ctx = Config::default();
|
||||
let context = Context::new(&GUTENBERG_TERA, &config_ctx, &permalinks_ctx, InsertAnchor::None);
|
||||
let res = markdown_to_html(r#"```{{ youtube(id="w7Ft2ymGmfc") }}```"#, &context).unwrap();
|
||||
assert_eq!(res, "<p><code>{{ youtube(id="w7Ft2ymGmfc") }}</code></p>\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_markdown_to_html_shortcode_with_body() {
|
||||
fn can_render_shortcode_with_body() {
|
||||
let mut tera = Tera::default();
|
||||
tera.extend(&GUTENBERG_TERA).unwrap();
|
||||
tera.add_raw_template("shortcodes/quote.html", "<blockquote>{{ body }} - {{ author}}</blockquote>").unwrap();
|
||||
let permalinks_ctx = HashMap::new();
|
||||
let config_ctx = Config::default();
|
||||
let context = Context::new(&tera, &config_ctx, &permalinks_ctx, InsertAnchor::None);
|
||||
|
||||
let res = markdown_to_html(r#"
|
||||
Hello
|
||||
{% quote(author="Keats") %}
|
||||
A quote
|
||||
{% end %}
|
||||
"#, &HashMap::new(), &tera, &Config::default()).unwrap();
|
||||
"#, &context).unwrap();
|
||||
assert_eq!(res, "<p>Hello\n</p><blockquote>A quote - Keats</blockquote>");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_markdown_to_html_unknown_shortcode() {
|
||||
let res = markdown_to_html("{{ hello(flash=true) }}", &HashMap::new(), &Tera::default(), &Config::default());
|
||||
fn errors_rendering_unknown_shortcode() {
|
||||
let tera_ctx = Tera::default();
|
||||
let permalinks_ctx = HashMap::new();
|
||||
let config_ctx = Config::default();
|
||||
let context = Context::new(&tera_ctx, &config_ctx, &permalinks_ctx, InsertAnchor::None);
|
||||
let res = markdown_to_html("{{ hello(flash=true) }}", &context);
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_markdown_to_html_relative_link_exists() {
|
||||
fn can_make_valid_relative_link() {
|
||||
let mut permalinks = HashMap::new();
|
||||
permalinks.insert("pages/about.md".to_string(), "https://vincent.is/about".to_string());
|
||||
let tera_ctx = Tera::default();
|
||||
let config_ctx = Config::default();
|
||||
let context = Context::new(&tera_ctx, &config_ctx, &permalinks, InsertAnchor::None);
|
||||
let res = markdown_to_html(
|
||||
r#"[rel link](./pages/about.md), [abs link](https://vincent.is/about)"#,
|
||||
&permalinks,
|
||||
&GUTENBERG_TERA,
|
||||
&Config::default()
|
||||
&context
|
||||
).unwrap();
|
||||
|
||||
assert!(
|
||||
|
@ -488,15 +438,13 @@ A quote
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_markdown_to_html_relative_links_with_anchors() {
|
||||
fn can_make_relative_links_with_anchors() {
|
||||
let mut permalinks = HashMap::new();
|
||||
permalinks.insert("pages/about.md".to_string(), "https://vincent.is/about".to_string());
|
||||
let res = markdown_to_html(
|
||||
r#"[rel link](./pages/about.md#cv)"#,
|
||||
&permalinks,
|
||||
&GUTENBERG_TERA,
|
||||
&Config::default()
|
||||
).unwrap();
|
||||
let tera_ctx = Tera::default();
|
||||
let config_ctx = Config::default();
|
||||
let context = Context::new(&tera_ctx, &config_ctx, &permalinks, InsertAnchor::None);
|
||||
let res = markdown_to_html(r#"[rel link](./pages/about.md#cv)"#, &context).unwrap();
|
||||
|
||||
assert!(
|
||||
res.contains(r#"<p><a href="https://vincent.is/about#cv">rel link</a></p>"#)
|
||||
|
@ -504,67 +452,94 @@ A quote
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_markdown_to_html_relative_link_inexistant() {
|
||||
let res = markdown_to_html("[rel link](./pages/about.md)", &HashMap::new(), &Tera::default(), &Config::default());
|
||||
fn errors_relative_link_inexistant() {
|
||||
let tera_ctx = Tera::default();
|
||||
let permalinks_ctx = HashMap::new();
|
||||
let config_ctx = Config::default();
|
||||
let context = Context::new(&tera_ctx, &config_ctx, &permalinks_ctx, InsertAnchor::None);
|
||||
let res = markdown_to_html("[rel link](./pages/about.md)", &context);
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_markdown_to_html_add_id_to_headers() {
|
||||
let res = markdown_to_html(r#"# Hello"#, &HashMap::new(), &GUTENBERG_TERA, &Config::default()).unwrap();
|
||||
fn can_add_id_to_headers() {
|
||||
let tera_ctx = Tera::default();
|
||||
let permalinks_ctx = HashMap::new();
|
||||
let config_ctx = Config::default();
|
||||
let context = Context::new(&tera_ctx, &config_ctx, &permalinks_ctx, InsertAnchor::None);
|
||||
let res = markdown_to_html(r#"# Hello"#, &context).unwrap();
|
||||
assert_eq!(res, "<h1 id=\"hello\">Hello</h1>\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_markdown_to_html_add_id_to_headers_same_slug() {
|
||||
let res = markdown_to_html("# Hello\n# Hello", &HashMap::new(), &GUTENBERG_TERA, &Config::default()).unwrap();
|
||||
fn can_add_id_to_headers_same_slug() {
|
||||
let tera_ctx = Tera::default();
|
||||
let permalinks_ctx = HashMap::new();
|
||||
let config_ctx = Config::default();
|
||||
let context = Context::new(&tera_ctx, &config_ctx, &permalinks_ctx, InsertAnchor::None);
|
||||
let res = markdown_to_html("# Hello\n# Hello", &context).unwrap();
|
||||
assert_eq!(res, "<h1 id=\"hello\">Hello</h1>\n<h1 id=\"hello-1\">Hello</h1>\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_markdown_to_html_insert_anchor() {
|
||||
let mut config = Config::default();
|
||||
config.insert_anchor_links = Some(true);
|
||||
let res = markdown_to_html("# Hello", &HashMap::new(), &GUTENBERG_TERA, &config).unwrap();
|
||||
fn can_insert_anchor_left() {
|
||||
let permalinks_ctx = HashMap::new();
|
||||
let config_ctx = Config::default();
|
||||
let context = Context::new(&GUTENBERG_TERA, &config_ctx, &permalinks_ctx, InsertAnchor::Left);
|
||||
let res = markdown_to_html("# Hello", &context).unwrap();
|
||||
assert_eq!(
|
||||
res,
|
||||
"<h1 id=\"hello\"><a class=\"anchor\" href=\"#hello\" aria-label=\"Anchor link for: hello\">🔗</a>\nHello</h1>\n"
|
||||
"<h1 id=\"hello\"><a class=\"gutenberg-anchor\" href=\"#hello\" aria-label=\"Anchor link for: hello\">🔗</a>\nHello</h1>\n"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_insert_anchor_right() {
|
||||
let permalinks_ctx = HashMap::new();
|
||||
let config_ctx = Config::default();
|
||||
let context = Context::new(&GUTENBERG_TERA, &config_ctx, &permalinks_ctx, InsertAnchor::Right);
|
||||
let res = markdown_to_html("# Hello", &context).unwrap();
|
||||
assert_eq!(
|
||||
res,
|
||||
"<h1 id=\"hello\">Hello<a class=\"gutenberg-anchor\" href=\"#hello\" aria-label=\"Anchor link for: hello\">🔗</a>\n</h1>\n"
|
||||
);
|
||||
}
|
||||
|
||||
// See https://github.com/Keats/gutenberg/issues/42
|
||||
#[test]
|
||||
fn test_markdown_to_html_insert_anchor_with_exclamation_mark() {
|
||||
let mut config = Config::default();
|
||||
config.insert_anchor_links = Some(true);
|
||||
let res = markdown_to_html("# Hello!", &HashMap::new(), &GUTENBERG_TERA, &config).unwrap();
|
||||
fn can_insert_anchor_with_exclamation_mark() {
|
||||
let permalinks_ctx = HashMap::new();
|
||||
let config_ctx = Config::default();
|
||||
let context = Context::new(&GUTENBERG_TERA, &config_ctx, &permalinks_ctx, InsertAnchor::Left);
|
||||
let res = markdown_to_html("# Hello!", &context).unwrap();
|
||||
assert_eq!(
|
||||
res,
|
||||
"<h1 id=\"hello\"><a class=\"anchor\" href=\"#hello\" aria-label=\"Anchor link for: hello\">🔗</a>\nHello!</h1>\n"
|
||||
"<h1 id=\"hello\"><a class=\"gutenberg-anchor\" href=\"#hello\" aria-label=\"Anchor link for: hello\">🔗</a>\nHello!</h1>\n"
|
||||
);
|
||||
}
|
||||
|
||||
// See https://github.com/Keats/gutenberg/issues/53
|
||||
#[test]
|
||||
fn test_markdown_to_html_insert_anchor_with_link() {
|
||||
let mut config = Config::default();
|
||||
config.insert_anchor_links = Some(true);
|
||||
let res = markdown_to_html("## [](#xresources)Xresources", &HashMap::new(), &GUTENBERG_TERA, &config).unwrap();
|
||||
fn can_insert_anchor_with_link() {
|
||||
let permalinks_ctx = HashMap::new();
|
||||
let config_ctx = Config::default();
|
||||
let context = Context::new(&GUTENBERG_TERA, &config_ctx, &permalinks_ctx, InsertAnchor::Left);
|
||||
let res = markdown_to_html("## [](#xresources)Xresources", &context).unwrap();
|
||||
assert_eq!(
|
||||
res,
|
||||
"<h2 id=\"xresources\"><a class=\"anchor\" href=\"#xresources\" aria-label=\"Anchor link for: xresources\">🔗</a>\nXresources</h2>\n"
|
||||
"<h2 id=\"xresources\"><a class=\"gutenberg-anchor\" href=\"#xresources\" aria-label=\"Anchor link for: xresources\">🔗</a>\nXresources</h2>\n"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_markdown_to_html_insert_anchor_with_other_special_chars() {
|
||||
let mut config = Config::default();
|
||||
config.insert_anchor_links = Some(true);
|
||||
let res = markdown_to_html("# Hello*_()", &HashMap::new(), &GUTENBERG_TERA, &config).unwrap();
|
||||
fn can_insert_anchor_with_other_special_chars() {
|
||||
let permalinks_ctx = HashMap::new();
|
||||
let config_ctx = Config::default();
|
||||
let context = Context::new(&GUTENBERG_TERA, &config_ctx, &permalinks_ctx, InsertAnchor::Left);
|
||||
let res = markdown_to_html("# Hello*_()", &context).unwrap();
|
||||
assert_eq!(
|
||||
res,
|
||||
"<h1 id=\"hello\"><a class=\"anchor\" href=\"#hello\" aria-label=\"Anchor link for: hello\">🔗</a>\nHello*_()</h1>\n"
|
||||
"<h1 id=\"hello\"><a class=\"gutenberg-anchor\" href=\"#hello\" aria-label=\"Anchor link for: hello\">🔗</a>\nHello*_()</h1>\n"
|
||||
);
|
||||
}
|
||||
}
|
4
src/rendering/mod.rs
Normal file
4
src/rendering/mod.rs
Normal file
|
@ -0,0 +1,4 @@
|
|||
pub mod highlighting;
|
||||
pub mod markdown;
|
||||
pub mod short_code;
|
||||
pub mod context;
|
101
src/rendering/short_code.rs
Normal file
101
src/rendering/short_code.rs
Normal file
|
@ -0,0 +1,101 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use regex::Regex;
|
||||
use tera::{Tera, Context};
|
||||
|
||||
use errors::{Result, ResultExt};
|
||||
|
||||
lazy_static!{
|
||||
static ref SHORTCODE_RE: Regex = Regex::new(r#"\{(?:%|\{)\s+([[:alnum:]]+?)\(([[:alnum:]]+?="?.+?"?)\)\s+(?:%|\})\}"#).unwrap();
|
||||
}
|
||||
|
||||
/// A shortcode that has a body
|
||||
/// Called by having some content like {% ... %} body {% end %}
|
||||
/// We need the struct to hold the data while we're processing the markdown
|
||||
#[derive(Debug)]
|
||||
pub struct ShortCode {
|
||||
name: String,
|
||||
args: HashMap<String, String>,
|
||||
body: String,
|
||||
}
|
||||
|
||||
impl ShortCode {
|
||||
pub fn new(name: &str, args: HashMap<String, String>) -> ShortCode {
|
||||
ShortCode {
|
||||
name: name.to_string(),
|
||||
args: args,
|
||||
body: String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn append(&mut self, text: &str) {
|
||||
self.body.push_str(text)
|
||||
}
|
||||
|
||||
pub fn render(&self, tera: &Tera) -> Result<String> {
|
||||
let mut context = Context::new();
|
||||
for (key, value) in &self.args {
|
||||
context.add(key, value);
|
||||
}
|
||||
context.add("body", &self.body);
|
||||
let tpl_name = format!("shortcodes/{}.html", self.name);
|
||||
tera.render(&tpl_name, &context)
|
||||
.chain_err(|| format!("Failed to render {} shortcode", self.name))
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a shortcode without a body
|
||||
pub fn parse_shortcode(input: &str) -> (String, HashMap<String, String>) {
|
||||
let mut args = HashMap::new();
|
||||
let caps = SHORTCODE_RE.captures(input).unwrap();
|
||||
// caps[0] is the full match
|
||||
let name = &caps[1];
|
||||
let arg_list = &caps[2];
|
||||
for arg in arg_list.split(',') {
|
||||
let bits = arg.split('=').collect::<Vec<_>>();
|
||||
args.insert(bits[0].trim().to_string(), bits[1].replace("\"", ""));
|
||||
}
|
||||
|
||||
(name.to_string(), args)
|
||||
}
|
||||
|
||||
/// Renders a shortcode or return an error
|
||||
pub fn render_simple_shortcode(tera: &Tera, name: &str, args: &HashMap<String, String>) -> Result<String> {
|
||||
let mut context = Context::new();
|
||||
for (key, value) in args.iter() {
|
||||
context.add(key, value);
|
||||
}
|
||||
let tpl_name = format!("shortcodes/{}.html", name);
|
||||
|
||||
tera.render(&tpl_name, &context).chain_err(|| format!("Failed to render {} shortcode", name))
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{parse_shortcode};
|
||||
|
||||
#[test]
|
||||
fn can_parse_simple_shortcode_one_arg() {
|
||||
let (name, args) = parse_shortcode(r#"{{ youtube(id="w7Ft2ymGmfc") }}"#);
|
||||
assert_eq!(name, "youtube");
|
||||
assert_eq!(args["id"], "w7Ft2ymGmfc");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_parse_simple_shortcode_several_arg() {
|
||||
let (name, args) = parse_shortcode(r#"{{ youtube(id="w7Ft2ymGmfc", autoplay=true) }}"#);
|
||||
assert_eq!(name, "youtube");
|
||||
assert_eq!(args["id"], "w7Ft2ymGmfc");
|
||||
assert_eq!(args["autoplay"], "true");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_parse_block_shortcode_several_arg() {
|
||||
let (name, args) = parse_shortcode(r#"{% youtube(id="w7Ft2ymGmfc", autoplay=true) %}"#);
|
||||
assert_eq!(name, "youtube");
|
||||
assert_eq!(args["id"], "w7Ft2ymGmfc");
|
||||
assert_eq!(args["autoplay"], "true");
|
||||
}
|
||||
|
||||
}
|
353
src/site.rs
353
src/site.rs
|
@ -1,47 +1,24 @@
|
|||
use std::collections::{HashMap};
|
||||
use std::iter::FromIterator;
|
||||
use std::collections::HashMap;
|
||||
use std::fs::{remove_dir_all, copy, create_dir_all};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use glob::glob;
|
||||
use tera::{Tera, Context};
|
||||
use slug::slugify;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use errors::{Result, ResultExt};
|
||||
use config::{Config, get_config};
|
||||
use utils::{create_file, create_directory};
|
||||
use content::{Page, Section, Paginator, SortBy, populate_previous_and_next_pages, sort_pages};
|
||||
use fs::{create_file, create_directory, ensure_directory_exists};
|
||||
use content::{Page, Section, Paginator, SortBy, Taxonomy, populate_previous_and_next_pages, sort_pages};
|
||||
use templates::{GUTENBERG_TERA, global_fns, render_redirect_template};
|
||||
use front_matter::InsertAnchor;
|
||||
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum RenderList {
|
||||
Tags,
|
||||
Categories,
|
||||
}
|
||||
|
||||
/// A tag or category
|
||||
#[derive(Debug, Serialize, PartialEq)]
|
||||
struct ListItem {
|
||||
name: String,
|
||||
slug: String,
|
||||
count: usize,
|
||||
}
|
||||
|
||||
impl ListItem {
|
||||
pub fn new(name: &str, count: usize) -> ListItem {
|
||||
ListItem {
|
||||
name: name.to_string(),
|
||||
slug: slugify(name),
|
||||
count: count,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Site {
|
||||
/// The base path of the gutenberg site
|
||||
pub base_path: PathBuf,
|
||||
/// The parsed config for the site
|
||||
pub config: Config,
|
||||
pub pages: HashMap<PathBuf, Page>,
|
||||
pub sections: HashMap<PathBuf, Section>,
|
||||
|
@ -49,8 +26,8 @@ pub struct Site {
|
|||
live_reload: bool,
|
||||
output_path: PathBuf,
|
||||
static_path: PathBuf,
|
||||
pub tags: HashMap<String, Vec<PathBuf>>,
|
||||
pub categories: HashMap<String, Vec<PathBuf>>,
|
||||
pub tags: Option<Taxonomy>,
|
||||
pub categories: Option<Taxonomy>,
|
||||
/// A map of all .md files (section and pages) and their permalink
|
||||
/// We need that if there are relative links in the content that need to be resolved
|
||||
pub permalinks: HashMap<String, String>,
|
||||
|
@ -75,8 +52,8 @@ impl Site {
|
|||
live_reload: false,
|
||||
output_path: path.join("public"),
|
||||
static_path: path.join("static"),
|
||||
tags: HashMap::new(),
|
||||
categories: HashMap::new(),
|
||||
tags: None,
|
||||
categories: None,
|
||||
permalinks: HashMap::new(),
|
||||
};
|
||||
|
||||
|
@ -88,15 +65,6 @@ impl Site {
|
|||
self.live_reload = true;
|
||||
}
|
||||
|
||||
/// Gets the path of all ignored pages in the site
|
||||
/// Used for reporting them in the CLI
|
||||
pub fn get_ignored_pages(&self) -> Vec<PathBuf> {
|
||||
self.sections
|
||||
.values()
|
||||
.flat_map(|s| s.ignored_pages.iter().map(|p| p.file_path.clone()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Get all the orphan (== without section) pages in the site
|
||||
pub fn get_all_orphan_pages(&self) -> Vec<&Page> {
|
||||
let mut pages_in_sections = vec![];
|
||||
|
@ -107,7 +75,7 @@ impl Site {
|
|||
}
|
||||
|
||||
for page in self.pages.values() {
|
||||
if !pages_in_sections.contains(&page.file_path) {
|
||||
if !pages_in_sections.contains(&page.file.path) {
|
||||
orphans.push(page);
|
||||
}
|
||||
}
|
||||
|
@ -115,17 +83,6 @@ impl Site {
|
|||
orphans
|
||||
}
|
||||
|
||||
/// Finds the section that contains the page given if there is one
|
||||
pub fn find_parent_section(&self, page: &Page) -> Option<&Section> {
|
||||
for section in self.sections.values() {
|
||||
if section.is_child_page(page) {
|
||||
return Some(section)
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
|
@ -146,8 +103,8 @@ impl Site {
|
|||
self.add_page(path, false)?;
|
||||
}
|
||||
}
|
||||
// Insert a default index section so we don't need to create a _index.md to render
|
||||
// the index page
|
||||
// Insert a default index section if necessary so we don't need to create
|
||||
// a _index.md to render the index page
|
||||
let index_path = self.base_path.join("content").join("_index.md");
|
||||
if !self.sections.contains_key(&index_path) {
|
||||
let mut index_section = Section::default();
|
||||
|
@ -155,9 +112,16 @@ impl Site {
|
|||
self.sections.insert(index_path, index_section);
|
||||
}
|
||||
|
||||
// Silly thing needed to make the borrow checker happy
|
||||
let mut pages_insert_anchors = HashMap::new();
|
||||
for page in self.pages.values() {
|
||||
pages_insert_anchors.insert(page.file.path.clone(), self.find_parent_section_insert_anchor(&page.file.parent.clone()));
|
||||
}
|
||||
|
||||
// TODO: make that parallel
|
||||
for page in self.pages.values_mut() {
|
||||
page.render_markdown(&self.permalinks, &self.tera, &self.config)?;
|
||||
let insert_anchor = pages_insert_anchors[&page.file.path];
|
||||
page.render_markdown(&self.permalinks, &self.tera, &self.config, insert_anchor)?;
|
||||
}
|
||||
// TODO: make that parallel
|
||||
for section in self.sections.values_mut() {
|
||||
|
@ -168,22 +132,30 @@ impl Site {
|
|||
self.populate_tags_and_categories();
|
||||
|
||||
self.tera.register_global_function("get_page", global_fns::make_get_page(&self.pages));
|
||||
self.tera.register_global_function("get_section", global_fns::make_get_section(&self.sections));
|
||||
self.register_get_url_fn();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Separate fn as it can be called in the serve command
|
||||
pub fn register_get_url_fn(&mut self) {
|
||||
self.tera.register_global_function("get_url", global_fns::make_get_url(self.permalinks.clone()));
|
||||
}
|
||||
|
||||
/// Add a page to the site
|
||||
/// The `render` parameter is used in the serve command, when rebuilding a page.
|
||||
/// If `true`, it will also render the markdown for that page
|
||||
/// Returns the previous page struct if there was one
|
||||
pub fn add_page(&mut self, path: &Path, render: bool) -> Result<Option<Page>> {
|
||||
let page = Page::from_file(&path, &self.config)?;
|
||||
self.permalinks.insert(page.relative_path.clone(), page.permalink.clone());
|
||||
let prev = self.pages.insert(page.file_path.clone(), page);
|
||||
self.permalinks.insert(page.file.relative.clone(), page.permalink.clone());
|
||||
let prev = self.pages.insert(page.file.path.clone(), page);
|
||||
|
||||
if render {
|
||||
let insert_anchor = self.find_parent_section_insert_anchor(&self.pages[path].file.parent);
|
||||
let mut page = self.pages.get_mut(path).unwrap();
|
||||
page.render_markdown(&self.permalinks, &self.tera, &self.config)?;
|
||||
page.render_markdown(&self.permalinks, &self.tera, &self.config, insert_anchor)?;
|
||||
}
|
||||
|
||||
Ok(prev)
|
||||
|
@ -192,11 +164,11 @@ impl Site {
|
|||
/// Add a section to the site
|
||||
/// The `render` parameter is used in the serve command, when rebuilding a page.
|
||||
/// If `true`, it will also render the markdown for that page
|
||||
/// Returns the previous page struct if there was one
|
||||
/// Returns the previous section struct if there was one
|
||||
pub fn add_section(&mut self, path: &Path, render: bool) -> Result<Option<Section>> {
|
||||
let section = Section::from_file(path, &self.config)?;
|
||||
self.permalinks.insert(section.relative_path.clone(), section.permalink.clone());
|
||||
let prev = self.sections.insert(section.file_path.clone(), section);
|
||||
self.permalinks.insert(section.file.relative.clone(), section.permalink.clone());
|
||||
let prev = self.sections.insert(section.file.path.clone(), section);
|
||||
|
||||
if render {
|
||||
let mut section = self.sections.get_mut(path).unwrap();
|
||||
|
@ -206,12 +178,21 @@ impl Site {
|
|||
Ok(prev)
|
||||
}
|
||||
|
||||
/// Finds the insert_anchor for the parent section of the directory at `path`.
|
||||
/// Defaults to `AnchorInsert::None` if no parent section found
|
||||
pub fn find_parent_section_insert_anchor(&self, parent_path: &PathBuf) -> InsertAnchor {
|
||||
match self.sections.get(&parent_path.join("_index.md")) {
|
||||
Some(s) => s.meta.insert_anchor.unwrap(),
|
||||
None => InsertAnchor::None
|
||||
}
|
||||
}
|
||||
|
||||
/// Find out the direct subsections of each subsection if there are some
|
||||
/// as well as the pages for each section
|
||||
pub fn populate_sections(&mut self) {
|
||||
let mut grandparent_paths = HashMap::new();
|
||||
for section in self.sections.values_mut() {
|
||||
if let Some(grand_parent) = section.parent_path.parent() {
|
||||
if let Some(ref grand_parent) = section.file.grand_parent {
|
||||
grandparent_paths.entry(grand_parent.to_path_buf()).or_insert_with(|| vec![]).push(section.clone());
|
||||
}
|
||||
// Make sure the pages of a section are empty since we can call that many times on `serve`
|
||||
|
@ -220,13 +201,14 @@ impl Site {
|
|||
}
|
||||
|
||||
for page in self.pages.values() {
|
||||
if self.sections.contains_key(&page.parent_path.join("_index.md")) {
|
||||
self.sections.get_mut(&page.parent_path.join("_index.md")).unwrap().pages.push(page.clone());
|
||||
let parent_section_path = page.file.parent.join("_index.md");
|
||||
if self.sections.contains_key(&parent_section_path) {
|
||||
self.sections.get_mut(&parent_section_path).unwrap().pages.push(page.clone());
|
||||
}
|
||||
}
|
||||
|
||||
for section in self.sections.values_mut() {
|
||||
match grandparent_paths.get(§ion.parent_path) {
|
||||
match grandparent_paths.get(§ion.file.parent) {
|
||||
Some(paths) => section.subsections.extend(paths.clone()),
|
||||
None => continue,
|
||||
};
|
||||
|
@ -250,24 +232,23 @@ impl Site {
|
|||
}
|
||||
}
|
||||
|
||||
/// Separated from `parse` for easier testing
|
||||
/// Find all the tags and categories if it's asked in the config
|
||||
pub fn populate_tags_and_categories(&mut self) {
|
||||
for page in self.pages.values() {
|
||||
if let Some(ref category) = page.meta.category {
|
||||
self.categories
|
||||
.entry(category.to_string())
|
||||
.or_insert_with(|| vec![])
|
||||
.push(page.file_path.clone());
|
||||
}
|
||||
let generate_tags_pages = self.config.generate_tags_pages.unwrap();
|
||||
let generate_categories_pages = self.config.generate_categories_pages.unwrap();
|
||||
if !generate_tags_pages && !generate_categories_pages {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(ref tags) = page.meta.tags {
|
||||
for tag in tags {
|
||||
self.tags
|
||||
.entry(tag.to_string())
|
||||
.or_insert_with(|| vec![])
|
||||
.push(page.file_path.clone());
|
||||
}
|
||||
}
|
||||
// TODO: can we pass a reference?
|
||||
let (tags, categories) = Taxonomy::find_tags_and_categories(
|
||||
self.pages.values().cloned().collect::<Vec<_>>()
|
||||
);
|
||||
if generate_tags_pages {
|
||||
self.tags = Some(tags);
|
||||
}
|
||||
if generate_categories_pages {
|
||||
self.categories = Some(categories);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -283,14 +264,6 @@ impl Site {
|
|||
html
|
||||
}
|
||||
|
||||
fn ensure_public_directory_exists(&self) -> Result<()> {
|
||||
let public = self.output_path.clone();
|
||||
if !public.exists() {
|
||||
create_directory(&public)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Copy static file to public directory.
|
||||
pub fn copy_static_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
|
||||
let relative_path = path.as_ref().strip_prefix(&self.static_path).unwrap();
|
||||
|
@ -332,7 +305,7 @@ impl Site {
|
|||
|
||||
/// Renders a single content page
|
||||
pub fn render_page(&self, page: &Page) -> Result<()> {
|
||||
self.ensure_public_directory_exists()?;
|
||||
ensure_directory_exists(&self.output_path)?;
|
||||
|
||||
// Copy the nesting of the content directory if we have sections for that page
|
||||
let mut current_path = self.output_path.to_path_buf();
|
||||
|
@ -350,7 +323,7 @@ impl Site {
|
|||
|
||||
// Finally, create a index.html file there with the page rendered
|
||||
let output = page.render_html(&self.tera, &self.config)?;
|
||||
create_file(current_path.join("index.html"), &self.inject_livereload(output))?;
|
||||
create_file(¤t_path.join("index.html"), &self.inject_livereload(output))?;
|
||||
|
||||
// Copy any asset we found previously into the same directory as the index.html
|
||||
for asset in &page.assets {
|
||||
|
@ -361,7 +334,7 @@ impl Site {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Builds the site to the `public` directory after deleting it
|
||||
/// Deletes the `public` directory and builds the site
|
||||
pub fn build(&self) -> Result<()> {
|
||||
self.clean()?;
|
||||
self.render_sections()?;
|
||||
|
@ -381,98 +354,45 @@ impl Site {
|
|||
|
||||
/// Renders robots.txt
|
||||
pub fn render_robots(&self) -> Result<()> {
|
||||
self.ensure_public_directory_exists()?;
|
||||
ensure_directory_exists(&self.output_path)?;
|
||||
create_file(
|
||||
self.output_path.join("robots.txt"),
|
||||
&self.output_path.join("robots.txt"),
|
||||
&self.tera.render("robots.txt", &Context::new())?
|
||||
)
|
||||
}
|
||||
|
||||
/// Renders all categories if the config allows it
|
||||
/// Renders all categories and the single category pages if there are some
|
||||
pub fn render_categories(&self) -> Result<()> {
|
||||
if self.config.generate_categories_pages.unwrap() {
|
||||
self.render_categories_and_tags(RenderList::Categories)
|
||||
} else {
|
||||
Ok(())
|
||||
if let Some(ref categories) = self.categories {
|
||||
self.render_taxonomy(categories)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Renders all tags if the config allows it
|
||||
/// Renders all tags and the single tag pages if there are some
|
||||
pub fn render_tags(&self) -> Result<()> {
|
||||
if self.config.generate_tags_pages.unwrap() {
|
||||
self.render_categories_and_tags(RenderList::Tags)
|
||||
} else {
|
||||
Ok(())
|
||||
if let Some(ref tags) = self.tags {
|
||||
self.render_taxonomy(tags)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Render the /{categories, list} pages and each individual category/tag page
|
||||
/// They are the same thing fundamentally, a list of pages with something in common
|
||||
/// TODO: revisit this function, lots of things have changed since then
|
||||
fn render_categories_and_tags(&self, kind: RenderList) -> Result<()> {
|
||||
let items = match kind {
|
||||
RenderList::Categories => &self.categories,
|
||||
RenderList::Tags => &self.tags,
|
||||
};
|
||||
fn render_taxonomy(&self, taxonomy: &Taxonomy) -> Result<()> {
|
||||
ensure_directory_exists(&self.output_path)?;
|
||||
|
||||
if items.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let (list_tpl_name, single_tpl_name, name, var_name) = if kind == RenderList::Categories {
|
||||
("categories.html", "category.html", "categories", "category")
|
||||
} else {
|
||||
("tags.html", "tag.html", "tags", "tag")
|
||||
};
|
||||
self.ensure_public_directory_exists()?;
|
||||
|
||||
// Create the categories/tags directory first
|
||||
let public = self.output_path.clone();
|
||||
let mut output_path = public.to_path_buf();
|
||||
output_path.push(name);
|
||||
let output_path = self.output_path.join(&taxonomy.get_list_name());
|
||||
let list_output = taxonomy.render_list(&self.tera, &self.config)?;
|
||||
create_directory(&output_path)?;
|
||||
create_file(&output_path.join("index.html"), &self.inject_livereload(list_output))?;
|
||||
|
||||
// Then render the index page for that kind.
|
||||
// We sort by number of page in that category/tag
|
||||
let mut sorted_items = vec![];
|
||||
for (item, count) in Vec::from_iter(items).into_iter().map(|(a, b)| (a, b.len())) {
|
||||
sorted_items.push(ListItem::new(item, count));
|
||||
}
|
||||
sorted_items.sort_by(|a, b| b.count.cmp(&a.count));
|
||||
let mut context = Context::new();
|
||||
context.add(name, &sorted_items);
|
||||
context.add("config", &self.config);
|
||||
context.add("current_url", &self.config.make_permalink(name));
|
||||
context.add("current_path", &format!("/{}", name));
|
||||
// And render it immediately
|
||||
let list_output = self.tera.render(list_tpl_name, &context)?;
|
||||
create_file(output_path.join("index.html"), &self.inject_livereload(list_output))?;
|
||||
for item in &taxonomy.items {
|
||||
let single_output = taxonomy.render_single_item(item, &self.tera, &self.config)?;
|
||||
|
||||
// Now, each individual item
|
||||
for (item_name, pages_paths) in items.iter() {
|
||||
let pages: Vec<&Page> = self.pages
|
||||
.iter()
|
||||
.filter(|&(path, _)| pages_paths.contains(path))
|
||||
.map(|(_, page)| page)
|
||||
.collect();
|
||||
// TODO: how to sort categories and tag content?
|
||||
// Have a setting in config.toml or a _category.md and _tag.md
|
||||
// The latter is more in line with the rest of Gutenberg but order ordering
|
||||
// doesn't really work across sections.
|
||||
|
||||
let mut context = Context::new();
|
||||
let slug = slugify(&item_name);
|
||||
context.add(var_name, &item_name);
|
||||
context.add(&format!("{}_slug", var_name), &slug);
|
||||
context.add("pages", &pages);
|
||||
context.add("config", &self.config);
|
||||
context.add("current_url", &self.config.make_permalink(&format!("{}/{}", name, slug)));
|
||||
context.add("current_path", &format!("/{}/{}", name, slug));
|
||||
let single_output = self.tera.render(single_tpl_name, &context)?;
|
||||
|
||||
create_directory(&output_path.join(&slug))?;
|
||||
create_directory(&output_path.join(&item.slug))?;
|
||||
create_file(
|
||||
output_path.join(&slug).join("index.html"),
|
||||
&output_path.join(&item.slug).join("index.html"),
|
||||
&self.inject_livereload(single_output)
|
||||
)?;
|
||||
}
|
||||
|
@ -482,28 +402,31 @@ impl Site {
|
|||
|
||||
/// What it says on the tin
|
||||
pub fn render_sitemap(&self) -> Result<()> {
|
||||
self.ensure_public_directory_exists()?;
|
||||
ensure_directory_exists(&self.output_path)?;
|
||||
|
||||
let mut context = Context::new();
|
||||
context.add("pages", &self.pages.values().collect::<Vec<&Page>>());
|
||||
context.add("sections", &self.sections.values().collect::<Vec<&Section>>());
|
||||
|
||||
let mut categories = vec![];
|
||||
if self.config.generate_categories_pages.unwrap() && !self.categories.is_empty() {
|
||||
categories.push(self.config.make_permalink("categories"));
|
||||
for category in self.categories.keys() {
|
||||
if let Some(ref c) = self.categories {
|
||||
let name = c.get_list_name();
|
||||
categories.push(self.config.make_permalink(&name));
|
||||
for item in &c.items {
|
||||
categories.push(
|
||||
self.config.make_permalink(&format!("categories/{}", slugify(category)))
|
||||
self.config.make_permalink(&format!("{}/{}", &name, item.slug))
|
||||
);
|
||||
}
|
||||
}
|
||||
context.add("categories", &categories);
|
||||
|
||||
let mut tags = vec![];
|
||||
if self.config.generate_tags_pages.unwrap() && !self.tags.is_empty() {
|
||||
tags.push(self.config.make_permalink("tags"));
|
||||
for tag in self.tags.keys() {
|
||||
if let Some(ref t) = self.tags {
|
||||
let name = t.get_list_name();
|
||||
tags.push(self.config.make_permalink(&name));
|
||||
for item in &t.items {
|
||||
tags.push(
|
||||
self.config.make_permalink(&format!("tags/{}", slugify(tag)))
|
||||
self.config.make_permalink(&format!("{}/{}", &name, item.slug))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -511,18 +434,18 @@ impl Site {
|
|||
|
||||
let sitemap = self.tera.render("sitemap.xml", &context)?;
|
||||
|
||||
create_file(self.output_path.join("sitemap.xml"), &sitemap)?;
|
||||
create_file(&self.output_path.join("sitemap.xml"), &sitemap)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn render_rss_feed(&self) -> Result<()> {
|
||||
self.ensure_public_directory_exists()?;
|
||||
ensure_directory_exists(&self.output_path)?;
|
||||
|
||||
let mut context = Context::new();
|
||||
let pages = self.pages.values()
|
||||
.filter(|p| p.meta.date.is_some())
|
||||
.take(15) // limit to the last 15 elements
|
||||
.take(self.config.rss_limit.unwrap()) // limit to the last n elements
|
||||
.cloned()
|
||||
.collect::<Vec<Page>>();
|
||||
|
||||
|
@ -544,7 +467,7 @@ impl Site {
|
|||
|
||||
let sitemap = self.tera.render("rss.xml", &context)?;
|
||||
|
||||
create_file(self.output_path.join("rss.xml"), &sitemap)?;
|
||||
create_file(&self.output_path.join("rss.xml"), &sitemap)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -554,17 +477,17 @@ impl Site {
|
|||
fn get_sections_map(&self) -> HashMap<String, Section> {
|
||||
self.sections
|
||||
.values()
|
||||
.map(|s| (s.components.join("/"), s.clone()))
|
||||
.map(|s| (s.file.components.join("/"), s.clone()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Renders a single section
|
||||
pub fn render_section(&self, section: &Section, render_pages: bool) -> Result<()> {
|
||||
self.ensure_public_directory_exists()?;
|
||||
ensure_directory_exists(&self.output_path)?;
|
||||
let public = self.output_path.clone();
|
||||
|
||||
let mut output_path = public.to_path_buf();
|
||||
for component in §ion.components {
|
||||
for component in §ion.file.components {
|
||||
output_path.push(component);
|
||||
|
||||
if !output_path.exists() {
|
||||
|
@ -590,7 +513,7 @@ impl Site {
|
|||
&self.tera,
|
||||
&self.config,
|
||||
)?;
|
||||
create_file(output_path.join("index.html"), &self.inject_livereload(output))?;
|
||||
create_file(&output_path.join("index.html"), &self.inject_livereload(output))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -610,7 +533,7 @@ impl Site {
|
|||
|
||||
/// Renders all pages that do not belong to any sections
|
||||
pub fn render_orphan_pages(&self) -> Result<()> {
|
||||
self.ensure_public_directory_exists()?;
|
||||
ensure_directory_exists(&self.output_path)?;
|
||||
|
||||
for page in self.get_all_orphan_pages() {
|
||||
self.render_page(page)?;
|
||||
|
@ -621,7 +544,7 @@ impl Site {
|
|||
|
||||
/// Renders a list of pages when the section/index is wanting pagination.
|
||||
fn render_paginated(&self, output_path: &Path, section: &Section) -> Result<()> {
|
||||
self.ensure_public_directory_exists()?;
|
||||
ensure_directory_exists(&self.output_path)?;
|
||||
|
||||
let paginate_path = match section.meta.paginate_path {
|
||||
Some(ref s) => s.clone(),
|
||||
|
@ -636,13 +559,63 @@ impl Site {
|
|||
create_directory(&page_path)?;
|
||||
let output = paginator.render_pager(pager, self)?;
|
||||
if i > 0 {
|
||||
create_file(page_path.join("index.html"), &self.inject_livereload(output))?;
|
||||
create_file(&page_path.join("index.html"), &self.inject_livereload(output))?;
|
||||
} else {
|
||||
create_file(output_path.join("index.html"), &self.inject_livereload(output))?;
|
||||
create_file(page_path.join("index.html"), &render_redirect_template(§ion.permalink, &self.tera)?)?;
|
||||
create_file(&output_path.join("index.html"), &self.inject_livereload(output))?;
|
||||
create_file(&page_path.join("index.html"), &render_redirect_template(§ion.permalink, &self.tera)?)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Resolves an internal link (of the `./posts/something.md#hey` sort) to its absolute link
|
||||
pub fn resolve_internal_link(link: &str, permalinks: &HashMap<String, String>) -> Result<String> {
|
||||
// First we remove the ./ since that's gutenberg specific
|
||||
let clean_link = link.replacen("./", "", 1);
|
||||
// Then we remove any potential anchor
|
||||
// parts[0] will be the file path and parts[1] the anchor if present
|
||||
let parts = clean_link.split('#').collect::<Vec<_>>();
|
||||
match permalinks.get(parts[0]) {
|
||||
Some(p) => {
|
||||
if parts.len() > 1 {
|
||||
Ok(format!("{}#{}", p, parts[1]))
|
||||
} else {
|
||||
Ok(p.to_string())
|
||||
}
|
||||
},
|
||||
None => bail!(format!("Relative link {} not found.", link)),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::resolve_internal_link;
|
||||
|
||||
#[test]
|
||||
fn can_resolve_valid_internal_link() {
|
||||
let mut permalinks = HashMap::new();
|
||||
permalinks.insert("pages/about.md".to_string(), "https://vincent.is/about".to_string());
|
||||
let res = resolve_internal_link("./pages/about.md", &permalinks).unwrap();
|
||||
assert_eq!(res, "https://vincent.is/about");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_resolve_internal_links_with_anchors() {
|
||||
let mut permalinks = HashMap::new();
|
||||
permalinks.insert("pages/about.md".to_string(), "https://vincent.is/about".to_string());
|
||||
let res = resolve_internal_link("./pages/about.md#hello", &permalinks).unwrap();
|
||||
assert_eq!(res, "https://vincent.is/about#hello");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn errors_resolve_inexistant_internal_link() {
|
||||
let res = resolve_internal_link("./pages/about.md#hello", &HashMap::new());
|
||||
assert!(res.is_err());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
<a class="anchor" href="#{{ id }}" aria-label="Anchor link for: {{ id }}">🔗</a>
|
||||
<a class="gutenberg-anchor" href="#{{ id }}" aria-label="Anchor link for: {{ id }}">🔗</a>
|
||||
|
|
|
@ -45,14 +45,14 @@ mod tests {
|
|||
use super::{markdown, base64_decode, base64_encode};
|
||||
|
||||
#[test]
|
||||
fn test_markdown() {
|
||||
fn markdown_filter() {
|
||||
let result = markdown(to_value(&"# Hey").unwrap(), HashMap::new());
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(result.unwrap(), to_value(&"<h1>Hey</h1>\n").unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_base64_encode() {
|
||||
fn base64_encode_filter() {
|
||||
// from https://tools.ietf.org/html/rfc4648#section-10
|
||||
let tests = vec![
|
||||
("", ""),
|
||||
|
@ -73,7 +73,7 @@ mod tests {
|
|||
|
||||
|
||||
#[test]
|
||||
fn test_base64_decode() {
|
||||
fn base64_decode_filter() {
|
||||
let tests = vec![
|
||||
("", ""),
|
||||
("Zg==", "f"),
|
||||
|
|
|
@ -3,13 +3,14 @@ use std::path::{PathBuf};
|
|||
|
||||
use tera::{GlobalFn, Value, from_value, to_value, Result};
|
||||
|
||||
use content::Page;
|
||||
use content::{Page, Section};
|
||||
use site::resolve_internal_link;
|
||||
|
||||
|
||||
pub fn make_get_page(all_pages: &HashMap<PathBuf, Page>) -> GlobalFn {
|
||||
let mut pages = HashMap::new();
|
||||
for page in all_pages.values() {
|
||||
pages.insert(page.relative_path.clone(), page.clone());
|
||||
pages.insert(page.file.relative.clone(), page.clone());
|
||||
}
|
||||
|
||||
Box::new(move |args| -> Result<Value> {
|
||||
|
@ -27,3 +28,40 @@ pub fn make_get_page(all_pages: &HashMap<PathBuf, Page>) -> GlobalFn {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn make_get_section(all_sections: &HashMap<PathBuf, Section>) -> GlobalFn {
|
||||
let mut sections = HashMap::new();
|
||||
for section in all_sections.values() {
|
||||
sections.insert(section.file.relative.clone(), section.clone());
|
||||
}
|
||||
|
||||
Box::new(move |args| -> Result<Value> {
|
||||
match args.get("path") {
|
||||
Some(val) => match from_value::<String>(val.clone()) {
|
||||
Ok(v) => {
|
||||
match sections.get(&v) {
|
||||
Some(p) => Ok(to_value(p).unwrap()),
|
||||
None => Err(format!("Section `{}` not found.", v).into())
|
||||
}
|
||||
},
|
||||
Err(_) => Err(format!("`get_section` received path={:?} but it requires a string", val).into()),
|
||||
},
|
||||
None => Err("`get_section` requires a `path` argument.".into()),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn make_get_url(permalinks: HashMap<String, String>,) -> GlobalFn {
|
||||
Box::new(move |args| -> Result<Value> {
|
||||
match args.get("link") {
|
||||
Some(val) => match from_value::<String>(val.clone()) {
|
||||
Ok(v) => match resolve_internal_link(&v, &permalinks) {
|
||||
Ok(url) => Ok(to_value(url).unwrap()),
|
||||
Err(_) => Err(format!("Could not resolve URL for link `{}` not found.", v).into())
|
||||
},
|
||||
Err(_) => Err(format!("`get_url` received link={:?} but it requires a string", val).into()),
|
||||
},
|
||||
None => Err("`get_url` requires a `link` argument.".into()),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
69
src/utils.rs
69
src/utils.rs
|
@ -1,69 +0,0 @@
|
|||
use std::io::prelude::*;
|
||||
use std::fs::{File, create_dir};
|
||||
use std::path::Path;
|
||||
|
||||
use errors::{Result, ResultExt};
|
||||
|
||||
pub fn create_file<P: AsRef<Path>>(path: P, content: &str) -> Result<()> {
|
||||
let mut file = File::create(&path)?;
|
||||
file.write_all(content.as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Very similar to `create_dir` from the std except it checks if the folder
|
||||
/// exists before creating it
|
||||
pub fn create_directory<P: AsRef<Path>>(path: P) -> Result<()> {
|
||||
let path = path.as_ref();
|
||||
if !path.exists() {
|
||||
create_dir(path)
|
||||
.chain_err(|| format!("Was not able to create folder {}", path.display()))?;
|
||||
}
|
||||
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 first `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()]);
|
||||
}
|
||||
}
|
|
@ -5,9 +5,10 @@ name: Jinja2
|
|||
file_extensions:
|
||||
- j2
|
||||
- jinja2
|
||||
scope: source.jinja2
|
||||
scope: text.html.jinja2
|
||||
contexts:
|
||||
main:
|
||||
- include: scope:text.html.basic
|
||||
- match: '({%)\s*(raw)\s*(%})'
|
||||
captures:
|
||||
1: entity.other.jinja2.delimiter.tag
|
Binary file not shown.
Binary file not shown.
|
@ -2,4 +2,5 @@
|
|||
title = "Posts"
|
||||
paginate_by = 2
|
||||
template = "section_paginated.html"
|
||||
insert_anchor = "left"
|
||||
+++
|
||||
|
|
|
@ -8,3 +8,5 @@ date = "2017-01-01"
|
|||
A simple page with a slug defined
|
||||
|
||||
# Title
|
||||
|
||||
Hey
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
{% for category in categories %}
|
||||
{{ category.name }} {{ category.slug }} {{ category.count }}
|
||||
{{ category.name }} {{ category.slug }} {{ category.pages | length }}
|
||||
{% endfor %}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
Category: {{ category }}
|
||||
Category: {{ category.name }}
|
||||
|
||||
|
||||
{% for page in pages %}
|
||||
{% for page in category.pages %}
|
||||
<article>
|
||||
<h3 class="post__title"><a href="{{ page.permalink }}">{{ page.title }}</a></h3>
|
||||
</article>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
Tag: {{ tag }}
|
||||
Tag: {{ tag.name }}
|
||||
|
||||
{% for page in pages %}
|
||||
{% for page in tag.pages %}
|
||||
<article>
|
||||
<h3 class="post__title"><a href="{{ page.permalink }}">{{ page.title }}</a></h3>
|
||||
</article>
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
{% for tag in tags %}
|
||||
{{ tag.name }} {{ tag.slug }} {{ tag.count }}
|
||||
{{ tag.name }} {{ tag.slug }} {{ tag.pages | length }}
|
||||
{% endfor %}
|
||||
|
|
251
tests/page.rs
251
tests/page.rs
|
@ -1,251 +0,0 @@
|
|||
extern crate gutenberg;
|
||||
extern crate tera;
|
||||
extern crate tempdir;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fs::{File, create_dir};
|
||||
use std::path::Path;
|
||||
|
||||
use tempdir::TempDir;
|
||||
use tera::Tera;
|
||||
|
||||
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 mut page = res.unwrap();
|
||||
page.render_markdown(&HashMap::default(), &Tera::default(), &Config::default()).unwrap();
|
||||
|
||||
assert_eq!(page.meta.title.unwrap(), "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 mut page = res.unwrap();
|
||||
page.render_markdown(&HashMap::default(), &Tera::default(), &Config::default()).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 mut page = res.unwrap();
|
||||
page.render_markdown(&HashMap::default(), &Tera::default(), &Config::default()).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 mut page = res.unwrap();
|
||||
page.render_markdown(&HashMap::default(), &Tera::default(), &Config::default()).unwrap();
|
||||
assert_eq!(page.path, "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 mut page = res.unwrap();
|
||||
page.render_markdown(&HashMap::default(), &Tera::default(), &Config::default()).unwrap();
|
||||
assert_eq!(page.path, "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 mut page = res.unwrap();
|
||||
page.render_markdown(&HashMap::default(), &Tera::default(), &Config::default()).unwrap();
|
||||
assert_eq!(page.path, "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 mut page = res.unwrap();
|
||||
page.render_markdown(&HashMap::default(), &Tera::default(), &Config::default()).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 mut page = res.unwrap();
|
||||
page.render_markdown(&HashMap::default(), &Tera::default(), &Config::default()).unwrap();
|
||||
assert_eq!(page.slug, "file-with-space");
|
||||
assert_eq!(page.permalink, format!("{}{}", Config::default().base_url, "file-with-space"));
|
||||
}
|
||||
|
||||
#[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 mut page = res.unwrap();
|
||||
page.render_markdown(&HashMap::default(), &Tera::default(), &Config::default()).unwrap();
|
||||
assert_eq!(page.summary, None);
|
||||
}
|
||||
|
||||
#[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 mut page = res.unwrap();
|
||||
page.render_markdown(&HashMap::default(), &Tera::default(), &Config::default()).unwrap();
|
||||
assert_eq!(page.summary, Some("<p>Hello world</p>\n".to_string()));
|
||||
}
|
||||
|
||||
#[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 mut page = res.unwrap();
|
||||
page.render_markdown(&HashMap::default(), &Tera::default(), &Config::default()).unwrap();
|
||||
assert!(page.content.starts_with("<pre"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_page_with_assets_gets_right_parent_path() {
|
||||
let tmp_dir = TempDir::new("example").expect("create temp dir");
|
||||
let path = tmp_dir.path();
|
||||
create_dir(&path.join("content")).expect("create content temp dir");
|
||||
create_dir(&path.join("content").join("posts")).expect("create posts temp dir");
|
||||
let nested_path = path.join("content").join("posts").join("assets");
|
||||
create_dir(&nested_path).expect("create nested temp dir");
|
||||
File::create(nested_path.join("index.md")).unwrap();
|
||||
File::create(nested_path.join("example.js")).unwrap();
|
||||
File::create(nested_path.join("graph.jpg")).unwrap();
|
||||
File::create(nested_path.join("fail.png")).unwrap();
|
||||
|
||||
let res = Page::parse(
|
||||
nested_path.join("index.md").as_path(),
|
||||
"+++\nurl=\"hey\"+++\n",
|
||||
&Config::default()
|
||||
);
|
||||
assert!(res.is_ok());
|
||||
let page = res.unwrap();
|
||||
assert_eq!(page.parent_path, path.join("content").join("posts"));
|
||||
}
|
||||
|
||||
#[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());
|
||||
}
|
|
@ -12,7 +12,7 @@ use gutenberg::{Site};
|
|||
|
||||
|
||||
#[test]
|
||||
fn test_can_parse_site() {
|
||||
fn can_parse_site() {
|
||||
let mut path = env::current_dir().unwrap().to_path_buf();
|
||||
path.push("test_site");
|
||||
let mut site = Site::new(&path, "config.toml").unwrap();
|
||||
|
@ -24,7 +24,7 @@ fn test_can_parse_site() {
|
|||
|
||||
// Make sure we remove all the pwd + content from the sections
|
||||
let basic = &site.pages[&posts_path.join("simple.md")];
|
||||
assert_eq!(basic.components, vec!["posts".to_string()]);
|
||||
assert_eq!(basic.file.components, vec!["posts".to_string()]);
|
||||
|
||||
// Make sure the page with a url doesn't have any sections
|
||||
let url_post = &site.pages[&posts_path.join("fixed-url.md")];
|
||||
|
@ -32,7 +32,7 @@ fn test_can_parse_site() {
|
|||
|
||||
// Make sure the article in a folder with only asset doesn't get counted as a section
|
||||
let asset_folder_post = &site.pages[&posts_path.join("with-assets").join("index.md")];
|
||||
assert_eq!(asset_folder_post.components, vec!["posts".to_string()]);
|
||||
assert_eq!(asset_folder_post.file.components, vec!["posts".to_string()]);
|
||||
|
||||
// That we have the right number of sections
|
||||
assert_eq!(site.sections.len(), 6);
|
||||
|
@ -89,7 +89,7 @@ macro_rules! file_contains {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_can_build_site_without_live_reload() {
|
||||
fn 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, "config.toml").unwrap();
|
||||
|
@ -131,7 +131,7 @@ fn test_can_build_site_without_live_reload() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_can_build_site_with_live_reload() {
|
||||
fn 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, "config.toml").unwrap();
|
||||
|
@ -169,7 +169,7 @@ fn test_can_build_site_with_live_reload() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_can_build_site_with_categories() {
|
||||
fn 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, "config.toml").unwrap();
|
||||
|
@ -190,7 +190,7 @@ fn test_can_build_site_with_categories() {
|
|||
site.build().unwrap();
|
||||
|
||||
assert!(Path::new(&public).exists());
|
||||
assert_eq!(site.categories.len(), 2);
|
||||
assert_eq!(site.categories.unwrap().len(), 2);
|
||||
|
||||
assert!(file_exists!(public, "index.html"));
|
||||
assert!(file_exists!(public, "sitemap.xml"));
|
||||
|
@ -221,7 +221,7 @@ fn test_can_build_site_with_categories() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_can_build_site_with_tags() {
|
||||
fn 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, "config.toml").unwrap();
|
||||
|
@ -243,7 +243,7 @@ fn test_can_build_site_with_tags() {
|
|||
site.build().unwrap();
|
||||
|
||||
assert!(Path::new(&public).exists());
|
||||
assert_eq!(site.tags.len(), 3);
|
||||
assert_eq!(site.tags.unwrap().len(), 3);
|
||||
|
||||
assert!(file_exists!(public, "index.html"));
|
||||
assert!(file_exists!(public, "sitemap.xml"));
|
||||
|
@ -273,11 +273,10 @@ fn test_can_build_site_with_tags() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_can_build_site_and_insert_anchor_links() {
|
||||
fn can_build_site_and_insert_anchor_links() {
|
||||
let mut path = env::current_dir().unwrap().to_path_buf();
|
||||
path.push("test_site");
|
||||
let mut site = Site::new(&path, "config.toml").unwrap();
|
||||
site.config.insert_anchor_links = Some(true);
|
||||
site.load().unwrap();
|
||||
let tmp_dir = TempDir::new("example").expect("create temp dir");
|
||||
let public = &tmp_dir.path().join("public");
|
||||
|
@ -286,11 +285,11 @@ fn test_can_build_site_and_insert_anchor_links() {
|
|||
|
||||
assert!(Path::new(&public).exists());
|
||||
// anchor link inserted
|
||||
assert!(file_contains!(public, "posts/something-else/index.html", "<h1 id=\"title\"><a class=\"anchor\" href=\"#title\""));
|
||||
assert!(file_contains!(public, "posts/something-else/index.html", "<h1 id=\"title\"><a class=\"gutenberg-anchor\" href=\"#title\""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_can_build_site_with_pagination_for_section() {
|
||||
fn can_build_site_with_pagination_for_section() {
|
||||
let mut path = env::current_dir().unwrap().to_path_buf();
|
||||
path.push("test_site");
|
||||
let mut site = Site::new(&path, "config.toml").unwrap();
|
||||
|
@ -349,7 +348,7 @@ fn test_can_build_site_with_pagination_for_section() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_can_build_site_with_pagination_for_index() {
|
||||
fn can_build_site_with_pagination_for_index() {
|
||||
let mut path = env::current_dir().unwrap().to_path_buf();
|
||||
path.push("test_site");
|
||||
let mut site = Site::new(&path, "config.toml").unwrap();
|
||||
|
@ -391,5 +390,4 @@ fn test_can_build_site_with_pagination_for_index() {
|
|||
assert!(file_contains!(public, "index.html", "Last: https://replace-this-with-your-url.com/"));
|
||||
assert_eq!(file_contains!(public, "index.html", "has_prev"), false);
|
||||
assert_eq!(file_contains!(public, "index.html", "has_next"), false);
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue