Change zola serve to load HTML from memory instead of disk (#1114)
* Change zola serve to load HTML from memory instead of disk * Be smart about assets copying * Be a tiny bit smarter on template changes * Add zola serve --fast
This commit is contained in:
parent
623817120c
commit
278cc82fc7
|
@ -4,7 +4,9 @@
|
|||
|
||||
### Breaking
|
||||
|
||||
- All paths (except colocated assets) now have a leading `/`
|
||||
- All paths like `current_path`, `page.path`, `section.path` (except colocated assets) now have a leading `/`
|
||||
- Search index generation for Chinese and Japanese has been disabled by default as it lead to a big increase in
|
||||
binary size
|
||||
|
||||
### Other
|
||||
|
||||
|
|
293
Cargo.lock
generated
293
Cargo.lock
generated
|
@ -8,9 +8,15 @@ checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e"
|
|||
|
||||
[[package]]
|
||||
name = "adler32"
|
||||
version = "1.1.0"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "567b077b825e468cc974f0020d4082ee6e03132512f207ef1a02fd5d00d1f32d"
|
||||
checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8fd72866655d1904d6b0997d0b07ba561047d070fbe29de039031c641b61217"
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
|
@ -113,7 +119,7 @@ version = "0.9.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
|
||||
dependencies = [
|
||||
"generic-array 0.14.3",
|
||||
"generic-array 0.14.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -183,6 +189,15 @@ version = "1.0.58"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9a06fb2e53271d7c279ec1efea6ab691c35a2ae67ec0d91d7acec0caf13b518"
|
||||
|
||||
[[package]]
|
||||
name = "cedarwood"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "963e82c7b94163808ca3a452608d260b64ba5bc7b5653b4af1af59887899f48d"
|
||||
dependencies = [
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "0.1.10"
|
||||
|
@ -213,9 +228,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "2.33.1"
|
||||
version = "2.33.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129"
|
||||
checksum = "10040cdf04294b565d9e0319955430099ec3813a64c952b86a41200ad714ae48"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"atty",
|
||||
|
@ -390,7 +405,7 @@ version = "0.9.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
|
||||
dependencies = [
|
||||
"generic-array 0.14.3",
|
||||
"generic-array 0.14.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -407,9 +422,9 @@ checksum = "134951f4028bdadb9b84baf4232681efbf277da25144b9b0ad65df75946c422b"
|
|||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.5.3"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3"
|
||||
checksum = "cd56b59865bce947ac5958779cfa508f6c3b9497cc762b7e24a12d11ccde2c4f"
|
||||
|
||||
[[package]]
|
||||
name = "elasticlunr-rs"
|
||||
|
@ -417,7 +432,9 @@ version = "2.3.9"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35622eb004c8f0c5e7e2032815f3314a93df0db30a1ce5c94e62c1ecc81e22b9"
|
||||
dependencies = [
|
||||
"jieba-rs",
|
||||
"lazy_static",
|
||||
"lindera",
|
||||
"regex",
|
||||
"rust-stemmers",
|
||||
"serde",
|
||||
|
@ -427,6 +444,70 @@ dependencies = [
|
|||
"strum_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "encoding"
|
||||
version = "0.2.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec"
|
||||
dependencies = [
|
||||
"encoding-index-japanese",
|
||||
"encoding-index-korean",
|
||||
"encoding-index-simpchinese",
|
||||
"encoding-index-singlebyte",
|
||||
"encoding-index-tradchinese",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "encoding-index-japanese"
|
||||
version = "1.20141219.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91"
|
||||
dependencies = [
|
||||
"encoding_index_tests",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "encoding-index-korean"
|
||||
version = "1.20141219.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4dc33fb8e6bcba213fe2f14275f0963fd16f0a02c878e3095ecfdf5bee529d81"
|
||||
dependencies = [
|
||||
"encoding_index_tests",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "encoding-index-simpchinese"
|
||||
version = "1.20141219.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d87a7194909b9118fc707194baa434a4e3b0fb6a5a757c73c3adb07aa25031f7"
|
||||
dependencies = [
|
||||
"encoding_index_tests",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "encoding-index-singlebyte"
|
||||
version = "1.20141219.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3351d5acffb224af9ca265f435b859c7c01537c0849754d3db3fdf2bfe2ae84a"
|
||||
dependencies = [
|
||||
"encoding_index_tests",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "encoding-index-tradchinese"
|
||||
version = "1.20141219.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18"
|
||||
dependencies = [
|
||||
"encoding_index_tests",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "encoding_index_tests"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569"
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.23"
|
||||
|
@ -509,12 +590,6 @@ dependencies = [
|
|||
"utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fs_extra"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f2a4a2034423744d2cc7ca2068453168dcdb82c438419e639a26bd87839c674"
|
||||
|
||||
[[package]]
|
||||
name = "fsevent"
|
||||
version = "0.4.0"
|
||||
|
@ -637,9 +712,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.3"
|
||||
version = "0.14.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60fb4bb6bba52f78a471264d9a3b7d026cc0af47b22cd2cffbc0b787ca003e63"
|
||||
checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
|
@ -726,10 +801,11 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.8.1"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34f595585f103464d8d2f6e9864682d74c1601fed5e07d62b1c9058dba8246fb"
|
||||
checksum = "e91b62f79061a0bc2e046024cb7ba44b08419ed238ecbd9adbd787434b9e8c25"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
|
@ -917,9 +993,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.5.0"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b88cd59ee5f71fea89a62248fc8f387d44400cefe05ef548466d61ced9029a7"
|
||||
checksum = "86b45e59b16c76b11bf9738fd5d38879d3bd28ad292d7b313608becb17ae2df9"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown",
|
||||
|
@ -966,6 +1042,20 @@ version = "0.4.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6"
|
||||
|
||||
[[package]]
|
||||
name = "jieba-rs"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ca2de723e93727460917d9542f7ae35a74d03d93923f03380a0238d860d137c"
|
||||
dependencies = [
|
||||
"cedarwood",
|
||||
"hashbrown",
|
||||
"lazy_static",
|
||||
"phf",
|
||||
"phf_codegen",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jpeg-decoder"
|
||||
version = "0.1.20"
|
||||
|
@ -1003,9 +1093,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
|||
|
||||
[[package]]
|
||||
name = "lazycell"
|
||||
version = "1.2.1"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f"
|
||||
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
||||
|
||||
[[package]]
|
||||
name = "levenshtein_automata"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73a004f877f468548d8d0ac4977456a249d8fabbdb8416c36db163dfc8f2e8ca"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
|
@ -1035,6 +1131,70 @@ dependencies = [
|
|||
"utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lindera"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71b867cd68f5fc19a6d8b8361a6aba55ed2485f243044b70da14b6ba5a128c00"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"byteorder",
|
||||
"encoding",
|
||||
"lindera-core",
|
||||
"lindera-dictionary",
|
||||
"lindera-fst",
|
||||
"lindera-ipadic",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lindera-core"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97b7f132a5d361c1236b28434c632097fb8867ebdf4e4c9ab4f793525bb681ff"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"byteorder",
|
||||
"encoding",
|
||||
"lindera-fst",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lindera-dictionary"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78a61a066057d24faab043586633274fa3468c5c54cb8191895659811218a8ec"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"byteorder",
|
||||
"lindera-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lindera-fst"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6098a7ca6679296cd2d227efa232f990552c5278394c845bec8a70ab0284ae0"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"levenshtein_automata",
|
||||
"regex-syntax 0.4.2",
|
||||
"utf8-ranges",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lindera-ipadic"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f12f44c385a6f4c1ff0863a2f0a91ce5f1ff6c2e0e44c69b37051b56fece112"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"byteorder",
|
||||
"lindera-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "line-wrap"
|
||||
version = "0.1.1"
|
||||
|
@ -1561,9 +1721,9 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
|
|||
|
||||
[[package]]
|
||||
name = "proc-macro-error"
|
||||
version = "1.0.3"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc175e9777c3116627248584e8f8b3e2987405cabe1c0adf7d1dd28f09dc7880"
|
||||
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
|
||||
dependencies = [
|
||||
"proc-macro-error-attr",
|
||||
"proc-macro2",
|
||||
|
@ -1574,14 +1734,12 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "proc-macro-error-attr"
|
||||
version = "1.0.3"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3cc9795ca17eb581285ec44936da7fc2335a3f34f2ddd13118b6f4d515435c50"
|
||||
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn-mid",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
|
@ -1703,18 +1861,6 @@ dependencies = [
|
|||
"num_cpus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rebuild"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"errors",
|
||||
"front_matter",
|
||||
"fs_extra",
|
||||
"library",
|
||||
"site",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.1.57"
|
||||
|
@ -1729,7 +1875,7 @@ checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6"
|
|||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
"regex-syntax 0.6.18",
|
||||
"thread_local",
|
||||
]
|
||||
|
||||
|
@ -1742,6 +1888,12 @@ dependencies = [
|
|||
"byteorder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e931c58b93d86f080c734bfd2bce7dd0079ae2331235818133c8be7f422e20e"
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.18"
|
||||
|
@ -1940,15 +2092,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.114"
|
||||
version = "1.0.115"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5317f7588f0a5078ee60ef675ef96735a1442132dc645eb1d12c018620ed8cd3"
|
||||
checksum = "e54c9a88f2da7238af84b5101443f0c0d0a3bbdc455e34a5c9497b1903ed55d5"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.114"
|
||||
version = "1.0.115"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a0be94b04690fbaed37cddffc5c134bf537c8e3329d53e982fe04c374978f8e"
|
||||
checksum = "609feed1d0a73cc36a0182a840a9b37b4a82f0b1150369f0536a9e3f2a31dc48"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -2019,6 +2174,7 @@ dependencies = [
|
|||
"front_matter",
|
||||
"glob",
|
||||
"imageproc",
|
||||
"lazy_static",
|
||||
"library",
|
||||
"link_checker",
|
||||
"rayon",
|
||||
|
@ -2053,6 +2209,12 @@ dependencies = [
|
|||
"deunicode",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbee7696b84bbf3d89a1c2eccff0850e3047ed46bfcd2e92c29a2d074d57e252"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.3.12"
|
||||
|
@ -2134,31 +2296,20 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.36"
|
||||
version = "1.0.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4cdb98bcb1f9d81d07b536179c269ea15999b5d14ea958196413869445bb5250"
|
||||
checksum = "e69abc24912995b3038597a7a593be5053eb0fb44f3cc5beec0deb421790c1f4"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn-mid"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syntect"
|
||||
version = "4.2.0"
|
||||
version = "4.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83b43a6ca1829ccb0c933b615c9ea83ffc8793ae240cecbd15119b13d741161d"
|
||||
checksum = "b57a45fdcf4891bc79f635be5c559210a4cfa464891f969724944c713282eedb"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"bitflags",
|
||||
|
@ -2168,7 +2319,7 @@ dependencies = [
|
|||
"lazycell",
|
||||
"onig",
|
||||
"plist",
|
||||
"regex-syntax",
|
||||
"regex-syntax 0.6.18",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
|
@ -2227,9 +2378,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tera"
|
||||
version = "1.4.0"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e9598067511caa7edb41886c4a29efe6d0564926837bde7dffa4a130ea6cc975"
|
||||
checksum = "1381c83828bedd5ce4e59473110afa5381ffe523406d9ade4b77c9f7be70ff9a"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"chrono-tz",
|
||||
|
@ -2374,9 +2525,9 @@ checksum = "e987b6bf443f4b5b3b6f38704195592cca41c5bb7aedd3c3693c7081f8289860"
|
|||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.17"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbdf4ccd1652592b01286a5dbe1e2a77d78afaa34beadd9872a5f7396f92aaa9"
|
||||
checksum = "6d79ca061b032d6ce30c660fded31189ca0b9922bf483cd70759f13a2d86786c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"log",
|
||||
|
@ -2385,9 +2536,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.11"
|
||||
version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94ae75f0d28ae10786f3b1895c55fe72e79928fd5ccdebb5438c75e93fec178f"
|
||||
checksum = "db63662723c316b43ca36d833707cc93dff82a02ba3d7e354f342682cc8b3545"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
@ -2528,6 +2679,12 @@ version = "0.7.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7"
|
||||
|
||||
[[package]]
|
||||
name = "utf8-ranges"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4ae116fef2b7fea257ed6440d3cfcff7f190865f170cdad00bb6465bf18ecba"
|
||||
|
||||
[[package]]
|
||||
name = "utils"
|
||||
version = "0.1.0"
|
||||
|
@ -2816,8 +2973,6 @@ dependencies = [
|
|||
"lazy_static",
|
||||
"notify",
|
||||
"open",
|
||||
"rebuild",
|
||||
"search",
|
||||
"site",
|
||||
"termcolor",
|
||||
"tokio",
|
||||
|
|
|
@ -22,7 +22,7 @@ name = "zola"
|
|||
atty = "0.2.11"
|
||||
clap = { version = "2", default-features = false }
|
||||
chrono = "0.4"
|
||||
lazy_static = "1.1.0"
|
||||
lazy_static = "1.1"
|
||||
termcolor = "1.0.4"
|
||||
# Used in init to ensure the url given as base_url is a valid one
|
||||
url = "2"
|
||||
|
@ -40,15 +40,12 @@ site = { path = "components/site" }
|
|||
errors = { path = "components/errors" }
|
||||
front_matter = { path = "components/front_matter" }
|
||||
utils = { path = "components/utils" }
|
||||
rebuild = { path = "components/rebuild" }
|
||||
search = { path = "components/search" }
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"components/config",
|
||||
"components/errors",
|
||||
"components/front_matter",
|
||||
"components/rebuild",
|
||||
"components/rendering",
|
||||
"components/site",
|
||||
"components/templates",
|
||||
|
|
|
@ -281,7 +281,7 @@ pub fn merge(into: &mut Toml, from: &Toml) -> Result<()> {
|
|||
(false, false) => {
|
||||
// These are not tables so we have nothing to merge
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
(true, true) => {
|
||||
// Recursively merge these tables
|
||||
let into_table = into.as_table_mut().unwrap();
|
||||
|
@ -295,7 +295,7 @@ pub fn merge(into: &mut Toml, from: &Toml) -> Result<()> {
|
|||
merge(into_table.get_mut(key).unwrap(), val)?;
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
_ => {
|
||||
// Trying to merge a table with something else
|
||||
Err(Error::msg(&format!("Cannot merge config.toml with theme.toml because the following values have incompatibles types:\n- {}\n - {}", into, from)))
|
||||
|
@ -464,7 +464,14 @@ truc = "default"
|
|||
assert_eq!(extra["sub"]["foo"].as_str().unwrap(), "bar".to_string());
|
||||
assert_eq!(extra["sub"].get("truc").expect("The whole extra.sub table was overriden by theme data, discarding extra.sub.truc").as_str().unwrap(), "default".to_string());
|
||||
assert_eq!(extra["sub"]["sub"]["foo"].as_str().unwrap(), "bar".to_string());
|
||||
assert_eq!(extra["sub"]["sub"].get("truc").expect("Failed to merge subsubtable extra.sub.sub").as_str().unwrap(), "default".to_string());
|
||||
assert_eq!(
|
||||
extra["sub"]["sub"]
|
||||
.get("truc")
|
||||
.expect("Failed to merge subsubtable extra.sub.sub")
|
||||
.as_str()
|
||||
.unwrap(),
|
||||
"default".to_string()
|
||||
);
|
||||
}
|
||||
|
||||
const CONFIG_TRANSLATION: &str = r#"
|
||||
|
@ -623,7 +630,6 @@ languages = [
|
|||
assert_eq!("Default language `fr` should not appear both in `config.default_language` and `config.languages`", format!("{}", err));
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn cannot_overwrite_theme_mapping_with_invalid_type() {
|
||||
let config_str = r#"
|
||||
|
@ -642,6 +648,4 @@ bar = "baz"
|
|||
// We expect an error here
|
||||
assert_eq!(false, config.add_theme_extra(&theme).is_ok());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
[package]
|
||||
name = "rebuild"
|
||||
version = "0.1.0"
|
||||
authors = ["Vincent Prouillet <prouillet.vincent@gmail.com>"]
|
||||
edition = "2018"
|
||||
include = ["src/lib.rs"]
|
||||
|
||||
[dependencies]
|
||||
errors = { path = "../errors" }
|
||||
front_matter = { path = "../front_matter" }
|
||||
library = { path = "../library" }
|
||||
site = { path = "../site" }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3"
|
||||
fs_extra = "1.1"
|
|
@ -1,493 +0,0 @@
|
|||
use std::path::{Component, Path};
|
||||
|
||||
use errors::{bail, Result};
|
||||
use front_matter::{PageFrontMatter, SectionFrontMatter};
|
||||
use library::{Page, Section};
|
||||
use site::Site;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum PageChangesNeeded {
|
||||
/// Editing `taxonomies`
|
||||
Taxonomies,
|
||||
/// Editing `date`, `order` or `weight`
|
||||
Sort,
|
||||
/// Editing anything causes a re-render of the page
|
||||
Render,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum SectionChangesNeeded {
|
||||
/// Editing `sort_by`
|
||||
Sort,
|
||||
/// Editing `title`, `description`, `extra`, `template` or setting `render` to true
|
||||
Render,
|
||||
/// Editing `paginate_by`, `paginate_path` or `insert_anchor_links`
|
||||
RenderWithPages,
|
||||
/// Setting `render` to false
|
||||
Delete,
|
||||
/// Changing `transparent`
|
||||
Transparent,
|
||||
}
|
||||
|
||||
/// Evaluates all the params in the front matter that changed so we can do the smallest
|
||||
/// delta in the serve command
|
||||
/// Order matters as the actions will be done in insertion order
|
||||
fn find_section_front_matter_changes(
|
||||
current: &SectionFrontMatter,
|
||||
new: &SectionFrontMatter,
|
||||
) -> Vec<SectionChangesNeeded> {
|
||||
let mut changes_needed = vec![];
|
||||
|
||||
if current.sort_by != new.sort_by {
|
||||
changes_needed.push(SectionChangesNeeded::Sort);
|
||||
}
|
||||
|
||||
if current.transparent != new.transparent {
|
||||
changes_needed.push(SectionChangesNeeded::Transparent);
|
||||
}
|
||||
|
||||
// We want to hide the section
|
||||
// TODO: what to do on redirect_path change?
|
||||
if current.render && !new.render {
|
||||
changes_needed.push(SectionChangesNeeded::Delete);
|
||||
// Nothing else we can do
|
||||
return changes_needed;
|
||||
}
|
||||
|
||||
if current.paginate_by != new.paginate_by
|
||||
|| current.paginate_path != new.paginate_path
|
||||
|| current.insert_anchor_links != new.insert_anchor_links
|
||||
{
|
||||
changes_needed.push(SectionChangesNeeded::RenderWithPages);
|
||||
// Nothing else we can do
|
||||
return changes_needed;
|
||||
}
|
||||
|
||||
// Any new change will trigger a re-rendering of the section page only
|
||||
changes_needed.push(SectionChangesNeeded::Render);
|
||||
changes_needed
|
||||
}
|
||||
|
||||
/// Evaluates all the params in the front matter that changed so we can do the smallest
|
||||
/// delta in the serve command
|
||||
/// Order matters as the actions will be done in insertion order
|
||||
fn find_page_front_matter_changes(
|
||||
current: &PageFrontMatter,
|
||||
other: &PageFrontMatter,
|
||||
) -> Vec<PageChangesNeeded> {
|
||||
let mut changes_needed = vec![];
|
||||
|
||||
if current.taxonomies != other.taxonomies {
|
||||
changes_needed.push(PageChangesNeeded::Taxonomies);
|
||||
}
|
||||
|
||||
if current.date != other.date || current.order != other.order || current.weight != other.weight
|
||||
{
|
||||
changes_needed.push(PageChangesNeeded::Sort);
|
||||
}
|
||||
|
||||
changes_needed.push(PageChangesNeeded::Render);
|
||||
changes_needed
|
||||
}
|
||||
|
||||
/// Handles a path deletion: could be a page, a section, a folder
|
||||
fn delete_element(site: &mut Site, path: &Path, is_section: bool) -> Result<()> {
|
||||
{
|
||||
let mut library = site.library.write().unwrap();
|
||||
// Ignore the event if this path was not known
|
||||
if !library.contains_section(&path.to_path_buf())
|
||||
&& !library.contains_page(&path.to_path_buf())
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if is_section {
|
||||
if let Some(s) = library.remove_section(&path.to_path_buf()) {
|
||||
site.permalinks.remove(&s.file.relative);
|
||||
}
|
||||
} else if let Some(p) = library.remove_page(&path.to_path_buf()) {
|
||||
site.permalinks.remove(&p.file.relative);
|
||||
}
|
||||
}
|
||||
|
||||
// We might have delete the root _index.md so ensure we have at least the default one
|
||||
// before populating
|
||||
site.create_default_index_sections()?;
|
||||
site.populate_sections();
|
||||
site.populate_taxonomies()?;
|
||||
// Ensure we have our fn updated so it doesn't contain the permalink(s)/section/page deleted
|
||||
site.register_early_global_fns();
|
||||
site.register_tera_global_fns();
|
||||
// Deletion is something that doesn't happen all the time so we
|
||||
// don't need to optimise it too much
|
||||
site.build()
|
||||
}
|
||||
|
||||
/// Handles a `_index.md` (a section) being edited in some ways
|
||||
fn handle_section_editing(site: &mut Site, path: &Path) -> Result<()> {
|
||||
let section = Section::from_file(path, &site.config, &site.base_path)?;
|
||||
let pathbuf = path.to_path_buf();
|
||||
match site.add_section(section, true)? {
|
||||
// Updating a section
|
||||
Some(prev) => {
|
||||
site.populate_sections();
|
||||
site.process_images()?;
|
||||
{
|
||||
let library = site.library.read().unwrap();
|
||||
|
||||
if library.get_section(&pathbuf).unwrap().meta == prev.meta {
|
||||
// Front matter didn't change, only content did
|
||||
// so we render only the section page, not its pages
|
||||
return site.render_section(&library.get_section(&pathbuf).unwrap(), false);
|
||||
}
|
||||
}
|
||||
|
||||
// Front matter changed
|
||||
let changes = find_section_front_matter_changes(
|
||||
&site.library.read().unwrap().get_section(&pathbuf).unwrap().meta,
|
||||
&prev.meta,
|
||||
);
|
||||
for change in changes {
|
||||
// Sort always comes first if present so the rendering will be fine
|
||||
match change {
|
||||
SectionChangesNeeded::Sort => {
|
||||
site.register_tera_global_fns();
|
||||
}
|
||||
SectionChangesNeeded::Render => site.render_section(
|
||||
&site.library.read().unwrap().get_section(&pathbuf).unwrap(),
|
||||
false,
|
||||
)?,
|
||||
SectionChangesNeeded::RenderWithPages => site.render_section(
|
||||
&site.library.read().unwrap().get_section(&pathbuf).unwrap(),
|
||||
true,
|
||||
)?,
|
||||
// not a common enough operation to make it worth optimizing
|
||||
SectionChangesNeeded::Delete | SectionChangesNeeded::Transparent => {
|
||||
site.build()?;
|
||||
}
|
||||
};
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
// New section, only render that one
|
||||
None => {
|
||||
site.populate_sections();
|
||||
site.process_images()?;
|
||||
site.register_tera_global_fns();
|
||||
site.render_section(&site.library.read().unwrap().get_section(&pathbuf).unwrap(), true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! render_parent_sections {
|
||||
($site: expr, $path: expr) => {
|
||||
for s in $site.library.read().unwrap().find_parent_sections($path) {
|
||||
$site.render_section(s, false)?;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Handles a page being edited in some ways
|
||||
fn handle_page_editing(site: &mut Site, path: &Path) -> Result<()> {
|
||||
let page = Page::from_file(path, &site.config, &site.base_path)?;
|
||||
let pathbuf = path.to_path_buf();
|
||||
match site.add_page(page, true)? {
|
||||
// Updating a page
|
||||
Some(prev) => {
|
||||
site.populate_sections();
|
||||
site.populate_taxonomies()?;
|
||||
site.register_tera_global_fns();
|
||||
site.process_images()?;
|
||||
{
|
||||
let library = site.library.read().unwrap();
|
||||
|
||||
// Front matter didn't change, only content did
|
||||
if library.get_page(&pathbuf).unwrap().meta == prev.meta {
|
||||
// Other than the page itself, the summary might be seen
|
||||
// on a paginated list for a blog for example
|
||||
if library.get_page(&pathbuf).unwrap().summary.is_some() {
|
||||
render_parent_sections!(site, path);
|
||||
}
|
||||
return site.render_page(&library.get_page(&pathbuf).unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
// Front matter changed
|
||||
let changes = find_page_front_matter_changes(
|
||||
&site.library.read().unwrap().get_page(&pathbuf).unwrap().meta,
|
||||
&prev.meta,
|
||||
);
|
||||
|
||||
for change in changes {
|
||||
site.register_tera_global_fns();
|
||||
|
||||
// Sort always comes first if present so the rendering will be fine
|
||||
match change {
|
||||
PageChangesNeeded::Taxonomies => {
|
||||
site.populate_taxonomies()?;
|
||||
site.render_taxonomies()?;
|
||||
}
|
||||
PageChangesNeeded::Sort => {
|
||||
site.render_index()?;
|
||||
}
|
||||
PageChangesNeeded::Render => {
|
||||
render_parent_sections!(site, path);
|
||||
site.render_page(
|
||||
&site.library.read().unwrap().get_page(&path.to_path_buf()).unwrap(),
|
||||
)?;
|
||||
}
|
||||
};
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
// It's a new page!
|
||||
None => {
|
||||
site.populate_sections();
|
||||
site.populate_taxonomies()?;
|
||||
site.register_early_global_fns();
|
||||
site.register_tera_global_fns();
|
||||
site.process_images()?;
|
||||
// No need to optimise that yet, we can revisit if it becomes an issue
|
||||
site.build()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// What happens when we rename a file/folder in the content directory.
|
||||
/// Note that this is only called for folders when it isn't empty
|
||||
pub fn after_content_rename(site: &mut Site, old: &Path, new: &Path) -> Result<()> {
|
||||
let new_path = if new.is_dir() {
|
||||
if new.join("_index.md").exists() {
|
||||
// This is a section keep the dir folder to differentiate from renaming _index.md
|
||||
// which doesn't do the same thing
|
||||
new.to_path_buf()
|
||||
} else if new.join("index.md").exists() {
|
||||
new.join("index.md")
|
||||
} else {
|
||||
bail!("Got unexpected folder {:?} while handling renaming that was not expected", new);
|
||||
}
|
||||
} else {
|
||||
new.to_path_buf()
|
||||
};
|
||||
|
||||
// A section folder has been renamed: just reload the whole site and rebuild it as we
|
||||
// do not really know what needs to be rendered
|
||||
if new_path.is_dir() {
|
||||
site.load()?;
|
||||
return site.build();
|
||||
}
|
||||
|
||||
// We ignore renames on non-markdown files for now
|
||||
if let Some(ext) = new_path.extension() {
|
||||
if ext != "md" {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
// Renaming a file to _index.md, let the section editing do something and hope for the best
|
||||
if new_path.file_name().unwrap() == "_index.md" {
|
||||
// We aren't entirely sure where the original thing was so just try to delete whatever was
|
||||
// at the old path
|
||||
{
|
||||
let mut library = site.library.write().unwrap();
|
||||
library.remove_page(&old.to_path_buf());
|
||||
library.remove_section(&old.to_path_buf());
|
||||
}
|
||||
return handle_section_editing(site, &new_path);
|
||||
}
|
||||
|
||||
// If it is a page, just delete what was there before and
|
||||
// fake it's a new page
|
||||
let old_path = if new_path.file_name().unwrap() == "index.md" {
|
||||
old.join("index.md")
|
||||
} else {
|
||||
old.to_path_buf()
|
||||
};
|
||||
site.library.write().unwrap().remove_page(&old_path);
|
||||
|
||||
let ignored_content_globset = site.config.ignored_content_globset.clone();
|
||||
let is_ignored_file = match ignored_content_globset {
|
||||
Some(gs) => gs.is_match(new),
|
||||
None => false,
|
||||
};
|
||||
|
||||
if !is_ignored_file {
|
||||
return handle_page_editing(site, &new_path);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_section(path: &str, languages_codes: &[&str]) -> bool {
|
||||
if path == "_index.md" {
|
||||
return true;
|
||||
}
|
||||
|
||||
for language_code in languages_codes {
|
||||
let lang_section_string = format!("_index.{}.md", language_code);
|
||||
if path == lang_section_string {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// What happens when a section or a page is created/edited
|
||||
pub fn after_content_change(site: &mut Site, path: &Path) -> Result<()> {
|
||||
let is_section = {
|
||||
let languages_codes = site.config.languages_codes();
|
||||
is_section(path.file_name().unwrap().to_str().unwrap(), &languages_codes)
|
||||
};
|
||||
|
||||
let is_md = path.extension().unwrap() == "md";
|
||||
let index = path.parent().unwrap().join("index.md");
|
||||
|
||||
let mut potential_indices = vec![path.parent().unwrap().join("index.md")];
|
||||
for language in &site.config.languages {
|
||||
potential_indices.push(path.parent().unwrap().join(format!("index.{}.md", language.code)));
|
||||
}
|
||||
let colocated_index = potential_indices.contains(&path.to_path_buf());
|
||||
|
||||
// A few situations can happen:
|
||||
// 1. Change on .md files
|
||||
// a. Is there already an `index.md`? Return an error if it's something other than delete
|
||||
// b. Deleted? remove the element
|
||||
// c. Edited?
|
||||
// 1. filename is `_index.md`, this is a section
|
||||
// 1. it's a page otherwise
|
||||
// 2. Change on non .md files
|
||||
// a. Try to find a corresponding `_index.md`
|
||||
// 1. Nothing? Return Ok
|
||||
// 2. Something? Update the page
|
||||
if is_md {
|
||||
// only delete if it was able to be added in the first place
|
||||
if !index.exists() && !path.exists() {
|
||||
return delete_element(site, path, is_section);
|
||||
}
|
||||
|
||||
// Added another .md in a assets directory
|
||||
if index.exists() && path.exists() && !colocated_index {
|
||||
bail!(
|
||||
"Change on {:?} detected but only files named `index.md` with an optional language code are allowed",
|
||||
path.display()
|
||||
);
|
||||
} else if index.exists() && !path.exists() {
|
||||
// deleted the wrong .md, do nothing
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if is_section {
|
||||
handle_section_editing(site, path)
|
||||
} else {
|
||||
handle_page_editing(site, path)
|
||||
}
|
||||
} else if index.exists() {
|
||||
handle_page_editing(site, &index)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// What happens when a template is changed
|
||||
pub fn after_template_change(site: &mut Site, path: &Path) -> Result<()> {
|
||||
site.tera.full_reload()?;
|
||||
let filename = path.file_name().unwrap().to_str().unwrap();
|
||||
|
||||
match filename {
|
||||
"sitemap.xml" => site.render_sitemap(),
|
||||
filename if filename == site.config.feed_filename => {
|
||||
// FIXME: this is insufficient; for multilingual sites, it’s rendering the wrong
|
||||
// content into the root feed, and it’s not regenerating any of the other feeds (other
|
||||
// languages or taxonomies with feed enabled).
|
||||
site.render_feed(
|
||||
site.library.read().unwrap().pages_values(),
|
||||
None,
|
||||
&site.config.default_language,
|
||||
|c| c,
|
||||
)
|
||||
}
|
||||
"split_sitemap_index.xml" => site.render_sitemap(),
|
||||
"robots.txt" => site.render_robots(),
|
||||
"single.html" | "list.html" => site.render_taxonomies(),
|
||||
"page.html" => {
|
||||
site.render_sections()?;
|
||||
site.render_orphan_pages()
|
||||
}
|
||||
"section.html" => site.render_sections(),
|
||||
"404.html" => site.render_404(),
|
||||
// Either the index or some unknown template changed
|
||||
// We can't really know what this change affects so rebuild all
|
||||
// the things
|
||||
_ => {
|
||||
// If we are updating a shortcode, re-render the markdown of all pages/site
|
||||
// because we have no clue which one needs rebuilding
|
||||
// Same for the anchor-link template
|
||||
// TODO: look if there the shortcode is used in the markdown instead of re-rendering
|
||||
// everything
|
||||
if filename == "anchor-link.html"
|
||||
|| path.components().any(|x| x == Component::Normal("shortcodes".as_ref()))
|
||||
{
|
||||
site.render_markdown()?;
|
||||
}
|
||||
site.populate_sections();
|
||||
site.populate_taxonomies()?;
|
||||
site.render_sections()?;
|
||||
site.process_images()?;
|
||||
site.render_orphan_pages()?;
|
||||
site.render_taxonomies()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::{
|
||||
find_page_front_matter_changes, find_section_front_matter_changes, PageChangesNeeded,
|
||||
SectionChangesNeeded,
|
||||
};
|
||||
use front_matter::{PageFrontMatter, SectionFrontMatter, SortBy};
|
||||
|
||||
#[test]
|
||||
fn can_find_taxonomy_changes_in_page_frontmatter() {
|
||||
let mut taxonomies = HashMap::new();
|
||||
taxonomies.insert("tags".to_string(), vec!["a tag".to_string()]);
|
||||
let new = PageFrontMatter { taxonomies, ..PageFrontMatter::default() };
|
||||
let changes = find_page_front_matter_changes(&PageFrontMatter::default(), &new);
|
||||
assert_eq!(changes, vec![PageChangesNeeded::Taxonomies, PageChangesNeeded::Render]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_find_multiple_changes_in_page_frontmatter() {
|
||||
let mut taxonomies = HashMap::new();
|
||||
taxonomies.insert("categories".to_string(), vec!["a category".to_string()]);
|
||||
let current = PageFrontMatter { taxonomies, order: Some(1), ..PageFrontMatter::default() };
|
||||
let changes = find_page_front_matter_changes(¤t, &PageFrontMatter::default());
|
||||
assert_eq!(
|
||||
changes,
|
||||
vec![PageChangesNeeded::Taxonomies, PageChangesNeeded::Sort, PageChangesNeeded::Render]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_find_sort_changes_in_section_frontmatter() {
|
||||
let new = SectionFrontMatter { sort_by: SortBy::Date, ..SectionFrontMatter::default() };
|
||||
let changes = find_section_front_matter_changes(&SectionFrontMatter::default(), &new);
|
||||
assert_eq!(changes, vec![SectionChangesNeeded::Sort, SectionChangesNeeded::Render]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_find_render_changes_in_section_frontmatter() {
|
||||
let new = SectionFrontMatter { render: false, ..SectionFrontMatter::default() };
|
||||
let changes = find_section_front_matter_changes(&SectionFrontMatter::default(), &new);
|
||||
assert_eq!(changes, vec![SectionChangesNeeded::Delete]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_find_paginate_by_changes_in_section_frontmatter() {
|
||||
let new = SectionFrontMatter { paginate_by: Some(10), ..SectionFrontMatter::default() };
|
||||
let changes = find_section_front_matter_changes(&SectionFrontMatter::default(), &new);
|
||||
assert_eq!(changes, vec![SectionChangesNeeded::RenderWithPages]);
|
||||
}
|
||||
}
|
|
@ -1,284 +0,0 @@
|
|||
use std::env;
|
||||
use std::fs::{self, File};
|
||||
use std::io::prelude::*;
|
||||
|
||||
use fs_extra::dir;
|
||||
use site::Site;
|
||||
use tempfile::tempdir;
|
||||
|
||||
use rebuild::{after_content_change, after_content_rename};
|
||||
|
||||
// Loads the test_site in a tempdir and build it there
|
||||
// Returns (site_path_in_tempdir, site)
|
||||
macro_rules! load_and_build_site {
|
||||
($tmp_dir: expr, $site: expr) => {{
|
||||
let mut path =
|
||||
env::current_dir().unwrap().parent().unwrap().parent().unwrap().to_path_buf();
|
||||
path.push($site);
|
||||
let mut options = dir::CopyOptions::new();
|
||||
options.copy_inside = true;
|
||||
dir::copy(&path, &$tmp_dir, &options).unwrap();
|
||||
|
||||
let site_path = $tmp_dir.path().join($site);
|
||||
let config_file = site_path.join("config.toml");
|
||||
let mut site = Site::new(&site_path, &config_file).unwrap();
|
||||
site.load().unwrap();
|
||||
let public = &site_path.join("public");
|
||||
site.set_output_path(&public);
|
||||
site.build().unwrap();
|
||||
|
||||
(site_path, site)
|
||||
}};
|
||||
}
|
||||
|
||||
/// Replace the file at the path (starting from root) by the given content
|
||||
/// and return the file path that was modified
|
||||
macro_rules! edit_file {
|
||||
($site_path: expr, $path: expr, $content: expr) => {{
|
||||
let mut t = $site_path.clone();
|
||||
for c in $path.split('/') {
|
||||
t.push(c);
|
||||
}
|
||||
let mut file = File::create(&t).expect("Could not open/create file");
|
||||
file.write_all($content).expect("Could not write to the file");
|
||||
t
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! file_contains {
|
||||
($site_path: expr, $path: expr, $text: expr) => {{
|
||||
let mut path = $site_path.clone();
|
||||
for component in $path.split("/") {
|
||||
path.push(component);
|
||||
}
|
||||
let mut file = File::open(&path).unwrap();
|
||||
let mut s = String::new();
|
||||
file.read_to_string(&mut s).unwrap();
|
||||
println!("{:?} -> {}", path, s);
|
||||
s.contains($text)
|
||||
}};
|
||||
}
|
||||
|
||||
/// Rename a file or a folder to the new given name
|
||||
macro_rules! rename {
|
||||
($site_path: expr, $path: expr, $new_name: expr) => {{
|
||||
let mut t = $site_path.clone();
|
||||
for c in $path.split('/') {
|
||||
t.push(c);
|
||||
}
|
||||
let mut new_path = t.parent().unwrap().to_path_buf();
|
||||
new_path.push($new_name);
|
||||
fs::rename(&t, &new_path).unwrap();
|
||||
println!("Renamed {:?} to {:?}", t, new_path);
|
||||
(t, new_path)
|
||||
}};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_rebuild_after_simple_change_to_page_content() {
|
||||
let tmp_dir = tempdir().expect("create temp dir");
|
||||
let (site_path, mut site) = load_and_build_site!(tmp_dir, "test_site");
|
||||
let file_path = edit_file!(
|
||||
site_path,
|
||||
"content/rebuild/first.md",
|
||||
br#"
|
||||
+++
|
||||
title = "first"
|
||||
weight = 1
|
||||
date = 2017-01-01
|
||||
+++
|
||||
|
||||
Some content"#
|
||||
);
|
||||
|
||||
let res = after_content_change(&mut site, &file_path);
|
||||
assert!(res.is_ok());
|
||||
assert!(file_contains!(site_path, "public/rebuild/first/index.html", "<p>Some content</p>"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_rebuild_after_title_change_page_global_func_usage() {
|
||||
let tmp_dir = tempdir().expect("create temp dir");
|
||||
let (site_path, mut site) = load_and_build_site!(tmp_dir, "test_site");
|
||||
let file_path = edit_file!(
|
||||
site_path,
|
||||
"content/rebuild/first.md",
|
||||
br#"
|
||||
+++
|
||||
title = "Premier"
|
||||
weight = 10
|
||||
date = 2017-01-01
|
||||
+++
|
||||
|
||||
# A title"#
|
||||
);
|
||||
|
||||
let res = after_content_change(&mut site, &file_path);
|
||||
assert!(res.is_ok());
|
||||
assert!(file_contains!(site_path, "public/rebuild/index.html", "<h1>Premier</h1>"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_rebuild_after_sort_change_in_section() {
|
||||
let tmp_dir = tempdir().expect("create temp dir");
|
||||
let (site_path, mut site) = load_and_build_site!(tmp_dir, "test_site");
|
||||
let file_path = edit_file!(
|
||||
site_path,
|
||||
"content/rebuild/_index.md",
|
||||
br#"
|
||||
+++
|
||||
paginate_by = 1
|
||||
sort_by = "weight"
|
||||
template = "rebuild.html"
|
||||
+++
|
||||
"#
|
||||
);
|
||||
|
||||
let res = after_content_change(&mut site, &file_path);
|
||||
assert!(res.is_ok());
|
||||
assert!(file_contains!(
|
||||
site_path,
|
||||
"public/rebuild/index.html",
|
||||
"<h1>first</h1><h1>second</h1>"
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_rebuild_after_transparent_change() {
|
||||
let tmp_dir = tempdir().expect("create temp dir");
|
||||
let (site_path, mut site) = load_and_build_site!(tmp_dir, "test_site");
|
||||
let file_path = edit_file!(
|
||||
site_path,
|
||||
"content/posts/2018/_index.md",
|
||||
br#"
|
||||
+++
|
||||
transparent = false
|
||||
render = false
|
||||
+++
|
||||
"#
|
||||
);
|
||||
// Also remove pagination from posts section so we check whether the transparent page title
|
||||
// is there or not without dealing with pagination
|
||||
edit_file!(
|
||||
site_path,
|
||||
"content/posts/_index.md",
|
||||
br#"
|
||||
+++
|
||||
template = "section.html"
|
||||
insert_anchor_links = "left"
|
||||
+++
|
||||
"#
|
||||
);
|
||||
|
||||
let res = after_content_change(&mut site, &file_path);
|
||||
assert!(res.is_ok());
|
||||
assert!(!file_contains!(site_path, "public/posts/index.html", "A transparent page"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_rebuild_after_renaming_page() {
|
||||
let tmp_dir = tempdir().expect("create temp dir");
|
||||
let (site_path, mut site) = load_and_build_site!(tmp_dir, "test_site");
|
||||
let (old_path, new_path) = rename!(site_path, "content/posts/simple.md", "hard.md");
|
||||
|
||||
let res = after_content_rename(&mut site, &old_path, &new_path);
|
||||
println!("{:?}", res);
|
||||
assert!(res.is_ok());
|
||||
assert!(file_contains!(site_path, "public/posts/hard/index.html", "A simple page"));
|
||||
}
|
||||
|
||||
// https://github.com/Keats/gutenberg/issues/385
|
||||
#[test]
|
||||
fn can_rebuild_after_renaming_colocated_asset_folder() {
|
||||
let tmp_dir = tempdir().expect("create temp dir");
|
||||
let (site_path, mut site) = load_and_build_site!(tmp_dir, "test_site");
|
||||
let (old_path, new_path) =
|
||||
rename!(site_path, "content/posts/with-assets", "with-assets-updated");
|
||||
assert!(file_contains!(site_path, "content/posts/with-assets-updated/index.md", "Hello"));
|
||||
|
||||
let res = after_content_rename(&mut site, &old_path, &new_path);
|
||||
println!("{:?}", res);
|
||||
assert!(res.is_ok());
|
||||
assert!(file_contains!(
|
||||
site_path,
|
||||
"public/posts/with-assets-updated/index.html",
|
||||
"Hello world"
|
||||
));
|
||||
}
|
||||
|
||||
// https://github.com/Keats/gutenberg/issues/385
|
||||
#[test]
|
||||
fn can_rebuild_after_renaming_section_folder() {
|
||||
let tmp_dir = tempdir().expect("create temp dir");
|
||||
let (site_path, mut site) = load_and_build_site!(tmp_dir, "test_site");
|
||||
let (old_path, new_path) = rename!(site_path, "content/posts", "new-posts");
|
||||
assert!(file_contains!(site_path, "content/new-posts/simple.md", "simple"));
|
||||
|
||||
let res = after_content_rename(&mut site, &old_path, &new_path);
|
||||
assert!(res.is_ok());
|
||||
|
||||
assert!(file_contains!(site_path, "public/new-posts/simple/index.html", "simple"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_rebuild_after_renaming_non_md_asset_in_colocated_folder() {
|
||||
let tmp_dir = tempdir().expect("create temp dir");
|
||||
let (site_path, mut site) = load_and_build_site!(tmp_dir, "test_site");
|
||||
let (old_path, new_path) =
|
||||
rename!(site_path, "content/posts/with-assets/zola.png", "gutenberg.png");
|
||||
|
||||
// Testing that we don't try to load some images as markdown or something
|
||||
let res = after_content_rename(&mut site, &old_path, &new_path);
|
||||
assert!(res.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_rebuild_after_deleting_file() {
|
||||
let tmp_dir = tempdir().expect("create temp dir");
|
||||
let (site_path, mut site) = load_and_build_site!(tmp_dir, "test_site");
|
||||
let path = site_path.join("content").join("posts").join("fixed-slug.md");
|
||||
fs::remove_file(&path).unwrap();
|
||||
|
||||
let res = after_content_change(&mut site, &path);
|
||||
println!("{:?}", res);
|
||||
assert!(res.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_rebuild_after_editing_in_colocated_asset_folder_with_language() {
|
||||
let tmp_dir = tempdir().expect("create temp dir");
|
||||
let (site_path, mut site) = load_and_build_site!(tmp_dir, "test_site_i18n");
|
||||
let file_path = edit_file!(
|
||||
site_path,
|
||||
"content/blog/with-assets/index.fr.md",
|
||||
br#"
|
||||
+++
|
||||
date = 2018-11-11
|
||||
+++
|
||||
|
||||
Edite
|
||||
"#
|
||||
);
|
||||
|
||||
let res = after_content_change(&mut site, &file_path);
|
||||
println!("{:?}", res);
|
||||
assert!(res.is_ok());
|
||||
assert!(file_contains!(site_path, "public/fr/blog/with-assets/index.html", "Edite"));
|
||||
}
|
||||
|
||||
// https://github.com/getzola/zola/issues/620
|
||||
#[test]
|
||||
fn can_rebuild_after_renaming_section_and_deleting_file() {
|
||||
let tmp_dir = tempdir().expect("create temp dir");
|
||||
let (site_path, mut site) = load_and_build_site!(tmp_dir, "test_site");
|
||||
let (old_path, new_path) = rename!(site_path, "content/posts/", "post/");
|
||||
let res = after_content_rename(&mut site, &old_path, &new_path);
|
||||
assert!(res.is_ok());
|
||||
|
||||
let path = site_path.join("content").join("_index.md");
|
||||
fs::remove_file(&path).unwrap();
|
||||
|
||||
let res = after_content_change(&mut site, &path);
|
||||
println!("{:?}", res);
|
||||
assert!(res.is_ok());
|
||||
}
|
|
@ -443,7 +443,11 @@ Some body {{ hello() }}{%/* end */%}"#,
|
|||
#[test]
|
||||
fn shortcodes_that_emit_markdown() {
|
||||
let mut tera = Tera::default();
|
||||
tera.add_raw_template("shortcodes/youtube.md", "{% for i in [1,2,3] %}\n* {{ i }}\n{%- endfor %}").unwrap();
|
||||
tera.add_raw_template(
|
||||
"shortcodes/youtube.md",
|
||||
"{% for i in [1,2,3] %}\n* {{ i }}\n{%- endfor %}",
|
||||
)
|
||||
.unwrap();
|
||||
let res = render_shortcodes("{{ youtube() }}", &tera);
|
||||
assert_eq!(res, "* 1\n* 2\n* 3");
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ rayon = "1"
|
|||
serde = "1"
|
||||
serde_derive = "1"
|
||||
sass-rs = "0.2"
|
||||
lazy_static = "1.1"
|
||||
|
||||
errors = { path = "../errors" }
|
||||
config = { path = "../config" }
|
||||
|
|
|
@ -5,11 +5,12 @@ pub mod sitemap;
|
|||
pub mod tpls;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fs::{copy, remove_dir_all};
|
||||
use std::fs::remove_dir_all;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
|
||||
use glob::glob;
|
||||
use lazy_static::lazy_static;
|
||||
use rayon::prelude::*;
|
||||
use tera::{Context, Tera};
|
||||
|
||||
|
@ -18,10 +19,26 @@ use errors::{bail, Error, Result};
|
|||
use front_matter::InsertAnchor;
|
||||
use library::{find_taxonomies, Library, Page, Paginator, Section, Taxonomy};
|
||||
use templates::render_redirect_template;
|
||||
use utils::fs::{copy_directory, create_directory, create_file, ensure_directory_exists};
|
||||
use utils::fs::{
|
||||
copy_directory, copy_file_if_needed, create_directory, create_file, ensure_directory_exists,
|
||||
};
|
||||
use utils::net::get_available_port;
|
||||
use utils::templates::render_template;
|
||||
|
||||
lazy_static! {
|
||||
/// The in-memory rendered map content
|
||||
pub static ref SITE_CONTENT: Arc<RwLock<HashMap<String, String>>> = Arc::new(RwLock::new(HashMap::new()));
|
||||
}
|
||||
|
||||
/// Where are we building the site
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
pub enum BuildMode {
|
||||
/// On the filesystem -> `zola build`, The path is the `output_path`
|
||||
Disk,
|
||||
/// In memory for the content -> `zola serve`
|
||||
Memory,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Site {
|
||||
/// The base path of the zola site
|
||||
|
@ -43,6 +60,7 @@ pub struct Site {
|
|||
pub library: Arc<RwLock<Library>>,
|
||||
/// Whether to load draft pages
|
||||
include_drafts: bool,
|
||||
build_mode: BuildMode,
|
||||
}
|
||||
|
||||
impl Site {
|
||||
|
@ -65,6 +83,7 @@ impl Site {
|
|||
let static_path = path.join("static");
|
||||
let imageproc =
|
||||
imageproc::Processor::new(content_path.clone(), &static_path, &config.base_url);
|
||||
let output_path = path.join("public");
|
||||
|
||||
let site = Site {
|
||||
base_path: path.to_path_buf(),
|
||||
|
@ -72,7 +91,7 @@ impl Site {
|
|||
tera,
|
||||
imageproc: Arc::new(Mutex::new(imageproc)),
|
||||
live_reload: None,
|
||||
output_path: path.join("public"),
|
||||
output_path,
|
||||
content_path,
|
||||
static_path,
|
||||
taxonomies: Vec::new(),
|
||||
|
@ -80,11 +99,19 @@ impl Site {
|
|||
include_drafts: false,
|
||||
// We will allocate it properly later on
|
||||
library: Arc::new(RwLock::new(Library::new(0, 0, false))),
|
||||
build_mode: BuildMode::Disk,
|
||||
};
|
||||
|
||||
Ok(site)
|
||||
}
|
||||
|
||||
/// Enable some `zola serve` related options
|
||||
pub fn enable_serve_mode(&mut self) {
|
||||
SITE_CONTENT.write().unwrap().clear();
|
||||
self.config.enable_serve_mode();
|
||||
self.build_mode = BuildMode::Memory;
|
||||
}
|
||||
|
||||
/// Set the site to load the drafts.
|
||||
/// Needs to be called before loading it
|
||||
pub fn include_drafts(&mut self) {
|
||||
|
@ -111,6 +138,18 @@ impl Site {
|
|||
self.live_reload = get_available_port(port_to_avoid);
|
||||
}
|
||||
|
||||
/// Only used in `zola serve` to re-use the initial websocket port
|
||||
pub fn enable_live_reload_with_port(&mut self, live_reload_port: u16) {
|
||||
self.live_reload = Some(live_reload_port);
|
||||
}
|
||||
|
||||
/// Reloads the templates and rebuild the site without re-rendering the Markdown.
|
||||
pub fn reload_templates(&mut self) -> Result<()> {
|
||||
self.tera.full_reload()?;
|
||||
// TODO: be smarter than that, no need to recompile sass for example
|
||||
self.build()
|
||||
}
|
||||
|
||||
pub fn set_base_url(&mut self, base_url: String) {
|
||||
let mut imageproc = self.imageproc.lock().expect("Couldn't lock imageproc (set_base_url)");
|
||||
imageproc.set_base_url(&base_url);
|
||||
|
@ -203,10 +242,10 @@ impl Site {
|
|||
// taxonomy Tera fns are loaded in `register_early_global_fns`
|
||||
// so we do need to populate it first.
|
||||
self.populate_taxonomies()?;
|
||||
self.register_early_global_fns();
|
||||
tpls::register_early_global_fns(self);
|
||||
self.populate_sections();
|
||||
self.render_markdown()?;
|
||||
self.register_tera_global_fns();
|
||||
tpls::register_tera_global_fns(self);
|
||||
|
||||
// Needs to be done after rendering markdown as we only get the anchors at that point
|
||||
link_checking::check_internal_links_with_anchors(&self)?;
|
||||
|
@ -301,48 +340,58 @@ impl Site {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
// TODO: remove me in favour of the direct call to the fn once rebuild has changed
|
||||
pub fn register_early_global_fns(&mut self) {
|
||||
tpls::register_early_global_fns(self);
|
||||
}
|
||||
|
||||
// TODO: remove me in favour of the direct call to the fn once rebuild has changed
|
||||
pub fn register_tera_global_fns(&mut self) {
|
||||
tpls::register_tera_global_fns(self);
|
||||
}
|
||||
|
||||
/// 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 at the same path
|
||||
pub fn add_page(&mut self, mut page: Page, render: bool) -> Result<Option<Page>> {
|
||||
/// The `render` parameter is used in the serve command with --fast, when rebuilding a page.
|
||||
pub fn add_page(&mut self, mut page: Page, render_md: bool) -> Result<()> {
|
||||
self.permalinks.insert(page.file.relative.clone(), page.permalink.clone());
|
||||
if render {
|
||||
if render_md {
|
||||
let insert_anchor =
|
||||
self.find_parent_section_insert_anchor(&page.file.parent, &page.lang);
|
||||
page.render_markdown(&self.permalinks, &self.tera, &self.config, insert_anchor)?;
|
||||
}
|
||||
|
||||
let mut library = self.library.write().expect("Get lock for add_page");
|
||||
let prev = library.remove_page(&page.file.path);
|
||||
library.remove_page(&page.file.path);
|
||||
library.insert_page(page);
|
||||
|
||||
Ok(prev)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Adds a page to the site and render it
|
||||
/// Only used in `zola serve --fast`
|
||||
pub fn add_and_render_page(&mut self, path: &Path) -> Result<()> {
|
||||
let page = Page::from_file(path, &self.config, &self.base_path)?;
|
||||
self.add_page(page, true)?;
|
||||
self.populate_sections();
|
||||
self.populate_taxonomies()?;
|
||||
let library = self.library.read().unwrap();
|
||||
let page = library.get_page(&path).unwrap();
|
||||
self.render_page(&page)
|
||||
}
|
||||
|
||||
/// 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 section struct if there was one at the same path
|
||||
pub fn add_section(&mut self, mut section: Section, render: bool) -> Result<Option<Section>> {
|
||||
/// The `render` parameter is used in the serve command with --fast, when rebuilding a page.
|
||||
pub fn add_section(&mut self, mut section: Section, render_md: bool) -> Result<()> {
|
||||
self.permalinks.insert(section.file.relative.clone(), section.permalink.clone());
|
||||
if render {
|
||||
if render_md {
|
||||
section.render_markdown(&self.permalinks, &self.tera, &self.config)?;
|
||||
}
|
||||
let mut library = self.library.write().expect("Get lock for add_section");
|
||||
let prev = library.remove_section(§ion.file.path);
|
||||
library.remove_section(§ion.file.path);
|
||||
library.insert_section(section);
|
||||
|
||||
Ok(prev)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Adds a section to the site and render it
|
||||
/// Only used in `zola serve --fast`
|
||||
pub fn add_and_render_section(&mut self, path: &Path) -> Result<()> {
|
||||
let section = Section::from_file(path, &self.config, &self.base_path)?;
|
||||
self.add_section(section, true)?;
|
||||
self.populate_sections();
|
||||
let library = self.library.read().unwrap();
|
||||
let section = library.get_section(&path).unwrap();
|
||||
self.render_section(§ion, true)
|
||||
}
|
||||
|
||||
/// Finds the insert_anchor for the parent section of the directory at `path`.
|
||||
|
@ -437,32 +486,70 @@ impl Site {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Renders a single content page
|
||||
pub fn render_page(&self, page: &Page) -> Result<()> {
|
||||
/// Handles whether to write to disk or to memory
|
||||
pub fn write_content(
|
||||
&self,
|
||||
components: &[&str],
|
||||
filename: &str,
|
||||
content: String,
|
||||
create_dirs: bool,
|
||||
) -> Result<PathBuf> {
|
||||
let write_dirs = self.build_mode == BuildMode::Disk || create_dirs;
|
||||
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();
|
||||
|
||||
for component in page.path.split('/') {
|
||||
for component in components {
|
||||
current_path.push(component);
|
||||
|
||||
if !current_path.exists() {
|
||||
if !current_path.exists() && write_dirs {
|
||||
create_directory(¤t_path)?;
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the folder exists
|
||||
create_directory(¤t_path)?;
|
||||
if write_dirs {
|
||||
create_directory(¤t_path)?;
|
||||
}
|
||||
|
||||
// Finally, create a index.html file there with the page rendered
|
||||
match self.build_mode {
|
||||
BuildMode::Disk => {
|
||||
let end_path = current_path.join(filename);
|
||||
create_file(&end_path, &content)?;
|
||||
}
|
||||
BuildMode::Memory => {
|
||||
let path = if filename != "index.html" {
|
||||
let p = current_path.join(filename);
|
||||
p.as_os_str().to_string_lossy().replace("public/", "/")
|
||||
} else {
|
||||
// TODO" remove unwrap
|
||||
let p = current_path.strip_prefix("public").unwrap();
|
||||
p.as_os_str().to_string_lossy().into_owned()
|
||||
}
|
||||
.trim_end_matches('/')
|
||||
.to_owned();
|
||||
&SITE_CONTENT.write().unwrap().insert(path, content);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(current_path)
|
||||
}
|
||||
|
||||
fn copy_asset(&self, src: &Path, dest: &PathBuf) -> Result<()> {
|
||||
copy_file_if_needed(src, dest, self.config.hard_link_static)
|
||||
}
|
||||
|
||||
/// Renders a single content page
|
||||
pub fn render_page(&self, page: &Page) -> Result<()> {
|
||||
let output = page.render_html(&self.tera, &self.config, &self.library.read().unwrap())?;
|
||||
create_file(¤t_path.join("index.html"), &self.inject_livereload(output))?;
|
||||
let content = self.inject_livereload(output);
|
||||
let components: Vec<&str> = page.path.split('/').collect();
|
||||
let current_path =
|
||||
self.write_content(&components, "index.html", content, !page.assets.is_empty())?;
|
||||
|
||||
// Copy any asset we found previously into the same directory as the index.html
|
||||
for asset in &page.assets {
|
||||
let asset_path = asset.as_path();
|
||||
copy(
|
||||
self.copy_asset(
|
||||
&asset_path,
|
||||
¤t_path
|
||||
.join(asset_path.file_name().expect("Couldn't get filename from page asset")),
|
||||
|
@ -472,9 +559,12 @@ impl Site {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Deletes the `public` directory and builds the site
|
||||
/// Deletes the `public` directory (only for `zola build`) and builds the site
|
||||
pub fn build(&self) -> Result<()> {
|
||||
self.clean()?;
|
||||
// Do not clean on `zola serve` otherwise we end up copying assets all the time
|
||||
if self.build_mode == BuildMode::Disk {
|
||||
self.clean()?;
|
||||
}
|
||||
|
||||
// Generate/move all assets before rendering any content
|
||||
if let Some(ref theme) = self.config.theme {
|
||||
|
@ -537,6 +627,8 @@ impl Site {
|
|||
|
||||
pub fn build_search_index(&self) -> Result<()> {
|
||||
ensure_directory_exists(&self.output_path)?;
|
||||
// TODO: add those to the SITE_CONTENT map
|
||||
|
||||
// index first
|
||||
create_file(
|
||||
&self.output_path.join(&format!("search_index.{}.js", self.config.default_language)),
|
||||
|
@ -573,7 +665,6 @@ impl Site {
|
|||
}
|
||||
|
||||
fn render_alias(&self, alias: &str, permalink: &str) -> Result<()> {
|
||||
let mut output_path = self.output_path.to_path_buf();
|
||||
let mut split = alias.split('/').collect::<Vec<_>>();
|
||||
|
||||
// If the alias ends with an html file name, use that instead of mapping
|
||||
|
@ -586,19 +677,9 @@ impl Site {
|
|||
}
|
||||
None => "index.html",
|
||||
};
|
||||
|
||||
for component in split {
|
||||
output_path.push(&component);
|
||||
|
||||
if !output_path.exists() {
|
||||
create_directory(&output_path)?;
|
||||
}
|
||||
}
|
||||
|
||||
create_file(
|
||||
&output_path.join(page_name),
|
||||
&render_redirect_template(&permalink, &self.tera)?,
|
||||
)
|
||||
let content = render_redirect_template(&permalink, &self.tera)?;
|
||||
self.write_content(&split, page_name, content, false)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Renders all the aliases for each page/section: a magic HTML template that redirects to
|
||||
|
@ -625,7 +706,9 @@ impl Site {
|
|||
let mut context = Context::new();
|
||||
context.insert("config", &self.config);
|
||||
let output = render_template("404.html", &self.tera, context, &self.config.theme)?;
|
||||
create_file(&self.output_path.join("404.html"), &self.inject_livereload(output))
|
||||
let content = self.inject_livereload(output);
|
||||
self.write_content(&[], "404.html", content, false)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Renders robots.txt
|
||||
|
@ -633,10 +716,9 @@ impl Site {
|
|||
ensure_directory_exists(&self.output_path)?;
|
||||
let mut context = Context::new();
|
||||
context.insert("config", &self.config);
|
||||
create_file(
|
||||
&self.output_path.join("robots.txt"),
|
||||
&render_template("robots.txt", &self.tera, context, &self.config.theme)?,
|
||||
)
|
||||
let content = render_template("robots.txt", &self.tera, context, &self.config.theme)?;
|
||||
self.write_content(&[], "robots.txt", content, false)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Renders all taxonomies
|
||||
|
@ -654,33 +736,37 @@ impl Site {
|
|||
}
|
||||
|
||||
ensure_directory_exists(&self.output_path)?;
|
||||
let output_path = if taxonomy.kind.lang != self.config.default_language {
|
||||
let mid_path = self.output_path.join(&taxonomy.kind.lang);
|
||||
create_directory(&mid_path)?;
|
||||
mid_path.join(&taxonomy.kind.name)
|
||||
} else {
|
||||
self.output_path.join(&taxonomy.kind.name)
|
||||
};
|
||||
|
||||
let mut components = Vec::new();
|
||||
if taxonomy.kind.lang != self.config.default_language {
|
||||
components.push(taxonomy.kind.lang.as_ref());
|
||||
}
|
||||
|
||||
components.push(taxonomy.kind.name.as_ref());
|
||||
|
||||
let list_output =
|
||||
taxonomy.render_all_terms(&self.tera, &self.config, &self.library.read().unwrap())?;
|
||||
create_directory(&output_path)?;
|
||||
create_file(&output_path.join("index.html"), &self.inject_livereload(list_output))?;
|
||||
let content = self.inject_livereload(list_output);
|
||||
self.write_content(&components, "index.html", content, false)?;
|
||||
|
||||
let library = self.library.read().unwrap();
|
||||
taxonomy
|
||||
.items
|
||||
.par_iter()
|
||||
.map(|item| {
|
||||
let path = output_path.join(&item.slug);
|
||||
let mut comp = components.clone();
|
||||
comp.push(&item.slug);
|
||||
|
||||
if taxonomy.kind.is_paginated() {
|
||||
self.render_paginated(
|
||||
&path,
|
||||
comp.clone(),
|
||||
&Paginator::from_taxonomy(&taxonomy, item, &library),
|
||||
)?;
|
||||
} else {
|
||||
let single_output =
|
||||
taxonomy.render_term(item, &self.tera, &self.config, &library)?;
|
||||
create_directory(&path)?;
|
||||
create_file(&path.join("index.html"), &self.inject_livereload(single_output))?;
|
||||
let content = self.inject_livereload(single_output);
|
||||
self.write_content(&comp, "index.html", content, false)?;
|
||||
}
|
||||
|
||||
if taxonomy.kind.feed {
|
||||
|
@ -719,8 +805,8 @@ impl Site {
|
|||
// Create single sitemap
|
||||
let mut context = Context::new();
|
||||
context.insert("entries", &all_sitemap_entries);
|
||||
let sitemap = &render_template("sitemap.xml", &self.tera, context, &self.config.theme)?;
|
||||
create_file(&self.output_path.join("sitemap.xml"), sitemap)?;
|
||||
let sitemap = render_template("sitemap.xml", &self.tera, context, &self.config.theme)?;
|
||||
self.write_content(&[], "sitemap.xml", sitemap, false)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
@ -731,10 +817,10 @@ impl Site {
|
|||
{
|
||||
let mut context = Context::new();
|
||||
context.insert("entries", &chunk);
|
||||
let sitemap = &render_template("sitemap.xml", &self.tera, context, &self.config.theme)?;
|
||||
let sitemap = render_template("sitemap.xml", &self.tera, context, &self.config.theme)?;
|
||||
let file_name = format!("sitemap{}.xml", i + 1);
|
||||
create_file(&self.output_path.join(&file_name), sitemap)?;
|
||||
let mut sitemap_url: String = self.config.make_permalink(&file_name);
|
||||
self.write_content(&[], &file_name, sitemap, false)?;
|
||||
let mut sitemap_url = self.config.make_permalink(&file_name);
|
||||
sitemap_url.pop(); // Remove trailing slash
|
||||
sitemap_index.push(sitemap_url);
|
||||
}
|
||||
|
@ -742,13 +828,13 @@ impl Site {
|
|||
// Create main sitemap that reference numbered sitemaps
|
||||
let mut main_context = Context::new();
|
||||
main_context.insert("sitemaps", &sitemap_index);
|
||||
let sitemap = &render_template(
|
||||
let sitemap = render_template(
|
||||
"split_sitemap_index.xml",
|
||||
&self.tera,
|
||||
main_context,
|
||||
&self.config.theme,
|
||||
)?;
|
||||
create_file(&self.output_path.join("sitemap.xml"), sitemap)?;
|
||||
self.write_content(&[], "sitemap.xml", sitemap, false)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -773,16 +859,19 @@ impl Site {
|
|||
let feed_filename = &self.config.feed_filename;
|
||||
|
||||
if let Some(ref base) = base_path {
|
||||
let mut output_path = self.output_path.clone();
|
||||
let mut components = Vec::new();
|
||||
for component in base.components() {
|
||||
output_path.push(component);
|
||||
if !output_path.exists() {
|
||||
create_directory(&output_path)?;
|
||||
}
|
||||
// TODO: avoid cloning the paths
|
||||
components.push(component.as_os_str().to_string_lossy().as_ref().to_string());
|
||||
}
|
||||
create_file(&output_path.join(feed_filename), &feed)?;
|
||||
self.write_content(
|
||||
&components.iter().map(|x| x.as_ref()).collect::<Vec<_>>(),
|
||||
&feed_filename,
|
||||
feed,
|
||||
false,
|
||||
)?;
|
||||
} else {
|
||||
create_file(&self.output_path.join(feed_filename), &feed)?;
|
||||
self.write_content(&[], &feed_filename, feed, false)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -791,18 +880,23 @@ impl Site {
|
|||
pub fn render_section(&self, section: &Section, render_pages: bool) -> Result<()> {
|
||||
ensure_directory_exists(&self.output_path)?;
|
||||
let mut output_path = self.output_path.clone();
|
||||
let mut components: Vec<&str> = Vec::new();
|
||||
let create_directories = self.build_mode == BuildMode::Disk || !section.assets.is_empty();
|
||||
|
||||
if section.lang != self.config.default_language {
|
||||
components.push(§ion.lang);
|
||||
output_path.push(§ion.lang);
|
||||
if !output_path.exists() {
|
||||
|
||||
if !output_path.exists() && create_directories {
|
||||
create_directory(&output_path)?;
|
||||
}
|
||||
}
|
||||
|
||||
for component in §ion.file.components {
|
||||
components.push(component);
|
||||
output_path.push(component);
|
||||
|
||||
if !output_path.exists() {
|
||||
if !output_path.exists() && create_directories {
|
||||
create_directory(&output_path)?;
|
||||
}
|
||||
}
|
||||
|
@ -810,7 +904,7 @@ impl Site {
|
|||
// Copy any asset we found previously into the same directory as the index.html
|
||||
for asset in §ion.assets {
|
||||
let asset_path = asset.as_path();
|
||||
copy(
|
||||
self.copy_asset(
|
||||
&asset_path,
|
||||
&output_path.join(
|
||||
asset_path.file_name().expect("Failed to get asset filename for section"),
|
||||
|
@ -832,41 +926,31 @@ impl Site {
|
|||
|
||||
if let Some(ref redirect_to) = section.meta.redirect_to {
|
||||
let permalink = self.config.make_permalink(redirect_to);
|
||||
create_file(
|
||||
&output_path.join("index.html"),
|
||||
&render_redirect_template(&permalink, &self.tera)?,
|
||||
self.write_content(
|
||||
&components,
|
||||
"index.html",
|
||||
render_redirect_template(&permalink, &self.tera)?,
|
||||
create_directories,
|
||||
)?;
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if section.meta.is_paginated() {
|
||||
self.render_paginated(
|
||||
&output_path,
|
||||
components,
|
||||
&Paginator::from_section(§ion, &self.library.read().unwrap()),
|
||||
)?;
|
||||
} else {
|
||||
let output =
|
||||
section.render_html(&self.tera, &self.config, &self.library.read().unwrap())?;
|
||||
create_file(&output_path.join("index.html"), &self.inject_livereload(output))?;
|
||||
let content = self.inject_livereload(output);
|
||||
self.write_content(&components, "index.html", content, false)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// TODO: remove me when reload has changed
|
||||
/// Used only on reload
|
||||
pub fn render_index(&self) -> Result<()> {
|
||||
self.render_section(
|
||||
&self
|
||||
.library
|
||||
.read()
|
||||
.unwrap()
|
||||
.get_section(&self.content_path.join("_index.md"))
|
||||
.expect("Failed to get index section"),
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
/// Renders all sections
|
||||
pub fn render_sections(&self) -> Result<()> {
|
||||
self.library
|
||||
|
@ -890,33 +974,43 @@ impl Site {
|
|||
}
|
||||
|
||||
/// Renders a list of pages when the section/index is wanting pagination.
|
||||
pub fn render_paginated(&self, output_path: &Path, paginator: &Paginator) -> Result<()> {
|
||||
pub fn render_paginated<'a>(
|
||||
&self,
|
||||
components: Vec<&'a str>,
|
||||
paginator: &'a Paginator,
|
||||
) -> Result<()> {
|
||||
ensure_directory_exists(&self.output_path)?;
|
||||
|
||||
let folder_path = output_path.join(&paginator.paginate_path);
|
||||
create_directory(&folder_path)?;
|
||||
let index_components = components.clone();
|
||||
|
||||
paginator
|
||||
.pagers
|
||||
.par_iter()
|
||||
.map(|pager| {
|
||||
let page_path = folder_path.join(&format!("{}", pager.index));
|
||||
create_directory(&page_path)?;
|
||||
let mut pager_components = index_components.clone();
|
||||
pager_components.push(&paginator.paginate_path);
|
||||
let pager_path = format!("{}", pager.index);
|
||||
pager_components.push(&pager_path);
|
||||
let output = paginator.render_pager(
|
||||
pager,
|
||||
&self.config,
|
||||
&self.tera,
|
||||
&self.library.read().unwrap(),
|
||||
)?;
|
||||
let content = self.inject_livereload(output);
|
||||
|
||||
if pager.index > 1 {
|
||||
create_file(&page_path.join("index.html"), &self.inject_livereload(output))?;
|
||||
self.write_content(&pager_components, "index.html", content, false)?;
|
||||
} else {
|
||||
create_file(&output_path.join("index.html"), &self.inject_livereload(output))?;
|
||||
create_file(
|
||||
&page_path.join("index.html"),
|
||||
&render_redirect_template(&paginator.permalink, &self.tera)?,
|
||||
self.write_content(&index_components, "index.html", content, false)?;
|
||||
self.write_content(
|
||||
&pager_components,
|
||||
"index.html",
|
||||
render_redirect_template(&paginator.permalink, &self.tera)?,
|
||||
false,
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.collect::<Result<()>>()
|
||||
|
|
|
@ -96,10 +96,6 @@ pub fn find_related_assets(path: &Path) -> Vec<PathBuf> {
|
|||
|
||||
/// Copy a file but takes into account where to start the copy as
|
||||
/// there might be folders we need to create on the way.
|
||||
/// No copy occurs if all of the following conditions are satisfied:
|
||||
/// 1. A file with the same name already exists in the dest path.
|
||||
/// 2. Its modification timestamp is identical to that of the src file.
|
||||
/// 3. Its filesize is identical to that of the src file.
|
||||
pub fn copy_file(src: &Path, dest: &PathBuf, base_path: &PathBuf, hard_link: bool) -> Result<()> {
|
||||
let relative_path = src.strip_prefix(base_path).unwrap();
|
||||
let target_path = dest.join(relative_path);
|
||||
|
@ -108,21 +104,33 @@ pub fn copy_file(src: &Path, dest: &PathBuf, base_path: &PathBuf, hard_link: boo
|
|||
create_dir_all(parent_directory)?;
|
||||
}
|
||||
|
||||
copy_file_if_needed(src, &target_path, hard_link)
|
||||
}
|
||||
|
||||
/// No copy occurs if all of the following conditions are satisfied:
|
||||
/// 1. A file with the same name already exists in the dest path.
|
||||
/// 2. Its modification timestamp is identical to that of the src file.
|
||||
/// 3. Its filesize is identical to that of the src file.
|
||||
pub fn copy_file_if_needed(src: &Path, dest: &PathBuf, hard_link: bool) -> Result<()> {
|
||||
if let Some(parent_directory) = dest.parent() {
|
||||
create_dir_all(parent_directory)?;
|
||||
}
|
||||
|
||||
if hard_link {
|
||||
std::fs::hard_link(src, target_path)?
|
||||
std::fs::hard_link(src, dest)?
|
||||
} else {
|
||||
let src_metadata = metadata(src)?;
|
||||
let src_mtime = FileTime::from_last_modification_time(&src_metadata);
|
||||
if Path::new(&target_path).is_file() {
|
||||
let target_metadata = metadata(&target_path)?;
|
||||
if Path::new(&dest).is_file() {
|
||||
let target_metadata = metadata(&dest)?;
|
||||
let target_mtime = FileTime::from_last_modification_time(&target_metadata);
|
||||
if !(src_mtime == target_mtime && src_metadata.len() == target_metadata.len()) {
|
||||
copy(src, &target_path)?;
|
||||
set_file_mtime(&target_path, src_mtime)?;
|
||||
copy(src, &dest)?;
|
||||
set_file_mtime(&dest, src_mtime)?;
|
||||
}
|
||||
} else {
|
||||
copy(src, &target_path)?;
|
||||
set_file_mtime(&target_path, src_mtime)?;
|
||||
copy(src, &dest)?;
|
||||
set_file_mtime(&dest, src_mtime)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
|
|
@ -2,6 +2,6 @@
|
|||
title = "Getting Started"
|
||||
weight = 1
|
||||
sort_by = "weight"
|
||||
redirect_to = "documentation/getting-started/installation"
|
||||
redirect_to = "documentation/getting-started/overview"
|
||||
insert_anchor_links = "left"
|
||||
+++
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
+++
|
||||
title = "CLI usage"
|
||||
weight = 2
|
||||
weight = 15
|
||||
+++
|
||||
|
||||
Zola only has 4 commands: `init`, `build`, `serve` and `check`.
|
||||
|
|
|
@ -90,6 +90,11 @@ pub fn build_cli() -> App<'static, 'static> {
|
|||
.long("open")
|
||||
.takes_value(false)
|
||||
.help("Open site in the default browser"),
|
||||
Arg::with_name("fast")
|
||||
.short("f")
|
||||
.long("fast")
|
||||
.takes_value(false)
|
||||
.help("Only rebuild the minimum on change - useful when working on a specific page/section"),
|
||||
]),
|
||||
SubCommand::with_name("check")
|
||||
.about("Try building the project without rendering it. Checks links")
|
||||
|
|
246
src/cmd/serve.rs
246
src/cmd/serve.rs
|
@ -21,7 +21,6 @@
|
|||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fs::{read_dir, remove_dir_all};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::mpsc::channel;
|
||||
|
@ -32,8 +31,6 @@ use hyper::header;
|
|||
use hyper::service::{make_service_fn, service_fn};
|
||||
use hyper::{Body, Method, Request, Response, Server, StatusCode};
|
||||
use hyper_staticfile::ResolveResult;
|
||||
use lazy_static::lazy_static;
|
||||
use tokio::io::AsyncReadExt;
|
||||
|
||||
use chrono::prelude::*;
|
||||
use notify::{watcher, RecursiveMode, Watcher};
|
||||
|
@ -42,10 +39,11 @@ use ws::{Message, Sender, WebSocket};
|
|||
use errors::{Error as ZolaError, Result};
|
||||
use globset::GlobSet;
|
||||
use site::sass::compile_sass;
|
||||
use site::Site;
|
||||
use site::{Site, SITE_CONTENT};
|
||||
use utils::fs::copy_file;
|
||||
|
||||
use crate::console;
|
||||
use std::ffi::OsStr;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum ChangeKind {
|
||||
|
@ -61,28 +59,19 @@ enum ChangeKind {
|
|||
enum WatchMode {
|
||||
Required,
|
||||
Optional,
|
||||
Condition(bool)
|
||||
Condition(bool),
|
||||
}
|
||||
|
||||
static INTERNAL_SERVER_ERROR_TEXT: &[u8] = b"Internal Server Error";
|
||||
static METHOD_NOT_ALLOWED_TEXT: &[u8] = b"Method Not Allowed";
|
||||
static NOT_FOUND_TEXT: &[u8] = b"Not Found";
|
||||
|
||||
// This is dist/livereload.min.js from the LiveReload.js v3.2.4 release
|
||||
const LIVE_RELOAD: &str = include_str!("livereload.js");
|
||||
|
||||
lazy_static! {
|
||||
pub static ref SITE_DATA: HashMap<String, String> = {
|
||||
let mut m = HashMap::new();
|
||||
m.insert("/hello".to_string(), "ho".to_string());
|
||||
m
|
||||
};
|
||||
}
|
||||
|
||||
async fn handle_request(req: Request<Body>, root: PathBuf) -> Result<Response<Body>> {
|
||||
let path = req.uri().path();
|
||||
let path = req.uri().path().trim_end_matches('/').trim_start_matches('/');
|
||||
// livereload.js is served using the LIVE_RELOAD str, not a file
|
||||
if path == "/livereload.js" {
|
||||
if path == "livereload.js" {
|
||||
if req.method() == Method::GET {
|
||||
return Ok(livereload_js());
|
||||
} else {
|
||||
|
@ -90,7 +79,7 @@ async fn handle_request(req: Request<Body>, root: PathBuf) -> Result<Response<Bo
|
|||
}
|
||||
}
|
||||
|
||||
if let Some(content) = SITE_DATA.get(path) {
|
||||
if let Some(content) = SITE_CONTENT.read().unwrap().get(path) {
|
||||
return Ok(in_memory_html(content));
|
||||
}
|
||||
|
||||
|
@ -98,7 +87,8 @@ async fn handle_request(req: Request<Body>, root: PathBuf) -> Result<Response<Bo
|
|||
match result {
|
||||
ResolveResult::MethodNotMatched => return Ok(method_not_allowed()),
|
||||
ResolveResult::NotFound | ResolveResult::UriNotMatched => {
|
||||
return Ok(not_found(Path::new(&root.join("404.html"))).await)
|
||||
let content_404 = SITE_CONTENT.read().unwrap().get("404.html").map(|x| x.clone());
|
||||
return Ok(not_found(content_404));
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
|
@ -122,14 +112,6 @@ fn in_memory_html(content: &str) -> Response<Body> {
|
|||
.expect("Could not build HTML response")
|
||||
}
|
||||
|
||||
fn internal_server_error() -> Response<Body> {
|
||||
Response::builder()
|
||||
.header(header::CONTENT_TYPE, "text/plain")
|
||||
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.body(INTERNAL_SERVER_ERROR_TEXT.into())
|
||||
.expect("Could not build Internal Server Error response")
|
||||
}
|
||||
|
||||
fn method_not_allowed() -> Response<Body> {
|
||||
Response::builder()
|
||||
.header(header::CONTENT_TYPE, "text/plain")
|
||||
|
@ -138,21 +120,16 @@ fn method_not_allowed() -> Response<Body> {
|
|||
.expect("Could not build Method Not Allowed response")
|
||||
}
|
||||
|
||||
async fn not_found(page_path: &Path) -> Response<Body> {
|
||||
if let Ok(mut file) = tokio::fs::File::open(page_path).await {
|
||||
let mut buf = Vec::new();
|
||||
if file.read_to_end(&mut buf).await.is_ok() {
|
||||
return Response::builder()
|
||||
.header(header::CONTENT_TYPE, "text/html")
|
||||
.status(StatusCode::NOT_FOUND)
|
||||
.body(buf.into())
|
||||
.expect("Could not build Not Found response");
|
||||
}
|
||||
|
||||
return internal_server_error();
|
||||
fn not_found(content: Option<String>) -> Response<Body> {
|
||||
if let Some(body) = content {
|
||||
return Response::builder()
|
||||
.header(header::CONTENT_TYPE, "text/html")
|
||||
.status(StatusCode::NOT_FOUND)
|
||||
.body(body.into())
|
||||
.expect("Could not build Not Found response");
|
||||
}
|
||||
|
||||
// Use a plain text response when page_path isn't available
|
||||
// Use a plain text response when we can't find the body of the 404
|
||||
Response::builder()
|
||||
.header(header::CONTENT_TYPE, "text/plain")
|
||||
.status(StatusCode::NOT_FOUND)
|
||||
|
@ -187,30 +164,35 @@ fn rebuild_done_handling(broadcaster: &Option<Sender>, res: Result<()>, reload_p
|
|||
fn create_new_site(
|
||||
root_dir: &Path,
|
||||
interface: &str,
|
||||
port: u16,
|
||||
interface_port: u16,
|
||||
output_dir: &Path,
|
||||
base_url: &str,
|
||||
config_file: &Path,
|
||||
include_drafts: bool,
|
||||
ws_port: Option<u16>,
|
||||
) -> Result<(Site, String)> {
|
||||
let mut site = Site::new(root_dir, config_file)?;
|
||||
|
||||
let base_address = format!("{}:{}", base_url, port);
|
||||
let address = format!("{}:{}", interface, port);
|
||||
let base_address = format!("{}:{}", base_url, interface_port);
|
||||
let address = format!("{}:{}", interface, interface_port);
|
||||
let base_url = if site.config.base_url.ends_with('/') {
|
||||
format!("http://{}/", base_address)
|
||||
} else {
|
||||
format!("http://{}", base_address)
|
||||
};
|
||||
|
||||
site.config.enable_serve_mode();
|
||||
site.enable_serve_mode();
|
||||
site.set_base_url(base_url);
|
||||
site.set_output_path(output_dir);
|
||||
if include_drafts {
|
||||
site.include_drafts();
|
||||
}
|
||||
site.load()?;
|
||||
site.enable_live_reload(port);
|
||||
if let Some(p) = ws_port {
|
||||
site.enable_live_reload_with_port(p);
|
||||
} else {
|
||||
site.enable_live_reload(interface_port);
|
||||
}
|
||||
console::notify_site_size(&site);
|
||||
console::warn_about_ignored_pages(&site);
|
||||
site.build()?;
|
||||
|
@ -220,36 +202,38 @@ fn create_new_site(
|
|||
pub fn serve(
|
||||
root_dir: &Path,
|
||||
interface: &str,
|
||||
port: u16,
|
||||
interface_port: u16,
|
||||
output_dir: &Path,
|
||||
base_url: &str,
|
||||
config_file: &Path,
|
||||
watch_only: bool,
|
||||
open: bool,
|
||||
include_drafts: bool,
|
||||
fast_rebuild: bool,
|
||||
) -> Result<()> {
|
||||
let start = Instant::now();
|
||||
let (mut site, address) = create_new_site(
|
||||
root_dir,
|
||||
interface,
|
||||
port,
|
||||
interface_port,
|
||||
output_dir,
|
||||
base_url,
|
||||
config_file,
|
||||
include_drafts,
|
||||
None,
|
||||
)?;
|
||||
console::report_elapsed_time(start);
|
||||
|
||||
// An array of (path, bool, bool) where the path should be watched for changes, and the boolean value
|
||||
// indicates whether this file/folder must exist for zola serve to operate
|
||||
let watch_this = vec!(
|
||||
let watch_this = vec![
|
||||
("config.toml", WatchMode::Required),
|
||||
("content", WatchMode::Required),
|
||||
("sass", WatchMode::Condition(site.config.compile_sass)),
|
||||
("static", WatchMode::Optional),
|
||||
("templates", WatchMode::Optional),
|
||||
("themes", WatchMode::Condition(site.config.theme.is_some()))
|
||||
);
|
||||
("themes", WatchMode::Condition(site.config.theme.is_some())),
|
||||
];
|
||||
|
||||
// Setup watchers
|
||||
let (tx, rx) = channel();
|
||||
|
@ -266,7 +250,7 @@ pub fn serve(
|
|||
let should_watch = match mode {
|
||||
WatchMode::Required => true,
|
||||
WatchMode::Optional => watch_path.exists(),
|
||||
WatchMode::Condition(b) => b
|
||||
WatchMode::Condition(b) => b,
|
||||
};
|
||||
if should_watch {
|
||||
watcher
|
||||
|
@ -276,7 +260,8 @@ pub fn serve(
|
|||
}
|
||||
}
|
||||
|
||||
let ws_address = format!("{}:{}", interface, site.live_reload.unwrap());
|
||||
let ws_port = site.live_reload;
|
||||
let ws_address = format!("{}:{}", interface, ws_port.unwrap());
|
||||
let output_path = Path::new(output_dir).to_path_buf();
|
||||
|
||||
// output path is going to need to be moved later on, so clone it for the
|
||||
|
@ -344,11 +329,7 @@ pub fn serve(
|
|||
None
|
||||
};
|
||||
|
||||
println!(
|
||||
"Listening for changes in {}{{{}}}",
|
||||
root_dir.display(),
|
||||
watchers.join(", ")
|
||||
);
|
||||
println!("Listening for changes in {}{{{}}}", root_dir.display(), watchers.join(", "));
|
||||
|
||||
println!("Press Ctrl+C to stop\n");
|
||||
// Delete the output folder on ctrl+C
|
||||
|
@ -363,17 +344,6 @@ pub fn serve(
|
|||
|
||||
use notify::DebouncedEvent::*;
|
||||
|
||||
let reload_templates = |site: &mut Site, path: &Path| {
|
||||
let msg = if path.is_dir() {
|
||||
format!("-> Directory in `templates` folder changed {}", path.display())
|
||||
} else {
|
||||
format!("-> Template changed {}", path.display())
|
||||
};
|
||||
console::info(&msg);
|
||||
// Force refresh
|
||||
rebuild_done_handling(&broadcaster, rebuild::after_template_change(site, &path), "/x.js");
|
||||
};
|
||||
|
||||
let reload_sass = |site: &Site, path: &Path, partial_path: &Path| {
|
||||
let msg = if path.is_dir() {
|
||||
format!("-> Directory in `sass` folder changed {}", path.display())
|
||||
|
@ -388,6 +358,10 @@ pub fn serve(
|
|||
);
|
||||
};
|
||||
|
||||
let reload_templates = |site: &mut Site, path: &Path| {
|
||||
rebuild_done_handling(&broadcaster, site.reload_templates(), &path.to_string_lossy());
|
||||
};
|
||||
|
||||
let copy_static = |site: &Site, path: &Path, partial_path: &Path| {
|
||||
// Do nothing if the file/dir was deleted
|
||||
if !path.exists() {
|
||||
|
@ -424,11 +398,12 @@ pub fn serve(
|
|||
let recreate_site = || match create_new_site(
|
||||
root_dir,
|
||||
interface,
|
||||
port,
|
||||
interface_port,
|
||||
output_dir,
|
||||
base_url,
|
||||
config_file,
|
||||
include_drafts,
|
||||
ws_port,
|
||||
) {
|
||||
Ok((s, _)) => {
|
||||
rebuild_done_handling(&broadcaster, Ok(()), "/x.js");
|
||||
|
@ -443,66 +418,25 @@ pub fn serve(
|
|||
loop {
|
||||
match rx.recv() {
|
||||
Ok(event) => {
|
||||
let can_do_fast_reload = match event {
|
||||
Remove(_) => false,
|
||||
_ => true,
|
||||
};
|
||||
|
||||
match event {
|
||||
Rename(old_path, path) => {
|
||||
if path.is_file() && is_temp_file(&path) {
|
||||
continue;
|
||||
}
|
||||
let (change_kind, partial_path) = detect_change_kind(&root_dir, &path);
|
||||
|
||||
// We only care about changes in non-empty folders
|
||||
if path.is_dir() && is_folder_empty(&path) {
|
||||
continue;
|
||||
}
|
||||
|
||||
println!(
|
||||
"Change detected @ {}",
|
||||
Local::now().format("%Y-%m-%d %H:%M:%S").to_string()
|
||||
);
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
match change_kind {
|
||||
ChangeKind::Content => {
|
||||
console::info(&format!("-> Content renamed {}", path.display()));
|
||||
// Force refresh
|
||||
rebuild_done_handling(
|
||||
&broadcaster,
|
||||
rebuild::after_content_rename(&mut site, &old_path, &path),
|
||||
"/x.js",
|
||||
);
|
||||
}
|
||||
ChangeKind::Templates => reload_templates(&mut site, &path),
|
||||
ChangeKind::StaticFiles => copy_static(&site, &path, &partial_path),
|
||||
ChangeKind::Sass => reload_sass(&site, &path, &partial_path),
|
||||
ChangeKind::Themes => {
|
||||
console::info(
|
||||
"-> Themes changed. The whole site will be reloaded.",
|
||||
);
|
||||
|
||||
if let Some(s) = recreate_site() {
|
||||
site = s;
|
||||
}
|
||||
}
|
||||
ChangeKind::Config => {
|
||||
console::info("-> Config changed. The whole site will be reloaded. The browser needs to be refreshed to make the changes visible.");
|
||||
|
||||
if let Some(s) = recreate_site() {
|
||||
site = s;
|
||||
}
|
||||
}
|
||||
}
|
||||
console::report_elapsed_time(start);
|
||||
}
|
||||
// Intellij does weird things on edit, chmod is there to count those changes
|
||||
// https://github.com/passcod/notify/issues/150#issuecomment-494912080
|
||||
Create(path) | Write(path) | Remove(path) | Chmod(path) => {
|
||||
Rename(_, path) | Create(path) | Write(path) | Remove(path) | Chmod(path) => {
|
||||
if is_ignored_file(&site.config.ignored_content_globset, &path) {
|
||||
continue;
|
||||
}
|
||||
if is_temp_file(&path) || path.is_dir() {
|
||||
continue;
|
||||
}
|
||||
// We only care about changes in non-empty folders
|
||||
if path.is_dir() && is_folder_empty(&path) {
|
||||
continue;
|
||||
}
|
||||
|
||||
println!(
|
||||
"Change detected @ {}",
|
||||
|
@ -513,27 +447,79 @@ pub fn serve(
|
|||
match detect_change_kind(&root_dir, &path) {
|
||||
(ChangeKind::Content, _) => {
|
||||
console::info(&format!("-> Content changed {}", path.display()));
|
||||
// Force refresh
|
||||
rebuild_done_handling(
|
||||
&broadcaster,
|
||||
rebuild::after_content_change(&mut site, &path),
|
||||
"/x.js",
|
||||
);
|
||||
|
||||
if fast_rebuild {
|
||||
if can_do_fast_reload {
|
||||
let filename = path
|
||||
.file_name()
|
||||
.unwrap_or_else(|| OsStr::new(""))
|
||||
.to_string_lossy();
|
||||
let res = if filename == "_index.md" {
|
||||
site.add_and_render_section(&path)
|
||||
} else if filename.ends_with(".md") {
|
||||
site.add_and_render_page(&path)
|
||||
} else {
|
||||
// an asset changed? a folder renamed?
|
||||
// should we make it smarter so it doesn't reload the whole site?
|
||||
Err("dummy".into())
|
||||
};
|
||||
|
||||
if res.is_err() {
|
||||
if let Some(s) = recreate_site() {
|
||||
site = s;
|
||||
}
|
||||
} else {
|
||||
rebuild_done_handling(
|
||||
&broadcaster,
|
||||
res,
|
||||
&path.to_string_lossy(),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Should we be smarter than that? Is it worth it?
|
||||
if let Some(s) = recreate_site() {
|
||||
site = s;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if let Some(s) = recreate_site() {
|
||||
site = s;
|
||||
}
|
||||
}
|
||||
}
|
||||
(ChangeKind::Templates, partial_path) => {
|
||||
let msg = if path.is_dir() {
|
||||
format!(
|
||||
"-> Directory in `templates` folder changed {}",
|
||||
path.display()
|
||||
)
|
||||
} else {
|
||||
format!("-> Template changed {}", path.display())
|
||||
};
|
||||
console::info(&msg);
|
||||
|
||||
// A shortcode changed, we need to rebuild everything
|
||||
if partial_path.starts_with("/templates/shortcodes") {
|
||||
if let Some(s) = recreate_site() {
|
||||
site = s;
|
||||
}
|
||||
} else {
|
||||
println!("Reloading only template");
|
||||
// A normal template changed, no need to re-render Markdown.
|
||||
reload_templates(&mut site, &path)
|
||||
}
|
||||
}
|
||||
(ChangeKind::Templates, _) => reload_templates(&mut site, &path),
|
||||
(ChangeKind::StaticFiles, p) => copy_static(&site, &path, &p),
|
||||
(ChangeKind::Sass, p) => reload_sass(&site, &path, &p),
|
||||
(ChangeKind::Themes, _) => {
|
||||
console::info(
|
||||
"-> Themes changed. The whole site will be reloaded.",
|
||||
);
|
||||
console::info("-> Themes changed.");
|
||||
|
||||
if let Some(s) = recreate_site() {
|
||||
site = s;
|
||||
}
|
||||
}
|
||||
(ChangeKind::Config, _) => {
|
||||
console::info("-> Config changed. The whole site will be reloaded. The browser needs to be refreshed to make the changes visible.");
|
||||
console::info("-> Config changed. The browser needs to be refreshed to make the changes visible.");
|
||||
|
||||
if let Some(s) = recreate_site() {
|
||||
site = s;
|
||||
|
|
|
@ -14,7 +14,9 @@ fn main() {
|
|||
|
||||
let root_dir = match matches.value_of("root").unwrap() {
|
||||
"." => env::current_dir().unwrap(),
|
||||
path => PathBuf::from(path).canonicalize().expect(&format!("Cannot find root directory: {}", path)),
|
||||
path => PathBuf::from(path)
|
||||
.canonicalize()
|
||||
.expect(&format!("Cannot find root directory: {}", path)),
|
||||
};
|
||||
let config_file = match matches.value_of("config") {
|
||||
Some(path) => PathBuf::from(path),
|
||||
|
@ -62,6 +64,7 @@ fn main() {
|
|||
let watch_only = matches.is_present("watch_only");
|
||||
let open = matches.is_present("open");
|
||||
let include_drafts = matches.is_present("drafts");
|
||||
let fast = matches.is_present("fast");
|
||||
|
||||
// Default one
|
||||
if port != 1111 && !watch_only && !port_is_available(port) {
|
||||
|
@ -90,6 +93,7 @@ fn main() {
|
|||
watch_only,
|
||||
open,
|
||||
include_drafts,
|
||||
fast,
|
||||
) {
|
||||
Ok(()) => (),
|
||||
Err(e) => {
|
||||
|
|
Loading…
Reference in a new issue