diff --git a/CHANGELOG.md b/CHANGELOG.md index 712eaefe21..a6126e708c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,53 @@ # Changelog -## 0.17.0 (unreleased) +## 0.17.1 (2023-02-24) +- Fix bugs with colocated directories in the root `content` directory +- Fix `zola serve` not respecting `preserve_dotfiles_in_output` +- Add `generate_feed` field to the `section` object in templates + +## 0.17.0 (2023-02-16) + +### Breaking +- `get_file_hash` is removed, use `get_hash` instead. Arguments do not change +- Replace libsass by a Rust implementation: [grass](https://github.com/connorskees/grass). See https://sass-lang.com/documentation/breaking-changes +for breaking changes with libsass: look for "beginning in Dart Sass" +- Merge settings for the default language set in the root of `config.toml` and in the `[languages.{default_lang}]` section. +This will error if the same keys are defined multiple times +- Code blocks content are no longer included in the search index +- Remove built-ins shortcodes +- Having a file called `index.md` in a folder with a `_index.md` is now an error +- Ignore temp files from vim/emacs/macos/etc as well as files without extensions when getting colocated assets +- Now integrates the file stem of the original file into the processed images filename: {stem}.{hash}.{extension} + +### Other + +- Add `get_taxonomy_term` function +- Add `slugify.paths_keep_dates` option +- Add command to generate shell completions +- Fix link generation to co-located assets other than images +- Add `get_hash` Tera function +- Minify CSS and JS embedded in HTML +- Fix slow image processing +- Fix `current_url` in taxonomy term +- Add new flag `zola serve --no_port_append` to give the ability to remove port from base url +- `config.markdown` is now available in templates +- Add `preserve_dotfiles_in_output` option in the config +- Add Elasticlunr JSON output for the search index +- Add sorting by slug for pages +- Enable locale date formatting for the Tera `date` filter +- Cachebust fingerprint is now only 20 chars long +- Add `text` alias for plain text highlighting (before, only `txt` was used) +- Adds a new field to `page`: `colocated_path` that points to the folder of the current file being rendered if it's a colocated folder. None otherwise. +- Add `author` as a first-class property to the config and `authors` to pages +- Allows using external URL for `redirect_to` +- Recognize links starting with `www` as external for the link checker + +## 0.16.1 (2022-08-14) + +- Fix many Windows bugs +- Fix overriding built-in shortcodes +- Support .yml files with `load_data` ## 0.16.0 (2022-07-16) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 494d127ac1..c915029ea2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -55,7 +55,7 @@ $ cargo run --example generate_sublime synpack ../../sublime/syntaxes ../../subl ``` ### Adding a theme -A gallery containing lots of themes is located at https://tmtheme-editor.herokuapp.com/#!/editor/theme/Agola%20Dark. +A gallery containing lots of themes is located at https://tmtheme-editor.glitch.me/#!/editor/theme/Solarized%20(light). More themes can be easily added to Zola, just make a PR with the wanted theme added in the `sublime/themes` directory. If you want to test Zola with a new theme, it needs to be built into the syntect file `all.themedump`. diff --git a/Cargo.lock b/Cargo.lock index 4d526d809d..d3424c3ac1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,36 +9,42 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] -name = "adler32" -version = "1.2.0" +name = "ahash" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom 0.2.8", + "once_cell", + "version_check", +] [[package]] name = "ahash" -version = "0.7.6" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" dependencies = [ - "getrandom 0.2.7", + "cfg-if 1.0.0", + "getrandom 0.2.8", "once_cell", "version_check", ] [[package]] name = "aho-corasick" -version = "0.7.18" +version = "0.7.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" dependencies = [ "memchr", ] [[package]] name = "ammonia" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5ed2509ee88cc023cccee37a6fab35826830fe8b748b3869790e7720c2c4a74" +checksum = "64e6d1c7838db705c9b756557ee27c384ce695a1c51a6fe528784cb1c6840170" dependencies = [ "html5ever", "maplit", @@ -47,6 +53,15 @@ dependencies = [ "url", ] +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "any_ascii" version = "0.1.7" @@ -55,24 +70,21 @@ checksum = "70033777eb8b5124a81a1889416543dddef2de240019b674c81285a2635a7e1e" [[package]] name = "anyhow" -version = "1.0.58" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704" +checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" [[package]] name = "arrayvec" -version = "0.4.12" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" -dependencies = [ - "nodrop", -] +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" [[package]] name = "arrayvec" -version = "0.5.2" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" [[package]] name = "assert-json-diff" @@ -90,7 +102,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", "winapi 0.3.9", ] @@ -103,9 +115,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "base64" -version = "0.13.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" [[package]] name = "bincode" @@ -128,18 +140,6 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" -[[package]] -name = "bitvec" -version = "0.19.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55f93d0ef3363c364d5976646a38f04cf67cfe1d4c8d160cdea02cab2c116b33" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] - [[package]] name = "block-buffer" version = "0.7.3" @@ -154,11 +154,11 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" dependencies = [ - "generic-array 0.14.5", + "generic-array 0.14.6", ] [[package]] @@ -172,13 +172,11 @@ dependencies = [ [[package]] name = "bstr" -version = "0.2.17" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +checksum = "5ffdb39cb703212f3c11973452c2861b972f757b021158f3516ba10f2fa8b2c1" dependencies = [ - "lazy_static", "memchr", - "regex-automata", "serde", ] @@ -190,9 +188,9 @@ checksum = "b4ae4235e6dac0694637c763029ecea1a2ec9e4e06ec2729bd21ba4d9c863eb7" [[package]] name = "bumpalo" -version = "3.10.0" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" [[package]] name = "byte-tools" @@ -214,9 +212,9 @@ checksum = "2c676a478f63e9fa2dd5368a42f28bba0d6c560b775f38583c8bbaa7fcd67c9c" [[package]] name = "bytemuck" -version = "1.11.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5377c8865e74a160d21f29c2d40669f53286db6eab59b88540cbb12ffc8b835" +checksum = "c041d3eab048880cb0b86b256447da3f18859a163c3b8d8893f4e6368abe6393" [[package]] name = "byteorder" @@ -236,24 +234,55 @@ dependencies = [ [[package]] name = "bytes" -version = "1.2.0" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "camino" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0b3de4a0c5e67e16066a0715723abd91edc2f9001d09c46e1dca929351e130e" +checksum = "6031a462f977dd38968b6f23378356512feeace69cef817e1a4475108093cec3" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", +] [[package]] name = "cc" -version = "1.0.73" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" dependencies = [ "jobserver", ] [[package]] name = "cedarwood" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa312498f9f41452998d984d3deb84c84f86aeb8a2499d7505bb8106d78d147d" +checksum = "6d910bedd62c24733263d0bed247460853c9d22e8956bd4cd964302095e04e90" dependencies = [ "smallvec", ] @@ -272,48 +301,49 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.19" +version = "0.4.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" dependencies = [ - "libc", + "iana-time-zone", "num-integer", "num-traits", + "pure-rust-locales", "winapi 0.3.9", ] [[package]] name = "chrono-tz" -version = "0.6.1" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58549f1842da3080ce63002102d5bc954c7bc843d4f47818e642abdc36253552" +checksum = "29c39203181991a7dd4343b8005bd804e7a9a37afb8ac070e43771e8c820bbde" dependencies = [ "chrono", "chrono-tz-build", - "phf", + "phf 0.11.1", ] [[package]] name = "chrono-tz-build" -version = "0.0.2" +version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db058d493fb2f65f41861bfed7e3fe6335264a9f0f92710cab5bdf01fef09069" +checksum = "6f509c3a87b33437b05e2458750a0700e5bdd6956176773e6c7d6dd15a283a0c" dependencies = [ "parse-zoneinfo", - "phf", - "phf_codegen", + "phf 0.11.1", + "phf_codegen 0.11.1", ] [[package]] name = "clap" -version = "3.2.14" +version = "3.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54635806b078b7925d6e36810b1755f2a4b5b4d57560432c1ecf60bcbe10602b" +checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" dependencies = [ "atty", "bitflags", - "clap_derive", - "clap_lex", + "clap_derive 3.2.18", + "clap_lex 0.2.4", "indexmap", "once_cell", "strsim", @@ -321,20 +351,48 @@ dependencies = [ "textwrap", ] +[[package]] +name = "clap" +version = "4.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0b0588d44d4d63a87dbd75c136c166bbfd9a86a31cb89e09906521c7d3f5e3" +dependencies = [ + "bitflags", + "clap_derive 4.1.0", + "clap_lex 0.3.2", + "is-terminal", + "once_cell", + "strsim", + "termcolor", +] + [[package]] name = "clap_complete" -version = "3.2.3" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0012995dc3a54314f4710f5631d74767e73c534b8757221708303e48eef7a19b" +dependencies = [ + "clap 4.1.6", +] + +[[package]] +name = "clap_derive" +version = "3.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ead064480dfc4880a10764488415a97fdd36a4cf1bb022d372f02e8faf8386e1" +checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65" dependencies = [ - "clap", + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "clap_derive" -version = "3.2.7" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "759bf187376e1afa7b85b959e6a664a3e7a95203415dba952ad19139e798f902" +checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8" dependencies = [ "heck", "proc-macro-error", @@ -352,6 +410,31 @@ dependencies = [ "os_str_bytes", ] +[[package]] +name = "clap_lex" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "350b9cf31731f9957399229e9b2adc51eeabdfbe9d71d9a0552275fd12710d09" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "codemap" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e769b5c8c8283982a987c6e948e540254f1058d5a74b8794914d4ef5fc2a24" + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + [[package]] name = "color_quant" version = "1.1.0" @@ -389,15 +472,14 @@ dependencies = [ [[package]] name = "console" -version = "0.15.0" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28b32d32ca44b70c3e4acd7db1babf555fa026e385fb95f18028f88848b3c31" +checksum = "c3d79fbe8970a77e3e34151cc13d3b3e248aa0faaecb9f6091fa07ebefe5ad60" dependencies = [ "encode_unicode", + "lazy_static", "libc", - "once_cell", - "terminal_size", - "winapi 0.3.9", + "windows-sys 0.42.0", ] [[package]] @@ -439,9 +521,9 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" [[package]] name = "cpufeatures" -version = "0.2.2" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" dependencies = [ "libc", ] @@ -487,58 +569,61 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.10" +version = "0.9.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "045ebe27666471bb549370b4b0b3e51b07f56325befa4284db65fc89c02511b1" +checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a" dependencies = [ "autocfg", "cfg-if 1.0.0", "crossbeam-utils", "memoffset", - "once_cell", "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.11" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc" +checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" dependencies = [ "cfg-if 1.0.0", - "once_cell", ] +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-common" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "generic-array 0.14.5", + "generic-array 0.14.6", "typenum", ] [[package]] name = "css-minify" -version = "0.2.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692b185e3b7c9af96b3195f3021f53a931d896968ed2ad3fb1cdb6558b30c9ab" +checksum = "874c6e2d19f8d4a285083b11a3241bfbe01ac3ed85f26e1e6b34888d960552bd" dependencies = [ "derive_more", "indexmap", - "nom 6.1.2", + "nom 7.1.3", ] [[package]] name = "csv" -version = "1.1.6" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" +checksum = "af91f40b7355f82b0a891f50e70399475945bb0b0da4f1700ce60761c9d3e359" dependencies = [ - "bstr", "csv-core", - "itoa 0.4.8", + "itoa", "ryu", "serde", ] @@ -554,21 +639,56 @@ dependencies = [ [[package]] name = "ctrlc" -version = "3.2.2" +version = "3.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b37feaa84e6861e00a1f5e5aa8da3ee56d605c9992d33e082786754828e20865" +checksum = "bbcf33c2a618cbe41ee43ae6e9f2e48368cd9f9db2896f10167d8d762679f639" dependencies = [ "nix", - "winapi 0.3.9", + "windows-sys 0.45.0", ] [[package]] -name = "deflate" -version = "1.0.0" +name = "cxx" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c86f7e25f518f4b81808a2cf1c50996a61f5c2eb394b2393bd87f2a4780a432f" +checksum = "86d3488e7665a7a483b57e25bdd90d0aeb2bc7608c8d0346acf2ad3f1caf1d62" dependencies = [ - "adler32", + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48fcaf066a053a41a81dfb14d57d99738b767febb8b735c3016e469fac5da690" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2ef98b8b717a829ca5603af80e1f9e2e48013ab227b68ef37872ef84ee479bf" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "086c685979a698443656e5cf7856c95c642295a38599f12fb1ff76fb28d19892" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -601,14 +721,25 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.3" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ - "block-buffer 0.10.2", + "block-buffer 0.10.3", "crypto-common", ] +[[package]] +name = "displaydoc" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bf95dc3f046b9da4f2d51833c0d3547d8564ef6910f5c1ed130306a75b92886" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "doc-comment" version = "0.3.3" @@ -617,9 +748,9 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "either" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" [[package]] name = "elasticlunr-rs" @@ -709,18 +840,18 @@ checksum = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569" [[package]] name = "encoding_rs" -version = "0.8.31" +version = "0.8.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" +checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" dependencies = [ "cfg-if 1.0.0", ] [[package]] name = "env_logger" -version = "0.9.0" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" +checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" dependencies = [ "atty", "humantime", @@ -729,6 +860,36 @@ dependencies = [ "termcolor", ] +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "error-chain" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" +dependencies = [ + "version_check", +] + [[package]] name = "errors" version = "0.1.0" @@ -738,18 +899,18 @@ dependencies = [ [[package]] name = "exr" -version = "1.4.2" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14cc0e06fb5f67e5d6beadf3a382fec9baca1aa751c6d5368fdeee7e5932c215" +checksum = "e8af5ef47e2ed89d23d0ecbc1b681b30390069de70260937877514377fc24feb" dependencies = [ "bit_field", - "deflate", "flume", "half", - "inflate", "lebe", + "miniz_oxide", "smallvec", "threadpool", + "zune-inflate", ] [[package]] @@ -760,30 +921,30 @@ checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" [[package]] name = "fastrand" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" dependencies = [ "instant", ] [[package]] name = "filetime" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94a7bbaa59354bc20dd75b67f23e2797b4490e9d6928203fb105c79e448c86c" +checksum = "8a3de6e8d11b22ff9edc6d916f890800597d60f8b2da1caf2955c274638d6412" dependencies = [ "cfg-if 1.0.0", "libc", "redox_syscall", - "windows-sys", + "windows-sys 0.45.0", ] [[package]] name = "flate2" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" +checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" dependencies = [ "crc32fast", "miniz_oxide", @@ -799,7 +960,7 @@ dependencies = [ "futures-sink", "nanorand", "pin-project", - "spin 0.9.4", + "spin 0.9.5", ] [[package]] @@ -825,11 +986,10 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" dependencies = [ - "matches", "percent-encoding", ] @@ -868,12 +1028,6 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" -[[package]] -name = "funty" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" - [[package]] name = "futf" version = "0.1.5" @@ -886,42 +1040,42 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.21" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5" dependencies = [ "futures-core", ] [[package]] name = "futures-core" -version = "0.3.21" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" +checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" [[package]] name = "futures-io" -version = "0.3.21" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" +checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531" [[package]] name = "futures-sink" -version = "0.3.21" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" +checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364" [[package]] name = "futures-task" -version = "0.3.21" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" +checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" [[package]] name = "futures-util" -version = "0.3.21" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" dependencies = [ "futures-core", "futures-io", @@ -952,9 +1106,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.5" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" dependencies = [ "typenum", "version_check", @@ -973,9 +1127,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -986,11 +1140,11 @@ dependencies = [ [[package]] name = "gh-emoji" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6af39cf9a679d7195b3370f5454381ba49c4791bc7ce3ae2a7bf1a2a89c7adf" +checksum = "30ad64b43d48c1745c0059e5ba223816eb599eec8956c668fc0a31f6b9cd799e" dependencies = [ - "phf", + "phf 0.11.1", "regex", ] @@ -1006,15 +1160,15 @@ dependencies = [ [[package]] name = "glob" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "globset" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a" +checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc" dependencies = [ "aho-corasick", "bstr", @@ -1034,13 +1188,36 @@ dependencies = [ "walkdir", ] +[[package]] +name = "grass" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4bfa010e6f9fe2f40727b4aedf67aa54e0439c57f855458efb1f43d730a028f" +dependencies = [ + "grass_compiler", +] + +[[package]] +name = "grass_compiler" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abe05b48c9c96f5ec64ad9af20c9016a8d57ec8b979e0f6dbdd9747f32b16df3" +dependencies = [ + "codemap", + "indexmap", + "lasso", + "once_cell", + "phf 0.10.1", + "rand 0.8.5", +] + [[package]] name = "h2" -version = "0.3.13" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" +checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" dependencies = [ - "bytes 1.2.0", + "bytes 1.4.0", "fnv", "futures-core", "futures-sink", @@ -1055,15 +1232,21 @@ dependencies = [ [[package]] name = "half" -version = "1.8.2" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" +checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0" +dependencies = [ + "crunchy", +] [[package]] name = "hashbrown" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash 0.7.6", +] [[package]] name = "hashbrown" @@ -1073,9 +1256,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "heck" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" @@ -1086,6 +1269,21 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + [[package]] name = "html5ever" version = "0.26.0" @@ -1102,13 +1300,13 @@ dependencies = [ [[package]] name = "http" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" dependencies = [ - "bytes 1.2.0", + "bytes 1.4.0", "fnv", - "itoa 1.0.2", + "itoa", ] [[package]] @@ -1117,16 +1315,16 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ - "bytes 1.2.0", + "bytes 1.4.0", "http", "pin-project-lite", ] [[package]] name = "httparse" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" @@ -1148,11 +1346,11 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.20" +version = "0.14.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" +checksum = "5e011372fa0b68db8350aa7a248930ecc7839bf46d8485577d69f117a75f164c" dependencies = [ - "bytes 1.2.0", + "bytes 1.4.0", "futures-channel", "futures-core", "futures-util", @@ -1161,7 +1359,7 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa 1.0.2", + "itoa", "pin-project-lite", "socket2", "tokio", @@ -1172,9 +1370,9 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.23.0" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d87c48c02e0dc5e3b849a2041db3029fd066650f8f717c07bf8ed78ccb895cac" +checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c" dependencies = [ "http", "hyper", @@ -1189,31 +1387,53 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ - "bytes 1.2.0", + "bytes 1.4.0", "hyper", "native-tls", "tokio", "tokio-native-tls", ] +[[package]] +name = "iana-time-zone" +version = "0.1.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "winapi 0.3.9", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +dependencies = [ + "cxx", + "cxx-build", +] + [[package]] name = "idna" -version = "0.2.3" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" dependencies = [ - "matches", "unicode-bidi", "unicode-normalization", ] [[package]] name = "ignore" -version = "0.4.18" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d" +checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492" dependencies = [ - "crossbeam-utils", "globset", "lazy_static", "log", @@ -1227,9 +1447,9 @@ dependencies = [ [[package]] name = "image" -version = "0.24.3" +version = "0.24.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e30ca2ecf7666107ff827a8e481de6a132a9b687ed3bb20bb1c144a36c00964" +checksum = "69b7ea949b537b0fd0af141fff8c77690f2ce96f4f41f042ccb6c69c6c965945" dependencies = [ "bytemuck", "byteorder", @@ -1259,23 +1479,14 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.9.1" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" dependencies = [ "autocfg", "hashbrown 0.12.3", ] -[[package]] -name = "inflate" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cdb29978cc5797bd8dcc8e5bf7de604891df2a8dc576973d71a281e916db2ff" -dependencies = [ - "adler32", -] - [[package]] name = "inotify" version = "0.7.1" @@ -1298,16 +1509,15 @@ dependencies = [ [[package]] name = "insta" -version = "1.16.0" +version = "1.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36fb7ec420af04ce7d1a422945cd19c52bf01772ead45934cee77f056dca1081" +checksum = "fea5b3894afe466b4bcf0388630fc15e11938a6074af0cd637c825ba2ec8a099" dependencies = [ - "console 0.15.0", - "once_cell", - "serde", - "serde_json", - "serde_yaml", + "console 0.15.5", + "lazy_static", + "linked-hash-map", "similar", + "yaml-rust", ] [[package]] @@ -1319,6 +1529,16 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "io-lifetimes" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1abeb7a0dd0f8181267ff8adc397075586500b81b28a73e8a0208b00fc170fb3" +dependencies = [ + "libc", + "windows-sys 0.45.0", +] + [[package]] name = "iovec" version = "0.1.4" @@ -1330,69 +1550,75 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.5.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" +checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146" [[package]] -name = "itoa" -version = "0.4.8" +name = "is-terminal" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" +checksum = "21b6b32576413a8e69b90e952e4a026476040d81017b80445deda5f2d3921857" +dependencies = [ + "hermit-abi 0.3.1", + "io-lifetimes", + "rustix", + "windows-sys 0.45.0", +] [[package]] name = "itoa" -version = "1.0.2" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" [[package]] name = "jieba-rs" -version = "0.6.6" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c7e12f50325401dde50c29ca32cff44bae20873135b39f4e19ecf305226dd80" +checksum = "37228e06c75842d1097432d94d02f37fe3ebfca9791c2e8fef6e9db17ed128c1" dependencies = [ "cedarwood", "fxhash", - "hashbrown 0.11.2", + "hashbrown 0.12.3", "lazy_static", - "phf", - "phf_codegen", + "phf 0.11.1", + "phf_codegen 0.11.1", "regex", ] [[package]] name = "jobserver" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" +checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b" dependencies = [ "libc", ] [[package]] name = "jpeg-decoder" -version = "0.2.6" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9478aa10f73e7528198d75109c8be5cd7d15fb530238040148d5f9a22d4c5b3b" +checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e" dependencies = [ "rayon", ] [[package]] name = "js-sys" -version = "0.3.58" +version = "0.3.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fac17f7123a73ca62df411b1bf727ccc805daa070338fda671c86dac1bdc27" +checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" dependencies = [ "wasm-bindgen", ] [[package]] name = "kamadak-exif" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70494964492bf8e491eb3951c5d70c9627eb7100ede6cc56d748b9a3f302cfb6" +checksum = "ef4fc70d0ab7e5b6bafa30216a6b48705ea964cdfc29c050f2412295eba58077" dependencies = [ "mutate_once", ] @@ -1407,6 +1633,15 @@ dependencies = [ "winapi-build", ] +[[package]] +name = "lasso" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aeb7b21a526375c5ca55f1a6dfd4e1fad9fa4edd750f530252a718a44b2608f0" +dependencies = [ + "hashbrown 0.11.2", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -1421,9 +1656,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "lebe" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7efd1d698db0759e6ef11a7cd44407407399a910c774dd804c64c032da7826ff" +checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] name = "lexical-core" @@ -1449,15 +1684,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.126" +version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" [[package]] name = "libs" version = "0.1.0" dependencies = [ - "ahash", + "ahash 0.8.3", "ammonia", "atty", "base64", @@ -1467,6 +1702,7 @@ dependencies = [ "gh-emoji", "glob", "globset", + "grass", "image", "lexical-sort", "minify-html", @@ -1480,7 +1716,6 @@ dependencies = [ "regex", "relative-path", "reqwest", - "sass-rs", "serde_json", "serde_yaml", "sha2", @@ -1490,7 +1725,7 @@ dependencies = [ "tera", "termcolor", "time", - "toml", + "toml 0.7.2", "unic-langid", "unicode-segmentation", "url", @@ -1538,7 +1773,7 @@ dependencies = [ "anyhow", "bincode", "byteorder", - "clap", + "clap 3.2.23", "csv", "encoding", "env_logger", @@ -1613,7 +1848,7 @@ dependencies = [ "anyhow", "bincode", "byteorder", - "clap", + "clap 3.2.23", "encoding", "env_logger", "glob", @@ -1633,7 +1868,7 @@ dependencies = [ "anyhow", "bincode", "byteorder", - "clap", + "clap 3.2.23", "csv", "encoding", "env_logger", @@ -1653,7 +1888,7 @@ dependencies = [ "anyhow", "bincode", "byteorder", - "clap", + "clap 3.2.23", "csv", "encoding", "env_logger", @@ -1673,6 +1908,15 @@ dependencies = [ "safemem", ] +[[package]] +name = "link-cplusplus" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" +dependencies = [ + "cc", +] + [[package]] name = "link_checker" version = "0.1.0" @@ -1690,11 +1934,17 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" +[[package]] +name = "linux-raw-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" + [[package]] name = "lock_api" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" dependencies = [ "autocfg", "scopeguard", @@ -1753,19 +2003,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016" dependencies = [ "log", - "phf", - "phf_codegen", + "phf 0.10.1", + "phf_codegen 0.10.0", "string_cache", "string_cache_codegen", "tendril", ] -[[package]] -name = "matches" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" - [[package]] name = "memchr" version = "2.5.0" @@ -1774,9 +2018,9 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memoffset" -version = "0.6.5" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" dependencies = [ "autocfg", ] @@ -1803,38 +2047,44 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe549115a674f5ec64c754d85e37d6f42664bd0ef4ffb62b619489ad99c6cb1a" dependencies = [ - "quick-xml", + "quick-xml 0.17.2", ] [[package]] name = "minify-html" -version = "0.9.2" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "640e7546ebd29c7d0fe523684a5a0661281ca93b7cf01fe3023a7fa979eaf17e" +checksum = "7754d4669873379ea6a8a5b56e406eb83de713af8a791517ef35a0c832b1e7d5" dependencies = [ "aho-corasick", "css-minify", "lazy_static", "memchr", "minify-js", + "rustc-hash", ] [[package]] name = "minify-js" -version = "0.1.5" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "428521c0c8faf89847479aee75160d2254f15b3d7c5e629f4af0b7fb2e19b251" +checksum = "c300f90ba1138b5c5daf5d9441dc9bdc67b808aac22cf638362a2647bc213be4" dependencies = [ - "aho-corasick", "lazy_static", - "memchr", + "parse-js", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" -version = "0.5.3" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" dependencies = [ "adler", ] @@ -1860,14 +2110,14 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" +checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys", + "windows-sys 0.45.0", ] [[package]] @@ -1896,9 +2146,9 @@ dependencies = [ [[package]] name = "mockito" -version = "0.31.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "401edc088069634afaa5f4a29617b36dba683c0c16fe4435a86debad23fa2f1a" +checksum = "80f9fece9bd97ab74339fe19f4bcaf52b76dcc18e5364c7977c1838f76b38de9" dependencies = [ "assert-json-diff", "colored", @@ -1924,14 +2174,14 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" dependencies = [ - "getrandom 0.2.7", + "getrandom 0.2.8", ] [[package]] name = "native-tls" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" dependencies = [ "lazy_static", "libc", @@ -1947,9 +2197,9 @@ dependencies = [ [[package]] name = "net2" -version = "0.2.37" +version = "0.2.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae" +checksum = "74d0df99cfcd2530b2e694f6e17e7f37b8e26bb23983ac530c0c97408837c631" dependencies = [ "cfg-if 0.1.10", "libc", @@ -1964,21 +2214,16 @@ checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" [[package]] name = "nix" -version = "0.24.2" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "195cdbc1741b8134346d515b3a56a1c94b0912758009cfd53f99ea0f57b065fc" +checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" dependencies = [ "bitflags", "cfg-if 1.0.0", "libc", + "static_assertions", ] -[[package]] -name = "nodrop" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" - [[package]] name = "nom" version = "5.1.2" @@ -1992,15 +2237,12 @@ dependencies = [ [[package]] name = "nom" -version = "6.1.2" +version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ - "bitvec", - "funty", - "lexical-core", "memchr", - "version_check", + "minimal-lexical", ] [[package]] @@ -2079,12 +2321,12 @@ dependencies = [ [[package]] name = "num-format" -version = "0.4.0" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bafe4179722c2894288ee77a9f044f02811c86af699344c498b0840c698a2465" +checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" dependencies = [ - "arrayvec 0.4.12", - "itoa 0.4.8", + "arrayvec 0.7.2", + "itoa", ] [[package]] @@ -2119,11 +2361,11 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.13.1" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "hermit-abi", + "hermit-abi 0.2.6", "libc", ] @@ -2138,27 +2380,27 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.13.0" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" [[package]] name = "onig" -version = "6.3.2" +version = "6.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1eb3502504c9c8b06634b38bfdda86a9a8cef6277f3dec4d8b17c115110dd2a3" +checksum = "8c4b31c8722ad9171c6d77d3557db078cab2bd50afcc9d09c8b315c59df8ca4f" dependencies = [ "bitflags", - "lazy_static", "libc", + "once_cell", "onig_sys", ] [[package]] name = "onig_sys" -version = "69.8.0" +version = "69.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bf3fbc9b931b6c9af85d219c7943c274a6ad26cff7488a2210215edd5f49bf8" +checksum = "7b829e3d7e9cc74c7e315ee8edb185bf4190da5acde74afd7fc59c35b1f086e7" dependencies = [ "cc", "pkg-config", @@ -2172,19 +2414,19 @@ checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" [[package]] name = "open" -version = "3.0.2" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f23a407004a1033f53e93f9b45580d14de23928faad187384f891507c9b0c045" +checksum = "2078c0039e6a54a0c42c28faa984e115fb4c2d5bf2208f77d1961002df8576f8" dependencies = [ "pathdiff", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] name = "openssl" -version = "0.10.41" +version = "0.10.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "618febf65336490dfcf20b73f885f5651a0c89c64c2d4a8c3662585a70bf5bd0" +checksum = "b102428fd03bc5edf97f62620f7298614c45cedf287c271e7ed450bbaf83f2e1" dependencies = [ "bitflags", "cfg-if 1.0.0", @@ -2214,9 +2456,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.75" +version = "0.9.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5f9bd0c2710541a3cda73d6f9ac4f1b240de4ae261065d309dbe73d9dceb42f" +checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7" dependencies = [ "autocfg", "cc", @@ -2227,9 +2469,9 @@ dependencies = [ [[package]] name = "os_str_bytes" -version = "6.2.0" +version = "6.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "648001efe5d5c0102d8cea768e348da85d90af8ba91f0bea908f157951493cd4" +checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" [[package]] name = "parking_lot" @@ -2243,15 +2485,26 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.3" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" dependencies = [ "cfg-if 1.0.0", "libc", "redox_syscall", "smallvec", - "windows-sys", + "windows-sys 0.45.0", +] + +[[package]] +name = "parse-js" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30534759e6ad87aa144c396544747e1c25b1020bd133356fd758c8facec764e5" +dependencies = [ + "aho-corasick", + "lazy_static", + "memchr", ] [[package]] @@ -2265,9 +2518,9 @@ dependencies = [ [[package]] name = "path-slash" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c54014ba3c1880122928735226f78b6f5bf5bd1fed15e41e92cf7aa20278ce28" +checksum = "1e91099d4268b0e11973f036e885d652fb0b21fedcf69738c627f94db6a44f42" [[package]] name = "pathdiff" @@ -2277,24 +2530,25 @@ checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" [[package]] name = "percent-encoding" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] name = "pest" -version = "2.1.3" +version = "2.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +checksum = "028accff104c4e513bad663bbcd2ad7cfd5304144404c31ed0a77ac103d00660" dependencies = [ + "thiserror", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.1.0" +version = "2.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" +checksum = "2ac3922aac69a40733080f53c1ce7f91dcf57e1a5f6c52f421fadec7fbdc4b69" dependencies = [ "pest", "pest_generator", @@ -2302,9 +2556,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.1.3" +version = "2.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55" +checksum = "d06646e185566b5961b4058dd107e0a7f56e77c3f484549fb119867773c0f202" dependencies = [ "pest", "pest_meta", @@ -2315,13 +2569,13 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.1.3" +version = "2.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d" +checksum = "e6f60b2ba541577e2a0c307c8f39d1439108120eb7903adeb6497fa880c59616" dependencies = [ - "maplit", + "once_cell", "pest", - "sha-1", + "sha2", ] [[package]] @@ -2330,34 +2584,88 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" dependencies = [ - "phf_shared", + "phf_macros", + "phf_shared 0.10.0", + "proc-macro-hack", +] + +[[package]] +name = "phf" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928c6535de93548188ef63bb7c4036bd415cd8f36ad25af44b9789b2ee72a48c" +dependencies = [ + "phf_shared 0.11.1", +] + +[[package]] +name = "phf_codegen" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", ] [[package]] name = "phf_codegen" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56ac890c5e3ca598bbdeaa99964edb5b0258a583a9eb6ef4e89fc85d9224770" +dependencies = [ + "phf_generator 0.11.1", + "phf_shared 0.11.1", +] + +[[package]] +name = "phf_generator" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +dependencies = [ + "phf_shared 0.10.0", + "rand 0.8.5", +] + +[[package]] +name = "phf_generator" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1181c94580fa345f50f19d738aaa39c0ed30a600d95cb2d3e23f94266f14fbf" +dependencies = [ + "phf_shared 0.11.1", + "rand 0.8.5", +] + +[[package]] +name = "phf_macros" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" +checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" dependencies = [ - "phf_generator", - "phf_shared", + "phf_generator 0.10.0", + "phf_shared 0.10.0", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "phf_generator" +name = "phf_shared" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" dependencies = [ - "phf_shared", - "rand 0.8.5", + "siphasher", ] [[package]] name = "phf_shared" -version = "0.10.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +checksum = "e1fb5f6f826b772a8d4c0394209441e7d37cbbb967ae9c7e0e8134365c9ee676" dependencies = [ "siphasher", "uncased", @@ -2365,18 +2673,18 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78203e83c48cffbe01e4a2d35d566ca4de445d79a85372fc64e378bfc812a260" +checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "710faf75e1b33345361201d36d04e98ac1ed8909151a017ed384700836104c74" +checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" dependencies = [ "proc-macro2", "quote", @@ -2397,41 +2705,41 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" [[package]] name = "plist" -version = "1.3.1" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd39bc6cdc9355ad1dc5eeedefee696bb35c34caf21768741e81826c0bbd7225" +checksum = "9469799ca90293a376f68f6fcb8f11990d9cff55602cfba0ba83893c973a7f46" dependencies = [ "base64", "indexmap", "line-wrap", + "quick-xml 0.26.0", "serde", "time", - "xml-rs", ] [[package]] name = "png" -version = "0.17.5" +version = "0.17.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc38c0ad57efb786dd57b9864e5b18bae478c00c824dc55a38bbc9da95dde3ba" +checksum = "5d708eaf860a19b19ce538740d2b4bdeeb8337fa53f7738455e706623ad5c638" dependencies = [ "bitflags", "crc32fast", - "deflate", + "flate2", "miniz_oxide", ] [[package]] name = "ppv-lite86" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "precomputed-hash" @@ -2463,26 +2771,38 @@ dependencies = [ "version_check", ] +[[package]] +name = "proc-macro-hack" +version = "0.5.20+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + [[package]] name = "proc-macro2" -version = "1.0.41" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdcc2916cde080c1876ff40292a396541241fe0072ef928cd76582e9ea5d60d2" +checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" dependencies = [ "unicode-ident", ] [[package]] name = "pulldown-cmark" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34f197a544b0c9ab3ae46c359a7ec9cbbb5c7bf97054266fecb7ead794a181d6" +checksum = "2d9cc634bc78768157b5cbfe988ffcd1dcba95cd2b2f03a88316c08c6d00ed63" dependencies = [ "bitflags", "memchr", "unicase", ] +[[package]] +name = "pure-rust-locales" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b45c49fc4f91f35bae654f85ebb3a44d60ac64f11b3166ffa609def390c732d8" + [[package]] name = "quick-error" version = "1.2.3" @@ -2498,6 +2818,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "quick-xml" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f50b1c63b38611e7d4d7f68b82d3ad0cc71a2ad2e7f61fc10f1328d917c93cd" +dependencies = [ + "memchr", +] + [[package]] name = "quickxml_to_serde" version = "0.5.0" @@ -2512,19 +2841,13 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.20" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" dependencies = [ "proc-macro2", ] -[[package]] -name = "radium" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8" - [[package]] name = "rand" version = "0.7.3" @@ -2546,7 +2869,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha 0.3.1", - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] @@ -2566,7 +2889,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] @@ -2580,11 +2903,11 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.7", + "getrandom 0.2.8", ] [[package]] @@ -2598,21 +2921,19 @@ dependencies = [ [[package]] name = "rayon" -version = "1.5.3" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" +checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7" dependencies = [ - "autocfg", - "crossbeam-deque", "either", "rayon-core", ] [[package]] name = "rayon-core" -version = "1.9.3" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" +checksum = "356a0625f1954f730c0201cdab48611198dc6ce21f4acff55089b5a78e6e835b" dependencies = [ "crossbeam-channel", "crossbeam-deque", @@ -2622,41 +2943,35 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534cfe58d6a18cc17120fbf4635d53d14691c1fe4d951064df9bd326178d7d5a" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags", ] [[package]] name = "regex" -version = "1.6.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" - [[package]] name = "regex-syntax" -version = "0.6.27" +version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" [[package]] name = "relative-path" -version = "1.7.2" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0df32d82cedd1499386877b062ebe8721f806de80b08d183c70184ef17dd1d42" +checksum = "d3bf6b372449361333ac1f498b7edae4dd5e70dccd7c0c2a7c7bce8f05ede648" [[package]] name = "remove_dir_all" @@ -2669,12 +2984,12 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.11" +version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b75aa69a3f06bbcc66ede33af2af253c6f7a86b1ca0033f60c580a27074fbf92" +checksum = "21eed90ec8570952d53b772ecf8f206aa1ec9a3d76b2521c56c42973f2d91ee9" dependencies = [ "base64", - "bytes 1.2.0", + "bytes 1.4.0", "encoding_rs", "futures-core", "futures-util", @@ -2686,10 +3001,10 @@ dependencies = [ "hyper-tls", "ipnet", "js-sys", - "lazy_static", "log", "mime", "native-tls", + "once_cell", "percent-encoding", "pin-project-lite", "rustls", @@ -2726,9 +3041,9 @@ dependencies = [ [[package]] name = "roxmltree" -version = "0.13.1" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbf7d7b1ea646d380d0e8153158063a6da7efe30ddbf3184042848e3f8a6f671" +checksum = "d8f595a457b6b8c6cda66a48503e92ee8d19342f905948f29c383200ec9eb1d8" dependencies = [ "xmlparser", ] @@ -2743,6 +3058,12 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc_version" version = "0.4.0" @@ -2752,11 +3073,25 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.36.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43abb88211988493c1abb44a70efa56ff0ce98f233b7b276146f1f3f7ba9644" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.45.0", +] + [[package]] name = "rustls" -version = "0.20.6" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033" +checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" dependencies = [ "log", "ring", @@ -2766,18 +3101,18 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7522c9de787ff061458fe9a829dc790a3f5b22dc571694fc5883f448b94d9a9" +checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" dependencies = [ "base64", ] [[package]] name = "ryu" -version = "1.0.10" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" +checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" [[package]] name = "safemem" @@ -2794,36 +3129,13 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "sass-rs" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cabcf7c6e55053f359911187ac401409aad2dc14338cae972dec266fee486abd" -dependencies = [ - "libc", - "sass-sys", -] - -[[package]] -name = "sass-sys" -version = "0.4.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "933bca23b402377f0ab71e79732a826deffc748013746ac3314f6abc7f9fc51c" -dependencies = [ - "cc", - "libc", - "num_cpus", - "pkg-config", -] - [[package]] name = "schannel" -version = "0.1.20" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" dependencies = [ - "lazy_static", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -2838,6 +3150,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "scratch" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2" + [[package]] name = "sct" version = "0.7.0" @@ -2860,9 +3178,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.6.1" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" +checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" dependencies = [ "bitflags", "core-foundation", @@ -2873,9 +3191,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.6.1" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" dependencies = [ "core-foundation-sys", "libc", @@ -2883,24 +3201,27 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.12" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2333e6df6d6598f2b1974829f853c2b4c5f4a6e503c10af918081aa6f8564e1" +checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" +dependencies = [ + "serde", +] [[package]] name = "serde" -version = "1.0.140" +version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc855a42c7967b7c369eb5860f7164ef1f6f81c20c7cc1141f2a604e18723b03" +checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.140" +version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f2122636b9fe3b81f1cb25099fcf2d3f542cdb1d45940d56c713158884a05da" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" dependencies = [ "proc-macro2", "quote", @@ -2909,16 +3230,25 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.82" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7" +checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76" dependencies = [ "indexmap", - "itoa 1.0.2", + "itoa", "ryu", "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -2926,21 +3256,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa 1.0.2", + "itoa", "ryu", "serde", ] [[package]] name = "serde_yaml" -version = "0.8.26" +version = "0.9.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b" +checksum = "8fb06d4b6cdaef0e0c51fa881acb721bed3c924cfaa71d9c94a3b771dfdf6567" dependencies = [ "indexmap", + "itoa", "ryu", "serde", - "yaml-rust", + "unsafe-libyaml", ] [[package]] @@ -2957,20 +3288,26 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.2" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if 1.0.0", "cpufeatures", - "digest 0.10.3", + "digest 0.10.6", ] +[[package]] +name = "simd-adler32" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14a5df39617d7c8558154693a1bb8157a4aab8179209540cc0b10e5dc24e0b18" + [[package]] name = "similar" -version = "2.1.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e24979f63a11545f5f2c60141afe249d4f19f84581ea2138065e400941d83d3" +checksum = "420acb44afdae038210c99e69aae24109f32f15500aa708e81d46c9f29d55fcf" [[package]] name = "siphasher" @@ -2997,11 +3334,26 @@ dependencies = [ "utils", ] +[[package]] +name = "skeptic" +version = "0.13.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16d23b015676c90a0f01c197bfdc786c20342c73a0afdda9025adb0bc42940a8" +dependencies = [ + "bytecount 0.6.3", + "cargo_metadata", + "error-chain", + "glob", + "pulldown-cmark", + "tempfile", + "walkdir", +] + [[package]] name = "slab" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" dependencies = [ "autocfg", ] @@ -3017,15 +3369,15 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "socket2" -version = "0.4.4" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" dependencies = [ "libc", "winapi 0.3.9", @@ -3039,9 +3391,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "spin" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6002a767bff9e83f8eeecf883ecb8011875a21ae8da43bffb817a57e78cc09" +checksum = "7dccf47db1b41fa1573ed27ccf5e08e3ca771cb994f776668c5ebda893b248fc" dependencies = [ "lock_api", ] @@ -3061,7 +3413,7 @@ dependencies = [ "new_debug_unreachable", "once_cell", "parking_lot", - "phf_shared", + "phf_shared 0.10.0", "precomputed-hash", "serde", ] @@ -3072,8 +3424,8 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988" dependencies = [ - "phf_generator", - "phf_shared", + "phf_generator 0.10.0", + "phf_shared 0.10.0", "proc-macro2", "quote", ] @@ -3086,21 +3438,22 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "svg_metadata" -version = "0.4.2" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d39f36262efe61096a17aa42140b0e44b9189806c3fa71ab3cff123429938eb0" +checksum = "a42f3d2c6f543a8c57cdb434697334f33faf56f9acc141e2bbe4bb1994055200" dependencies = [ "doc-comment", "lazy_static", "regex", "roxmltree", + "skeptic", ] [[package]] name = "syn" -version = "1.0.98" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", @@ -3130,12 +3483,6 @@ dependencies = [ "yaml-rust", ] -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - [[package]] name = "tar" version = "0.4.38" @@ -3189,9 +3536,9 @@ dependencies = [ [[package]] name = "tera" -version = "1.16.0" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c9783d6ff395ae80cf17ed9a25360e7ba37742a79fa8fddabb073c5c7c8856d" +checksum = "3df578c295f9ec044ff1c829daf31bb7581d5b3c2a7a3d87419afe1f2531438c" dependencies = [ "chrono", "chrono-tz", @@ -3211,65 +3558,68 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.1.3" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" dependencies = [ "winapi-util", ] [[package]] -name = "terminal_size" -version = "0.1.17" +name = "test-case" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" +checksum = "679b019fb241da62cc449b33b224d19ebe1c6767b495569765115dd7f7f9fba4" dependencies = [ - "libc", - "winapi 0.3.9", + "test-case-macros", ] [[package]] -name = "test-case" -version = "2.2.1" +name = "test-case-core" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07aea929e9488998b64adc414c29fe5620398f01c2e3f58164122b17e567a6d5" +checksum = "72dc21b5887f4032c4656502d085dc28f2afbb686f25f216472bb0526f4b1b88" dependencies = [ - "test-case-macros", + "cfg-if 1.0.0", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "test-case-macros" -version = "2.2.1" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c95968eedc6fc4f5c21920e0f4264f78ec5e4c56bb394f319becc1a5830b3e54" +checksum = "f3786898e0be151a96f730fd529b0e8a10f5990fa2a7ea14e37ca27613c05190" dependencies = [ - "cfg-if 1.0.0", "proc-macro-error", "proc-macro2", "quote", "syn", + "test-case-core", ] [[package]] name = "textwrap" -version = "0.15.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" +checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" [[package]] name = "thiserror" -version = "1.0.31" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.31" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" dependencies = [ "proc-macro2", "quote", @@ -3278,10 +3628,11 @@ dependencies = [ [[package]] name = "thread_local" -version = "1.1.4" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" dependencies = [ + "cfg-if 1.0.0", "once_cell", ] @@ -3296,9 +3647,9 @@ dependencies = [ [[package]] name = "tiff" -version = "0.7.3" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7259662e32d1e219321eb309d5f9d898b779769d81b76e762c07c8e5d38fcb65" +checksum = "7449334f9ff2baf290d55d73983a7d6fa15e01198faef72af07e2a8db851e471" dependencies = [ "flate2", "jpeg-decoder", @@ -3307,27 +3658,41 @@ dependencies = [ [[package]] name = "time" -version = "0.3.11" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72c91f41dcb2f096c05f0873d667dceec1087ce5bcf984ec8ffb19acddbb3217" +checksum = "53250a3b3fed8ff8fd988587d8925d26a83ac3845d9e03b220b37f34c2b8d6c2" dependencies = [ - "itoa 1.0.2", + "itoa", "libc", "num_threads", + "serde", + "time-core", "time-macros", ] +[[package]] +name = "time-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" + [[package]] name = "time-macros" -version = "0.2.4" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" +checksum = "a460aeb8de6dcb0f381e1ee05f1cd56fcf5a5f6eb8187ff3d8f0b11078d38b7c" +dependencies = [ + "time-core", +] [[package]] name = "tinystr" -version = "0.3.4" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29738eedb4388d9ea620eeab9384884fc3f06f586a2eddb56bedc5885126c7c1" +checksum = "7ac3f5b6856e931e15e07b478e98c8045239829a65f9156d4fa7e7788197a5ef" +dependencies = [ + "displaydoc", +] [[package]] name = "tinyvec" @@ -3340,33 +3705,32 @@ dependencies = [ [[package]] name = "tinyvec_macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.20.0" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57aec3cfa4c296db7255446efb4928a6be304b431a806216105542a67b6ca82e" +checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af" dependencies = [ "autocfg", - "bytes 1.2.0", + "bytes 1.4.0", "libc", "memchr", - "mio 0.8.4", + "mio 0.8.6", "num_cpus", - "once_cell", "pin-project-lite", "socket2", - "winapi 0.3.9", + "windows-sys 0.42.0", ] [[package]] name = "tokio-native-tls" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" dependencies = [ "native-tls", "tokio", @@ -3385,11 +3749,11 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.3" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" +checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" dependencies = [ - "bytes 1.2.0", + "bytes 1.4.0", "futures-core", "futures-sink", "pin-project-lite", @@ -3399,11 +3763,45 @@ dependencies = [ [[package]] name = "toml" -version = "0.5.9" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "toml" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7afcae9e3f0fe2c370fd4657108972cbb2fa9db1b9f84849cefd80741b01cb6" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +checksum = "9a1eb0622d28f4b9c90adc4ea4b2b46b47663fde9ac5fafcb14a1369d5508825" dependencies = [ + "indexmap", "serde", + "serde_spanned", + "toml_datetime", + "winnow", ] [[package]] @@ -3414,9 +3812,9 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.35" +version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if 1.0.0", "pin-project-lite", @@ -3425,30 +3823,30 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.28" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b7358be39f2f274f322d2aaed611acc57f382e8eb1e5b48cb9ae30933495ce7" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" dependencies = [ "once_cell", ] [[package]] name = "try-lock" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "typenum" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "ucd-trie" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89570599c4fe5585de2b388aab47e99f7fa4e9238a1399f707a02e356058141c" +checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" [[package]] name = "uncased" @@ -3482,18 +3880,18 @@ checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" [[package]] name = "unic-langid" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73328fcd730a030bdb19ddf23e192187a6b01cd98be6d3140622a89129459ce5" +checksum = "398f9ad7239db44fd0f80fe068d12ff22d78354080332a5077dc6f52f14dcf2f" dependencies = [ "unic-langid-impl", ] [[package]] name = "unic-langid-impl" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a4a8eeaf0494862c1404c95ec2f4c33a2acff5076f64314b465e3ddae1b934d" +checksum = "e35bfd2f2b8796545b55d7d3fd3e89a0613f68a0d1c8bc28cb7ff96b411a35ff" dependencies = [ "tinystr", ] @@ -3538,30 +3936,42 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.8" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" +checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58" [[package]] name = "unicode-ident" -version = "1.0.2" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" [[package]] name = "unicode-normalization" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" dependencies = [ "tinyvec", ] [[package]] name = "unicode-segmentation" -version = "1.9.0" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" +checksum = "bc7ed8ba44ca06be78ea1ad2c3682a43349126c8818054231ee6f4748012aed2" [[package]] name = "untrusted" @@ -3571,13 +3981,12 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "url" -version = "2.2.2" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" dependencies = [ "form_urlencoded", "idna", - "matches", "percent-encoding", ] @@ -3644,9 +4053,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.81" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -3654,13 +4063,13 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.81" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" dependencies = [ "bumpalo", - "lazy_static", "log", + "once_cell", "proc-macro2", "quote", "syn", @@ -3669,9 +4078,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.31" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de9a9cec1733468a8c657e57fa2413d2ae2c0129b95e87c5b72b8ace4d13f31f" +checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -3681,9 +4090,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.81" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3691,9 +4100,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.81" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" dependencies = [ "proc-macro2", "quote", @@ -3704,15 +4113,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.81" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" [[package]] name = "web-sys" -version = "0.3.58" +version = "0.3.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fed94beee57daf8dd7d51f2b15dc2bcde92d7a72304cdf662a4371008b71b90" +checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" dependencies = [ "js-sys", "wasm-bindgen", @@ -3740,9 +4149,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.22.4" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1c760f0d366a6c24a02ed7816e23e691f5d92291f94d15e836006fd11b04daf" +checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" dependencies = [ "webpki", ] @@ -3798,46 +4207,93 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" -version = "0.36.1" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" dependencies = [ + "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_msvc", "windows_x86_64_gnu", + "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" + [[package]] name = "windows_aarch64_msvc" -version = "0.36.1" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" +checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" [[package]] name = "windows_i686_gnu" -version = "0.36.1" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" +checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" [[package]] name = "windows_i686_msvc" -version = "0.36.1" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" +checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" [[package]] name = "windows_x86_64_gnu" -version = "0.36.1" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" +checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" [[package]] name = "windows_x86_64_msvc" -version = "0.36.1" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" + +[[package]] +name = "winnow" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" +checksum = "44c06e7dbfe731192c512aa707249279d720876a868eb08f21ea93eb9de1eca9" +dependencies = [ + "memchr", +] [[package]] name = "winreg" @@ -3854,7 +4310,7 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b68db261ef59e9e52806f688020631e987592bd83619edccda9c47d42cde4f6c" dependencies = [ - "toml", + "toml 0.5.11", ] [[package]] @@ -3885,12 +4341,6 @@ dependencies = [ "winapi-build", ] -[[package]] -name = "wyz" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" - [[package]] name = "xattr" version = "0.2.3" @@ -3900,17 +4350,11 @@ dependencies = [ "libc", ] -[[package]] -name = "xml-rs" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" - [[package]] name = "xmlparser" -version = "0.13.3" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "114ba2b24d2167ef6d67d7d04c8cc86522b87f490025f39f0303b7db5bf5e3d8" +checksum = "4d25c75bf9ea12c4040a97f829154768bbbce366287e2dc044af160cd79a13fd" [[package]] name = "yada" @@ -3929,9 +4373,9 @@ dependencies = [ [[package]] name = "zola" -version = "0.16.0" +version = "0.17.1" dependencies = [ - "clap", + "clap 4.1.6", "clap_complete", "console 0.1.0", "ctrlc", @@ -3951,3 +4395,12 @@ dependencies = [ "winres", "ws", ] + +[[package]] +name = "zune-inflate" +version = "0.2.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589245df6230839c305984dcc0a8385cc72af1fd223f360ffd5d65efa4216d40" +dependencies = [ + "simd-adler32", +] diff --git a/Cargo.toml b/Cargo.toml index 1e4917ec9c..c460c4393c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "zola" -version = "0.16.0" +version = "0.17.1" authors = ["Vincent Prouillet "] edition = "2018" license = "MIT" @@ -13,8 +13,6 @@ keywords = ["static", "site", "generator", "blog"] include = ["src/**/*", "LICENSE", "README.md"] [build-dependencies] -clap = "3" -clap_complete = "3" winres = "0.1" time = "0.3" @@ -22,7 +20,8 @@ time = "0.3" name = "zola" [dependencies] -clap = { version = "3", features = ["derive"] } +clap = { version = "4", features = ["derive"] } +clap_complete = "4" # Below is for the serve cmd hyper = { version = "0.14.1", default-features = false, features = ["runtime", "server", "http2", "http1"] } tokio = { version = "1.0.1", default-features = false, features = ["rt", "fs", "time"] } @@ -61,6 +60,7 @@ members = ["components/*"] [profile.release] lto = true codegen-units = 1 +strip = true [profile.dev] # Disabling debug info speeds up builds a bunch, diff --git a/EXAMPLES.md b/EXAMPLES.md index f6eafe88d8..58434106bd 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -23,7 +23,7 @@ | [shaleenjain.com](https://shaleenjain.com) | https://github.com/shalzz/shalzz.github.io | | [Hello, Rust!](https://hello-rust.show) | https://github.com/hello-rust/hello-rust.github.io | | [maxdeviant.com](https://maxdeviant.com/) | | -| [Uwes Blog](https://uwe-arzt.de) | https://github.com/uwearzt/site-uwe-arzt | +| [Uwes Blog](https://uwe-arzt.de) | https://codeberg.org/uwearzt/site-uwe-arzt | | [ozkriff.games](https://ozkriff.games) | https://github.com/ozkriff/ozkriff.github.io-src | | [Sylvain Kerkour](https://kerkour.fr) | https://gitlab.com/z0mbie42/kerkour.fr | | [CodeShow by Bruno Rocha](https://codeshow.com.br) | https://github.com/codeshow/site | @@ -43,3 +43,7 @@ | [146 Parks](https://146parks.blog/) | https://github.com/scouten/146parks.blog | | [films.mlcdf.fr](https://films.mlcdf.fr) | https://github.com/mlcdf/films | | [Mish Ushakov](https://mish.co) | | +| [castor](https://castorisdead.xyz) | https://github.com/whoisYoges/website | +| [mrkaran](https://mrkaran.dev) | https://github.com/mr-karan/website | +| [giuseppedepalma](https://giuseppedepalma.com/) | https://github.com/giusdp/giuseppedepalma.com/ | + diff --git a/azure-pipelines.yml b/azure-pipelines.yml index e8b08d0a0e..b88e90db40 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -21,7 +21,7 @@ stages: rustup_toolchain: stable linux-pinned: imageName: 'ubuntu-20.04' - rustup_toolchain: 1.57.0 + rustup_toolchain: 1.64.0 pool: vmImage: $(imageName) steps: @@ -42,6 +42,10 @@ stages: displayName: Cargo build (Rust TLS) - script: cargo test --all displayName: Cargo test + - script: cargo fmt --check + displayName: Cargo fmt + - script: cargo clippy --workspace -- -Dwarnings + displayName: Cargo clippy - stage: Release @@ -52,7 +56,7 @@ stages: strategy: matrix: windows-stable: - imageName: 'vs2017-win2016' + imageName: 'windows-2019' rustup_toolchain: stable target: 'x86_64-pc-windows-msvc' mac-stable: diff --git a/build.rs b/build.rs index 40f11040bb..71b1c452fd 100644 --- a/build.rs +++ b/build.rs @@ -1,7 +1,3 @@ -// use clap::Shell; - -include!("src/cli.rs"); - fn generate_pe_header() { use time::OffsetDateTime; @@ -18,12 +14,6 @@ fn generate_pe_header() { } fn main() { - // disabled below as it fails in CI - // let mut app = build_cli(); - // app.gen_completions("zola", Shell::Bash, "completions/"); - // app.gen_completions("zola", Shell::Fish, "completions/"); - // app.gen_completions("zola", Shell::Zsh, "completions/"); - // app.gen_completions("zola", Shell::PowerShell, "completions/"); if std::env::var("CARGO_CFG_TARGET_OS").unwrap() != "windows" && std::env::var("PROFILE").unwrap() != "release" { diff --git a/completions/_zola b/completions/_zola deleted file mode 100644 index 3ca5a4d28f..0000000000 --- a/completions/_zola +++ /dev/null @@ -1,144 +0,0 @@ -#compdef zola - -autoload -U is-at-least - -_zola() { - typeset -A opt_args - typeset -a _arguments_options - local ret=1 - - if is-at-least 5.2; then - _arguments_options=(-s -S -C) - else - _arguments_options=(-s -C) - fi - - local context curcontext="$curcontext" state line - _arguments "${_arguments_options[@]}" \ -'-c+[Path to a config file other than config.toml]' \ -'--config=[Path to a config file other than config.toml]' \ -'-h[Prints help information]' \ -'--help[Prints help information]' \ -'-V[Prints version information]' \ -'--version[Prints version information]' \ -":: :_zola_commands" \ -"*::: :->zola" \ -&& ret=0 - case $state in - (zola) - words=($line[1] "${words[@]}") - (( CURRENT += 1 )) - curcontext="${curcontext%:*:*}:zola-command-$line[1]:" - case $line[1] in - (init) -_arguments "${_arguments_options[@]}" \ -'-h[Prints help information]' \ -'--help[Prints help information]' \ -'-V[Prints version information]' \ -'--version[Prints version information]' \ -'::name -- Name of the project. Will create a new directory with that name in the current directory:_files' \ -&& ret=0 -;; -(build) -_arguments "${_arguments_options[@]}" \ -'-u+[Force the base URL to be that value (default to the one in config.toml)]' \ -'--base-url=[Force the base URL to be that value (default to the one in config.toml)]' \ -'-o+[Outputs the generated site in the given path]' \ -'--output-dir=[Outputs the generated site in the given path]' \ -'--drafts[Include drafts when loading the site]' \ -'-h[Prints help information]' \ -'--help[Prints help information]' \ -'-V[Prints version information]' \ -'--version[Prints version information]' \ -&& ret=0 -;; -(serve) -_arguments "${_arguments_options[@]}" \ -'-i+[Interface to bind on]' \ -'--interface=[Interface to bind on]' \ -'-p+[Which port to use]' \ -'--port=[Which port to use]' \ -'-o+[Outputs the generated site in the given path]' \ -'--output-dir=[Outputs the generated site in the given path]' \ -'-u+[Changes the base_url]' \ -'--base-url=[Changes the base_url]' \ -'--watch-only[Do not start a server, just re-build project on changes]' \ -'--drafts[Include drafts when loading the site]' \ -'-O[Open site in the default browser]' \ -'--open[Open site in the default browser]' \ -'-h[Prints help information]' \ -'--help[Prints help information]' \ -'-V[Prints version information]' \ -'--version[Prints version information]' \ -&& ret=0 -;; -(check) -_arguments "${_arguments_options[@]}" \ -'--drafts[Include drafts when loading the site]' \ -'-h[Prints help information]' \ -'--help[Prints help information]' \ -'-V[Prints version information]' \ -'--version[Prints version information]' \ -&& ret=0 -;; -(help) -_arguments "${_arguments_options[@]}" \ -'-h[Prints help information]' \ -'--help[Prints help information]' \ -'-V[Prints version information]' \ -'--version[Prints version information]' \ -&& ret=0 -;; - esac - ;; -esac -} - -(( $+functions[_zola_commands] )) || -_zola_commands() { - local commands; commands=( - "init:Create a new Zola project" \ -"build:Deletes the output directory if there is one and builds the site" \ -"serve:Serve the site. Rebuild and reload on change automatically" \ -"check:Try building the project without rendering it. Checks links" \ -"help:Prints this message or the help of the given subcommand(s)" \ - ) - _describe -t commands 'zola commands' commands "$@" -} -(( $+functions[_zola__build_commands] )) || -_zola__build_commands() { - local commands; commands=( - - ) - _describe -t commands 'zola build commands' commands "$@" -} -(( $+functions[_zola__check_commands] )) || -_zola__check_commands() { - local commands; commands=( - - ) - _describe -t commands 'zola check commands' commands "$@" -} -(( $+functions[_zola__help_commands] )) || -_zola__help_commands() { - local commands; commands=( - - ) - _describe -t commands 'zola help commands' commands "$@" -} -(( $+functions[_zola__init_commands] )) || -_zola__init_commands() { - local commands; commands=( - - ) - _describe -t commands 'zola init commands' commands "$@" -} -(( $+functions[_zola__serve_commands] )) || -_zola__serve_commands() { - local commands; commands=( - - ) - _describe -t commands 'zola serve commands' commands "$@" -} - -_zola "$@" \ No newline at end of file diff --git a/completions/_zola.ps1 b/completions/_zola.ps1 deleted file mode 100644 index 977ee07122..0000000000 --- a/completions/_zola.ps1 +++ /dev/null @@ -1,93 +0,0 @@ - -using namespace System.Management.Automation -using namespace System.Management.Automation.Language - -Register-ArgumentCompleter -Native -CommandName 'zola' -ScriptBlock { - param($wordToComplete, $commandAst, $cursorPosition) - - $commandElements = $commandAst.CommandElements - $command = @( - 'zola' - for ($i = 1; $i -lt $commandElements.Count; $i++) { - $element = $commandElements[$i] - if ($element -isnot [StringConstantExpressionAst] -or - $element.StringConstantType -ne [StringConstantType]::BareWord -or - $element.Value.StartsWith('-')) { - break - } - $element.Value - }) -join ';' - - $completions = @(switch ($command) { - 'zola' { - [CompletionResult]::new('-c', 'c', [CompletionResultType]::ParameterName, 'Path to a config file other than config.toml in the root of project') - [CompletionResult]::new('--config', 'config', [CompletionResultType]::ParameterName, 'Path to a config file other than config.toml in the root of project') - [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Prints help information') - [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information') - [CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Prints version information') - [CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Prints version information') - [CompletionResult]::new('init', 'init', [CompletionResultType]::ParameterValue, 'Create a new Zola project') - [CompletionResult]::new('build', 'build', [CompletionResultType]::ParameterValue, 'Deletes the output directory if there is one and builds the site') - [CompletionResult]::new('serve', 'serve', [CompletionResultType]::ParameterValue, 'Serve the site. Rebuild and reload on change automatically') - [CompletionResult]::new('check', 'check', [CompletionResultType]::ParameterValue, 'Try building the project without rendering it. Checks links') - [CompletionResult]::new('help', 'help', [CompletionResultType]::ParameterValue, 'Prints this message or the help of the given subcommand(s)') - break - } - 'zola;init' { - [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Prints help information') - [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information') - [CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Prints version information') - [CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Prints version information') - break - } - 'zola;build' { - [CompletionResult]::new('-u', 'u', [CompletionResultType]::ParameterName, 'Force the base URL to be that value (default to the one in config.toml)') - [CompletionResult]::new('--base-url', 'base-url', [CompletionResultType]::ParameterName, 'Force the base URL to be that value (default to the one in config.toml)') - [CompletionResult]::new('-o', 'o', [CompletionResultType]::ParameterName, 'Outputs the generated site in the given path') - [CompletionResult]::new('--output-dir', 'output-dir', [CompletionResultType]::ParameterName, 'Outputs the generated site in the given path') - [CompletionResult]::new('--drafts', 'drafts', [CompletionResultType]::ParameterName, 'Include drafts when loading the site') - [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Prints help information') - [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information') - [CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Prints version information') - [CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Prints version information') - break - } - 'zola;serve' { - [CompletionResult]::new('-i', 'i', [CompletionResultType]::ParameterName, 'Interface to bind on') - [CompletionResult]::new('--interface', 'interface', [CompletionResultType]::ParameterName, 'Interface to bind on') - [CompletionResult]::new('-p', 'p', [CompletionResultType]::ParameterName, 'Which port to use') - [CompletionResult]::new('--port', 'port', [CompletionResultType]::ParameterName, 'Which port to use') - [CompletionResult]::new('-o', 'o', [CompletionResultType]::ParameterName, 'Outputs the generated site in the given path') - [CompletionResult]::new('--output-dir', 'output-dir', [CompletionResultType]::ParameterName, 'Outputs the generated site in the given path') - [CompletionResult]::new('-u', 'u', [CompletionResultType]::ParameterName, 'Changes the base_url') - [CompletionResult]::new('--base-url', 'base-url', [CompletionResultType]::ParameterName, 'Changes the base_url') - [CompletionResult]::new('--watch-only', 'watch-only', [CompletionResultType]::ParameterName, 'Do not start a server, just re-build project on changes') - [CompletionResult]::new('--drafts', 'drafts', [CompletionResultType]::ParameterName, 'Include drafts when loading the site') - [CompletionResult]::new('-O', 'O', [CompletionResultType]::ParameterName, 'Open site in the default browser') - [CompletionResult]::new('--open', 'open', [CompletionResultType]::ParameterName, 'Open site in the default browser') - [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Prints help information') - [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information') - [CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Prints version information') - [CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Prints version information') - break - } - 'zola;check' { - [CompletionResult]::new('--drafts', 'drafts', [CompletionResultType]::ParameterName, 'Include drafts when loading the site') - [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Prints help information') - [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information') - [CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Prints version information') - [CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Prints version information') - break - } - 'zola;help' { - [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Prints help information') - [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information') - [CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Prints version information') - [CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Prints version information') - break - } - }) - - $completions.Where{ $_.CompletionText -like "$wordToComplete*" } | - Sort-Object -Property ListItemText -} diff --git a/completions/zola.bash b/completions/zola.bash deleted file mode 100644 index dcaa073423..0000000000 --- a/completions/zola.bash +++ /dev/null @@ -1,187 +0,0 @@ -_zola() { - local i cur prev opts cmds - COMPREPLY=() - cur="${COMP_WORDS[COMP_CWORD]}" - prev="${COMP_WORDS[COMP_CWORD-1]}" - cmd="" - opts="" - - for i in ${COMP_WORDS[@]} - do - case "${i}" in - zola) - cmd="zola" - ;; - - build) - cmd+="__build" - ;; - check) - cmd+="__check" - ;; - help) - cmd+="__help" - ;; - init) - cmd+="__init" - ;; - serve) - cmd+="__serve" - ;; - *) - ;; - esac - done - - case "${cmd}" in - zola) - opts=" -h -V -c --help --version --config init build serve check help" - if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then - COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) - return 0 - fi - case "${prev}" in - - --config) - COMPREPLY=($(compgen -f "${cur}")) - return 0 - ;; - -c) - COMPREPLY=($(compgen -f "${cur}")) - return 0 - ;; - *) - COMPREPLY=() - ;; - esac - COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) - return 0 - ;; - - zola__build) - opts=" -h -V -u -o --drafts --help --version --base-url --output-dir " - if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then - COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) - return 0 - fi - case "${prev}" in - - --base-url) - COMPREPLY=($(compgen -f "${cur}")) - return 0 - ;; - -u) - COMPREPLY=($(compgen -f "${cur}")) - return 0 - ;; - --output-dir) - COMPREPLY=($(compgen -f "${cur}")) - return 0 - ;; - -o) - COMPREPLY=($(compgen -f "${cur}")) - return 0 - ;; - *) - COMPREPLY=() - ;; - esac - COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) - return 0 - ;; - zola__check) - opts=" -h -V --drafts --help --version " - if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then - COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) - return 0 - fi - case "${prev}" in - - *) - COMPREPLY=() - ;; - esac - COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) - return 0 - ;; - zola__help) - opts=" -h -V --help --version " - if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then - COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) - return 0 - fi - case "${prev}" in - - *) - COMPREPLY=() - ;; - esac - COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) - return 0 - ;; - zola__init) - opts=" -h -V --help --version " - if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then - COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) - return 0 - fi - case "${prev}" in - - *) - COMPREPLY=() - ;; - esac - COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) - return 0 - ;; - zola__serve) - opts=" -O -h -V -i -p -o -u --watch-only --drafts --open --help --version --interface --port --output-dir --base-url " - if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then - COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) - return 0 - fi - case "${prev}" in - - --interface) - COMPREPLY=($(compgen -f "${cur}")) - return 0 - ;; - -i) - COMPREPLY=($(compgen -f "${cur}")) - return 0 - ;; - --port) - COMPREPLY=($(compgen -f "${cur}")) - return 0 - ;; - -p) - COMPREPLY=($(compgen -f "${cur}")) - return 0 - ;; - --output-dir) - COMPREPLY=($(compgen -f "${cur}")) - return 0 - ;; - -o) - COMPREPLY=($(compgen -f "${cur}")) - return 0 - ;; - --base-url) - COMPREPLY=($(compgen -f "${cur}")) - return 0 - ;; - -u) - COMPREPLY=($(compgen -f "${cur}")) - return 0 - ;; - *) - COMPREPLY=() - ;; - esac - COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) - return 0 - ;; - esac -} - -complete -F _zola -o bashdefault -o default zola diff --git a/completions/zola.fish b/completions/zola.fish deleted file mode 100644 index a0d1bad893..0000000000 --- a/completions/zola.fish +++ /dev/null @@ -1,29 +0,0 @@ -complete -c zola -n "__fish_use_subcommand" -s c -l config -d 'Path to a config file other than config.toml in the root of project' -complete -c zola -n "__fish_use_subcommand" -s h -l help -d 'Prints help information' -complete -c zola -n "__fish_use_subcommand" -s V -l version -d 'Prints version information' -complete -c zola -n "__fish_use_subcommand" -f -a "init" -d 'Create a new Zola project' -complete -c zola -n "__fish_use_subcommand" -f -a "build" -d 'Deletes the output directory if there is one and builds the site' -complete -c zola -n "__fish_use_subcommand" -f -a "serve" -d 'Serve the site. Rebuild and reload on change automatically' -complete -c zola -n "__fish_use_subcommand" -f -a "check" -d 'Try building the project without rendering it. Checks links' -complete -c zola -n "__fish_use_subcommand" -f -a "help" -d 'Prints this message or the help of the given subcommand(s)' -complete -c zola -n "__fish_seen_subcommand_from init" -s h -l help -d 'Prints help information' -complete -c zola -n "__fish_seen_subcommand_from init" -s V -l version -d 'Prints version information' -complete -c zola -n "__fish_seen_subcommand_from build" -s u -l base-url -d 'Force the base URL to be that value (default to the one in config.toml)' -complete -c zola -n "__fish_seen_subcommand_from build" -s o -l output-dir -d 'Outputs the generated site in the given path' -complete -c zola -n "__fish_seen_subcommand_from build" -l drafts -d 'Include drafts when loading the site' -complete -c zola -n "__fish_seen_subcommand_from build" -s h -l help -d 'Prints help information' -complete -c zola -n "__fish_seen_subcommand_from build" -s V -l version -d 'Prints version information' -complete -c zola -n "__fish_seen_subcommand_from serve" -s i -l interface -d 'Interface to bind on' -complete -c zola -n "__fish_seen_subcommand_from serve" -s p -l port -d 'Which port to use' -complete -c zola -n "__fish_seen_subcommand_from serve" -s o -l output-dir -d 'Outputs the generated site in the given path' -complete -c zola -n "__fish_seen_subcommand_from serve" -s u -l base-url -d 'Changes the base_url' -complete -c zola -n "__fish_seen_subcommand_from serve" -l watch-only -d 'Do not start a server, just re-build project on changes' -complete -c zola -n "__fish_seen_subcommand_from serve" -l drafts -d 'Include drafts when loading the site' -complete -c zola -n "__fish_seen_subcommand_from serve" -s O -l open -d 'Open site in the default browser' -complete -c zola -n "__fish_seen_subcommand_from serve" -s h -l help -d 'Prints help information' -complete -c zola -n "__fish_seen_subcommand_from serve" -s V -l version -d 'Prints version information' -complete -c zola -n "__fish_seen_subcommand_from check" -l drafts -d 'Include drafts when loading the site' -complete -c zola -n "__fish_seen_subcommand_from check" -s h -l help -d 'Prints help information' -complete -c zola -n "__fish_seen_subcommand_from check" -s V -l version -d 'Prints version information' -complete -c zola -n "__fish_seen_subcommand_from help" -s h -l help -d 'Prints help information' -complete -c zola -n "__fish_seen_subcommand_from help" -s V -l version -d 'Prints version information' diff --git a/components/config/examples/generate_sublime.rs b/components/config/examples/generate_sublime.rs index 06a0ba0fbc..283366944f 100644 --- a/components/config/examples/generate_sublime.rs +++ b/components/config/examples/generate_sublime.rs @@ -5,7 +5,7 @@ use libs::syntect::dumps::*; use libs::syntect::highlighting::ThemeSet; -use libs::syntect::parsing::SyntaxSetBuilder; +use libs::syntect::parsing::{SyntaxDefinition, SyntaxSetBuilder}; use std::collections::HashMap; use std::collections::HashSet; use std::env; @@ -27,6 +27,12 @@ fn main() { (Some(ref cmd), Some(ref package_dir), Some(ref packpath_newlines)) if cmd == "synpack" => { let mut builder = SyntaxSetBuilder::new(); builder.add_plain_text_syntax(); + // We add an alias to txt for text + // https://github.com/getzola/zola/issues/1633 + let s = "---\nname: Plain Text\nfile_extensions: [text]\nscope: text.plain\ncontexts: \ + {main: []}"; + let syn = SyntaxDefinition::load_from_str(s, false, None).unwrap(); + builder.add(syn); let base_path = Path::new(&package_dir).to_path_buf(); // First the official Sublime packages diff --git a/components/config/src/config/languages.rs b/components/config/src/config/languages.rs index f7b33288fa..acc0db6af9 100644 --- a/components/config/src/config/languages.rs +++ b/components/config/src/config/languages.rs @@ -30,6 +30,72 @@ pub struct LanguageOptions { pub translations: HashMap, } +impl LanguageOptions { + /// Merges self with another LanguageOptions, erroring if 2 equivalent fields are not None, + /// empty or the default value. + pub fn merge(&mut self, other: &LanguageOptions) -> Result<()> { + macro_rules! merge_field { + ($orig_field:expr,$other_field:expr,$name:expr) => { + match &$orig_field { + None => $orig_field = $other_field.clone(), + Some(cur_value) => { + if let Some(other_field_value) = &$other_field { + bail!( + "`{}` for default language is specified twice, as {:?} and {:?}.", + $name, + cur_value, + other_field_value + ); + } + } + }; + }; + ($cond:expr,$orig_field:expr,$other_field:expr,$name:expr) => { + if $cond { + $orig_field = $other_field.clone(); + } else if !$other_field.is_empty() { + bail!( + "`{}` for default language is specified twice, as {:?} and {:?}.", + $name, + $orig_field, + $other_field + ) + } + }; + } + merge_field!(self.title, other.title, "title"); + merge_field!(self.description, other.description, "description"); + merge_field!( + self.feed_filename == "atom.xml", + self.feed_filename, + other.feed_filename, + "feed_filename" + ); + merge_field!(self.taxonomies.is_empty(), self.taxonomies, other.taxonomies, "taxonomies"); + merge_field!( + self.translations.is_empty(), + self.translations, + other.translations, + "translations" + ); + + self.generate_feed = self.generate_feed || other.generate_feed; + self.build_search_index = self.build_search_index || other.build_search_index; + + if self.search == search::Search::default() { + self.search = other.search.clone(); + } else if self.search != other.search { + bail!( + "`search` for default language is specified twice, as {:?} and {:?}.", + self.search, + other.search + ); + } + + Ok(()) + } +} + /// We want to ensure the language codes are valid ones pub fn validate_code(code: &str) -> Result<()> { if LanguageIdentifier::from_bytes(code.as_bytes()).is_err() { @@ -38,3 +104,64 @@ pub fn validate_code(code: &str) -> Result<()> { Ok(()) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn merge_without_conflict() { + let mut base_default_language_options = LanguageOptions { + title: Some("Site's title".to_string()), + description: None, + generate_feed: true, + feed_filename: "atom.xml".to_string(), + taxonomies: vec![], + build_search_index: true, + search: search::Search::default(), + translations: HashMap::new(), + }; + + let section_default_language_options = LanguageOptions { + title: None, + description: Some("Site's description".to_string()), + generate_feed: false, + feed_filename: "rss.xml".to_string(), + taxonomies: vec![], + build_search_index: true, + search: search::Search::default(), + translations: HashMap::new(), + }; + + base_default_language_options.merge(§ion_default_language_options).unwrap(); + } + + #[test] + fn merge_with_conflict() { + let mut base_default_language_options = LanguageOptions { + title: Some("Site's title".to_string()), + description: Some("Duplicate site description".to_string()), + generate_feed: true, + feed_filename: "".to_string(), + taxonomies: vec![], + build_search_index: true, + search: search::Search::default(), + translations: HashMap::new(), + }; + + let section_default_language_options = LanguageOptions { + title: None, + description: Some("Site's description".to_string()), + generate_feed: false, + feed_filename: "Some feed_filename".to_string(), + taxonomies: vec![], + build_search_index: true, + search: search::Search::default(), + translations: HashMap::new(), + }; + + let res = + base_default_language_options.merge(§ion_default_language_options).unwrap_err(); + assert!(res.to_string().contains("`description` for default language is specified twice")); + } +} diff --git a/components/config/src/config/markup.rs b/components/config/src/config/markup.rs index 9b80ed8935..7cd03520c8 100644 --- a/components/config/src/config/markup.rs +++ b/components/config/src/config/markup.rs @@ -130,8 +130,11 @@ impl Markdown { ) } } else { - bail!("Highlight theme {} not available.\n\ - You can load custom themes by configuring `extra_syntaxes_and_themes` to include a list of folders containing '.tmTheme' files", self.highlight_theme) + bail!( + "Highlight theme {} not available.\n\ + You can load custom themes by configuring `extra_syntaxes_and_themes` to include a list of folders containing '.tmTheme' files", + self.highlight_theme + ) } } @@ -142,8 +145,11 @@ impl Markdown { // Check extra themes if let Some(extra) = &*self.extra_theme_set { if !extra.themes.contains_key(theme_name) { - bail!("Can't export highlight theme {}, as it does not exist.\n\ - Make sure it's spelled correctly, or your custom .tmTheme' is defined properly.", theme_name) + bail!( + "Can't export highlight theme {}, as it does not exist.\n\ + Make sure it's spelled correctly, or your custom .tmTheme' is defined properly.", + theme_name + ) } } } diff --git a/components/config/src/config/mod.rs b/components/config/src/config/mod.rs index 3221166773..e0674276dd 100644 --- a/components/config/src/config/mod.rs +++ b/components/config/src/config/mod.rs @@ -58,6 +58,8 @@ pub struct Config { /// If set, files from static/ will be hardlinked instead of copied to the output dir. pub hard_link_static: bool, pub taxonomies: Vec, + /// The default author for pages. + pub author: Option, /// Whether to compile the `sass` directory and output the css files into the static folder pub compile_sass: bool, @@ -78,6 +80,8 @@ pub struct Config { pub mode: Mode, pub output_dir: String, + /// Whether dotfiles inside the output directory are preserved when rebuilding the site + pub preserve_dotfiles_in_output: bool, pub link_checker: link_checker::LinkChecker, /// The setup for which slugification strategies to use for paths, taxonomies and anchors @@ -101,8 +105,10 @@ pub struct SerializedConfig<'a> { generate_feed: bool, feed_filename: &'a str, taxonomies: &'a [taxonomies::TaxonomyConfig], + author: &'a Option, build_search_index: bool, extra: &'a HashMap, + markdown: &'a markup::Markdown, } impl Config { @@ -124,7 +130,7 @@ impl Config { languages::validate_code(code)?; } - config.add_default_language(); + config.add_default_language()?; config.slugify_taxonomies(); if !config.ignored_content.is_empty() { @@ -150,7 +156,7 @@ impl Config { pub fn default_for_test() -> Self { let mut config = Config::default(); - config.add_default_language(); + config.add_default_language().unwrap(); config.slugify_taxonomies(); config } @@ -203,26 +209,32 @@ impl Config { } } - /// Adds the default language to the list of languages if not present - pub fn add_default_language(&mut self) { - // We automatically insert a language option for the default language *if* it isn't present - // TODO: what to do if there is like an empty dict for the lang? merge it or use the language - // TODO: as source of truth? - if !self.languages.contains_key(&self.default_language) { - self.languages.insert( - self.default_language.clone(), - languages::LanguageOptions { - title: self.title.clone(), - description: self.description.clone(), - generate_feed: self.generate_feed, - feed_filename: self.feed_filename.clone(), - build_search_index: self.build_search_index, - taxonomies: self.taxonomies.clone(), - search: self.search.clone(), - translations: self.translations.clone(), - }, - ); + /// Adds the default language to the list of languages if options for it are specified at base level of config.toml. + /// If section for the same language also exists, the options at this section and base are merged and then adds it + /// to list. + pub fn add_default_language(&mut self) -> Result<()> { + let mut base_language_options = languages::LanguageOptions { + title: self.title.clone(), + description: self.description.clone(), + generate_feed: self.generate_feed, + feed_filename: self.feed_filename.clone(), + build_search_index: self.build_search_index, + taxonomies: self.taxonomies.clone(), + search: self.search.clone(), + translations: self.translations.clone(), + }; + + if let Some(section_language_options) = self.languages.get(&self.default_language) { + if base_language_options == languages::LanguageOptions::default() { + return Ok(()); + } + println!("Warning: config.toml contains both default language specific information at base and under section `[languages.{}]`, \ + which may cause merge conflicts. Please use only one to specify language specific information", self.default_language); + base_language_options.merge(section_language_options)?; } + self.languages.insert(self.default_language.clone(), base_language_options); + + Ok(()) } /// Merges the extra data from the theme with the config extra data @@ -315,8 +327,10 @@ impl Config { generate_feed: options.generate_feed, feed_filename: &options.feed_filename, taxonomies: &options.taxonomies, + author: &self.author, build_search_index: options.build_search_index, extra: &self.extra, + markdown: &self.markdown, } } } @@ -363,6 +377,7 @@ impl Default for Config { feed_filename: "atom.xml".to_string(), hard_link_static: false, taxonomies: Vec::new(), + author: None, compile_sass: false, minify_html: false, mode: Mode::Build, @@ -371,6 +386,7 @@ impl Default for Config { ignored_content_globset: None, translations: HashMap::new(), output_dir: "public".to_string(), + preserve_dotfiles_in_output: false, link_checker: link_checker::LinkChecker::default(), slugify: slugify::Slugify::default(), search: search::Search::default(), @@ -385,6 +401,74 @@ mod tests { use super::*; use utils::slugs::SlugifyStrategy; + #[test] + fn can_add_default_language_with_data_only_at_base_section() { + let title_base = Some("Base section title".to_string()); + let description_base = Some("Base section description".to_string()); + + let mut config = Config::default(); + config.title = title_base.clone(); + config.description = description_base.clone(); + config.add_default_language().unwrap(); + + let default_language_options = + config.languages.get(&config.default_language).unwrap().clone(); + assert_eq!(default_language_options.title, title_base); + assert_eq!(default_language_options.description, description_base); + } + + #[test] + fn can_add_default_language_with_data_at_base_and_language_section() { + let title_base = Some("Base section title".to_string()); + let description_lang_section = Some("Language section description".to_string()); + + let mut config = Config::default(); + config.title = title_base.clone(); + config.languages.insert( + config.default_language.clone(), + languages::LanguageOptions { + title: None, + description: description_lang_section.clone(), + generate_feed: true, + feed_filename: config.feed_filename.clone(), + taxonomies: config.taxonomies.clone(), + build_search_index: false, + search: search::Search::default(), + translations: config.translations.clone(), + }, + ); + config.add_default_language().unwrap(); + + let default_language_options = + config.languages.get(&config.default_language).unwrap().clone(); + assert_eq!(default_language_options.title, title_base); + assert_eq!(default_language_options.description, description_lang_section); + } + + #[test] + fn errors_when_same_field_present_at_base_and_language_section() { + let title_base = Some("Base section title".to_string()); + let title_lang_section = Some("Language section title".to_string()); + + let mut config = Config::default(); + config.title = title_base.clone(); + config.languages.insert( + config.default_language.clone(), + languages::LanguageOptions { + title: title_lang_section.clone(), + description: None, + generate_feed: true, + feed_filename: config.feed_filename.clone(), + taxonomies: config.taxonomies.clone(), + build_search_index: false, + search: search::Search::default(), + translations: config.translations.clone(), + }, + ); + let result = config.add_default_language(); + assert!(result.is_err()); + } + #[test] fn can_import_valid_config() { let config = r#" @@ -658,10 +742,30 @@ anchors = "off" let config = Config::parse(config_str).unwrap(); assert_eq!(config.slugify.paths, SlugifyStrategy::On); + assert_eq!(config.slugify.paths_keep_dates, false); assert_eq!(config.slugify.taxonomies, SlugifyStrategy::Safe); assert_eq!(config.slugify.anchors, SlugifyStrategy::Off); } + #[test] + fn slugify_paths_keep_dates() { + let config_str = r#" +title = "My site" +base_url = "example.com" + +[slugify] +paths_keep_dates = true +taxonomies = "off" +anchors = "safe" + "#; + + let config = Config::parse(config_str).unwrap(); + assert_eq!(config.slugify.paths, SlugifyStrategy::On); + assert_eq!(config.slugify.paths_keep_dates, true); + assert_eq!(config.slugify.taxonomies, SlugifyStrategy::Off); + assert_eq!(config.slugify.anchors, SlugifyStrategy::Safe); + } + #[test] fn cannot_overwrite_theme_mapping_with_invalid_type() { let config_str = r#" @@ -744,4 +848,30 @@ title = "Zola" let serialised = config.serialize(&config.default_language); assert_eq!(serialised.title, &config.title); } + + #[test] + fn markdown_config_in_serializedconfig() { + let config = r#" +base_url = "https://www.getzola.org/" +title = "Zola" +[markdown] +highlight_code = true +highlight_theme = "css" + "#; + + let config = Config::parse(config).unwrap(); + let serialised = config.serialize(&config.default_language); + assert_eq!(serialised.markdown.highlight_theme, config.markdown.highlight_theme); + } + + #[test] + fn sets_default_author_if_present() { + let config = r#" +title = "My Site" +base_url = "example.com" +author = "person@example.com (Some Person)" +"#; + let config = Config::parse(config).unwrap(); + assert_eq!(config.author, Some("person@example.com (Some Person)".to_owned())) + } } diff --git a/components/config/src/config/search.rs b/components/config/src/config/search.rs index aa36c46e76..c96a874847 100644 --- a/components/config/src/config/search.rs +++ b/components/config/src/config/search.rs @@ -1,5 +1,18 @@ use serde::{Deserialize, Serialize}; +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum IndexFormat { + ElasticlunrJson, + ElasticlunrJavascript, +} + +impl Default for IndexFormat { + fn default() -> IndexFormat { + IndexFormat::ElasticlunrJavascript + } +} + #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(default)] pub struct Search { @@ -15,6 +28,8 @@ pub struct Search { pub include_description: bool, /// Include the path of the page in the search index. `false` by default. pub include_path: bool, + /// Foramt of the search index to be produced. Javascript by default + pub index_format: IndexFormat, } impl Default for Search { @@ -25,6 +40,7 @@ impl Default for Search { include_description: false, include_path: false, truncate_content_length: None, + index_format: Default::default(), } } } diff --git a/components/config/src/config/slugify.rs b/components/config/src/config/slugify.rs index c22065b34d..1a67376c2a 100644 --- a/components/config/src/config/slugify.rs +++ b/components/config/src/config/slugify.rs @@ -6,6 +6,7 @@ use utils::slugs::SlugifyStrategy; #[serde(default)] pub struct Slugify { pub paths: SlugifyStrategy, + pub paths_keep_dates: bool, pub taxonomies: SlugifyStrategy, pub anchors: SlugifyStrategy, } diff --git a/components/config/src/lib.rs b/components/config/src/lib.rs index 59c6568b74..05d621615c 100644 --- a/components/config/src/lib.rs +++ b/components/config/src/lib.rs @@ -5,8 +5,13 @@ mod theme; use std::path::Path; pub use crate::config::{ - languages::LanguageOptions, link_checker::LinkChecker, link_checker::LinkCheckerLevel, - search::Search, slugify::Slugify, taxonomies::TaxonomyConfig, Config, + languages::LanguageOptions, + link_checker::LinkChecker, + link_checker::LinkCheckerLevel, + search::{IndexFormat, Search}, + slugify::Slugify, + taxonomies::TaxonomyConfig, + Config, }; use errors::Result; diff --git a/components/content/Cargo.toml b/components/content/Cargo.toml index 9a958badcf..df99263b12 100644 --- a/components/content/Cargo.toml +++ b/components/content/Cargo.toml @@ -16,5 +16,5 @@ config = { path = "../config" } markdown = { path = "../markdown" } [dev-dependencies] -test-case = "2" # TODO: can we solve that usecase in src/page.rs in a simpler way? A custom macro_rules! maybe +test-case = "3" # TODO: can we solve that usecase in src/page.rs in a simpler way? A custom macro_rules! maybe tempfile = "3.3.0" diff --git a/components/content/src/file_info.rs b/components/content/src/file_info.rs index 941d8ea129..a94cb4310a 100644 --- a/components/content/src/file_info.rs +++ b/components/content/src/file_info.rs @@ -26,7 +26,7 @@ pub fn find_content_components>(path: P) -> Vec { } /// Struct that contains all the information about the actual file -#[derive(Debug, Default, Clone, PartialEq)] +#[derive(Debug, Default, Clone, PartialEq, Eq)] pub struct FileInfo { /// The full path to the .md file pub path: PathBuf, @@ -37,6 +37,9 @@ pub struct FileInfo { pub name: String, /// The .md path, starting from the content directory, with `/` slashes pub relative: String, + /// The path from the content directory to the colocated directory. Ends with a `/` when set. + /// Only filled if it is a colocated directory, None otherwise. + pub colocated_path: Option, /// Path of the directory containing the .md file pub parent: PathBuf, /// Path of the grand parent directory for that file. Only used in sections to find subsections. @@ -57,17 +60,24 @@ impl FileInfo { let name = path.file_stem().unwrap().to_string_lossy().to_string(); let canonical = parent.join(&name); let mut components = - find_content_components(&file_path.strip_prefix(base_path).unwrap_or(&file_path)); + find_content_components(file_path.strip_prefix(base_path).unwrap_or(&file_path)); let relative = if !components.is_empty() { format!("{}/{}.md", components.join("/"), name) } else { format!("{}.md", name) }; + let mut colocated_path = None; // If we have a folder with an asset, don't consider it as a component // Splitting on `.` as we might have a language so it isn't *only* index but also index.fr // etc if !components.is_empty() && name.split('.').collect::>()[0] == "index" { + colocated_path = Some({ + let mut val = components.join("/"); + val.push('/'); + val + }); + components.pop(); // also set parent_path to grandparent instead parent = parent.parent().unwrap().to_path_buf(); @@ -83,6 +93,7 @@ impl FileInfo { name, components, relative, + colocated_path, } } @@ -91,7 +102,7 @@ impl FileInfo { let parent = path.parent().expect("Get parent of section").to_path_buf(); let name = path.file_stem().unwrap().to_string_lossy().to_string(); let components = - find_content_components(&file_path.strip_prefix(base_path).unwrap_or(&file_path)); + find_content_components(file_path.strip_prefix(base_path).unwrap_or(&file_path)); let relative = if !components.is_empty() { format!("{}/{}.md", components.join("/"), name) } else { @@ -108,6 +119,7 @@ impl FileInfo { name, components, relative, + colocated_path: None, } } @@ -171,6 +183,7 @@ mod tests { &PathBuf::new(), ); assert_eq!(file.components, ["posts".to_string(), "tutorials".to_string()]); + assert_eq!(file.colocated_path, Some("posts/tutorials/python/".to_string())); } #[test] @@ -211,6 +224,7 @@ mod tests { &PathBuf::new(), ); assert_eq!(file.components, ["posts".to_string(), "tutorials".to_string()]); + assert_eq!(file.colocated_path, Some("posts/tutorials/python/".to_string())); let res = file.find_language("en", &["fr"]); assert!(res.is_ok()); assert_eq!(res.unwrap(), "fr"); diff --git a/components/content/src/front_matter/page.rs b/components/content/src/front_matter/page.rs index 8213b6712e..4d2ed5ebfb 100644 --- a/components/content/src/front_matter/page.rs +++ b/components/content/src/front_matter/page.rs @@ -12,7 +12,7 @@ use utils::de::{fix_toml_dates, from_toml_datetime}; use crate::front_matter::split::RawFrontMatter; /// The front matter of every page -#[derive(Debug, Clone, PartialEq, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] #[serde(default)] pub struct PageFrontMatter { /// of the page @@ -49,6 +49,8 @@ pub struct PageFrontMatter { pub taxonomies: HashMap<String, Vec<String>>, /// Integer to use to order content. Highest is at the bottom, lowest first pub weight: Option<usize>, + /// The authors of the page. + pub authors: Vec<String>, /// All aliases for that page. Zola will create HTML templates that will /// redirect to this #[serde(skip_serializing)] @@ -103,6 +105,14 @@ impl PageFrontMatter { f.date_to_datetime(); + for terms in f.taxonomies.values() { + for term in terms { + if term.trim().is_empty() { + bail!("A taxonomy term cannot be an empty string"); + } + } + } + if let Some(ref date) = f.date { if f.datetime.is_none() { bail!("`date` could not be parsed: {}.", date); @@ -145,6 +155,7 @@ impl Default for PageFrontMatter { path: None, taxonomies: HashMap::new(), weight: None, + authors: Vec::new(), aliases: Vec::new(), template: None, extra: Map::new(), @@ -384,20 +395,20 @@ title = "Hello" description = "hey there" [extra] -some-date = 2002-14-01 +some-date = 2002-11-01 "#); "toml")] #[test_case(&RawFrontMatter::Yaml(r#" title: Hello description: hey there extra: - some-date: 2002-14-01 + some-date: 2002-11-01 "#); "yaml")] fn can_parse_dates_in_extra(content: &RawFrontMatter) { let res = PageFrontMatter::parse(content); println!("{:?}", res); assert!(res.is_ok()); - assert_eq!(res.unwrap().extra["some-date"], to_value("2002-14-01").unwrap()); + assert_eq!(res.unwrap().extra["some-date"], to_value("2002-11-01").unwrap()); } #[test_case(&RawFrontMatter::Toml(r#" @@ -405,7 +416,7 @@ title = "Hello" description = "hey there" [extra.something] -some-date = 2002-14-01 +some-date = 2002-11-01 "#); "toml")] #[test_case(&RawFrontMatter::Yaml(r#" title: Hello @@ -413,13 +424,13 @@ description: hey there extra: something: - some-date: 2002-14-01 + some-date: 2002-11-01 "#); "yaml")] fn can_parse_nested_dates_in_extra(content: &RawFrontMatter) { let res = PageFrontMatter::parse(content); println!("{:?}", res); assert!(res.is_ok()); - assert_eq!(res.unwrap().extra["something"]["some-date"], to_value("2002-14-01").unwrap()); + assert_eq!(res.unwrap().extra["something"]["some-date"], to_value("2002-11-01").unwrap()); } #[test_case(&RawFrontMatter::Toml(r#" @@ -474,4 +485,47 @@ taxonomies: assert_eq!(res2.taxonomies["categories"], vec!["Dev"]); assert_eq!(res2.taxonomies["tags"], vec!["Rust", "JavaScript"]); } + + #[test_case(&RawFrontMatter::Toml(r#" +title = "Hello World" + +[taxonomies] +tags = [""] +"#); "toml")] + #[test_case(&RawFrontMatter::Yaml(r#" +title: Hello World + +taxonomies: + tags: + - +"#); "yaml")] + fn errors_on_empty_taxonomy_term(content: &RawFrontMatter) { + // https://github.com/getzola/zola/issues/2085 + let res = PageFrontMatter::parse(content); + println!("{:?}", res); + assert!(res.is_err()); + } + + #[test_case(&RawFrontMatter::Toml(r#" +authors = ["person1@example.com (Person One)", "person2@example.com (Person Two)"] +"#); "toml")] + #[test_case(&RawFrontMatter::Yaml(r#" +title: Hello World +authors: + - person1@example.com (Person One) + - person2@example.com (Person Two) +"#); "yaml")] + fn can_parse_authors(content: &RawFrontMatter) { + let res = PageFrontMatter::parse(content); + assert!(res.is_ok()); + let res2 = res.unwrap(); + assert_eq!(res2.authors.len(), 2); + assert_eq!( + vec!( + "person1@example.com (Person One)".to_owned(), + "person2@example.com (Person Two)".to_owned() + ), + res2.authors + ); + } } diff --git a/components/content/src/front_matter/section.rs b/components/content/src/front_matter/section.rs index 9a26697329..ee0777998f 100644 --- a/components/content/src/front_matter/section.rs +++ b/components/content/src/front_matter/section.rs @@ -11,7 +11,7 @@ use crate::SortBy; static DEFAULT_PAGINATE_PATH: &str = "page"; /// The front matter of every section -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(default)] pub struct SectionFrontMatter { /// <title> of the page diff --git a/components/content/src/library.rs b/components/content/src/library.rs index a4e4b4e3ce..485b88dacd 100644 --- a/components/content/src/library.rs +++ b/components/content/src/library.rs @@ -271,12 +271,12 @@ impl Library { section.ignored_pages.clear(); section.ancestors.clear(); - if let Some(children) = subsections.get(&*path) { + if let Some(children) = subsections.get(path) { let mut children: Vec<_> = children.clone(); children.sort_by(|a, b| sections_weight[a].cmp(§ions_weight[b])); section.subsections = children; } - if let Some(parents) = ancestors.get(&*path) { + if let Some(parents) = ancestors.get(path) { section.ancestors = parents.clone(); } } @@ -295,7 +295,7 @@ impl Library { for (path, page) in self.pages.iter_mut() { let parent_filename = &index_filename_by_lang[&page.lang]; add_translation(&page.file.canonical, path); - let mut parent_section_path = page.file.parent.join(&parent_filename); + let mut parent_section_path = page.file.parent.join(parent_filename); while let Some(parent_section) = self.sections.get_mut(&parent_section_path) { let is_transparent = parent_section.meta.transparent; @@ -323,7 +323,7 @@ impl Library { // We've added `_index(.{LANG})?.md` so if we are here so we need to go up twice match parent_section_path.clone().parent().unwrap().parent() { - Some(parent) => parent_section_path = parent.join(&parent_filename), + Some(parent) => parent_section_path = parent.join(parent_filename), None => break, } } diff --git a/components/content/src/page.rs b/components/content/src/page.rs index 57d9812f85..94672b2bd2 100644 --- a/components/content/src/page.rs +++ b/components/content/src/page.rs @@ -31,9 +31,11 @@ static RFC3339_DATE: Lazy<Regex> = Lazy::new(|| { ).unwrap() }); -static FOOTNOTES_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"<sup\s*.*?>\s*.*?</sup>").unwrap()); +static FOOTNOTES_RE: Lazy<Regex> = Lazy::new(|| { + Regex::new(r#"<sup class="footnote-reference"><a href=\s*.*?>\s*.*?</a></sup>"#).unwrap() +}); -#[derive(Clone, Debug, Default, PartialEq)] +#[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct Page { /// All info about the actual file pub file: FileInfo, @@ -126,7 +128,9 @@ impl Page { }; if let Some(ref caps) = RFC3339_DATE.captures(&file_path_for_slug) { - slug_from_dated_filename = Some(caps.name("slug").unwrap().as_str().to_string()); + if !config.slugify.paths_keep_dates { + slug_from_dated_filename = Some(caps.name("slug").unwrap().as_str().to_string()); + } if page.meta.date.is_none() { page.meta.date = Some(caps.name("datetime").unwrap().as_str().to_string()); page.meta.date_to_datetime(); @@ -153,7 +157,11 @@ impl Page { } } else { let mut path = if page.file.components.is_empty() { - page.slug.clone() + if page.file.name == "index" && page.file.colocated_path.is_none() { + String::new() + } else { + page.slug.clone() + } } else { format!("{}/{}", page.file.components.join("/"), page.slug) }; @@ -227,7 +235,7 @@ impl Page { self.summary = res .summary_len .map(|l| &res.body[0..l]) - .map(|s| FOOTNOTES_RE.replace(s, "").into_owned()); + .map(|s| FOOTNOTES_RE.replace_all(s, "").into_owned()); self.content = res.body; self.toc = res.toc; self.external_links = res.external_links; @@ -258,7 +266,7 @@ impl Page { fn serialize_assets(&self, base_path: &Path) -> Vec<String> { self.assets .iter() - .filter_map(|asset| asset.strip_prefix(&self.file.path.parent().unwrap()).ok()) + .filter_map(|asset| asset.strip_prefix(self.file.path.parent().unwrap()).ok()) .filter_map(|filename| filename.to_str()) .map(|filename| { let mut path = self.file.path.clone(); @@ -337,6 +345,32 @@ Hello world"#; assert_eq!(page.content, "<p>Hello world</p>\n".to_string()); } + #[test] + fn can_parse_author() { + let config = Config::default_for_test(); + let content = r#" ++++ +title = "Hello" +description = "hey there" +authors = ["person@example.com (A. Person)"] ++++ +Hello world"#; + let res = Page::parse(Path::new("post.md"), content, &config, &PathBuf::new()); + assert!(res.is_ok()); + let mut page = res.unwrap(); + page.render_markdown( + &HashMap::default(), + &Tera::default(), + &config, + InsertAnchor::None, + &HashMap::new(), + ) + .unwrap(); + + assert_eq!(1, page.meta.authors.len()); + assert_eq!("person@example.com (A. Person)", page.meta.authors.get(0).unwrap()); + } + #[test] fn test_can_make_url_from_sections_and_slug() { let content = r#" @@ -511,15 +545,19 @@ Hello world let content = r#" +++ +++ -This page has footnotes, here's one. [^1] +This page use <sup>1.5</sup> and has footnotes, here's one. [^1] + +Here's another. [^2] <!-- more --> -And here's another. [^2] +And here's another. [^3] [^1]: This is the first footnote. -[^2]: This is the second footnote."# +[^2]: This is the secund footnote. + +[^3]: This is the third footnote."# .to_string(); let res = Page::parse(Path::new("hello.md"), &content, &config, &PathBuf::new()); assert!(res.is_ok()); @@ -534,7 +572,7 @@ And here's another. [^2] .unwrap(); assert_eq!( page.summary, - Some("<p>This page has footnotes, here\'s one. </p>\n".to_string()) + Some("<p>This page use <sup>1.5</sup> and has footnotes, here\'s one. </p>\n<p>Here's another. </p>\n".to_string()) ); } @@ -663,6 +701,37 @@ And here's another. [^2] assert_eq!(page.assets[0].file_name().unwrap().to_str(), Some("graph.jpg")); } + // https://github.com/getzola/zola/issues/1566 + #[test] + fn colocated_page_with_slug_and_date_in_path() { + let tmp_dir = tempdir().expect("create temp dir"); + let path = tmp_dir.path(); + create_dir(&path.join("content")).expect("create content temp dir"); + let articles_path = path.join("content").join("articles"); + create_dir(&articles_path).expect("create posts temp dir"); + + let config = Config::default(); + + // first a non-colocated one + let file_path = articles_path.join("2021-07-29-sample-article-1.md"); + let mut f = File::create(&file_path).unwrap(); + f.write_all(b"+++\nslug=\"hey\"\n+++\n").unwrap(); + let res = Page::from_file(&file_path, &config, path); + assert!(res.is_ok()); + let page = res.unwrap(); + assert_eq!(page.path, "/articles/hey/"); + + // then a colocated one, it should still work + let dir_path = articles_path.join("2021-07-29-sample-article-2.md"); + create_dir(&dir_path).expect("create posts temp dir"); + let mut f = File::create(&dir_path.join("index.md")).unwrap(); + f.write_all(b"+++\nslug=\"ho\"\n+++\n").unwrap(); + let res = Page::from_file(&dir_path.join("index.md"), &config, path); + assert!(res.is_ok()); + let page = res.unwrap(); + assert_eq!(page.path, "/articles/ho/"); + } + #[test] fn can_get_date_from_short_date_in_filename() { let config = Config::default(); diff --git a/components/content/src/pagination.rs b/components/content/src/pagination.rs index d4d23de0d9..b1680d229c 100644 --- a/components/content/src/pagination.rs +++ b/components/content/src/pagination.rs @@ -13,14 +13,14 @@ use crate::ser::{SectionSerMode, SerializingPage, SerializingSection}; use crate::taxonomies::{Taxonomy, TaxonomyTerm}; use crate::Section; -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] enum PaginationRoot<'a> { Section(&'a Section), Taxonomy(&'a Taxonomy, &'a TaxonomyTerm), } /// A list of all the pages in the paginator with their index and links -#[derive(Clone, Debug, PartialEq, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize)] pub struct Pager<'a> { /// The page number in the paginator (1-indexed) pub index: usize, @@ -43,7 +43,7 @@ impl<'a> Pager<'a> { } } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct Paginator<'a> { /// All pages in the section/taxonomy all_pages: Cow<'a, [PathBuf]>, @@ -205,13 +205,13 @@ impl<'a> Paginator<'a> { } else { paginator.insert("next", Value::Null); } - paginator.insert("number_pagers", to_value(&self.pagers.len()).unwrap()); + paginator.insert("number_pagers", to_value(self.pagers.len()).unwrap()); let base_url = if self.paginate_path.is_empty() { self.permalink.to_string() } else { format!("{}{}/", self.permalink, self.paginate_path) }; - paginator.insert("base_url", to_value(&base_url).unwrap()); + paginator.insert("base_url", to_value(base_url).unwrap()); paginator.insert("pages", to_value(¤t_pager.pages).unwrap()); paginator.insert("current_index", to_value(current_pager.index).unwrap()); paginator.insert("total_pages", to_value(self.all_pages.len()).unwrap()); diff --git a/components/content/src/section.rs b/components/content/src/section.rs index 4dadfc20c2..920f863ee6 100644 --- a/components/content/src/section.rs +++ b/components/content/src/section.rs @@ -7,6 +7,7 @@ use config::Config; use errors::{Context, Result}; use markdown::{render_content, RenderContext}; use utils::fs::read_file; +use utils::net::is_external_link; use utils::table_of_contents::Heading; use utils::templates::{render_template, ShortcodeDefinition}; @@ -17,7 +18,7 @@ use crate::ser::{SectionSerMode, SerializingSection}; use crate::utils::{find_related_assets, get_reading_analytics, has_anchor}; // Default is used to create a default index section if there is no _index.md in the root content directory -#[derive(Clone, Debug, Default, PartialEq)] +#[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct Section { /// All info about the actual file pub file: FileInfo, @@ -168,7 +169,14 @@ impl Section { .with_context(|| format!("Failed to render content of {}", self.file.path.display()))?; self.content = res.body; self.toc = res.toc; + self.external_links = res.external_links; + if let Some(ref redirect_to) = self.meta.redirect_to { + if is_external_link(redirect_to) { + self.external_links.push(redirect_to.to_owned()); + } + } + self.internal_links = res.internal_links; Ok(()) @@ -198,7 +206,7 @@ impl Section { fn serialize_assets(&self) -> Vec<String> { self.assets .iter() - .filter_map(|asset| asset.strip_prefix(&self.file.path.parent().unwrap()).ok()) + .filter_map(|asset| asset.strip_prefix(self.file.path.parent().unwrap()).ok()) .filter_map(|filename| filename.to_str()) .map(|filename| format!("{}{}", self.path, filename)) .collect() @@ -356,4 +364,24 @@ Bonjour le monde"# assert_eq!(section.lang, "fr".to_string()); assert_eq!(section.permalink, "http://a-website.com/fr/subcontent/"); } + + #[test] + fn can_redirect_to_external_site() { + let config = Config::default(); + let content = r#" ++++ +redirect_to = "https://bar.com/something" ++++ +Example"# + .to_string(); + let res = Section::parse( + Path::new("content/subcontent/_index.md"), + &content, + &config, + &PathBuf::new(), + ); + assert!(res.is_ok()); + let section = res.unwrap(); + assert_eq!(section.meta.redirect_to, Some("https://bar.com/something".to_owned())); + } } diff --git a/components/content/src/ser.rs b/components/content/src/ser.rs index e27a49fbc0..18e175f11d 100644 --- a/components/content/src/ser.rs +++ b/components/content/src/ser.rs @@ -8,13 +8,13 @@ use crate::{Page, Section}; use libs::tera::{Map, Value}; use utils::table_of_contents::Heading; -#[derive(Clone, Debug, PartialEq, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize)] pub struct BackLink<'a> { pub permalink: &'a str, pub title: &'a Option<String>, } -#[derive(Clone, Debug, PartialEq, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize)] pub struct TranslatedContent<'a> { pub lang: &'a str, pub permalink: &'a str, @@ -39,9 +39,10 @@ fn find_backlinks<'a>(relative_path: &str, library: &'a Library) -> Vec<BackLink backlinks } -#[derive(Clone, Debug, PartialEq, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize)] pub struct SerializingPage<'a> { relative_path: &'a str, + colocated_path: &'a Option<String>, content: &'a str, permalink: &'a str, slug: &'a str, @@ -54,6 +55,7 @@ pub struct SerializingPage<'a> { month: Option<u8>, day: Option<u8>, taxonomies: &'a HashMap<String, Vec<String>>, + authors: &'a [String], extra: &'a Map<String, Value>, path: &'a str, components: &'a [String], @@ -104,6 +106,7 @@ impl<'a> SerializingPage<'a> { Self { relative_path: &page.file.relative, + colocated_path: &page.file.colocated_path, ancestors: &page.ancestors, content: &page.content, permalink: &page.permalink, @@ -117,6 +120,7 @@ impl<'a> SerializingPage<'a> { month, day, taxonomies: &page.meta.taxonomies, + authors: &page.meta.authors, path: &page.path, components: &page.components, summary: &page.summary, @@ -134,9 +138,10 @@ impl<'a> SerializingPage<'a> { } } -#[derive(Clone, Debug, PartialEq, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize)] pub struct SerializingSection<'a> { relative_path: &'a str, + colocated_path: &'a Option<String>, content: &'a str, permalink: &'a str, draft: bool, @@ -155,6 +160,7 @@ pub struct SerializingSection<'a> { subsections: Vec<&'a str>, translations: Vec<TranslatedContent<'a>>, backlinks: Vec<BackLink<'a>>, + generate_feed: bool, } #[derive(Debug)] @@ -198,6 +204,7 @@ impl<'a> SerializingSection<'a> { Self { relative_path: §ion.file.relative, + colocated_path: §ion.file.colocated_path, ancestors: §ion.ancestors, draft: section.meta.draft, content: §ion.content, @@ -212,6 +219,7 @@ impl<'a> SerializingSection<'a> { reading_time: section.reading_time, assets: §ion.serialized_assets, lang: §ion.lang, + generate_feed: section.meta.generate_feed, pages, subsections, translations, diff --git a/components/content/src/sorting.rs b/components/content/src/sorting.rs index 313df3674c..eb4b91fb1e 100644 --- a/components/content/src/sorting.rs +++ b/components/content/src/sorting.rs @@ -16,6 +16,7 @@ pub fn sort_pages(pages: &[&Page], sort_by: SortBy) -> (Vec<PathBuf>, Vec<PathBu } SortBy::Title | SortBy::TitleBytes => page.meta.title.is_some(), SortBy::Weight => page.meta.weight.is_some(), + SortBy::Slug => true, SortBy::None => unreachable!(), }); @@ -32,6 +33,7 @@ pub fn sort_pages(pages: &[&Page], sort_by: SortBy) -> (Vec<PathBuf>, Vec<PathBu a.meta.title.as_ref().unwrap().cmp(b.meta.title.as_ref().unwrap()) } SortBy::Weight => a.meta.weight.unwrap().cmp(&b.meta.weight.unwrap()), + SortBy::Slug => natural_lexical_cmp(&a.slug, &b.slug), SortBy::None => unreachable!(), }; @@ -73,6 +75,16 @@ mod tests { Page::new(format!("content/hello-{}.md", weight), front_matter, &PathBuf::new()) } + fn create_page_with_slug(slug: &str) -> Page { + let front_matter = PageFrontMatter { slug: Some(slug.to_owned()), ..Default::default() }; + let mut page = + Page::new(format!("content/hello-{}.md", slug), front_matter, &PathBuf::new()); + // Normally, the slug field is populated when a page is parsed, but + // since we're creating one manually, we have to set it explicitly + page.slug = slug.to_owned(); + page + } + #[test] fn can_sort_by_dates() { let page1 = create_page_with_date("2018-01-01", None); @@ -185,6 +197,28 @@ mod tests { ); } + #[test] + fn can_sort_by_slug() { + let page1 = create_page_with_slug("2"); + let page2 = create_page_with_slug("3"); + let page3 = create_page_with_slug("1"); + let (pages, ignored_pages) = sort_pages(&[&page1, &page2, &page3], SortBy::Slug); + assert_eq!(pages[0], page3.file.path); + assert_eq!(pages[1], page1.file.path); + assert_eq!(pages[2], page2.file.path); + assert_eq!(ignored_pages.len(), 0); + + // 10 should come after 2 + let page1 = create_page_with_slug("1"); + let page2 = create_page_with_slug("10"); + let page3 = create_page_with_slug("2"); + let (pages, ignored_pages) = sort_pages(&[&page1, &page2, &page3], SortBy::Slug); + assert_eq!(pages[0], page1.file.path); + assert_eq!(pages[1], page3.file.path); + assert_eq!(pages[2], page2.file.path); + assert_eq!(ignored_pages.len(), 0); + } + #[test] fn can_find_ignored_pages() { let page1 = create_page_with_date("2018-01-01", None); diff --git a/components/content/src/taxonomies.rs b/components/content/src/taxonomies.rs index 52c86e0c01..b06996668b 100644 --- a/components/content/src/taxonomies.rs +++ b/components/content/src/taxonomies.rs @@ -16,21 +16,24 @@ use crate::{Page, SortBy}; use crate::sorting::sort_pages; -#[derive(Debug, Clone, PartialEq, Serialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] pub struct SerializedTaxonomyTerm<'a> { name: &'a str, slug: &'a str, path: &'a str, permalink: &'a str, pages: Vec<SerializingPage<'a>>, + page_count: usize, } impl<'a> SerializedTaxonomyTerm<'a> { - pub fn from_item(item: &'a TaxonomyTerm, library: &'a Library) -> Self { + pub fn from_item(item: &'a TaxonomyTerm, library: &'a Library, include_pages: bool) -> Self { let mut pages = vec![]; - for p in &item.pages { - pages.push(SerializingPage::new(&library.pages[p], Some(library), false)); + if include_pages { + for p in &item.pages { + pages.push(SerializingPage::new(&library.pages[p], Some(library), false)); + } } SerializedTaxonomyTerm { @@ -39,6 +42,7 @@ impl<'a> SerializedTaxonomyTerm<'a> { path: &item.path, permalink: &item.permalink, pages, + page_count: item.pages.len(), } } } @@ -79,7 +83,14 @@ impl TaxonomyTerm { } pub fn serialize<'a>(&'a self, library: &'a Library) -> SerializedTaxonomyTerm<'a> { - SerializedTaxonomyTerm::from_item(self, library) + SerializedTaxonomyTerm::from_item(self, library, true) + } + + pub fn serialize_without_pages<'a>( + &'a self, + library: &'a Library, + ) -> SerializedTaxonomyTerm<'a> { + SerializedTaxonomyTerm::from_item(self, library, false) } pub fn merge(&mut self, other: Self) { @@ -93,7 +104,9 @@ impl PartialEq for TaxonomyTerm { } } -#[derive(Debug, Clone, PartialEq, Serialize)] +impl Eq for TaxonomyTerm {} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] pub struct SerializedTaxonomy<'a> { kind: &'a TaxonomyConfig, lang: &'a str, @@ -103,8 +116,11 @@ pub struct SerializedTaxonomy<'a> { impl<'a> SerializedTaxonomy<'a> { pub fn from_taxonomy(taxonomy: &'a Taxonomy, library: &'a Library) -> Self { - let items: Vec<SerializedTaxonomyTerm> = - taxonomy.items.iter().map(|i| SerializedTaxonomyTerm::from_item(i, library)).collect(); + let items: Vec<SerializedTaxonomyTerm> = taxonomy + .items + .iter() + .map(|i| SerializedTaxonomyTerm::from_item(i, library, true)) + .collect(); SerializedTaxonomy { kind: &taxonomy.kind, lang: &taxonomy.lang, @@ -114,7 +130,7 @@ impl<'a> SerializedTaxonomy<'a> { } } /// All different taxonomies we have and their content -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Taxonomy { pub kind: TaxonomyConfig, pub lang: String, @@ -173,13 +189,7 @@ impl Taxonomy { config: &Config, library: &Library, ) -> Result<String> { - let mut context = Context::new(); - context.insert("config", &config.serialize(&self.lang)); - context.insert("lang", &self.lang); - context.insert("term", &SerializedTaxonomyTerm::from_item(item, library)); - context.insert("taxonomy", &self.kind); - context.insert("current_url", &self.permalink); - context.insert("current_path", &self.path); + let context = self.build_term_context(item, config, library); // Check for taxon-specific template, or use generic as fallback. let specific_template = format!("{}/single.html", self.kind.name); @@ -190,6 +200,22 @@ impl Taxonomy { .with_context(|| format!("Failed to render single term {} page.", self.kind.name)) } + fn build_term_context( + &self, + item: &TaxonomyTerm, + config: &Config, + library: &Library, + ) -> Context { + let mut context = Context::new(); + context.insert("config", &config.serialize(&self.lang)); + context.insert("lang", &self.lang); + context.insert("term", &SerializedTaxonomyTerm::from_item(item, library, true)); + context.insert("taxonomy", &self.kind); + context.insert("current_url", &item.permalink); + context.insert("current_path", &item.path); + context + } + pub fn render_all_terms( &self, tera: &Tera, @@ -198,8 +224,11 @@ impl Taxonomy { ) -> Result<String> { let mut context = Context::new(); context.insert("config", &config.serialize(&self.lang)); - let terms: Vec<SerializedTaxonomyTerm> = - self.items.iter().map(|i| SerializedTaxonomyTerm::from_item(i, library)).collect(); + let terms: Vec<SerializedTaxonomyTerm> = self + .items + .iter() + .map(|i| SerializedTaxonomyTerm::from_item(i, library, true)) + .collect(); context.insert("terms", &terms); context.insert("lang", &self.lang); context.insert("taxonomy", &self.kind); @@ -229,7 +258,7 @@ impl Taxonomy { } /// Only used while building the taxonomies -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Eq)] pub(crate) struct TaxonomyFound<'a> { pub lang: &'a str, pub slug: String, @@ -242,3 +271,30 @@ impl<'a> TaxonomyFound<'a> { Self { slug, lang, config, terms: AHashMap::new() } } } + +#[cfg(test)] +mod tests { + use config::{Config, TaxonomyConfig}; + + use crate::{Library, Taxonomy, TaxonomyTerm}; + + use super::TaxonomyFound; + + #[test] + fn can_build_term_context() { + let conf = Config::default_for_test(); + let tax_conf = TaxonomyConfig::default(); + let tax_found = TaxonomyFound::new("tag".into(), &conf.default_language, &tax_conf); + let tax = Taxonomy::new(tax_found, &conf); + let pages = &[]; + let term = TaxonomyTerm::new("rust", &conf.default_language, "tags", pages, &conf); + let lib = Library::default(); + + let ctx = tax.build_term_context(&term, &conf, &lib); + + assert_eq!(ctx.get("current_path").and_then(|x| x.as_str()), Some("/tags/rust/")); + + let path = format!("{}{}", conf.base_url, "/tags/rust/"); + assert_eq!(ctx.get("current_url").and_then(|x| x.as_str()), Some(path.as_str())); + } +} diff --git a/components/content/src/types.rs b/components/content/src/types.rs index e058dec9a3..e9809986e8 100644 --- a/components/content/src/types.rs +++ b/components/content/src/types.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize, Eq)] #[serde(rename_all = "lowercase")] pub enum SortBy { /// Most recent to oldest @@ -15,6 +15,8 @@ pub enum SortBy { TitleBytes, /// Lower weight comes first Weight, + /// Sort by slug + Slug, /// No sorting None, } diff --git a/components/content/src/utils.rs b/components/content/src/utils.rs index d44959c10d..58fd3ec847 100644 --- a/components/content/src/utils.rs +++ b/components/content/src/utils.rs @@ -4,6 +4,7 @@ use libs::unicode_segmentation::UnicodeSegmentation; use libs::walkdir::WalkDir; use config::Config; +use utils::fs::is_temp_file; use utils::table_of_contents::Heading; pub fn has_anchor(headings: &[Heading], anchor: &str) -> bool { @@ -33,7 +34,8 @@ pub fn find_related_assets(path: &Path, config: &Config, recursive: bool) -> Vec } for entry in builder.into_iter().filter_map(std::result::Result::ok) { let entry_path = entry.path(); - if entry_path.is_file() { + + if entry_path.is_file() && !is_temp_file(entry_path) { match entry_path.extension() { Some(e) => match e.to_str() { Some("md") => continue, @@ -45,7 +47,7 @@ pub fn find_related_assets(path: &Path, config: &Config, recursive: bool) -> Vec } if let Some(ref globset) = config.ignored_content_globset { - assets = assets.into_iter().filter(|p| !globset.is_match(p)).collect(); + assets.retain(|p| !globset.is_match(p)); } assets @@ -82,10 +84,10 @@ mod tests { File::create(path.join("subdir").join("example.js")).unwrap(); let assets = find_related_assets(path, &Config::default(), true); - assert_eq!(assets.len(), 5); - assert_eq!(assets.iter().filter(|p| p.extension().unwrap_or_default() != "md").count(), 5); + assert_eq!(assets.len(), 4); + assert_eq!(assets.iter().filter(|p| p.extension().unwrap_or_default() != "md").count(), 4); - for asset in ["example.js", "graph.jpg", "fail.png", "subdir/example.js", "extensionless"] { + for asset in ["example.js", "graph.jpg", "fail.png", "subdir/example.js"] { assert!(assets.iter().any(|p| p.strip_prefix(path).unwrap() == Path::new(asset))) } } @@ -103,10 +105,10 @@ mod tests { File::create(path.join("subdir").join("index.md")).unwrap(); File::create(path.join("subdir").join("example.js")).unwrap(); let assets = find_related_assets(path, &Config::default(), false); - assert_eq!(assets.len(), 4); - assert_eq!(assets.iter().filter(|p| p.extension().unwrap_or_default() != "md").count(), 4); + assert_eq!(assets.len(), 3); + assert_eq!(assets.iter().filter(|p| p.extension().unwrap_or_default() != "md").count(), 3); - for asset in ["example.js", "graph.jpg", "fail.png", "extensionless"] { + for asset in ["example.js", "graph.jpg", "fail.png"] { assert!(assets.iter().any(|p| p.strip_prefix(path).unwrap() == Path::new(asset))) } } diff --git a/components/imageproc/src/format.rs b/components/imageproc/src/format.rs new file mode 100644 index 0000000000..4ff2221ba1 --- /dev/null +++ b/components/imageproc/src/format.rs @@ -0,0 +1,66 @@ +use errors::{anyhow, Result}; +use std::hash::{Hash, Hasher}; + +const DEFAULT_Q_JPG: u8 = 75; + +/// Thumbnail image format +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Format { + /// JPEG, The `u8` argument is JPEG quality (in percent). + Jpeg(u8), + /// PNG + Png, + /// WebP, The `u8` argument is WebP quality (in percent), None meaning lossless. + WebP(Option<u8>), +} + +impl Format { + pub fn from_args(is_lossy: bool, format: &str, quality: Option<u8>) -> Result<Format> { + use Format::*; + if let Some(quality) = quality { + assert!(quality > 0 && quality <= 100, "Quality must be within the range [1; 100]"); + } + let jpg_quality = quality.unwrap_or(DEFAULT_Q_JPG); + match format { + "auto" => { + if is_lossy { + Ok(Jpeg(jpg_quality)) + } else { + Ok(Png) + } + } + "jpeg" | "jpg" => Ok(Jpeg(jpg_quality)), + "png" => Ok(Png), + "webp" => Ok(WebP(quality)), + _ => Err(anyhow!("Invalid image format: {}", format)), + } + } + + pub fn extension(&self) -> &str { + // Kept in sync with RESIZED_FILENAME and op_filename + use Format::*; + + match *self { + Png => "png", + Jpeg(_) => "jpg", + WebP(_) => "webp", + } + } +} + +#[allow(clippy::derive_hash_xor_eq)] +impl Hash for Format { + fn hash<H: Hasher>(&self, hasher: &mut H) { + use Format::*; + + let q = match *self { + Png => 0, + Jpeg(q) => 1001 + q as u16, + WebP(None) => 2000, + WebP(Some(q)) => 2001 + q as u16, + }; + + hasher.write_u16(q); + hasher.write(self.extension().as_bytes()); + } +} diff --git a/components/imageproc/src/helpers.rs b/components/imageproc/src/helpers.rs new file mode 100644 index 0000000000..8c6a3cd6b2 --- /dev/null +++ b/components/imageproc/src/helpers.rs @@ -0,0 +1,55 @@ +use std::borrow::Cow; +use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; +use std::path::Path; + +use crate::format::Format; +use crate::ResizeOperation; +use libs::image::DynamicImage; + +/// Apply image rotation based on EXIF data +/// Returns `None` if no transformation is needed +pub fn fix_orientation(img: &DynamicImage, path: &Path) -> Option<DynamicImage> { + let file = std::fs::File::open(path).ok()?; + let mut buf_reader = std::io::BufReader::new(&file); + let exif_reader = exif::Reader::new(); + let exif = exif_reader.read_from_container(&mut buf_reader).ok()?; + let orientation = + exif.get_field(exif::Tag::Orientation, exif::In::PRIMARY)?.value.get_uint(0)?; + match orientation { + // Values are taken from the page 30 of + // https://www.cipa.jp/std/documents/e/DC-008-2012_E.pdf + // For more details check http://sylvana.net/jpegcrop/exif_orientation.html + 1 => None, + 2 => Some(img.fliph()), + 3 => Some(img.rotate180()), + 4 => Some(img.flipv()), + 5 => Some(img.fliph().rotate270()), + 6 => Some(img.rotate90()), + 7 => Some(img.fliph().rotate90()), + 8 => Some(img.rotate270()), + _ => None, + } +} + +/// We only use the input_path to get the file stem. +/// Hashing the resolved `input_path` would include the absolute path to the image +/// with all filesystem components. +pub fn get_processed_filename( + input_path: &Path, + input_src: &str, + op: &ResizeOperation, + format: &Format, +) -> String { + let mut hasher = DefaultHasher::new(); + hasher.write(input_src.as_ref()); + op.hash(&mut hasher); + format.hash(&mut hasher); + let hash = hasher.finish(); + let filename = input_path + .file_stem() + .map(|s| s.to_string_lossy()) + .unwrap_or_else(|| Cow::Borrowed("unknown")); + + format!("{}.{:016x}.{}", filename, hash, format.extension()) +} diff --git a/components/imageproc/src/lib.rs b/components/imageproc/src/lib.rs index db56d4888e..63748c1fdb 100644 --- a/components/imageproc/src/lib.rs +++ b/components/imageproc/src/lib.rs @@ -1,641 +1,10 @@ -use std::collections::hash_map::Entry as HEntry; -use std::collections::HashMap; -use std::ffi::OsStr; -use std::fs::{self, File}; -use std::hash::{Hash, Hasher}; -use std::path::{Path, PathBuf}; -use std::{collections::hash_map::DefaultHasher, io::Write}; - -use image::error::ImageResult; -use image::io::Reader as ImgReader; -use image::{imageops::FilterType, EncodableLayout}; -use image::{ImageFormat, ImageOutputFormat}; -use libs::image::DynamicImage; -use libs::{image, once_cell, rayon, regex, svg_metadata, webp}; -use once_cell::sync::Lazy; -use rayon::prelude::*; -use regex::Regex; -use serde::{Deserialize, Serialize}; -use svg_metadata::Metadata as SvgMetadata; - -use config::Config; -use errors::{anyhow, Context, Error, Result}; -use utils::fs as ufs; - -static RESIZED_SUBDIR: &str = "processed_images"; -const DEFAULT_Q_JPG: u8 = 75; - -static RESIZED_FILENAME: Lazy<Regex> = - Lazy::new(|| Regex::new(r#"([0-9a-f]{16})([0-9a-f]{2})[.](jpg|png|webp)"#).unwrap()); - -/// Size and format read cheaply with `image`'s `Reader`. -#[derive(Debug)] -struct ImageMeta { - size: (u32, u32), - format: Option<ImageFormat>, -} - -impl ImageMeta { - fn read(path: &Path) -> ImageResult<Self> { - let reader = ImgReader::open(path).and_then(ImgReader::with_guessed_format)?; - let format = reader.format(); - let size = reader.into_dimensions()?; - - Ok(Self { size, format }) - } - - fn is_lossy(&self) -> bool { - use ImageFormat::*; - - // We assume lossy by default / if unknown format - let format = self.format.unwrap_or(Jpeg); - !matches!(format, Png | Pnm | Tiff | Tga | Bmp | Ico | Hdr | Farbfeld) - } -} - -/// De-serialized & sanitized arguments of `resize_image` -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum ResizeArgs { - /// A simple scale operation that doesn't take aspect ratio into account - Scale(u32, u32), - /// Scales the image to a specified width with height computed such - /// that aspect ratio is preserved - FitWidth(u32), - /// Scales the image to a specified height with width computed such - /// that aspect ratio is preserved - FitHeight(u32), - /// If the image is larger than the specified width or height, scales the image such - /// that it fits within the specified width and height preserving aspect ratio. - /// Either dimension may end up being smaller, but never larger than specified. - Fit(u32, u32), - /// Scales the image such that it fills the specified width and height. - /// Output will always have the exact dimensions specified. - /// The part of the image that doesn't fit in the thumbnail due to differing - /// aspect ratio will be cropped away, if any. - Fill(u32, u32), -} - -impl ResizeArgs { - pub fn from_args(op: &str, width: Option<u32>, height: Option<u32>) -> Result<Self> { - use ResizeArgs::*; - - // Validate args: - match op { - "fit_width" => { - if width.is_none() { - return Err(anyhow!("op=\"fit_width\" requires a `width` argument")); - } - } - "fit_height" => { - if height.is_none() { - return Err(anyhow!("op=\"fit_height\" requires a `height` argument")); - } - } - "scale" | "fit" | "fill" => { - if width.is_none() || height.is_none() { - return Err(anyhow!("op={} requires a `width` and `height` argument", op)); - } - } - _ => return Err(anyhow!("Invalid image resize operation: {}", op)), - }; - - Ok(match op { - "scale" => Scale(width.unwrap(), height.unwrap()), - "fit_width" => FitWidth(width.unwrap()), - "fit_height" => FitHeight(height.unwrap()), - "fit" => Fit(width.unwrap(), height.unwrap()), - "fill" => Fill(width.unwrap(), height.unwrap()), - _ => unreachable!(), - }) - } -} - -/// Contains image crop/resize instructions for use by `Processor` -/// -/// The `Processor` applies `crop` first, if any, and then `resize`, if any. -#[derive(Clone, PartialEq, Eq, Hash, Default, Debug)] -struct ResizeOp { - crop: Option<(u32, u32, u32, u32)>, // x, y, w, h - resize: Option<(u32, u32)>, // w, h -} - -impl ResizeOp { - fn new(args: ResizeArgs, (orig_w, orig_h): (u32, u32)) -> Self { - use ResizeArgs::*; - - let res = ResizeOp::default(); - - match args { - Scale(w, h) => res.resize((w, h)), - FitWidth(w) => { - let h = (orig_h as u64 * w as u64) / orig_w as u64; - res.resize((w, h as u32)) - } - FitHeight(h) => { - let w = (orig_w as u64 * h as u64) / orig_h as u64; - res.resize((w as u32, h)) - } - Fit(w, h) => { - if orig_w <= w && orig_h <= h { - return res; // ie. no-op - } - - let orig_w_h = orig_w as u64 * h as u64; - let orig_h_w = orig_h as u64 * w as u64; - - if orig_w_h > orig_h_w { - Self::new(FitWidth(w), (orig_w, orig_h)) - } else { - Self::new(FitHeight(h), (orig_w, orig_h)) - } - } - Fill(w, h) => { - const RATIO_EPSILLION: f32 = 0.1; - - let factor_w = orig_w as f32 / w as f32; - let factor_h = orig_h as f32 / h as f32; - - if (factor_w - factor_h).abs() <= RATIO_EPSILLION { - // If the horizontal and vertical factor is very similar, - // that means the aspect is similar enough that there's not much point - // in cropping, so just perform a simple scale in this case. - res.resize((w, h)) - } else { - // We perform the fill such that a crop is performed first - // and then resize_exact can be used, which should be cheaper than - // resizing and then cropping (smaller number of pixels to resize). - let (crop_w, crop_h) = if factor_w < factor_h { - (orig_w, (factor_w * h as f32).round() as u32) - } else { - ((factor_h * w as f32).round() as u32, orig_h) - }; - - let (offset_w, offset_h) = if factor_w < factor_h { - (0, (orig_h - crop_h) / 2) - } else { - ((orig_w - crop_w) / 2, 0) - }; - - res.crop((offset_w, offset_h, crop_w, crop_h)).resize((w, h)) - } - } - } - } - - fn crop(mut self, crop: (u32, u32, u32, u32)) -> Self { - self.crop = Some(crop); - self - } - - fn resize(mut self, size: (u32, u32)) -> Self { - self.resize = Some(size); - self - } -} - -/// Thumbnail image format -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Format { - /// JPEG, The `u8` argument is JPEG quality (in percent). - Jpeg(u8), - /// PNG - Png, - /// WebP, The `u8` argument is WebP quality (in percent), None meaning lossless. - WebP(Option<u8>), -} - -impl Format { - fn from_args(meta: &ImageMeta, format: &str, quality: Option<u8>) -> Result<Format> { - use Format::*; - if let Some(quality) = quality { - assert!(quality > 0 && quality <= 100, "Quality must be within the range [1; 100]"); - } - let jpg_quality = quality.unwrap_or(DEFAULT_Q_JPG); - match format { - "auto" => { - if meta.is_lossy() { - Ok(Jpeg(jpg_quality)) - } else { - Ok(Png) - } - } - "jpeg" | "jpg" => Ok(Jpeg(jpg_quality)), - "png" => Ok(Png), - "webp" => Ok(WebP(quality)), - _ => Err(anyhow!("Invalid image format: {}", format)), - } - } - - /// Looks at file's extension and, if it's a supported image format, returns whether the format is lossless - pub fn is_lossy<P: AsRef<Path>>(p: P) -> Option<bool> { - p.as_ref() - .extension() - .and_then(std::ffi::OsStr::to_str) - .map(|ext| match ext.to_lowercase().as_str() { - "jpg" | "jpeg" => Some(true), - "png" => Some(false), - "gif" => Some(false), - "bmp" => Some(false), - // It is assumed that webp is lossy, but it can be both - "webp" => Some(true), - _ => None, - }) - .unwrap_or(None) - } - - fn extension(&self) -> &str { - // Kept in sync with RESIZED_FILENAME and op_filename - use Format::*; - - match *self { - Png => "png", - Jpeg(_) => "jpg", - WebP(_) => "webp", - } - } -} - -#[allow(clippy::derive_hash_xor_eq)] -impl Hash for Format { - fn hash<H: Hasher>(&self, hasher: &mut H) { - use Format::*; - - let q = match *self { - Png => 0, - Jpeg(q) => q, - WebP(None) => 0, - WebP(Some(q)) => q, - }; - - hasher.write_u8(q); - hasher.write(self.extension().as_bytes()); - } -} - -/// Holds all data needed to perform a resize operation -#[derive(Debug, PartialEq, Eq)] -pub struct ImageOp { - /// This is the source input path string as passed in the template, we need this to compute the hash. - /// Hashing the resolved `input_path` would include the absolute path to the image - /// with all filesystem components. - input_src: String, - input_path: PathBuf, - op: ResizeOp, - format: Format, - /// Hash of the above parameters - hash: u64, - /// If there is a hash collision with another ImageOp, this contains a sequential ID > 1 - /// identifying the collision in the order as encountered (which is essentially random). - /// Therefore, ImageOps with collisions (ie. collision_id > 0) are always considered out of date. - /// Note that this is very unlikely to happen in practice - collision_id: u32, -} - -impl ImageOp { - const RESIZE_FILTER: FilterType = FilterType::Lanczos3; - - fn new(input_src: String, input_path: PathBuf, op: ResizeOp, format: Format) -> ImageOp { - let mut hasher = DefaultHasher::new(); - hasher.write(input_src.as_ref()); - op.hash(&mut hasher); - format.hash(&mut hasher); - let hash = hasher.finish(); - - ImageOp { input_src, input_path, op, format, hash, collision_id: 0 } - } - - fn perform(&self, target_path: &Path) -> Result<()> { - if !ufs::file_stale(&self.input_path, target_path) { - return Ok(()); - } - - let mut img = image::open(&self.input_path)?; - - let img = match self.op.crop { - Some((x, y, w, h)) => img.crop(x, y, w, h), - None => img, - }; - let img = match self.op.resize { - Some((w, h)) => img.resize_exact(w, h, Self::RESIZE_FILTER), - None => img, - }; - - let img = fix_orientation(&img, &self.input_path).unwrap_or(img); - - let mut f = File::create(target_path)?; - - match self.format { - Format::Png => { - img.write_to(&mut f, ImageOutputFormat::Png)?; - } - Format::Jpeg(q) => { - img.write_to(&mut f, ImageOutputFormat::Jpeg(q))?; - } - Format::WebP(q) => { - let encoder = webp::Encoder::from_image(&img) - .map_err(|_| anyhow!("Unable to load this kind of image with webp"))?; - let memory = match q { - Some(q) => encoder.encode(q as f32), - None => encoder.encode_lossless(), - }; - f.write_all(memory.as_bytes())?; - } - } - - Ok(()) - } -} - -/// Apply image rotation based on EXIF data -/// Returns `None` if no transformation is needed -pub fn fix_orientation(img: &DynamicImage, path: &Path) -> Option<DynamicImage> { - let file = std::fs::File::open(path).ok()?; - let mut buf_reader = std::io::BufReader::new(&file); - let exif_reader = exif::Reader::new(); - let exif = exif_reader.read_from_container(&mut buf_reader).ok()?; - let orientation = - exif.get_field(exif::Tag::Orientation, exif::In::PRIMARY)?.value.get_uint(0)?; - match orientation { - // Values are taken from the page 30 of - // https://www.cipa.jp/std/documents/e/DC-008-2012_E.pdf - // For more details check http://sylvana.net/jpegcrop/exif_orientation.html - 1 => None, - 2 => Some(img.fliph()), - 3 => Some(img.rotate180()), - 4 => Some(img.flipv()), - 5 => Some(img.fliph().rotate270()), - 6 => Some(img.rotate90()), - 7 => Some(img.fliph().rotate90()), - 8 => Some(img.rotate270()), - _ => None, - } -} - -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] -pub struct EnqueueResponse { - /// The final URL for that asset - pub url: String, - /// The path to the static asset generated - pub static_path: String, - /// New image width - pub width: u32, - /// New image height - pub height: u32, - /// Original image width - pub orig_width: u32, - /// Original image height - pub orig_height: u32, -} - -impl EnqueueResponse { - fn new(url: String, static_path: PathBuf, meta: &ImageMeta, op: &ResizeOp) -> Self { - let static_path = static_path.to_string_lossy().into_owned(); - let (width, height) = op.resize.unwrap_or(meta.size); - let (orig_width, orig_height) = meta.size; - - Self { url, static_path, width, height, orig_width, orig_height } - } -} - -/// A struct into which image operations can be enqueued and then performed. -/// All output is written in a subdirectory in `static_path`, -/// taking care of file stale status based on timestamps and possible hash collisions. -#[derive(Debug)] -pub struct Processor { - base_url: String, - output_dir: PathBuf, - /// A map of a ImageOps by their stored hash. - /// Note that this cannot be a HashSet, because hashset handles collisions and we don't want that, - /// we need to be aware of and handle collisions ourselves. - img_ops: HashMap<u64, ImageOp>, - /// Hash collisions go here: - img_ops_collisions: Vec<ImageOp>, -} - -impl Processor { - pub fn new(base_path: PathBuf, config: &Config) -> Processor { - Processor { - output_dir: base_path.join("static").join(RESIZED_SUBDIR), - base_url: config.make_permalink(RESIZED_SUBDIR), - img_ops: HashMap::new(), - img_ops_collisions: Vec::new(), - } - } - - pub fn set_base_url(&mut self, config: &Config) { - self.base_url = config.make_permalink(RESIZED_SUBDIR); - } - - pub fn num_img_ops(&self) -> usize { - self.img_ops.len() + self.img_ops_collisions.len() - } - - #[allow(clippy::too_many_arguments)] - pub fn enqueue( - &mut self, - input_src: String, - input_path: PathBuf, - op: &str, - width: Option<u32>, - height: Option<u32>, - format: &str, - quality: Option<u8>, - ) -> Result<EnqueueResponse> { - let meta = ImageMeta::read(&input_path) - .with_context(|| format!("Failed to read image: {}", input_path.display()))?; - - let args = ResizeArgs::from_args(op, width, height)?; - let op = ResizeOp::new(args, meta.size); - let format = Format::from_args(&meta, format, quality)?; - let img_op = ImageOp::new(input_src, input_path, op.clone(), format); - let (static_path, url) = self.insert(img_op); - - Ok(EnqueueResponse::new(url, static_path, &meta, &op)) - } - - fn insert_with_collisions(&mut self, mut img_op: ImageOp) -> u32 { - match self.img_ops.entry(img_op.hash) { - HEntry::Occupied(entry) => { - if *entry.get() == img_op { - return 0; - } - } - HEntry::Vacant(entry) => { - entry.insert(img_op); - return 0; - } - } - - // If we get here, that means a hash collision. - // This is detected when there is an ImageOp with the same hash in the `img_ops` - // map but which is not equal to this one. - // To deal with this, all collisions get a (random) sequential ID number. - - // First try to look up this ImageOp in `img_ops_collisions`, maybe we've - // already seen the same ImageOp. - // At the same time, count IDs to figure out the next free one. - // Start with the ID of 2, because we'll need to use 1 for the ImageOp - // already present in the map: - let mut collision_id = 2; - for op in self.img_ops_collisions.iter().filter(|op| op.hash == img_op.hash) { - if *op == img_op { - // This is a colliding ImageOp, but we've already seen an equal one - // (not just by hash, but by content too), so just return its ID: - return collision_id; - } else { - collision_id += 1; - } - } - - // If we get here, that means this is a new colliding ImageOp and - // `collision_id` is the next free ID - if collision_id == 2 { - // This is the first collision found with this hash, update the ID - // of the matching ImageOp in the map. - self.img_ops.get_mut(&img_op.hash).unwrap().collision_id = 1; - } - img_op.collision_id = collision_id; - self.img_ops_collisions.push(img_op); - collision_id - } - - fn op_filename(hash: u64, collision_id: u32, format: Format) -> String { - // Please keep this in sync with RESIZED_FILENAME - assert!(collision_id < 256, "Unexpectedly large number of collisions: {}", collision_id); - format!("{:016x}{:02x}.{}", hash, collision_id, format.extension()) - } - - /// Adds the given operation to the queue but do not process it immediately. - /// Returns (path in static folder, final URL). - fn insert(&mut self, img_op: ImageOp) -> (PathBuf, String) { - let hash = img_op.hash; - let format = img_op.format; - let collision_id = self.insert_with_collisions(img_op); - let filename = Self::op_filename(hash, collision_id, format); - let url = format!("{}{}", self.base_url, filename); - (Path::new("static").join(RESIZED_SUBDIR).join(filename), url) - } - - /// Remove stale processed images in the output directory - pub fn prune(&self) -> Result<()> { - // Do not create folders if they don't exist - if !self.output_dir.exists() { - return Ok(()); - } - - ufs::ensure_directory_exists(&self.output_dir)?; - let entries = fs::read_dir(&self.output_dir)?; - for entry in entries { - let entry_path = entry?.path(); - if entry_path.is_file() { - let filename = entry_path.file_name().unwrap().to_string_lossy(); - if let Some(capts) = RESIZED_FILENAME.captures(filename.as_ref()) { - let hash = u64::from_str_radix(capts.get(1).unwrap().as_str(), 16).unwrap(); - let collision_id = - u32::from_str_radix(capts.get(2).unwrap().as_str(), 16).unwrap(); - - if collision_id > 0 || !self.img_ops.contains_key(&hash) { - fs::remove_file(&entry_path)?; - } - } - } - } - Ok(()) - } - - /// Run the enqueued image operations - pub fn do_process(&mut self) -> Result<()> { - if !self.img_ops.is_empty() { - ufs::ensure_directory_exists(&self.output_dir)?; - } - - self.img_ops - .par_iter() - .map(|(hash, op)| { - let target = - self.output_dir.join(Self::op_filename(*hash, op.collision_id, op.format)); - - op.perform(&target).with_context(|| { - format!("Failed to process image: {}", op.input_path.display()) - }) - }) - .collect::<Result<()>>() - } -} - -#[derive(Debug, Serialize, Eq, PartialEq)] -pub struct ImageMetaResponse { - pub width: u32, - pub height: u32, - pub format: Option<&'static str>, -} - -impl ImageMetaResponse { - pub fn new_svg(width: u32, height: u32) -> Self { - Self { width, height, format: Some("svg") } - } -} - -impl From<ImageMeta> for ImageMetaResponse { - fn from(im: ImageMeta) -> Self { - Self { - width: im.size.0, - height: im.size.1, - format: im.format.and_then(|f| f.extensions_str().get(0)).copied(), - } - } -} - -impl From<webp::WebPImage> for ImageMetaResponse { - fn from(img: webp::WebPImage) -> Self { - Self { width: img.width(), height: img.height(), format: Some("webp") } - } -} - -/// Read image dimensions (cheaply), used in `get_image_metadata()`, supports SVG -pub fn read_image_metadata<P: AsRef<Path>>(path: P) -> Result<ImageMetaResponse> { - let path = path.as_ref(); - let ext = path.extension().and_then(OsStr::to_str).unwrap_or("").to_lowercase(); - - let err_context = || format!("Failed to read image: {}", path.display()); - - match ext.as_str() { - "svg" => { - let img = SvgMetadata::parse_file(&path).with_context(err_context)?; - match (img.height(), img.width(), img.view_box()) { - (Some(h), Some(w), _) => Ok((h, w)), - (_, _, Some(view_box)) => Ok((view_box.height, view_box.width)), - _ => Err(anyhow!("Invalid dimensions: SVG width/height and viewbox not set.")), - } - //this is not a typo, this returns the correct values for width and height. - .map(|(h, w)| ImageMetaResponse::new_svg(w as u32, h as u32)) - } - "webp" => { - // Unfortunately we have to load the entire image here, unlike with the others :| - let data = fs::read(path).with_context(err_context)?; - let decoder = webp::Decoder::new(&data[..]); - decoder.decode().map(ImageMetaResponse::from).ok_or_else(|| { - Error::msg(format!("Failed to decode WebP image: {}", path.display())) - }) - } - _ => ImageMeta::read(path).map(ImageMetaResponse::from).with_context(err_context), - } -} - -/// Assert that `address` matches `prefix` + RESIZED_FILENAME regex + "." + `extension`, -/// this is useful in test so that we don't need to hardcode hash, which is annoying. -pub fn assert_processed_path_matches(path: &str, prefix: &str, extension: &str) { - let filename = path - .strip_prefix(prefix) - .unwrap_or_else(|| panic!("Path `{}` doesn't start with `{}`", path, prefix)); - - let suffix = format!(".{}", extension); - assert!(filename.ends_with(&suffix), "Path `{}` doesn't end with `{}`", path, suffix); - - assert!( - RESIZED_FILENAME.is_match_at(filename, 0), - "In path `{}`, file stem `{}` doesn't match the RESIZED_FILENAME regex", - path, - filename - ); -} +mod format; +mod helpers; +mod meta; +mod ops; +mod processor; + +pub use helpers::fix_orientation; +pub use meta::{read_image_metadata, ImageMeta, ImageMetaResponse}; +pub use ops::{ResizeInstructions, ResizeOperation}; +pub use processor::{EnqueueResponse, Processor, RESIZED_SUBDIR}; diff --git a/components/imageproc/src/meta.rs b/components/imageproc/src/meta.rs new file mode 100644 index 0000000000..f00741cbab --- /dev/null +++ b/components/imageproc/src/meta.rs @@ -0,0 +1,89 @@ +use errors::{anyhow, Context, Result}; +use libs::image::io::Reader as ImgReader; +use libs::image::{ImageFormat, ImageResult}; +use libs::svg_metadata::Metadata as SvgMetadata; +use serde::Serialize; +use std::ffi::OsStr; +use std::path::Path; + +/// Size and format read cheaply with `image`'s `Reader`. +#[derive(Debug)] +pub struct ImageMeta { + /// (w, h) + pub size: (u32, u32), + pub format: Option<ImageFormat>, +} + +impl ImageMeta { + pub fn read(path: &Path) -> ImageResult<Self> { + let reader = ImgReader::open(path).and_then(ImgReader::with_guessed_format)?; + let format = reader.format(); + let size = reader.into_dimensions()?; + + Ok(Self { size, format }) + } + + pub fn is_lossy(&self) -> bool { + use ImageFormat::*; + + // We assume lossy by default / if unknown format + let format = self.format.unwrap_or(Jpeg); + !matches!(format, Png | Pnm | Tiff | Tga | Bmp | Ico | Hdr | Farbfeld) + } +} + +#[derive(Debug, Serialize, Eq, PartialEq)] +pub struct ImageMetaResponse { + pub width: u32, + pub height: u32, + pub format: Option<&'static str>, +} + +impl ImageMetaResponse { + pub fn new_svg(width: u32, height: u32) -> Self { + Self { width, height, format: Some("svg") } + } +} + +impl From<ImageMeta> for ImageMetaResponse { + fn from(im: ImageMeta) -> Self { + Self { + width: im.size.0, + height: im.size.1, + format: im.format.and_then(|f| f.extensions_str().first()).copied(), + } + } +} + +/// Read image dimensions (cheaply), used in `get_image_metadata()`, supports SVG +pub fn read_image_metadata<P: AsRef<Path>>(path: P) -> Result<ImageMetaResponse> { + let path = path.as_ref(); + let ext = path.extension().and_then(OsStr::to_str).unwrap_or("").to_lowercase(); + + let err_context = || format!("Failed to read image: {}", path.display()); + + match ext.as_str() { + "svg" => { + let img = SvgMetadata::parse_file(path).with_context(err_context)?; + match (img.height(), img.width(), img.view_box()) { + (Some(h), Some(w), _) => Ok((h, w)), + (_, _, Some(view_box)) => Ok((view_box.height, view_box.width)), + _ => Err(anyhow!("Invalid dimensions: SVG width/height and viewbox not set.")), + } + // this is not a typo, this returns the correct values for width and height. + .map(|(h, w)| ImageMetaResponse::new_svg(w as u32, h as u32)) + } + _ => ImageMeta::read(path).map(ImageMetaResponse::from).with_context(err_context), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn can_get_webp_metadata() { + let filepath = Path::new("tests/test_imgs/webp.webp"); + assert!(filepath.exists()); + } +} diff --git a/components/imageproc/src/ops.rs b/components/imageproc/src/ops.rs new file mode 100644 index 0000000000..a5b783c104 --- /dev/null +++ b/components/imageproc/src/ops.rs @@ -0,0 +1,141 @@ +use errors::{anyhow, Result}; + +/// De-serialized & sanitized arguments of `resize_image` +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum ResizeOperation { + /// A simple scale operation that doesn't take aspect ratio into account + Scale(u32, u32), + /// Scales the image to a specified width with height computed such + /// that aspect ratio is preserved + FitWidth(u32), + /// Scales the image to a specified height with width computed such + /// that aspect ratio is preserved + FitHeight(u32), + /// If the image is larger than the specified width or height, scales the image such + /// that it fits within the specified width and height preserving aspect ratio. + /// Either dimension may end up being smaller, but never larger than specified. + Fit(u32, u32), + /// Scales the image such that it fills the specified width and height. + /// Output will always have the exact dimensions specified. + /// The part of the image that doesn't fit in the thumbnail due to differing + /// aspect ratio will be cropped away, if any. + Fill(u32, u32), +} + +impl ResizeOperation { + pub fn from_args(op: &str, width: Option<u32>, height: Option<u32>) -> Result<Self> { + use ResizeOperation::*; + + // Validate args: + match op { + "fit_width" => { + if width.is_none() { + return Err(anyhow!("op=\"fit_width\" requires a `width` argument")); + } + } + "fit_height" => { + if height.is_none() { + return Err(anyhow!("op=\"fit_height\" requires a `height` argument")); + } + } + "scale" | "fit" | "fill" => { + if width.is_none() || height.is_none() { + return Err(anyhow!("op={} requires a `width` and `height` argument", op)); + } + } + _ => return Err(anyhow!("Invalid image resize operation: {}", op)), + }; + + Ok(match op { + "scale" => Scale(width.unwrap(), height.unwrap()), + "fit_width" => FitWidth(width.unwrap()), + "fit_height" => FitHeight(height.unwrap()), + "fit" => Fit(width.unwrap(), height.unwrap()), + "fill" => Fill(width.unwrap(), height.unwrap()), + _ => unreachable!(), + }) + } +} + +/// Contains image crop/resize instructions for use by `Processor` +/// +/// The `Processor` applies `crop` first, if any, and then `resize`, if any. +#[derive(Clone, PartialEq, Eq, Hash, Default, Debug)] +pub struct ResizeInstructions { + pub crop_instruction: Option<(u32, u32, u32, u32)>, // x, y, w, h + pub resize_instruction: Option<(u32, u32)>, // w, h +} + +impl ResizeInstructions { + pub fn new(args: ResizeOperation, (orig_w, orig_h): (u32, u32)) -> Self { + use ResizeOperation::*; + + let res = ResizeInstructions::default(); + + match args { + Scale(w, h) => res.resize((w, h)), + FitWidth(w) => { + let h = (orig_h as u64 * w as u64) / orig_w as u64; + res.resize((w, h as u32)) + } + FitHeight(h) => { + let w = (orig_w as u64 * h as u64) / orig_h as u64; + res.resize((w as u32, h)) + } + Fit(w, h) => { + if orig_w <= w && orig_h <= h { + return res; // ie. no-op + } + + let orig_w_h = orig_w as u64 * h as u64; + let orig_h_w = orig_h as u64 * w as u64; + + if orig_w_h > orig_h_w { + Self::new(FitWidth(w), (orig_w, orig_h)) + } else { + Self::new(FitHeight(h), (orig_w, orig_h)) + } + } + Fill(w, h) => { + const RATIO_EPSILLION: f32 = 0.1; + + let factor_w = orig_w as f32 / w as f32; + let factor_h = orig_h as f32 / h as f32; + + if (factor_w - factor_h).abs() <= RATIO_EPSILLION { + // If the horizontal and vertical factor is very similar, + // that means the aspect is similar enough that there's not much point + // in cropping, so just perform a simple scale in this case. + res.resize((w, h)) + } else { + // We perform the fill such that a crop is performed first + // and then resize_exact can be used, which should be cheaper than + // resizing and then cropping (smaller number of pixels to resize). + let (crop_w, crop_h) = if factor_w < factor_h { + (orig_w, (factor_w * h as f32).round() as u32) + } else { + ((factor_h * w as f32).round() as u32, orig_h) + }; + + let (offset_w, offset_h) = if factor_w < factor_h { + (0, (orig_h - crop_h) / 2) + } else { + ((orig_w - crop_w) / 2, 0) + }; + + res.crop((offset_w, offset_h, crop_w, crop_h)).resize((w, h)) + } + } + } + } + + pub fn crop(mut self, crop: (u32, u32, u32, u32)) -> Self { + self.crop_instruction = Some(crop); + self + } + + pub fn resize(mut self, size: (u32, u32)) -> Self { + self.resize_instruction = Some(size); + self + } +} diff --git a/components/imageproc/src/processor.rs b/components/imageproc/src/processor.rs new file mode 100644 index 0000000000..4ef9869698 --- /dev/null +++ b/components/imageproc/src/processor.rs @@ -0,0 +1,218 @@ +use std::fs; +use std::fs::File; +use std::io::{BufWriter, Write}; +use std::path::{Path, PathBuf}; + +use config::Config; +use errors::{anyhow, Context, Result}; +use libs::ahash::{HashMap, HashSet}; +use libs::image::imageops::FilterType; +use libs::image::{EncodableLayout, ImageOutputFormat}; +use libs::rayon::prelude::*; +use libs::{image, webp}; +use serde::{Deserialize, Serialize}; +use utils::fs as ufs; + +use crate::format::Format; +use crate::helpers::get_processed_filename; +use crate::{fix_orientation, ImageMeta, ResizeInstructions, ResizeOperation}; + +pub static RESIZED_SUBDIR: &str = "processed_images"; + +/// Holds all data needed to perform a resize operation +#[derive(Debug, PartialEq, Eq, Hash)] +pub struct ImageOp { + input_path: PathBuf, + output_path: PathBuf, + instr: ResizeInstructions, + format: Format, + /// Whether we actually want to perform that op. + /// In practice we set it to true if the output file already + /// exists and is not stale. We do need to keep the ImageOp around for pruning though. + ignore: bool, +} + +impl ImageOp { + fn perform(&self) -> Result<()> { + if self.ignore { + return Ok(()); + } + + let mut img = image::open(&self.input_path)?; + + let img = match self.instr.crop_instruction { + Some((x, y, w, h)) => img.crop(x, y, w, h), + None => img, + }; + let img = match self.instr.resize_instruction { + Some((w, h)) => img.resize_exact(w, h, FilterType::Lanczos3), + None => img, + }; + + let img = fix_orientation(&img, &self.input_path).unwrap_or(img); + + let f = File::create(&self.output_path)?; + let mut buffered_f = BufWriter::new(f); + + match self.format { + Format::Png => { + img.write_to(&mut buffered_f, ImageOutputFormat::Png)?; + } + Format::Jpeg(q) => { + img.write_to(&mut buffered_f, ImageOutputFormat::Jpeg(q))?; + } + Format::WebP(q) => { + let encoder = webp::Encoder::from_image(&img) + .map_err(|_| anyhow!("Unable to load this kind of image with webp"))?; + let memory = match q { + Some(q) => encoder.encode(q as f32), + None => encoder.encode_lossless(), + }; + buffered_f.write_all(memory.as_bytes())?; + } + } + + Ok(()) + } +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +pub struct EnqueueResponse { + /// The final URL for that asset + pub url: String, + /// The path to the static asset generated + pub static_path: String, + /// New image width + pub width: u32, + /// New image height + pub height: u32, + /// Original image width + pub orig_width: u32, + /// Original image height + pub orig_height: u32, +} + +impl EnqueueResponse { + fn new( + url: String, + static_path: PathBuf, + meta: &ImageMeta, + instr: &ResizeInstructions, + ) -> Self { + let static_path = static_path.to_string_lossy().into_owned(); + let (width, height) = instr.resize_instruction.unwrap_or(meta.size); + let (orig_width, orig_height) = meta.size; + + Self { url, static_path, width, height, orig_width, orig_height } + } +} + +/// A struct into which image operations can be enqueued and then performed. +/// All output is written in a subdirectory in `static_path`, +/// taking care of file stale status based on timestamps +#[derive(Debug)] +pub struct Processor { + base_url: String, + output_dir: PathBuf, + img_ops: HashSet<ImageOp>, + /// We want to make sure we only ever get metadata for an image once + meta_cache: HashMap<PathBuf, ImageMeta>, +} + +impl Processor { + pub fn new(base_path: PathBuf, config: &Config) -> Processor { + Processor { + output_dir: base_path.join("static").join(RESIZED_SUBDIR), + base_url: config.make_permalink(RESIZED_SUBDIR), + img_ops: HashSet::default(), + meta_cache: HashMap::default(), + } + } + + pub fn set_base_url(&mut self, config: &Config) { + self.base_url = config.make_permalink(RESIZED_SUBDIR); + } + + pub fn num_img_ops(&self) -> usize { + self.img_ops.len() + } + + pub fn enqueue( + &mut self, + op: ResizeOperation, + input_src: String, + input_path: PathBuf, + format: &str, + quality: Option<u8>, + ) -> Result<EnqueueResponse> { + // First we load metadata from the cache if possible, otherwise from the file itself + if !self.meta_cache.contains_key(&input_path) { + let meta = ImageMeta::read(&input_path) + .with_context(|| format!("Failed to read image: {}", input_path.display()))?; + self.meta_cache.insert(input_path.clone(), meta); + } + // We will have inserted it just above + let meta = &self.meta_cache[&input_path]; + // We get the output format + let format = Format::from_args(meta.is_lossy(), format, quality)?; + // Now we have all the data we need to generate the output filename and the response + let filename = get_processed_filename(&input_path, &input_src, &op, &format); + let url = format!("{}{}", self.base_url, filename); + let static_path = Path::new("static").join(RESIZED_SUBDIR).join(&filename); + let output_path = self.output_dir.join(&filename); + let instr = ResizeInstructions::new(op, meta.size); + let enqueue_response = EnqueueResponse::new(url, static_path, meta, &instr); + let img_op = ImageOp { + ignore: output_path.exists() && !ufs::file_stale(&input_path, &output_path), + input_path, + output_path, + instr, + format, + }; + self.img_ops.insert(img_op); + + Ok(enqueue_response) + } + + /// Run the enqueued image operations + pub fn do_process(&mut self) -> Result<()> { + if !self.img_ops.is_empty() { + ufs::ensure_directory_exists(&self.output_dir)?; + } + + self.img_ops + .par_iter() + .map(|op| { + op.perform().with_context(|| { + format!("Failed to process image: {}", op.input_path.display()) + }) + }) + .collect::<Result<()>>() + } + + /// Remove stale processed images in the output directory + pub fn prune(&self) -> Result<()> { + // Do not create folders if they don't exist + if !self.output_dir.exists() { + return Ok(()); + } + + ufs::ensure_directory_exists(&self.output_dir)?; + let output_paths: HashSet<_> = self + .img_ops + .iter() + .map(|o| o.output_path.file_name().unwrap().to_string_lossy()) + .collect(); + + for entry in fs::read_dir(&self.output_dir)? { + let entry_path = entry?.path(); + if entry_path.is_file() { + let filename = entry_path.file_name().unwrap().to_string_lossy(); + if !output_paths.contains(&filename) { + fs::remove_file(&entry_path)?; + } + } + } + Ok(()) + } +} diff --git a/components/imageproc/tests/resize_image.rs b/components/imageproc/tests/resize_image.rs index 507de5e242..7715687f8e 100644 --- a/components/imageproc/tests/resize_image.rs +++ b/components/imageproc/tests/resize_image.rs @@ -2,10 +2,20 @@ use std::env; use std::path::{PathBuf, MAIN_SEPARATOR as SLASH}; use config::Config; -use imageproc::{assert_processed_path_matches, fix_orientation, ImageMetaResponse, Processor}; +use imageproc::{fix_orientation, ImageMetaResponse, Processor, ResizeOperation}; use libs::image::{self, DynamicImage, GenericImageView, Pixel}; use libs::once_cell::sync::Lazy; +/// Assert that `address` matches `prefix` + RESIZED_FILENAME regex + "." + `extension`, +fn assert_processed_path_matches(path: &str, prefix: &str, extension: &str) { + let filename = path + .strip_prefix(prefix) + .unwrap_or_else(|| panic!("Path `{}` doesn't start with `{}`", path, prefix)); + + let suffix = format!(".{}", extension); + assert!(filename.ends_with(&suffix), "Path `{}` doesn't end with `{}`", path, suffix); +} + static CONFIG: &str = r#" title = "imageproc integration tests" base_url = "https://example.com" @@ -38,9 +48,9 @@ fn image_op_test( let tmpdir = tempfile::tempdir().unwrap().into_path(); let config = Config::parse(CONFIG).unwrap(); let mut proc = Processor::new(tmpdir.clone(), &config); + let resize_op = ResizeOperation::from_args(op, width, height).unwrap(); - let resp = - proc.enqueue(source_img.into(), source_path, op, width, height, format, None).unwrap(); + let resp = proc.enqueue(resize_op, source_img.into(), source_path, format, None).unwrap(); assert_processed_path_matches(&resp.url, "https://example.com/processed_images/", expect_ext); assert_processed_path_matches(&resp.static_path, PROCESSED_PREFIX.as_str(), expect_ext); assert_eq!(resp.width, expect_width); @@ -202,10 +212,9 @@ fn resize_and_check(source_img: &str) -> bool { let tmpdir = tempfile::tempdir().unwrap().into_path(); let config = Config::parse(CONFIG).unwrap(); let mut proc = Processor::new(tmpdir.clone(), &config); + let resize_op = ResizeOperation::from_args("scale", Some(16), Some(16)).unwrap(); - let resp = proc - .enqueue(source_img.into(), source_path, "scale", Some(16), Some(16), "jpg", None) - .unwrap(); + let resp = proc.enqueue(resize_op, source_img.into(), source_path, "jpg", None).unwrap(); proc.do_process().unwrap(); let processed_path = PathBuf::from(&resp.static_path); @@ -224,5 +233,3 @@ fn check_img(img: DynamicImage) -> bool { // bottom right is white && img.get_pixel(15, 15).channels() == [255, 255, 255, 255] } - -// TODO: Test that hash remains the same if physical path is changed diff --git a/components/libs/Cargo.toml b/components/libs/Cargo.toml index cb83dc6562..2eb8bf8afa 100644 --- a/components/libs/Cargo.toml +++ b/components/libs/Cargo.toml @@ -4,10 +4,10 @@ version = "0.1.0" edition = "2021" [dependencies] -ahash = "0.7.6" +ahash = "0.8" ammonia = "3" atty = "0.2.11" -base64 = "0.13" +base64 = "0.21" csv = "1" elasticlunr-rs = { version = "3.0.0", features = ["da", "no", "de", "du", "es", "fi", "fr", "it", "pt", "ro", "ru", "sv", "tr"] } filetime = "0.2" @@ -16,7 +16,7 @@ glob = "0.3" globset = "0.4" image = "0.24" lexical-sort = "0.3" -minify-html = "0.9" +minify-html = "0.10" nom-bibtex = "0.3" num-format = "0.4" once_cell = "1" @@ -27,17 +27,17 @@ rayon = "1" regex = "1" relative-path = "1" reqwest = { version = "0.11", default-features = false, features = ["blocking"] } -sass-rs = "0.2" +grass = {version = "0.12.1", default-features = false, features = ["random"]} serde_json = "1" -serde_yaml = "0.8" +serde_yaml = "0.9" sha2 = "0.10" slug = "0.1" svg_metadata = "0.4" syntect = "5" -tera = { version = "1", features = ["preserve_order"] } +tera = { version = "1.17", features = ["preserve_order", "date-locale"] } termcolor = "1.0.4" time = "0.3" -toml = "0.5" +toml = "0.7" unic-langid = "0.9" unicode-segmentation = "1.2" url = "2" diff --git a/components/libs/src/lib.rs b/components/libs/src/lib.rs index a1afcbb425..d0772dc0a4 100644 --- a/components/libs/src/lib.rs +++ b/components/libs/src/lib.rs @@ -14,6 +14,7 @@ pub use filetime; pub use gh_emoji; pub use glob; pub use globset; +pub use grass; pub use image; pub use lexical_sort; pub use minify_html; @@ -27,7 +28,6 @@ pub use rayon; pub use regex; pub use relative_path; pub use reqwest; -pub use sass_rs; pub use serde_json; pub use serde_yaml; pub use sha2; diff --git a/components/markdown/src/markdown.rs b/components/markdown/src/markdown.rs index caa4d7c870..88417fce22 100644 --- a/components/markdown/src/markdown.rs +++ b/components/markdown/src/markdown.rs @@ -5,6 +5,7 @@ use libs::gh_emoji::Replacer as EmojiReplacer; use libs::once_cell::sync::Lazy; use libs::pulldown_cmark as cmark; use libs::tera; +use utils::net::is_external_link; use crate::context::RenderContext; use errors::{Context, Error, Result}; @@ -56,7 +57,10 @@ fn insert_many<T>(input: &mut Vec<T>, elem_to_insert: Vec<(usize, T)>) { /// Colocated asset links refers to the files in the same directory. fn is_colocated_asset_link(link: &str) -> bool { - !link.starts_with('/') && !link.starts_with('#') && !STARTS_WITH_SCHEMA_RE.is_match(link) + !link.starts_with('/') + && !link.starts_with("..") + && !link.starts_with('#') + && !STARTS_WITH_SCHEMA_RE.is_match(link) } #[derive(Debug)] @@ -130,11 +134,6 @@ fn find_anchor(anchors: &[String], name: String, level: u16) -> String { find_anchor(anchors, name, level + 1) } -/// Returns whether a link starts with an HTTP(s) scheme. -fn is_external_link(link: &str) -> bool { - link.starts_with("http:") || link.starts_with("https:") -} - fn fix_link( link_type: LinkType, link: &str, @@ -174,6 +173,8 @@ fn fix_link( } } } + } else if is_colocated_asset_link(link) { + format!("{}{}", context.current_page_permalink, link) } else if is_external_link(link) { external_links.push(link.to_owned()); link.to_owned() @@ -480,13 +481,10 @@ pub fn markdown_to_html( } // We remove all the empty things we might have pushed before so we don't get some random \n - events = events - .into_iter() - .filter(|e| match e { - Event::Text(text) | Event::Html(text) => !text.is_empty(), - _ => true, - }) - .collect(); + events.retain(|e| match e { + Event::Text(text) | Event::Html(text) => !text.is_empty(), + _ => true, + }); let heading_refs = get_heading_refs(&events); @@ -540,12 +538,10 @@ pub fn markdown_to_html( .context("Failed to render anchor link template")?; if context.insert_anchor != InsertAnchor::Heading { anchors_to_insert.push((anchor_idx, Event::Html(anchor_link.into()))); - } else { - if let Some(captures) = A_HTML_TAG.captures(&anchor_link) { - let opening_tag = captures.get(1).map_or("", |m| m.as_str()).to_string(); - anchors_to_insert.push((start_idx + 1, Event::Html(opening_tag.into()))); - anchors_to_insert.push((end_idx, Event::Html("</a>".into()))); - } + } else if let Some(captures) = A_HTML_TAG.captures(&anchor_link) { + let opening_tag = captures.get(1).map_or("", |m| m.as_str()).to_string(); + anchors_to_insert.push((start_idx + 1, Event::Html(opening_tag.into()))); + anchors_to_insert.push((end_idx, Event::Html("</a>".into()))); } } @@ -600,6 +596,7 @@ mod tests { fn test_is_external_link() { assert!(is_external_link("http://example.com/")); assert!(is_external_link("https://example.com/")); + assert!(is_external_link("www.example.com")); assert!(is_external_link("https://example.com/index.html#introduction")); assert!(!is_external_link("mailto:user@example.com")); @@ -609,4 +606,22 @@ mod tests { assert!(!is_external_link("http.jpg")) } + + #[test] + // Tests for link that points to files in the same directory + fn test_is_colocated_asset_link_true() { + let links: [&str; 3] = ["./same-dir.md", "file.md", "qwe.js"]; + for link in links { + assert!(is_colocated_asset_link(link)); + } + } + + #[test] + // Tests for files where the link points to a different directory + fn test_is_colocated_asset_link_false() { + let links: [&str; 2] = ["/other-dir/file.md", "../sub-dir/file.md"]; + for link in links { + assert!(!is_colocated_asset_link(link)); + } + } } diff --git a/components/markdown/src/shortcode/parser.rs b/components/markdown/src/shortcode/parser.rs index 230c36cf93..4ce30b7bd7 100644 --- a/components/markdown/src/shortcode/parser.rs +++ b/components/markdown/src/shortcode/parser.rs @@ -10,7 +10,7 @@ use utils::templates::ShortcodeFileType; pub const SHORTCODE_PLACEHOLDER: &str = "@@ZOLA_SC_PLACEHOLDER@@"; -#[derive(PartialEq, Debug)] +#[derive(PartialEq, Debug, Eq)] pub struct Shortcode { pub(crate) name: String, pub(crate) args: Value, diff --git a/components/markdown/tests/snapshots/markdown__can_render_basic_markdown.snap b/components/markdown/tests/snapshots/markdown__can_render_basic_markdown.snap index e839beab96..8794d6e1e1 100644 --- a/components/markdown/tests/snapshots/markdown__can_render_basic_markdown.snap +++ b/components/markdown/tests/snapshots/markdown__can_render_basic_markdown.snap @@ -8,6 +8,6 @@ expression: body Hello world Non rendered emoji :smile: -<a href="image.jpg">a link</a> +<a href="https://www.getzola.org/test/image.jpg">a link</a> <img src="https://www.getzola.org/test/image.jpg" alt="alt text" /></p> <h1>some html</h1> diff --git a/components/search/src/lib.rs b/components/search/src/lib.rs index d03b622a81..c905c3f93d 100644 --- a/components/search/src/lib.rs +++ b/components/search/src/lib.rs @@ -14,9 +14,12 @@ static AMMONIA: Lazy<ammonia::Builder<'static>> = Lazy::new(|| { let mut clean_content = HashSet::new(); clean_content.insert("script"); clean_content.insert("style"); + let mut rm_tags = HashSet::new(); + rm_tags.insert("pre"); let mut builder = ammonia::Builder::new(); builder .tags(HashSet::new()) + .rm_tags(rm_tags) .tag_attributes(HashMap::new()) .generic_attributes(HashSet::new()) .link_rel(None) diff --git a/components/site/src/feed.rs b/components/site/src/feed.rs index 8db2cc4617..5ae5836852 100644 --- a/components/site/src/feed.rs +++ b/components/site/src/feed.rs @@ -10,7 +10,7 @@ use content::{Page, TaxonomyTerm}; use errors::Result; use utils::templates::render_template; -#[derive(Debug, Clone, PartialEq, Serialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] pub struct SerializedFeedTaxonomyItem<'a> { name: &'a str, slug: &'a str, diff --git a/components/site/src/lib.rs b/components/site/src/lib.rs index 788d46a5c1..df62ae9ed3 100644 --- a/components/site/src/lib.rs +++ b/components/site/src/lib.rs @@ -5,8 +5,8 @@ pub mod sass; pub mod sitemap; pub mod tpls; -use std::collections::HashMap; -use std::fs::remove_dir_all; +use std::borrow::Cow; +use std::collections::{HashMap, HashSet}; use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex, RwLock}; @@ -15,16 +15,17 @@ use libs::rayon::prelude::*; use libs::tera::{Context, Tera}; use libs::walkdir::{DirEntry, WalkDir}; -use config::{get_config, Config}; +use config::{get_config, Config, IndexFormat}; use content::{Library, Page, Paginator, Section, Taxonomy}; -use errors::{anyhow, bail, Context as ErrorContext, Result}; +use errors::{anyhow, bail, Result}; use libs::relative_path::RelativePathBuf; use std::time::Instant; use templates::{load_tera, render_redirect_template}; use utils::fs::{ - copy_directory, copy_file_if_needed, create_directory, create_file, ensure_directory_exists, + clean_site_output_folder, copy_directory, copy_file_if_needed, create_directory, create_file, + ensure_directory_exists, }; -use utils::net::get_available_port; +use utils::net::{get_available_port, is_external_link}; use utils::templates::{render_template, ShortcodeDefinition}; use utils::types::InsertAnchor; @@ -162,8 +163,6 @@ impl Site { /// Reads all .md files in the `content` directory and create pages/sections /// out of them pub fn load(&mut self) -> Result<()> { - let base_path = self.base_path.to_string_lossy().replace('\\', "/"); - self.library = Arc::new(RwLock::new(Library::new(&self.config))); let mut pages_insert_anchors = HashMap::new(); @@ -171,15 +170,21 @@ impl Site { // which we can only decide to use after we've deserialised the section // so it's kinda necessecary let mut dir_walker = - WalkDir::new(format!("{}/{}", base_path, "content/")).follow_links(true).into_iter(); + WalkDir::new(self.base_path.join("content")).follow_links(true).into_iter(); let mut allowed_index_filenames: Vec<_> = self .config .other_languages() - .iter() - .map(|(code, _)| format!("_index.{}.md", code)) + .keys() + .map(|code| format!("_index.{}.md", code)) .collect(); allowed_index_filenames.push("_index.md".to_string()); + // We will insert colocated pages (those with a index.md filename) + // at the end to detect pages that are actually errors: + // when there is both a _index.md and index.md in the same folder + let mut pages = Vec::new(); + let mut components = HashSet::new(); + loop { let entry: DirEntry = match dir_walker.next() { None => break, @@ -220,7 +225,7 @@ impl Site { // if we are processing a section we have to collect // index files for all languages and process them simultaneously // before any of the pages - let index_files = WalkDir::new(&path) + let index_files = WalkDir::new(path) .follow_links(true) .max_depth(1) .into_iter() @@ -243,8 +248,9 @@ impl Site { for index_file in index_files { let section = Section::from_file(index_file.path(), &self.config, &self.base_path)?; + components.extend(section.file.components.clone()); - // if the section is drafted we can skip the enitre dir + // if the section is drafted we can skip the entire dir if section.meta.draft && !self.include_drafts { dir_walker.skip_current_dir(); continue; @@ -254,19 +260,37 @@ impl Site { } } else { let page = Page::from_file(path, &self.config, &self.base_path)?; + pages.push(page); + } + } + self.create_default_index_sections()?; + + for page in pages { + // should we skip drafts? + if page.meta.draft && !self.include_drafts { + continue; + } - // should we skip drafts? - if page.meta.draft && !self.include_drafts { - continue; + // We are only checking it on load and not in add_page since we have access to + // all the components there. + if page.file.filename == "index.md" { + let is_invalid = match page.components.last() { + Some(last) => components.contains(last), + // content/index.md is always invalid, but content/colocated/index.md is ok + None => page.file.colocated_path.is_none(), + }; + + if is_invalid { + bail!("We can't have a page called `index.md` in the same folder as an index section in {:?}", page.file.parent); } - pages_insert_anchors.insert( - page.file.path.clone(), - self.find_parent_section_insert_anchor(&page.file.parent.clone(), &page.lang), - ); - self.add_page(page, false)?; } + + pages_insert_anchors.insert( + page.file.path.clone(), + self.find_parent_section_insert_anchor(&page.file.parent.clone(), &page.lang), + ); + self.add_page(page, false)?; } - self.create_default_index_sections()?; { let library = self.library.read().unwrap(); @@ -585,14 +609,10 @@ impl Site { imageproc.do_process() } - /// Deletes the `public` directory if it exists + /// Deletes the `public` directory if it exists and the `preserve_dotfiles_in_output` option is set to false, + /// or if set to true: its contents except for the dotfiles at the root level. pub fn clean(&self) -> Result<()> { - if self.output_path.exists() { - // Delete current `public` directory so we can start fresh - remove_dir_all(&self.output_path).context("Couldn't delete output directory")?; - } - - Ok(()) + clean_site_output_folder(&self.output_path, self.config.preserve_dotfiles_in_output) } /// Handles whether to write to disk or to memory @@ -666,7 +686,7 @@ impl Site { asset_path, ¤t_path.join( asset_path - .strip_prefix(&page.file.path.parent().unwrap()) + .strip_prefix(page.file.path.parent().unwrap()) .expect("Couldn't get filename from page asset"), ), )?; @@ -766,32 +786,36 @@ impl Site { Ok(()) } + fn index_for_lang(&self, lang: &str) -> Result<()> { + let index_json = search::build_index( + &self.config.default_language, + &self.library.read().unwrap(), + &self.config, + )?; + let (path, content) = match &self.config.search.index_format { + IndexFormat::ElasticlunrJson => { + let path = self.output_path.join(format!("search_index.{}.json", lang)); + (path, index_json) + } + IndexFormat::ElasticlunrJavascript => { + let path = self.output_path.join(format!("search_index.{}.js", lang)); + let content = format!("window.searchIndex = {};", index_json); + (path, content) + } + }; + create_file(&path, &content) + } + 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)), - &format!( - "window.searchIndex = {};", - search::build_index( - &self.config.default_language, - &self.library.read().unwrap(), - &self.config - )? - ), - )?; + self.index_for_lang(&self.config.default_language)?; for (code, language) in &self.config.other_languages() { if code != &self.config.default_language && language.build_search_index { - create_file( - &self.output_path.join(&format!("search_index.{}.js", &code)), - &format!( - "window.searchIndex = {};", - search::build_index(code, &self.library.read().unwrap(), &self.config)? - ), - )?; + self.index_for_lang(code)?; } } @@ -1069,7 +1093,7 @@ impl Site { asset_path, &output_path.join( asset_path - .strip_prefix(§ion.file.path.parent().unwrap()) + .strip_prefix(section.file.path.parent().unwrap()) .expect("Failed to get asset filename for section"), ), )?; @@ -1088,7 +1112,11 @@ impl Site { } if let Some(ref redirect_to) = section.meta.redirect_to { - let permalink = self.config.make_permalink(redirect_to); + let permalink: Cow<String> = if is_external_link(redirect_to) { + Cow::Borrowed(redirect_to) + } else { + Cow::Owned(self.config.make_permalink(redirect_to)) + }; self.write_content( &components, "index.html", diff --git a/components/site/src/minify.rs b/components/site/src/minify.rs index 46a48f4808..8f3862b902 100644 --- a/components/site/src/minify.rs +++ b/components/site/src/minify.rs @@ -1,11 +1,11 @@ use errors::{bail, Result}; use libs::minify_html::{minify, Cfg}; -// TODO: move to site - pub fn html(html: String) -> Result<String> { let mut cfg = Cfg::spec_compliant(); cfg.keep_html_and_head_opening_tags = true; + cfg.minify_css = true; + cfg.minify_js = true; let minified = minify(html.as_bytes(), &cfg); match std::str::from_utf8(&minified) { @@ -83,4 +83,60 @@ mod tests { let res = html(input.to_owned()).unwrap(); assert_eq!(res, expected); } + + // https://github.com/getzola/zola/issues/1765 + #[test] + fn can_minify_css() { + let input = r#" +<!doctype html> +<html> +<head> + <meta charset="utf-8"> + <style> + p { + color: white; + margin-left: 10000px; + } + </style> +</head> +<body> + + + <p>Example blog post</p> + + FOO BAR +</body> +</html> +"#; + let expected = r#"<!doctype html><html><head><meta charset=utf-8><style>p{color:white;margin-left:10000px}</style><body><p>Example blog post</p> FOO BAR"#; + let res = html(input.to_owned()).unwrap(); + assert_eq!(res, expected); + } + + // https://github.com/getzola/zola/issues/1765 + #[test] + fn can_minify_js() { + let input = r#" +<!doctype html> +<html> +<head> + <meta charset="utf-8"> + <script> + alert("Hello World!"); + console.log("Some information: %o", information); + </script> +</head> +<body> + + + <p>Example blog post</p> + + FOO BAR +</body> +</html> +"#; + let expected = r#"<!doctype html><html><head><meta charset=utf-8><script>alert("Hello World!");console.log("Some information: %o",information)</script><body><p>Example blog post</p> FOO BAR"#; + let res = html(input.to_owned()).unwrap(); + assert_eq!(res, expected); + } } diff --git a/components/site/src/sass.rs b/components/site/src/sass.rs index ab6bb55695..87f5c7dd8c 100644 --- a/components/site/src/sass.rs +++ b/components/site/src/sass.rs @@ -1,8 +1,9 @@ use std::fs::create_dir_all; use std::path::{Path, PathBuf}; -use libs::glob::glob; -use libs::sass_rs::{compile_file, Options, OutputStyle}; +use libs::globset::Glob; +use libs::grass::{from_path as compile_file, Options, OutputStyle}; +use libs::walkdir::{DirEntry, WalkDir}; use crate::anyhow; use errors::{bail, Result}; @@ -17,11 +18,24 @@ pub fn compile_sass(base_path: &Path, output_path: &Path) -> Result<()> { sass_path }; - let mut options = Options { output_style: OutputStyle::Compressed, ..Default::default() }; - let mut compiled_paths = compile_sass_glob(&sass_path, output_path, "scss", &options)?; + let options = Options::default().style(OutputStyle::Compressed); + let files = get_non_partial_scss(&sass_path); + let mut compiled_paths = Vec::new(); + + for file in files { + let css = compile_file(&file, &options).map_err(|e| anyhow!(e))?; - options.indented_syntax = true; - compiled_paths.extend(compile_sass_glob(&sass_path, output_path, "sass", &options)?); + let path_inside_sass = file.strip_prefix(&sass_path).unwrap(); + let parent_inside_sass = path_inside_sass.parent(); + let css_output_path = output_path.join(path_inside_sass).with_extension("css"); + + if parent_inside_sass.is_some() { + create_dir_all(css_output_path.parent().unwrap())?; + } + + create_file(&css_output_path, &css)?; + compiled_paths.push((path_inside_sass.to_owned(), css_output_path)); + } compiled_paths.sort(); for window in compiled_paths.windows(2) { @@ -38,74 +52,55 @@ pub fn compile_sass(base_path: &Path, output_path: &Path) -> Result<()> { Ok(()) } -fn compile_sass_glob( - sass_path: &Path, - output_path: &Path, - extension: &str, - options: &Options, -) -> Result<Vec<(PathBuf, PathBuf)>> { - let files = get_non_partial_scss(sass_path, extension); - - let mut compiled_paths = Vec::new(); - for file in files { - let css = compile_file(&file, options.clone()).map_err(|e| anyhow!(e))?; - - let path_inside_sass = file.strip_prefix(&sass_path).unwrap(); - let parent_inside_sass = path_inside_sass.parent(); - let css_output_path = output_path.join(path_inside_sass).with_extension("css"); - - if parent_inside_sass.is_some() { - create_dir_all(&css_output_path.parent().unwrap())?; - } - - create_file(&css_output_path, &css)?; - compiled_paths.push((path_inside_sass.to_owned(), css_output_path)); - } - - Ok(compiled_paths) +fn is_partial_scss(entry: &DirEntry) -> bool { + entry.file_name().to_str().map(|s| s.starts_with('_')).unwrap_or(false) } -fn get_non_partial_scss(sass_path: &Path, extension: &str) -> Vec<PathBuf> { - let glob_string = format!("{}/**/*.{}", sass_path.display(), extension); - glob(&glob_string) - .expect("Invalid glob for sass") +fn get_non_partial_scss(sass_path: &Path) -> Vec<PathBuf> { + let glob = Glob::new("*.{sass,scss}").expect("Invalid glob for sass").compile_matcher(); + + WalkDir::new(sass_path) + .into_iter() + .filter_entry(|e| !is_partial_scss(e)) .filter_map(|e| e.ok()) - .filter(|entry| { - !entry - .as_path() - .iter() - .last() - .map(|c| c.to_string_lossy().starts_with('_')) - .unwrap_or(true) - }) + .map(|e| e.into_path()) + .filter(|e| glob.is_match(e)) .collect::<Vec<_>>() } -#[test] -fn test_get_non_partial_scss() { - use std::env; +#[cfg(test)] +mod tests { + use super::*; - let mut path = env::current_dir().unwrap().parent().unwrap().parent().unwrap().to_path_buf(); - path.push("test_site"); - path.push("sass"); + #[test] + fn test_get_non_partial_scss() { + use std::env; - let result = get_non_partial_scss(&path, "scss"); + let mut path = + env::current_dir().unwrap().parent().unwrap().parent().unwrap().to_path_buf(); + path.push("test_site"); + path.push("sass"); - assert!(!result.is_empty()); - assert!(result.iter().filter_map(|path| path.file_name()).any(|file| file == "scss.scss")) -} -#[test] -fn test_get_non_partial_scss_underscores() { - use std::env; + let result = get_non_partial_scss(&path); - let mut path = env::current_dir().unwrap().parent().unwrap().parent().unwrap().to_path_buf(); - path.push("test_site"); - path.push("_dir_with_underscores"); - path.push(".."); - path.push("sass"); + assert!(!result.is_empty()); + assert!(result.iter().filter_map(|path| path.file_name()).any(|file| file == "scss.scss")) + } + + #[test] + fn test_get_non_partial_scss_underscores() { + use std::env; - let result = get_non_partial_scss(&path, "scss"); + let mut path = + env::current_dir().unwrap().parent().unwrap().parent().unwrap().to_path_buf(); + path.push("test_site"); + path.push("_dir_with_underscores"); + path.push(".."); + path.push("sass"); - assert!(!result.is_empty()); - assert!(result.iter().filter_map(|path| path.file_name()).any(|file| file == "scss.scss")) + let result = get_non_partial_scss(&path); + + assert!(!result.is_empty()); + assert!(result.iter().filter_map(|path| path.file_name()).any(|file| file == "scss.scss")) + } } diff --git a/components/site/src/tpls.rs b/components/site/src/tpls.rs index 98d8066534..d1e2eacd5a 100644 --- a/components/site/src/tpls.rs +++ b/components/site/src/tpls.rs @@ -53,14 +53,13 @@ pub fn register_early_global_fns(site: &mut Site) -> TeraResult<()> { ), ); site.tera.register_function( - "get_file_hash", - global_fns::GetFileHash::new( + "get_hash", + global_fns::GetHash::new( site.base_path.clone(), site.config.theme.clone(), site.output_path.clone(), ), ); - site.tera.register_filter( "markdown", filters::MarkdownFilter::new( @@ -91,4 +90,12 @@ pub fn register_tera_global_fns(site: &mut Site) { site.library.clone(), ), ); + site.tera.register_function( + "get_taxonomy_term", + global_fns::GetTaxonomyTerm::new( + &site.config.default_language, + site.taxonomies.clone(), + site.library.clone(), + ), + ); } diff --git a/components/site/tests/invalid.rs b/components/site/tests/invalid.rs new file mode 100644 index 0000000000..5081b9e2a7 --- /dev/null +++ b/components/site/tests/invalid.rs @@ -0,0 +1,18 @@ +mod common; + +use site::Site; +use std::env; + +#[test] +fn errors_on_index_md_page_in_section() { + let mut path = env::current_dir().unwrap().parent().unwrap().parent().unwrap().to_path_buf(); + path.push("test_sites_invalid"); + path.push("indexmd"); + let config_file = path.join("config.toml"); + let mut site = Site::new(&path, &config_file).unwrap(); + let res = site.load(); + assert!(res.is_err()); + let err = res.unwrap_err(); + assert!(format!("{:?}", err) + .contains("We can't have a page called `index.md` in the same folder as an index section")); +} diff --git a/components/site/tests/site.rs b/components/site/tests/site.rs index b2cce71b9d..64936124bc 100644 --- a/components/site/tests/site.rs +++ b/components/site/tests/site.rs @@ -21,7 +21,7 @@ fn can_parse_site() { let library = site.library.read().unwrap(); // Correct number of pages (sections do not count as pages, draft are ignored) - assert_eq!(library.pages.len(), 33); + assert_eq!(library.pages.len(), 34); let posts_path = path.join("content").join("posts"); // Make sure the page with a url doesn't have any sections @@ -39,7 +39,7 @@ fn can_parse_site() { // And that the sections are correct let index_section = library.sections.get(&path.join("content").join("_index.md")).unwrap(); assert_eq!(index_section.subsections.len(), 5); - assert_eq!(index_section.pages.len(), 3); + assert_eq!(index_section.pages.len(), 4); assert!(index_section.ancestors.is_empty()); let posts_section = library.sections.get(&posts_path.join("_index.md")).unwrap(); @@ -221,6 +221,13 @@ fn can_build_site_without_live_reload() { "robots.txt", "Sitemap: https://replace-this-with-your-url.com/sitemap.xml" )); + + // And + assert!(file_contains!( + public, + "colocated-assets/index.html", + "Assets in root content directory" + )); } #[test] @@ -777,7 +784,7 @@ fn can_ignore_markdown_content() { fn can_cachebust_static_files() { let (_, _tmp_dir, public) = build_site("test_site"); assert!(file_contains!(public, "index.html", - "<link href=\"https://replace-this-with-your-url.com/site.css?h=83bd983e8899946ee33d0fde18e82b04d7bca1881d10846c769b486640da3de9\" rel=\"stylesheet\">")); + "<link href=\"https://replace-this-with-your-url.com/site.css?h=83bd983e8899946ee33d\" rel=\"stylesheet\">")); } #[test] @@ -836,6 +843,31 @@ fn panics_on_invalid_external_domain() { site.load().expect("link check test_site"); } +#[test] +fn can_find_site_and_page_authors() { + let mut path = env::current_dir().unwrap().parent().unwrap().parent().unwrap().to_path_buf(); + path.push("test_site"); + let config_file = path.join("config.toml"); + let mut site = Site::new(&path, config_file).unwrap(); + site.load().unwrap(); + let library = site.library.read().unwrap(); + + // The config has a global default author set. + let author = site.config.author; + assert_eq!(Some("config@example.com (Config Author)".to_string()), author); + + let posts_path = path.join("content").join("posts"); + let posts_section = library.sections.get(&posts_path.join("_index.md")).unwrap(); + + let p1 = &library.pages[&posts_section.pages[0]]; + let p2 = &library.pages[&posts_section.pages[1]]; + + // Only the first page has had an author added. + assert_eq!(1, p1.meta.authors.len()); + assert_eq!("page@example.com (Page Author)", p1.meta.authors.get(0).unwrap()); + assert_eq!(0, p2.meta.authors.len()); +} + // Follows test_site/themes/sample/templates/current_path.html fn current_path(path: &str) -> String { format!("[current_path]({})", path) diff --git a/components/templates/src/builtins/atom.xml b/components/templates/src/builtins/atom.xml index e515636d06..acdd6b2e39 100644 --- a/components/templates/src/builtins/atom.xml +++ b/components/templates/src/builtins/atom.xml @@ -1,32 +1,43 @@ <?xml version="1.0" encoding="UTF-8"?> <feed xmlns="http://www.w3.org/2005/Atom" xml:lang="{{ lang }}"> - <title>{{ config.title }} - {%- if term %} - {{ term.name }} + <title>{{ config.title }} + {%- if term %} - {{ term.name }} {%- elif section.title %} - {{ section.title }} - {%- endif -%} - - {%- if config.description %} - {{ config.description }} - {%- endif %} - - + - Zola - {{ last_updated | date(format="%+") }} - {{ feed_url | safe }} - {%- for page in pages %} - - {{ page.title }} - {{ page.date | date(format="%+") }} - {{ page.updated | default(value=page.date) | date(format="%+") }} - - {{ page.permalink | safe }} - {{ page.content }} - - {%- endfor %} + Zola + {{ last_updated | date(format="%+") }} + {{ feed_url | safe }} + {%- for page in pages %} + + {{ page.title }} + {{ page.date | date(format="%+") }} + {{ page.updated | default(value=page.date) | date(format="%+") }} + + + {%- if page.authors -%} + {{ page.authors[0] }} + {%- elif config.author -%} + {{ config.author }} + {%- else -%} + Unknown + {%- endif -%} + + + + {{ page.permalink | safe }} + {{ page.content }} + + {%- endfor %} diff --git a/components/templates/src/builtins/rss.xml b/components/templates/src/builtins/rss.xml index d68302b543..1c09e4cf4d 100644 --- a/components/templates/src/builtins/rss.xml +++ b/components/templates/src/builtins/rss.xml @@ -6,25 +6,35 @@ {%- elif section.title %} - {{ section.title }} {%- endif -%} - {%- if section -%} - {{ section.permalink | escape_xml | safe }} - {%- else -%} - {{ config.base_url | escape_xml | safe }} - {%- endif -%} - - {{ config.description }} - Zola - {{ lang }} - - {{ last_updated | date(format="%a, %d %b %Y %H:%M:%S %z") }} - {%- for page in pages %} - - {{ page.title }} - {{ page.date | date(format="%a, %d %b %Y %H:%M:%S %z") }} - {{ page.permalink | escape_xml | safe }} - {{ page.permalink | escape_xml | safe }} - {% if page.summary %}{{ page.summary }}{% else %}{{ page.content }}{% endif %} - - {%- endfor %} + + {%- if section -%} + {{ section.permalink | escape_xml | safe }} + {%- else -%} + {{ config.base_url | escape_xml | safe }} + {%- endif -%} + + {{ config.description }} + Zola + {{ lang }} + + {{ last_updated | date(format="%a, %d %b %Y %H:%M:%S %z") }} + {%- for page in pages %} + + {{ page.title }} + {{ page.date | date(format="%a, %d %b %Y %H:%M:%S %z") }} + + {%- if page.authors -%} + {{ page.authors[0] }} + {%- elif config.author -%} + {{ config.author }} + {%- else -%} + Unknown + {%- endif -%} + + {{ page.permalink | escape_xml | safe }} + {{ page.permalink | escape_xml | safe }} + {% if page.summary %}{{ page.summary }}{% else %}{{ page.content }}{% endif %} + + {%- endfor %} diff --git a/components/templates/src/builtins/shortcodes/gist.html b/components/templates/src/builtins/shortcodes/gist.html deleted file mode 100644 index 44f051fa15..0000000000 --- a/components/templates/src/builtins/shortcodes/gist.html +++ /dev/null @@ -1,3 +0,0 @@ -
- -
diff --git a/components/templates/src/builtins/shortcodes/streamable.html b/components/templates/src/builtins/shortcodes/streamable.html deleted file mode 100644 index d45fa642a7..0000000000 --- a/components/templates/src/builtins/shortcodes/streamable.html +++ /dev/null @@ -1,3 +0,0 @@ -
- -
diff --git a/components/templates/src/builtins/shortcodes/vimeo.html b/components/templates/src/builtins/shortcodes/vimeo.html deleted file mode 100644 index 0a4298c7bb..0000000000 --- a/components/templates/src/builtins/shortcodes/vimeo.html +++ /dev/null @@ -1,3 +0,0 @@ -
- -
diff --git a/components/templates/src/filters.rs b/components/templates/src/filters.rs index 4360b46a1f..b9b53d3767 100644 --- a/components/templates/src/filters.rs +++ b/components/templates/src/filters.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use std::hash::BuildHasher; use config::Config; -use libs::base64::{decode, encode}; +use libs::base64::engine::{general_purpose::STANDARD as standard_b64, Engine}; use libs::tera::{ to_value, try_get_value, Error as TeraError, Filter as TeraFilter, Result as TeraResult, Tera, Value, @@ -62,7 +62,7 @@ pub fn base64_encode( _: &HashMap, ) -> TeraResult { let s = try_get_value!("base64_encode", "value", String, value); - Ok(to_value(&encode(s.as_bytes())).unwrap()) + Ok(to_value(standard_b64.encode(s.as_bytes())).unwrap()) } pub fn base64_decode( @@ -70,7 +70,12 @@ pub fn base64_decode( _: &HashMap, ) -> TeraResult { let s = try_get_value!("base64_decode", "value", String, value); - Ok(to_value(&String::from_utf8(decode(s.as_bytes()).unwrap()).unwrap()).unwrap()) + let decoded = standard_b64 + .decode(s.as_bytes()) + .map_err(|e| format!("`base64_decode`: failed to decode: {}", e))?; + let as_str = + String::from_utf8(decoded).map_err(|e| format!("`base64_decode`: invalid utf-8: {}", e))?; + Ok(to_value(as_str).unwrap()) } #[derive(Debug)] diff --git a/components/templates/src/global_fns/content.rs b/components/templates/src/global_fns/content.rs index a4555b42a9..84c155fd71 100644 --- a/components/templates/src/global_fns/content.rs +++ b/components/templates/src/global_fns/content.rs @@ -1,4 +1,4 @@ -use content::{Library, Taxonomy}; +use content::{Library, Taxonomy, TaxonomyTerm}; use libs::tera::{from_value, to_value, Function as TeraFn, Result, Value}; use std::collections::HashMap; use std::path::PathBuf; @@ -182,6 +182,93 @@ impl TeraFn for GetTaxonomy { } } +#[derive(Debug)] +pub struct GetTaxonomyTerm { + library: Arc>, + taxonomies: HashMap, + default_lang: String, +} +impl GetTaxonomyTerm { + pub fn new( + default_lang: &str, + all_taxonomies: Vec, + library: Arc>, + ) -> Self { + let mut taxonomies = HashMap::new(); + for taxo in all_taxonomies { + taxonomies.insert(format!("{}-{}", taxo.kind.name, taxo.lang), taxo); + } + Self { taxonomies, library, default_lang: default_lang.to_string() } + } +} +impl TeraFn for GetTaxonomyTerm { + fn call(&self, args: &HashMap) -> Result { + let kind = required_arg!( + String, + args.get("kind"), + "`get_taxonomy_term` requires a `kind` argument with a string value" + ); + let term = required_arg!( + String, + args.get("term"), + "`get_taxonomy_term` requires a `term` argument with a string value" + ); + let include_pages = optional_arg!( + bool, + args.get("include_pages"), + "`get_taxonomy_term`: `include_pages` must be a boolean (true or false)" + ) + .unwrap_or(true); + let required = optional_arg!( + bool, + args.get("required"), + "`get_taxonomy_term`: `required` must be a boolean (true or false)" + ) + .unwrap_or(true); + + let lang = optional_arg!( + String, + args.get("lang"), + "`get_taxonomy_term_by_name`: `lang` must be a string" + ) + .unwrap_or_else(|| self.default_lang.clone()); + + let tax: &Taxonomy = match (self.taxonomies.get(&format!("{}-{}", kind, lang)), required) { + (Some(t), _) => t, + (None, false) => { + return Ok(Value::Null); + } + (None, true) => { + return Err(format!( + "`get_taxonomy_term_by_name` received an unknown taxonomy as kind: {}", + kind + ) + .into()); + } + }; + + let term: &TaxonomyTerm = match (tax.items.iter().find(|i| i.name == term), required) { + (Some(t), _) => t, + (None, false) => { + return Ok(Value::Null); + } + (None, true) => { + return Err(format!( + "`get_taxonomy_term_by_name` received an unknown taxonomy as kind: {}", + kind + ) + .into()); + } + }; + + if include_pages { + Ok(to_value(term.serialize(&self.library.read().unwrap())).unwrap()) + } else { + Ok(to_value(term.serialize_without_pages(&self.library.read().unwrap())).unwrap()) + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -325,4 +412,68 @@ mod tests { args.insert("name".to_string(), to_value("random").unwrap()); assert!(static_fn.call(&args).is_err()); } + + #[test] + fn can_get_taxonomy_term() { + let mut config = Config::default_for_test(); + config.slugify.taxonomies = SlugifyStrategy::On; + let taxo_config = TaxonomyConfig { name: "tags".to_string(), ..TaxonomyConfig::default() }; + let taxo_config_fr = + TaxonomyConfig { name: "tags".to_string(), ..TaxonomyConfig::default() }; + config.slugify_taxonomies(); + let library = Arc::new(RwLock::new(Library::new(&config))); + let tag = TaxonomyTerm::new("Programming", &config.default_language, "tags", &[], &config); + let tag_fr = TaxonomyTerm::new("Programmation", "fr", "tags", &[], &config); + let tags = Taxonomy { + kind: taxo_config, + lang: config.default_language.clone(), + slug: "tags".to_string(), + path: "/tags/".to_string(), + permalink: "https://vincent.is/tags/".to_string(), + items: vec![tag], + }; + let tags_fr = Taxonomy { + kind: taxo_config_fr, + lang: "fr".to_owned(), + slug: "tags".to_string(), + path: "/fr/tags/".to_string(), + permalink: "https://vincent.is/fr/tags/".to_string(), + items: vec![tag_fr], + }; + + let taxonomies = vec![tags.clone(), tags_fr.clone()]; + let static_fn = GetTaxonomyTerm::new(&config.default_language, taxonomies, library); + // can find it correctly + let mut args = HashMap::new(); + args.insert("kind".to_string(), to_value("tags").unwrap()); + args.insert("term".to_string(), to_value("Programming").unwrap()); + let res = static_fn.call(&args).unwrap(); + let res_obj = res.as_object().unwrap(); + assert_eq!(res_obj["name"], Value::String("Programming".to_string())); + assert_eq!(res_obj["slug"], Value::String("programming".to_string())); + assert_eq!( + res_obj["permalink"], + Value::String("http://a-website.com/tags/programming/".to_string()) + ); + assert_eq!(res_obj["pages"], Value::Array(vec![])); + // Works with other languages as well + let mut args = HashMap::new(); + args.insert("kind".to_string(), to_value("tags").unwrap()); + args.insert("term".to_string(), to_value("Programmation").unwrap()); + args.insert("lang".to_string(), to_value("fr").unwrap()); + let res = static_fn.call(&args).unwrap(); + let res_obj = res.as_object().unwrap(); + assert_eq!(res_obj["name"], Value::String("Programmation".to_string())); + + // and errors if it can't find either taxonomy or term + let mut args = HashMap::new(); + args.insert("kind".to_string(), to_value("something-else").unwrap()); + args.insert("term".to_string(), to_value("Programming").unwrap()); + assert!(static_fn.call(&args).is_err()); + + let mut args = HashMap::new(); + args.insert("kind".to_string(), to_value("tags").unwrap()); + args.insert("kind".to_string(), to_value("something-else").unwrap()); + assert!(static_fn.call(&args).is_err()); + } } diff --git a/components/templates/src/global_fns/files.rs b/components/templates/src/global_fns/files.rs index 57c72f398a..cc8c1a9409 100644 --- a/components/templates/src/global_fns/files.rs +++ b/components/templates/src/global_fns/files.rs @@ -1,29 +1,27 @@ use std::collections::HashMap; +use std::fs; +use std::io::Read; use std::path::PathBuf; -use std::{fs, io, result}; use crate::global_fns::helpers::search_for_file; use config::Config; -use libs::base64::encode as encode_b64; + +use libs::base64::engine::{general_purpose::STANDARD as standard_b64, Engine}; use libs::sha2::{digest, Sha256, Sha384, Sha512}; use libs::tera::{from_value, to_value, Function as TeraFn, Result, Value}; -use libs::url; use utils::site::resolve_internal_link; -fn compute_file_hash( - mut file: fs::File, - as_base64: bool, -) -> result::Result +fn compute_hash(literal: String, as_base64: bool) -> String where digest::Output: core::fmt::LowerHex, D: std::io::Write, { let mut hasher = D::new(); - io::copy(&mut file, &mut hasher)?; + hasher.update(literal); if as_base64 { - Ok(encode_b64(hasher.finalize())) + standard_b64.encode(hasher.finalize()) } else { - Ok(format!("{:x}", hasher.finalize())) + format!("{:x}", hasher.finalize()) } } @@ -104,10 +102,10 @@ impl TeraFn for GetUrl { // anything else let mut segments = vec![]; - if lang != self.config.default_language { - if path.is_empty() || !path[1..].starts_with(&lang) { - segments.push(lang); - } + if lang != self.config.default_language + && (path.is_empty() || !path[1..].starts_with(&lang)) + { + segments.push(lang); } segments.push(path); @@ -127,11 +125,17 @@ impl TeraFn for GetUrl { &self.output_path, ) .map_err(|e| format!("`get_url`: {}", e))? - .and_then(|(p, _)| fs::File::open(&p).ok()) - .and_then(|f| compute_file_hash::(f, false).ok()) - { + .and_then(|(p, _)| fs::File::open(p).ok()) + .and_then(|mut f| { + let mut contents = String::new(); + + f.read_to_string(&mut contents).ok()?; + + Some(compute_hash::(contents, false)) + }) { Some(hash) => { - permalink = format!("{}?h={}", permalink, hash); + let shorthash = &hash[..20]; // 2^-80 chance of false positive + permalink = format!("{}?h={}", permalink, shorthash); } None => { return Err(format!( @@ -143,19 +147,6 @@ impl TeraFn for GetUrl { }; } - if cfg!(target_os = "windows") { - permalink = match url::Url::parse(&permalink) { - Ok(parsed) => parsed.into(), - Err(_) => { - return Err(format!( - "`get_url`: Could not parse link `{}` as a valid URL", - permalink - ) - .into()) - } - }; - } - Ok(to_value(permalink).unwrap()) } } @@ -166,71 +157,97 @@ impl TeraFn for GetUrl { } #[derive(Debug)] -pub struct GetFileHash { +pub struct GetHash { base_path: PathBuf, theme: Option, output_path: PathBuf, } -impl GetFileHash { +impl GetHash { pub fn new(base_path: PathBuf, theme: Option, output_path: PathBuf) -> Self { Self { base_path, theme, output_path } } } -impl TeraFn for GetFileHash { +impl TeraFn for GetHash { fn call(&self, args: &HashMap) -> Result { - let path = required_arg!( + let path = optional_arg!( String, args.get("path"), - "`get_file_hash` requires a `path` argument with a string value" + "`get_hash` requires either a `path` or a `literal` argument with a string value" ); + + let literal = optional_arg!( + String, + args.get("literal"), + "`get_hash` requires either a `path` or a `literal` argument with a string value" + ); + + let contents = match (path, literal) { + (Some(_), Some(_)) => { + return Err("`get_hash`: must have only one of `path` or `literal` argument".into()); + } + (None, None) => { + return Err( + "`get_hash`: must have at least one of `path` or `literal` argument".into() + ); + } + (Some(path_v), None) => { + let file_path = + match search_for_file(&self.base_path, &path_v, &self.theme, &self.output_path) + .map_err(|e| format!("`get_hash`: {}", e))? + { + Some((f, _)) => f, + None => { + return Err(format!("`get_hash`: Cannot find file: {}", path_v).into()); + } + }; + + let mut f = match std::fs::File::open(file_path) { + Ok(f) => f, + Err(e) => { + return Err(format!("File {} could not be open: {}", path_v, e).into()); + } + }; + + let mut contents = String::new(); + + match f.read_to_string(&mut contents) { + Ok(f) => f, + Err(e) => { + return Err(format!("File {} could not be read: {}", path_v, e).into()); + } + }; + + contents + } + (None, Some(literal_v)) => literal_v, + }; + let sha_type = optional_arg!( u16, args.get("sha_type"), - "`get_file_hash`: `sha_type` must be 256, 384 or 512" + "`get_hash`: `sha_type` must be 256, 384 or 512" ) .unwrap_or(384); - let base64 = optional_arg!( - bool, - args.get("base64"), - "`get_file_hash`: `base64` must be true or false" - ) - .unwrap_or(true); - - let file_path = - match search_for_file(&self.base_path, &path, &self.theme, &self.output_path) - .map_err(|e| format!("`get_file_hash`: {}", e))? - { - Some((f, _)) => f, - None => { - return Err(format!("`get_file_hash`: Cannot find file: {}", path).into()); - } - }; - let f = match std::fs::File::open(file_path) { - Ok(f) => f, - Err(e) => { - return Err(format!("File {} could not be open: {}", path, e).into()); - } - }; + let base64 = + optional_arg!(bool, args.get("base64"), "`get_hash`: `base64` must be true or false") + .unwrap_or(true); let hash = match sha_type { - 256 => compute_file_hash::(f, base64), - 384 => compute_file_hash::(f, base64), - 512 => compute_file_hash::(f, base64), - _ => return Err("`get_file_hash`: Invalid sha value".into()), + 256 => compute_hash::(contents, base64), + 384 => compute_hash::(contents, base64), + 512 => compute_hash::(contents, base64), + _ => return Err("`get_hash`: Invalid sha value".into()), }; - match hash { - Ok(digest) => Ok(to_value(digest).unwrap()), - Err(_) => Err("`get_file_hash`: could no compute hash".into()), - } + Ok(to_value(hash).unwrap()) } } #[cfg(test)] mod tests { - use super::{GetFileHash, GetUrl}; + use super::{GetHash, GetUrl}; use std::collections::HashMap; use std::fs::create_dir; @@ -272,7 +289,10 @@ title = "A title" let mut args = HashMap::new(); args.insert("path".to_string(), to_value("app.css").unwrap()); args.insert("cachebust".to_string(), to_value(true).unwrap()); - assert_eq!(static_fn.call(&args).unwrap(), "http://a-website.com/app.css?h=572e691dc68c3fcd653ae463261bdb38f35dc6f01715d9ce68799319dd158840"); + assert_eq!( + static_fn.call(&args).unwrap(), + "http://a-website.com/app.css?h=572e691dc68c3fcd653a" + ); } #[test] @@ -303,7 +323,10 @@ title = "A title" args.insert("path".to_string(), to_value("app.css").unwrap()); args.insert("trailing_slash".to_string(), to_value(true).unwrap()); args.insert("cachebust".to_string(), to_value(true).unwrap()); - assert_eq!(static_fn.call(&args).unwrap(), "http://a-website.com/app.css/?h=572e691dc68c3fcd653ae463261bdb38f35dc6f01715d9ce68799319dd158840"); + assert_eq!( + static_fn.call(&args).unwrap(), + "http://a-website.com/app.css/?h=572e691dc68c3fcd653a" + ); } #[test] @@ -456,7 +479,7 @@ title = "A title" #[test] fn can_get_file_hash_sha256_no_base64() { let dir = create_temp_dir(); - let static_fn = GetFileHash::new(dir.into_path(), None, PathBuf::new()); + let static_fn = GetHash::new(dir.into_path(), None, PathBuf::new()); let mut args = HashMap::new(); args.insert("path".to_string(), to_value("app.css").unwrap()); args.insert("sha_type".to_string(), to_value(256).unwrap()); @@ -470,7 +493,7 @@ title = "A title" #[test] fn can_get_file_hash_sha256_base64() { let dir = create_temp_dir(); - let static_fn = GetFileHash::new(dir.into_path(), None, PathBuf::new()); + let static_fn = GetHash::new(dir.into_path(), None, PathBuf::new()); let mut args = HashMap::new(); args.insert("path".to_string(), to_value("app.css").unwrap()); args.insert("sha_type".to_string(), to_value(256).unwrap()); @@ -481,7 +504,7 @@ title = "A title" #[test] fn can_get_file_hash_sha384_no_base64() { let dir = create_temp_dir(); - let static_fn = GetFileHash::new(dir.into_path(), None, PathBuf::new()); + let static_fn = GetHash::new(dir.into_path(), None, PathBuf::new()); let mut args = HashMap::new(); args.insert("path".to_string(), to_value("app.css").unwrap()); args.insert("base64".to_string(), to_value(false).unwrap()); @@ -494,7 +517,7 @@ title = "A title" #[test] fn can_get_file_hash_sha384() { let dir = create_temp_dir(); - let static_fn = GetFileHash::new(dir.into_path(), None, PathBuf::new()); + let static_fn = GetHash::new(dir.into_path(), None, PathBuf::new()); let mut args = HashMap::new(); args.insert("path".to_string(), to_value("app.css").unwrap()); assert_eq!( @@ -506,7 +529,7 @@ title = "A title" #[test] fn can_get_file_hash_sha512_no_base64() { let dir = create_temp_dir(); - let static_fn = GetFileHash::new(dir.into_path(), None, PathBuf::new()); + let static_fn = GetHash::new(dir.into_path(), None, PathBuf::new()); let mut args = HashMap::new(); args.insert("path".to_string(), to_value("app.css").unwrap()); args.insert("sha_type".to_string(), to_value(512).unwrap()); @@ -520,7 +543,7 @@ title = "A title" #[test] fn can_get_file_hash_sha512() { let dir = create_temp_dir(); - let static_fn = GetFileHash::new(dir.into_path(), None, PathBuf::new()); + let static_fn = GetHash::new(dir.into_path(), None, PathBuf::new()); let mut args = HashMap::new(); args.insert("path".to_string(), to_value("app.css").unwrap()); args.insert("sha_type".to_string(), to_value(512).unwrap()); @@ -531,30 +554,86 @@ title = "A title" } #[test] - fn can_resolve_asset_path_to_valid_url() { - let config = Config::parse(CONFIG_DATA).unwrap(); + fn can_get_hash_sha256_no_base64() { let dir = create_temp_dir(); - let static_fn = - GetUrl::new(dir.path().to_path_buf(), config, HashMap::new(), PathBuf::new()); + let static_fn = GetHash::new(dir.into_path(), None, PathBuf::new()); let mut args = HashMap::new(); - args.insert( - "path".to_string(), - to_value(dir.path().join("app.css").strip_prefix(std::env::temp_dir()).unwrap()) - .unwrap(), + args.insert("literal".to_string(), to_value("Hello World").unwrap()); + args.insert("sha_type".to_string(), to_value(256).unwrap()); + args.insert("base64".to_string(), to_value(false).unwrap()); + assert_eq!( + static_fn.call(&args).unwrap(), + "a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e" ); + } + + #[test] + fn can_get_hash_sha256_base64() { + let dir = create_temp_dir(); + let static_fn = GetHash::new(dir.into_path(), None, PathBuf::new()); + let mut args = HashMap::new(); + args.insert("literal".to_string(), to_value("Hello World").unwrap()); + args.insert("sha_type".to_string(), to_value(256).unwrap()); + args.insert("base64".to_string(), to_value(true).unwrap()); + assert_eq!(static_fn.call(&args).unwrap(), "pZGm1Av0IEBKARczz7exkNYsZb8LzaMrV7J32a2fFG4="); + } + + #[test] + fn can_get_hash_sha384_no_base64() { + let dir = create_temp_dir(); + let static_fn = GetHash::new(dir.into_path(), None, PathBuf::new()); + let mut args = HashMap::new(); + args.insert("literal".to_string(), to_value("Hello World").unwrap()); + args.insert("base64".to_string(), to_value(false).unwrap()); assert_eq!( static_fn.call(&args).unwrap(), - format!( - "https://remplace-par-ton-url.fr/{}/app.css", - dir.path().file_stem().unwrap().to_string_lossy() - ) - ) + "99514329186b2f6ae4a1329e7ee6c610a729636335174ac6b740f9028396fcc803d0e93863a7c3d90f86beee782f4f3f" + ); + } + + #[test] + fn can_get_hash_sha384() { + let dir = create_temp_dir(); + let static_fn = GetHash::new(dir.into_path(), None, PathBuf::new()); + let mut args = HashMap::new(); + args.insert("literal".to_string(), to_value("Hello World").unwrap()); + assert_eq!( + static_fn.call(&args).unwrap(), + "mVFDKRhrL2rkoTKefubGEKcpY2M1F0rGt0D5AoOW/MgD0Ok4Y6fD2Q+Gvu54L08/" + ); + } + + #[test] + fn can_get_hash_sha512_no_base64() { + let dir = create_temp_dir(); + let static_fn = GetHash::new(dir.into_path(), None, PathBuf::new()); + let mut args = HashMap::new(); + args.insert("literal".to_string(), to_value("Hello World").unwrap()); + args.insert("sha_type".to_string(), to_value(512).unwrap()); + args.insert("base64".to_string(), to_value(false).unwrap()); + assert_eq!( + static_fn.call(&args).unwrap(), + "2c74fd17edafd80e8447b0d46741ee243b7eb74dd2149a0ab1b9246fb30382f27e853d8585719e0e67cbda0daa8f51671064615d645ae27acb15bfb1447f459b" + ); + } + + #[test] + fn can_get_hash_sha512() { + let dir = create_temp_dir(); + let static_fn = GetHash::new(dir.into_path(), None, PathBuf::new()); + let mut args = HashMap::new(); + args.insert("literal".to_string(), to_value("Hello World").unwrap()); + args.insert("sha_type".to_string(), to_value(512).unwrap()); + assert_eq!( + static_fn.call(&args).unwrap(), + "LHT9F+2v2A6ER7DUZ0HuJDt+t03SFJoKsbkkb7MDgvJ+hT2FhXGeDmfL2g2qj1FnEGRhXWRa4nrLFb+xRH9Fmw==" + ); } #[test] fn error_when_file_not_found_for_hash() { let dir = create_temp_dir(); - let static_fn = GetFileHash::new(dir.into_path(), None, PathBuf::new()); + let static_fn = GetHash::new(dir.into_path(), None, PathBuf::new()); let mut args = HashMap::new(); args.insert("path".to_string(), to_value("doesnt-exist").unwrap()); let err = format!("{}", static_fn.call(&args).unwrap_err()); diff --git a/components/templates/src/global_fns/images.rs b/components/templates/src/global_fns/images.rs index e9aad501cb..641e26391c 100644 --- a/components/templates/src/global_fns/images.rs +++ b/components/templates/src/global_fns/images.rs @@ -60,7 +60,8 @@ impl TeraFn for ResizeImage { return Err("`resize_image`: `quality` must be in range 1-100".to_string().into()); } } - + let resize_op = imageproc::ResizeOperation::from_args(&op, width, height) + .map_err(|e| format!("`resize_image`: {}", e))?; let mut imageproc = self.imageproc.lock().unwrap(); let (file_path, unified_path) = match search_for_file(&self.base_path, &path, &self.theme, &self.output_path) @@ -73,7 +74,7 @@ impl TeraFn for ResizeImage { }; let response = imageproc - .enqueue(unified_path, file_path, &op, width, height, &format, quality) + .enqueue(resize_op, unified_path, file_path, &format, quality) .map_err(|e| format!("`resize_image`: {}", e))?; to_value(response).map_err(Into::into) @@ -127,7 +128,7 @@ impl TeraFn for GetImageMetadata { return Ok(cached_result.clone()); } - let response = imageproc::read_image_metadata(&src_path) + let response = imageproc::read_image_metadata(src_path) .map_err(|e| format!("`resize_image`: {}", e))?; let out = to_value(response).unwrap(); cache.insert(unified_path, out.clone()); @@ -183,22 +184,22 @@ mod tests { args.insert("height".to_string(), to_value(40).unwrap()); args.insert("width".to_string(), to_value(40).unwrap()); - // hashing is stable based on filename and params so we can compare with hashes + // hashing is stable based on filepath and params so we can compare with hashes // 1. resizing an image in static args.insert("path".to_string(), to_value("static/gutenberg.jpg").unwrap()); let data = static_fn.call(&args).unwrap().as_object().unwrap().clone(); let static_path = Path::new("static").join("processed_images"); - // TODO: Use `assert_processed_path_matches()` from imageproc so that hashes don't need to be hardcoded - assert_eq!( data["static_path"], - to_value(&format!("{}", static_path.join("6a89d6483cdc5f7700.jpg").display())).unwrap() + to_value(&format!("{}", static_path.join("gutenberg.da10f4be4f1c441e.jpg").display())) + .unwrap() ); assert_eq!( data["url"], - to_value("http://a-website.com/processed_images/6a89d6483cdc5f7700.jpg").unwrap() + to_value("http://a-website.com/processed_images/gutenberg.da10f4be4f1c441e.jpg") + .unwrap() ); // 2. resizing an image in content with a relative path @@ -206,11 +207,13 @@ mod tests { let data = static_fn.call(&args).unwrap().as_object().unwrap().clone(); assert_eq!( data["static_path"], - to_value(&format!("{}", static_path.join("202d9263f4dbc95900.jpg").display())).unwrap() + to_value(&format!("{}", static_path.join("gutenberg.3301b37eed389d2e.jpg").display())) + .unwrap() ); assert_eq!( data["url"], - to_value("http://a-website.com/processed_images/202d9263f4dbc95900.jpg").unwrap() + to_value("http://a-website.com/processed_images/gutenberg.3301b37eed389d2e.jpg") + .unwrap() ); // 3. resizing with an absolute path is the same as the above @@ -228,22 +231,26 @@ mod tests { let data = static_fn.call(&args).unwrap().as_object().unwrap().clone(); assert_eq!( data["static_path"], - to_value(&format!("{}", static_path.join("6296a3c153f701be00.jpg").display())).unwrap() + to_value(&format!("{}", static_path.join("asset.d2fde9a750b68471.jpg").display())) + .unwrap() ); assert_eq!( data["url"], - to_value("http://a-website.com/processed_images/6296a3c153f701be00.jpg").unwrap() + to_value("http://a-website.com/processed_images/asset.d2fde9a750b68471.jpg").unwrap() ); // 6. Looking up a file in the theme args.insert("path".to_string(), to_value("in-theme.jpg").unwrap()); + let data = static_fn.call(&args).unwrap().as_object().unwrap().clone(); assert_eq!( data["static_path"], - to_value(&format!("{}", static_path.join("6296a3c153f701be00.jpg").display())).unwrap() + to_value(&format!("{}", static_path.join("in-theme.9b0d29e07d588b60.jpg").display())) + .unwrap() ); assert_eq!( data["url"], - to_value("http://a-website.com/processed_images/6296a3c153f701be00.jpg").unwrap() + to_value("http://a-website.com/processed_images/in-theme.9b0d29e07d588b60.jpg") + .unwrap() ); } diff --git a/components/templates/src/global_fns/load_data.rs b/components/templates/src/global_fns/load_data.rs index dc6c4210b8..c719495f70 100644 --- a/components/templates/src/global_fns/load_data.rs +++ b/components/templates/src/global_fns/load_data.rs @@ -61,7 +61,7 @@ impl FromStr for OutputFormat { "bibtex" => Ok(OutputFormat::Bibtex), "xml" => Ok(OutputFormat::Xml), "plain" => Ok(OutputFormat::Plain), - "yaml" => Ok(OutputFormat::Yaml), + "yaml" | "yml" => Ok(OutputFormat::Yaml), format => Err(format!("Unknown output format {}", format).into()), } } diff --git a/components/templates/src/global_fns/mod.rs b/components/templates/src/global_fns/mod.rs index e817ba935a..154dc266b6 100644 --- a/components/templates/src/global_fns/mod.rs +++ b/components/templates/src/global_fns/mod.rs @@ -8,8 +8,8 @@ mod i18n; mod images; mod load_data; -pub use self::content::{GetPage, GetSection, GetTaxonomy, GetTaxonomyUrl}; -pub use self::files::{GetFileHash, GetUrl}; +pub use self::content::{GetPage, GetSection, GetTaxonomy, GetTaxonomyTerm, GetTaxonomyUrl}; +pub use self::files::{GetHash, GetUrl}; pub use self::i18n::Trans; pub use self::images::{GetImageMetadata, ResizeImage}; pub use self::load_data::LoadData; diff --git a/components/templates/src/lib.rs b/components/templates/src/lib.rs index 120b8fc88b..6441ff4999 100644 --- a/components/templates/src/lib.rs +++ b/components/templates/src/lib.rs @@ -23,16 +23,6 @@ pub static ZOLA_TERA: Lazy = Lazy::new(|| { include_str!("builtins/split_sitemap_index.xml"), ), ("__zola_builtins/anchor-link.html", include_str!("builtins/anchor-link.html")), - ( - "__zola_builtins/shortcodes/youtube.html", - include_str!("builtins/shortcodes/youtube.html"), - ), - ("__zola_builtins/shortcodes/vimeo.html", include_str!("builtins/shortcodes/vimeo.html")), - ("__zola_builtins/shortcodes/gist.html", include_str!("builtins/shortcodes/gist.html")), - ( - "__zola_builtins/shortcodes/streamable.html", - include_str!("builtins/shortcodes/streamable.html"), - ), ("internal/alias.html", include_str!("builtins/internal/alias.html")), ]) .unwrap(); @@ -62,7 +52,7 @@ pub fn load_tera(path: &Path, config: &Config) -> Result { if let Some(ref theme) = config.theme { // Test that the templates folder exist for that theme - let theme_path = path.join("themes").join(&theme); + let theme_path = path.join("themes").join(theme); if !theme_path.join("templates").exists() { bail!("Theme `{}` is missing a templates folder", theme); } diff --git a/components/utils/src/fs.rs b/components/utils/src/fs.rs index 8d3c22c8a8..8a53df1480 100644 --- a/components/utils/src/fs.rs +++ b/components/utils/src/fs.rs @@ -1,6 +1,6 @@ use libs::filetime::{set_file_mtime, FileTime}; use libs::walkdir::WalkDir; -use std::fs::{copy, create_dir_all, metadata, File}; +use std::fs::{copy, create_dir_all, metadata, remove_dir_all, remove_file, File}; use std::io::prelude::*; use std::path::Path; use std::time::SystemTime; @@ -21,7 +21,7 @@ pub fn is_path_in_directory(parent: &Path, path: &Path) -> Result { /// Create a file with the content given pub fn create_file(path: &Path, content: &str) -> Result<()> { let mut file = - File::create(&path).with_context(|| format!("Failed to create file {}", path.display()))?; + File::create(path).with_context(|| format!("Failed to create file {}", path.display()))?; file.write_all(content.as_bytes())?; Ok(()) } @@ -92,19 +92,19 @@ pub fn copy_file_if_needed(src: &Path, dest: &Path, hard_link: bool) -> Result<( .with_context(|| format!("Failed to get metadata of {}", src.display()))?; let src_mtime = FileTime::from_last_modification_time(&src_metadata); if Path::new(&dest).is_file() { - let target_metadata = metadata(&dest)?; + 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, &dest).with_context(|| { + copy(src, dest).with_context(|| { format!("Was not able to copy file {} to {}", src.display(), dest.display()) })?; - set_file_mtime(&dest, src_mtime)?; + set_file_mtime(dest, src_mtime)?; } } else { - copy(src, &dest).with_context(|| { + copy(src, dest).with_context(|| { format!("Was not able to copy directory {} to {}", src.display(), dest.display()) })?; - set_file_mtime(&dest, src_mtime)?; + set_file_mtime(dest, src_mtime)?; } } Ok(()) @@ -166,6 +166,76 @@ where time_source.and_then(|ts| time_target.map(|tt| ts > tt)).unwrap_or(true) } +/// Checks if the file or folder for the given path is a dotfile, meaning starts with '.' +pub fn is_dotfile

(path: P) -> bool +where + P: AsRef, +{ + path.as_ref().file_name().and_then(|s| s.to_str()).map(|s| s.starts_with('.')).unwrap_or(false) +} + +/// Returns whether the path we received corresponds to a temp file created +/// by an editor or the OS +pub fn is_temp_file(path: &Path) -> bool { + let ext = path.extension(); + match ext { + Some(ex) => match ex.to_str().unwrap() { + "swp" | "swx" | "tmp" | ".DS_STORE" | ".DS_Store" => true, + // jetbrains IDE + x if x.ends_with("jb_old___") => true, + x if x.ends_with("jb_tmp___") => true, + x if x.ends_with("jb_bak___") => true, + // vim & jetbrains + x if x.ends_with('~') => true, + _ => { + if let Some(filename) = path.file_stem() { + // emacs + let name = filename.to_str().unwrap(); + name.starts_with('#') || name.starts_with(".#") + } else { + false + } + } + }, + None => true, + } +} + +/// Deletes the `output_path` directory if it exists and `preserve_dotfiles_in_output` is set to false, +/// or if set to true: its contents except for the dotfiles at the root level. +pub fn clean_site_output_folder( + output_path: &Path, + preserve_dotfiles_in_output: bool, +) -> Result<()> { + if output_path.exists() { + if !preserve_dotfiles_in_output { + return remove_dir_all(output_path).context("Couldn't delete output directory"); + } + + for entry in output_path + .read_dir() + .context(format!("Couldn't read output directory `{}`", output_path.display()))? + { + let entry = entry.context("Couldn't read entry in output directory")?.path(); + + // Skip dotfiles if the preserve_dotfiles_in_output configuration option is set + if is_dotfile(&entry) { + continue; + } + + if entry.is_dir() { + remove_dir_all(entry) + .context("Couldn't delete folder while cleaning the output directory")?; + } else { + remove_file(entry) + .context("Couldn't delete file while cleaning the output directory")?; + } + } + } + + Ok(()) +} + #[cfg(test)] mod tests { use std::fs::{metadata, read_to_string, File}; diff --git a/components/utils/src/net.rs b/components/utils/src/net.rs index 314ec8e9a3..61deeae363 100644 --- a/components/utils/src/net.rs +++ b/components/utils/src/net.rs @@ -9,3 +9,8 @@ pub fn get_available_port(avoid: u16) -> Option { pub fn port_is_available(port: u16) -> bool { TcpListener::bind(("127.0.0.1", port)).is_ok() } + +/// Returns whether a link starts with an HTTP(s) scheme or "www.". +pub fn is_external_link(link: &str) -> bool { + link.starts_with("http:") || link.starts_with("https:") || link.starts_with("www.") +} diff --git a/components/utils/src/site.rs b/components/utils/src/site.rs index 3561c3fc99..ca3287b60c 100644 --- a/components/utils/src/site.rs +++ b/components/utils/src/site.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use errors::{anyhow, Result}; /// Result of a successful resolution of an internal link. -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Eq, Clone)] pub struct ResolvedInternalLink { /// Resolved link target, as absolute URL address. pub permalink: String, diff --git a/components/utils/src/table_of_contents.rs b/components/utils/src/table_of_contents.rs index 75bbf21487..8110ee9438 100644 --- a/components/utils/src/table_of_contents.rs +++ b/components/utils/src/table_of_contents.rs @@ -1,7 +1,7 @@ use serde::Serialize; /// Populated while receiving events from the markdown parser -#[derive(Debug, Default, PartialEq, Clone, Serialize)] +#[derive(Debug, Default, PartialEq, Eq, Clone, Serialize)] pub struct Heading { pub level: u32, pub id: String, diff --git a/components/utils/src/templates.rs b/components/utils/src/templates.rs index 62188a304a..b51206bac5 100644 --- a/components/utils/src/templates.rs +++ b/components/utils/src/templates.rs @@ -15,7 +15,7 @@ macro_rules! render_default_tpl { }}; } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum ShortcodeFileType { Markdown, Html, @@ -56,11 +56,13 @@ pub fn get_shortcodes(tera: &Tera) -> HashMap { if template.name.starts_with("__zola_builtins/shortcodes/") { let head_len = "__zola_builtins/shortcodes/".len(); - shortcode_definitions.insert( - identifier[head_len..(identifier.len() - ext_len - 1)].to_string(), - ShortcodeDefinition::new(file_type, &template.name), - ); - continue; + let name = &identifier[head_len..(identifier.len() - ext_len - 1)]; + // We don't keep the built-ins one if the user provided one + if shortcode_definitions.contains_key(name) { + continue; + } + shortcode_definitions + .insert(name.to_string(), ShortcodeDefinition::new(file_type, &template.name)); } } @@ -147,7 +149,7 @@ pub fn check_template_fallbacks<'a>( #[cfg(test)] mod tests { - use crate::templates::check_template_fallbacks; + use crate::templates::{check_template_fallbacks, get_shortcodes}; use super::rewrite_theme_paths; use libs::tera::Tera; @@ -193,4 +195,13 @@ mod tests { Some("hyde/templates/theme-only.html") ); } + + #[test] + fn can_overwrite_builtin_shortcodes() { + let mut tera = Tera::parse("test-templates/*.html").unwrap(); + tera.add_raw_template("__zola_builtins/shortcodes/youtube.html", "Builtin").unwrap(); + tera.add_raw_template("shortcodes/youtube.html", "Hello").unwrap(); + let definitions = get_shortcodes(&tera); + assert_eq!(definitions["youtube"].tera_name, "shortcodes/youtube.html"); + } } diff --git a/components/utils/src/types.rs b/components/utils/src/types.rs index d4d53a151d..8670589ed2 100644 --- a/components/utils/src/types.rs +++ b/components/utils/src/types.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] pub enum InsertAnchor { Left, diff --git a/docs/config.toml b/docs/config.toml index 87a0215e2e..1a462a72ce 100644 --- a/docs/config.toml +++ b/docs/config.toml @@ -5,6 +5,9 @@ description = "Everything you need to make a static site engine in one binary." compile_sass = true build_search_index = true +[search] +index_format = "elasticlunr_json" + [markdown] highlight_code = true highlight_theme = "kronuz" diff --git a/docs/content/documentation/content/page.md b/docs/content/documentation/content/page.md index 0f018e04cd..c5e9a962d0 100644 --- a/docs/content/documentation/content/page.md +++ b/docs/content/documentation/content/page.md @@ -16,11 +16,13 @@ create a **page** at `[base_url]/about`). If the file is given any name _other_ than `index.md` or `_index.md`, then it will create a page with that name (without the `.md`). For example, naming a file in the root of your content directory `about.md` would create a page at `[base_url]/about`. + Another exception to this rule is that a filename starting with a datetime (YYYY-mm-dd or [an RFC3339 datetime](https://www.ietf.org/rfc/rfc3339.txt)) followed by an underscore (`_`) or a dash (`-`) will use that date as the page date, unless already set in the front matter. The page name will be anything after `_`/`-`, so the file `2018-10-10-hello-world.md` will be available at `[base_url]/hello-world`. Note that the full RFC3339 datetime contains colons, which is not a valid character in a filename on Windows. +This behavior can be disabled by setting `slugify.paths_keep_date` to `true` (the default is `false`). Note that a `_` separating the date would be slugified into a `-` with the default value for `slugify.paths` of `"on"`. As you can see, creating an `about.md` file is equivalent to creating an `about/index.md` file. The only difference between the two methods is that creating @@ -124,6 +126,10 @@ path = "" # current one. This takes an array of paths, not URLs. aliases = [] +# A list of page authors. If a site feed is enabled, the first author (if any) +# will be used as the page's author in the default feed template. +authors = [] + # When set to "true", the page will be in the search index. This is only used if # `build_search_index` is set to "true" in the Zola configuration and the parent section # hasn't set `in_search_index` to "false" in its front matter. diff --git a/docs/content/documentation/content/sass.md b/docs/content/documentation/content/sass.md index 980955ec99..1c08bbd742 100644 --- a/docs/content/documentation/content/sass.md +++ b/docs/content/documentation/content/sass.md @@ -11,7 +11,8 @@ may be of interest: * The [official Sass website](https://sass-lang.com/) * [Why Sass?](https://alistapart.com/article/why-sass), by Dan Cederholm -It currently uses a wrapper around LibSass 3.6.4. +It currently uses [grass](https://github.com/connorskees/grass), a Rust implementation of Sass roughly equivalent +with dart-sass. ## Using Sass in Zola diff --git a/docs/content/documentation/content/search.md b/docs/content/documentation/content/search.md index d0f8779cb5..c57c1fc114 100644 --- a/docs/content/documentation/content/search.md +++ b/docs/content/documentation/content/search.md @@ -17,6 +17,9 @@ After `zola build` or `zola serve`, you should see two files in your public dire - `search_index.${default_language}.js`: so `search_index.en.js` for a default setup - `elasticlunr.min.js` +If you set `index_format = "elasticlunr_json"` in your `config.toml`, a `search_index.${default_language}.json` is generated +instead of the default `search_index.${default_language}.js`. + As each site will be different, Zola makes no assumptions about your search function and doesn't provide the JavaScript/CSS code to do an actual search and display results. You can look at how this site implements it to get an idea: [search.js](https://github.com/getzola/zola/tree/master/docs/static/search.js). diff --git a/docs/content/documentation/content/section.md b/docs/content/documentation/content/section.md index 527451473d..9a726e7bd9 100644 --- a/docs/content/documentation/content/section.md +++ b/docs/content/documentation/content/section.md @@ -48,7 +48,7 @@ description = "" # A draft section is only loaded if the `--drafts` flag is passed to `zola build`, `zola serve` or `zola check`. draft = false -# Used to sort pages by "date", "update_date", "title", "title_bytes", "weight", or "none". See below for more information. +# Used to sort pages by "date", "update_date", "title", "title_bytes", "weight", "slug" or "none". See below for more information. sort_by = "none" # Used by the parent section to order its subsections. @@ -144,7 +144,7 @@ create a list of links to the posts, a simple template might look like this: This would iterate over the posts in the order specified by the `sort_by` variable set in the `_index.md` page for the corresponding section. The `sort_by` variable can be given a few values: `date`, `update_date` -`title`, `title_bytes`, `weight` or `none`. If `sort_by` is not set, the pages will be +`title`, `title_bytes`, `weight`, `slug` or `none`. If `sort_by` is not set, the pages will be sorted in the `none` order, which is not intended for sorted content. Any page that is missing the data it needs to be sorted will be ignored and @@ -188,11 +188,14 @@ of the Swedish alphabet, åäö, for example would be considered by the natural sort as aao. In that case the standard byte-order sort may be more suitable. ### `weight` -This will be sort all pages by their `weight` field, from lightest weight -(at the top of the list) to heaviest (at the bottom of the list). Each +This will sort all pages by their `weight` field, from the lightest weight +(at the top of the list) to the heaviest (at the bottom of the list). Each page gets `page.lower` and `page.higher` variables that contain the pages with lighter and heavier weights, respectively. +### `slug` +This will sort pages or sections by their slug in natural lexical order. + ### Reversed sorting When iterating through pages, you may wish to use the Tera `reverse` filter, which reverses the order of the pages. For example, after using the `reverse` filter, diff --git a/docs/content/documentation/content/shortcodes.md b/docs/content/documentation/content/shortcodes.md index 953b319ce5..fdadfb86de 100644 --- a/docs/content/documentation/content/shortcodes.md +++ b/docs/content/documentation/content/shortcodes.md @@ -4,9 +4,7 @@ weight = 40 +++ Zola borrows the concept of [shortcodes](https://codex.wordpress.org/Shortcode_API) from WordPress. -In our case, a shortcode corresponds to a template defined in the `templates/shortcodes` directory or -a built-in one that can be used in a Markdown file. If you want to use something similar to shortcodes in your templates, -try [Tera macros](https://tera.netlify.com/docs#macros). +In our case, a shortcode corresponds to a template defined in the `templates/shortcodes` directory that can be used in a Markdown file. Broadly speaking, Zola's shortcodes cover two distinct use cases: @@ -18,6 +16,8 @@ The latter may also be solved by writing HTML, however Zola allows the use of Ma rather than `.html`. This may be particularly useful if you want to include headings generated by the shortcode in the [table of contents](@/documentation/content/table-of-contents.md). +If you want to use something similar to shortcodes in your templates, you can use [Tera macros](https://tera.netlify.com/docs#macros). They are functions or components that you can call to return some text. + ## Writing a shortcode Let's write a shortcode to embed YouTube videos as an example. In a file called `youtube.html` in the `templates/shortcodes` directory, paste the @@ -59,7 +59,7 @@ This will create a shortcode `books` with the argument `path` pointing to a `.to titles and descriptions. They will flow with the rest of the document in which `books` is called. Shortcodes are rendered before the page's Markdown is parsed so they don't have access to the page's table of contents. -Because of that, you also cannot use the [`get_page`](@/documentation/templates/overview.md#get-page)/[`get_section`](@/documentation/templates/overview.md#get-section)/[`get_taxonomy`](@/documentation/templates/overview.md#get-taxonomy) global functions. It might work while +Because of that, you also cannot use the [`get_page`](@/documentation/templates/overview.md#get-page)/[`get_section`](@/documentation/templates/overview.md#get-section)/[`get_taxonomy`](@/documentation/templates/overview.md#get-taxonomy)/[`get_taxonomy_term`](@/documentation/templates/overview.md#get-taxonomy-term) global functions. It might work while running `zola serve` because it has been loaded but it will fail during `zola build`. ## Using shortcodes @@ -92,8 +92,7 @@ should not be used as argument names in shortcodes. ### Shortcodes without body -Simply call the shortcode as if it was a Tera function in a variable block. All the examples below are valid -calls of the YouTube shortcode. +Simply call the shortcode as if it was a Tera function in a variable block. ```md Here is a YouTube video: @@ -164,10 +163,11 @@ Every shortcode can access some variables, beyond what you explicitly passed as - invocation count (`nth`) - current language (`lang`), unless called from the `markdown` template filter (in which case it will always be the same value as `default_language` in configuration, or `en` when it is unset) +- `colocated_path` When one of these variables conflict with a variable passed as argument, the argument value will be used. -### Invocation Count +### `nth`: invocation count Every shortcode context is passed in a variable named `nth` that tracks how many times a particular shortcode has been invoked in the current Markdown file. Given a shortcode `true_statement.html` template: @@ -185,107 +185,72 @@ It could be used in our Markdown as follows: This is useful when implementing custom markup for features such as sidenotes or end notes. -### Current language - -**NOTE:** When calling a shortcode from within the `markdown` template filter, the `lang` variable will always be `en`. If you feel like you need that, please consider using template macros instead. If you really need that, you can rewrite your Markdown content to pass `lang` as argument to the shortcode. +### `lang`: current language +**NOTE:** When calling a shortcode from within the `markdown` template filter, the `lang` variable will always be `en`. +If you feel like you need that, please consider using template macros instead. +If you really need that, you can rewrite your Markdown content to pass `lang` as argument to the shortcode. -Every shortcode can access the current language in the `lang` variable in the context. This is useful for presenting/filtering information in a shortcode depending in a per-language manner. For example, to display a per-language book cover for the current page in a shortcode called `bookcover.md`: +Every shortcode can access the current language in the `lang` variable in the context. +This is useful for presenting/filtering information in a shortcode depending in a per-language manner. For example, to display a per-language book cover for the current page in a shortcode called `bookcover.md`: ```jinja2 ![Book cover in {{ lang }}](cover.{{ lang }}.png) ``` -You can then use it in your Markdown like so: `{{/* bookcover() */}}` +### `page` or `section` +You can access a slighty stripped down version of the equivalent variables in the normal templates. +The following attributes will be empty: -## Built-in shortcodes +- translations +- backlinks +- pages -Zola comes with a few built-in shortcodes. If you want to override a default shortcode template, -simply place a `{shortcode_name}.html` file in the `templates/shortcodes` directory and Zola will -use that instead. - -### YouTube -Embed a responsive player for a YouTube video. - -The arguments are: +(Note: this is because the rendering of markdown is done before populating the sections) -- `id`: the video id (mandatory) -- `playlist`: the playlist id (optional) -- `class`: a class to add to the `

` surrounding the iframe -- `autoplay`: when set to "true", the video autoplays on load - -Usage example: +A useful attribute to `page` in shortcodes is `colocated_path`. +This is used when you want to pass the name of some assets to shortcodes without repeating the full folders path. +Mostly useful when combined with `load_data` or `resize_image`. -```md -{{/* youtube(id="dQw4w9WgXcQ") */}} - -{{/* youtube(id="dQw4w9WgXcQ", playlist="RDdQw4w9WgXcQ") */}} - -{{/* youtube(id="dQw4w9WgXcQ", autoplay=true) */}} - -{{/* youtube(id="dQw4w9WgXcQ", autoplay=true, class="youtube") */}} +```jinja2 +{% set resized = resize_image(format="jpg", path=page.colocated_path ~ img_name, width=width, op="fit_width") %} +{{ alt }} ``` -Result example: +## Examples + +Here are some shortcodes for inspiration. -{{ youtube(id="dQw4w9WgXcQ") }} +### YouTube -### Vimeo -Embed a player for a Vimeo video. +Embed a responsive player for a YouTube video. The arguments are: - `id`: the video id (mandatory) +- `playlist`: the playlist id (optional) - `class`: a class to add to the `
` surrounding the iframe +- `autoplay`: when set to "true", the video autoplays on load -Usage example: - -```md -{{/* vimeo(id="124313553") */}} +Code: -{{/* vimeo(id="124313553", class="vimeo") */}} ``` - -Result example: - -{{ vimeo(id="124313553") }} - -### Streamable -Embed a player for a Streamable video. - -The arguments are: - -- `id`: the video id (mandatory) -- `class`: a class to add to the `
` surrounding the iframe +
+ +
+``` Usage example: ```md -{{/* streamable(id="92ok4") */}} - -{{/* streamable(id="92ok4", class="streamble") */}} -``` - -Result example: - -{{ streamable(id="92ok4") }} - -### Gist -Embed a [Github gist](https://gist.github.com). +{{/* youtube(id="dCKeXuVHl1o") */}} -The arguments are: - -- `url`: the url to the gist (mandatory) -- `file`: by default, the shortcode will pull every file from the URL unless a specific filename is requested -- `class`: a class to add to the `
` surrounding the iframe +{{/* youtube(id="dCKeXuVHl1o", playlist="RDdQw4w9WgXcQ") */}} -Usage example: - -```md -{{/* gist(url="https://gist.github.com/Keats/e5fb6aad409f28721c0ba14161644c57") */}} +{{/* youtube(id="dCKeXuVHl1o", autoplay=true) */}} -{{/* gist(url="https://gist.github.com/Keats/e5fb6aad409f28721c0ba14161644c57", class="gist") */}} +{{/* youtube(id="dCKeXuVHl1o", autoplay=true, class="youtube") */}} ``` -Result example: +### Image Gallery -{{ gist(url="https://gist.github.com/Keats/e5fb6aad409f28721c0ba14161644c57") }} +See [content processing page](@/documentation/content/image-processing/index.md#creating-picture-galleries) for code and example. diff --git a/docs/content/documentation/content/syntax-highlighting.md b/docs/content/documentation/content/syntax-highlighting.md index d248d3d69b..0d32bf43e3 100644 --- a/docs/content/documentation/content/syntax-highlighting.md +++ b/docs/content/documentation/content/syntax-highlighting.md @@ -246,6 +246,25 @@ You can then support light and dark mode like so: @import url("syntax-theme-light.css") (prefers-color-scheme: light); ``` +Alternately, you can reference the stylesheets in your base template to reduce request chains: + +```html + + + + + +``` + +Themes can conditionally include code-highlighting stylesheet `` tags by wrapping them in a conditional: + +```jinja2 +{% if config.markdown.highlight_code and config.markdown.highlight_theme == "css" %} + + +{% endif %} +``` + ## Annotations diff --git a/docs/content/documentation/content/taxonomies.md b/docs/content/documentation/content/taxonomies.md index ef9bf75cb1..4ba2c8416a 100644 --- a/docs/content/documentation/content/taxonomies.md +++ b/docs/content/documentation/content/taxonomies.md @@ -62,7 +62,7 @@ In this example the page for `Release year` would include links to pages for bot ## Configuration -A taxonomy has five variables: +A taxonomy has six variables: - `name`: a required string that will be used in the URLs, usually the plural version (i.e., tags, categories, etc.) - `paginate_by`: if this is set to a number, each term page will be paginated by this much. @@ -70,6 +70,7 @@ A taxonomy has five variables: For example the default would be page/1. - `feed`: if set to `true`, a feed (atom by default) will be generated for each term. - `lang`: only set this if you are making a multilingual site and want to indicate which language this taxonomy is for +- `render`: if set to `false`, pages will not be rendered for the taxonomy or for individual terms. Insert into the configuration file (config.toml): diff --git a/docs/content/documentation/deployment/edgio.md b/docs/content/documentation/deployment/edgio.md new file mode 100644 index 0000000000..f2c7e30b48 --- /dev/null +++ b/docs/content/documentation/deployment/edgio.md @@ -0,0 +1,51 @@ ++++ +title = "Edgio" +weight = 50 ++++ + +If you don't have an account with Edgio, you can sign up [here](https://app.layer0.co/signup). + +## Manual deploys + +For a command-line manual deploy, follow these steps: + +1. Install the Edgio CLI: + +```bash +npm i -g @edgio/cli +``` + +2. Create a package.json at the root of your project with the following: + +```bash +npm init -y +``` + +3. Initialize your project with: + +```bash +edgio init +``` + +4. Update routes.js at the root of your project to the following: + +```js +// This file was added by edgio init. +// You should commit this file to source control. + +import { Router } from '@edgio/core/router' + +export default new Router().static('public') +``` + +5. Build your zola app: + +```bash +zola build +``` + +6. Deploy! + +```bash +edgio deploy +``` diff --git a/docs/content/documentation/deployment/github-pages.md b/docs/content/documentation/deployment/github-pages.md index f8bfa6d133..2bd5be7d72 100644 --- a/docs/content/documentation/deployment/github-pages.md +++ b/docs/content/documentation/deployment/github-pages.md @@ -30,9 +30,9 @@ Using *Github Actions* for the deployment of your Zola-Page on Github-Pages is p 2. Create the *Github Action*. 3. Check the *Github Pages* section in repository settings. -Let's start with the token. Remember, if you are publishing the site on the same repo, you do not need to follow that step. +Let's start with the token. Remember, if you are publishing the site on the same repo, you do not need to follow that step. But you will still need to pass in the automatic `GITHUB_TOKEN` as explained [here](https://docs.github.com/en/actions/security-guides/automatic-token-authentication#example-1-passing-the-github_token-as-an-input). -For creating the token either click on [here](https://github.com/settings/tokens) or go to Settings > Developer Settings > Personal access tokens. Under the *Select Scopes* section, give it *repo* permissions and click *Generate token*. Then copy the token, navigate to your repository and add in the Settings tab the *Secret* `TOKEN` and paste your token in it. +For creating the token either click on [here](https://github.com/settings/tokens/new?scopes=public_repo) or go to Settings > Developer Settings > Personal access tokens. Under the *Select Scopes* section, give it *public_repo* permissions and click *Generate token*. Then copy the token, navigate to your repository and add in the Settings tab the *Secret* `TOKEN` and paste your token in it. Next we need to create the *Github Action*. Here we can make use of the [zola-deploy-action](https://github.com/shalzz/zola-deploy-action). Go to the *Actions* tab of your repository, click on *set up a workflow yourself* to get a blank workflow file. Copy the following script into it and commit it afterwards; note that you may need to change the `github.ref` branch from `main` to `master` or similar, as the action will only run for the branch you choose. @@ -46,14 +46,16 @@ jobs: if: github.ref == 'refs/heads/main' steps: - name: checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3.0.0 - name: build_and_deploy - uses: shalzz/zola-deploy-action@v0.14.1 + uses: shalzz/zola-deploy-action@v0.16.1-1 env: # Target branch PAGES_BRANCH: gh-pages # Provide personal access token TOKEN: ${{ secrets.TOKEN }} + # Or if publishing to the same repo, use the automatic token + #TOKEN: ${{ secrets.GITHUB_TOKEN }} ``` This script is pretty simple, because the [zola-deploy-action](https://github.com/shalzz/zola-deploy-action) is doing everything for you. You just need to provide some details. For more configuration options check out the [README](https://github.com/shalzz/zola-deploy-action/blob/master/README.md). @@ -75,9 +77,9 @@ jobs: if: github.ref != 'refs/heads/main' steps: - name: 'checkout' - uses: actions/checkout@v2 + uses: actions/checkout@v3.0.0 - name: 'build' - uses: shalzz/zola-deploy-action@v0.13.0 + uses: shalzz/zola-deploy-action@v0.16.1 env: PAGES_BRANCH: gh-pages BUILD_DIR: . @@ -88,9 +90,9 @@ jobs: if: github.ref == 'refs/heads/main' steps: - name: 'checkout' - uses: actions/checkout@v2 + uses: actions/checkout@v3.0.0 - name: 'build and deploy' - uses: shalzz/zola-deploy-action@v0.13.0 + uses: shalzz/zola-deploy-action@v0.16.1 env: PAGES_BRANCH: master BUILD_DIR: . diff --git a/docs/content/documentation/deployment/layer0.md b/docs/content/documentation/deployment/layer0.md deleted file mode 100644 index 11bdac9dff..0000000000 --- a/docs/content/documentation/deployment/layer0.md +++ /dev/null @@ -1,55 +0,0 @@ -+++ -title = "Layer0" -weight = 50 -+++ - -If you don't have an account with Layer0, you can sign up [here](https://app.layer0.co/signup). - -## Manual deploys - -For a command-line manual deploy, follow these steps: -1. Install the Layer0 CLI: -```bash -npm i -g @layer0/cli -``` - -2. Create a package.json at the root of your project with the following: -```bash -npm init -``` - -3. Initialize your project with: -```bash -0 init -``` - -4. Update routes.js at the root of your project to the following: -```js -// This file was added by layer0 init. -// You should commit this file to source control. - -import { Router } from '@layer0/core/router' - -export default new Router().static('public', ({ cache }) => { - cache({ - edge: { - maxAgeSeconds: 60 * 60 * 60 * 365, - forcePrivateCaching: true, - }, - browser: { - maxAgeSeconds: 0, - serviceWorkerSeconds: 60 * 60 * 24, - }, - }) -}) -``` - -5. Build your zola app: -```bash -zola build -``` - -6. Deploy! -```bash -0 deploy -``` diff --git a/docs/content/documentation/deployment/sourcehut.md b/docs/content/documentation/deployment/sourcehut.md index 7f5449f26e..ca3fa417ad 100644 --- a/docs/content/documentation/deployment/sourcehut.md +++ b/docs/content/documentation/deployment/sourcehut.md @@ -5,16 +5,17 @@ weight = 15 Deploying your static Zola website on [Sourcehut Pages][srht] is very simple. -You only need to create a manifest `.build.yml` file in your root folder of your Zola project and push your changes to the Sourcehut git/hg repository. To create your `.build.yml` you can start by [a template][srht-tpl]. - -Example: - +You need to create a `.build.yml` manifest file in the root folder of your Zola project and push your changes to the +Sourcehut git/hg repository. +To create your `.build.yml` file you can start with [a template][srht-tpl] or use the following example: ``` yaml image: alpine/edge -packages: [ zola ] +packages: + - hut + - zola oauth: pages.sr.ht/PAGES:RW environment: - site: www.example.org + site: your_username.srht.site sources: - https://git.sr.ht/~your_username/my-website tasks: @@ -25,15 +26,18 @@ tasks: cd my-website tar -C public -cvz . > ../site.tar.gz - upload: | - acurl -f https://pages.sr.ht/publish/$site -Fcontent=@site.tar.gz + hut pages publish -d $site site.tar.gz ``` -This manifest will checkout your code from `sources`, build and upload the generated static files to `site` using a wrapper script around `curl` (called `acurl`, already available in all Sourcehut builds). +This manifest will clone your source code, build the website and upload the generated static files to the domain +you specified in `site`. +For publishing the website, the build manifest uses `hut`, a commandline tool which takes care of automatically +generating authentication tokens, so there is nothing else you need to do. -From this template you need to customize the variable `site` with the domain that will host your website and `sources` to point to your Sourcehut git/hg public URL (in this example `my-website` is the name of the repository). +From this template you need to customize the variable `site` with the domain that will host your website and +`sources` to point to your Sourcehut git/hg public URL (in this example `my-website` is the name of the repository). Then commit and push your changes: - ``` sh $ git push Enumerating objects: 5, done. @@ -44,9 +48,13 @@ To git.sr.ht:~your_username/www fbe9afa..59ae556 master -> master ``` -The build job will be automatically triggered. Notice that Sourcehut returns a direct link to the build page. +The build job will be automatically triggered. +Notice that Sourcehut returns a direct link to the build page, where you can check the progress and success status. -By default you can use a subdomain of Sourcehut Pages to host your static website (e.g. "your_username.srht.site"). If you want to use a custom domain (e.g. "blog.mydomain.org"), you will need to configure a DNS record to point to the Sourcehut server. Instructions to do this are detailed on [Sourcehut][srht-custom-domain]. +By default you can use a subdomain of Sourcehut Pages to host your static website - `your_username.srht.site`. +If you want to use a custom domain (e.g. "blog.mydomain.org"), you will need to configure a DNS record to point to +the Sourcehut server. +Instructions on how to do this are available on [Sourcehut][srht-custom-domain]. [srht]: https://srht.site [srht-tpl]: https://git.sr.ht/~sircmpwn/pages.sr.ht-examples diff --git a/docs/content/documentation/getting-started/cli-usage.md b/docs/content/documentation/getting-started/cli-usage.md index 842f5b61c4..10275037f3 100644 --- a/docs/content/documentation/getting-started/cli-usage.md +++ b/docs/content/documentation/getting-started/cli-usage.md @@ -46,7 +46,7 @@ $ zola build --base-url $DEPLOY_URL This is useful for example when you want to deploy previews of a site to a dynamic URL, such as Netlify deploy previews. -You can override the default output directory `public` by passing another value to the `output-dir` flag (if this directory already exists, the user will be prompted whether to replace the folder). +You can override the default output directory `public` by passing another value to the `output-dir` flag. If this directory already exists, the user will be prompted whether to replace the folder; you can override this prompt by passing the --force flag. ```bash $ zola build --output-dir $DOCUMENT_ROOT diff --git a/docs/content/documentation/getting-started/configuration.md b/docs/content/documentation/getting-started/configuration.md index f8f2d473fc..b2ff781f6a 100644 --- a/docs/content/documentation/getting-started/configuration.md +++ b/docs/content/documentation/getting-started/configuration.md @@ -41,6 +41,10 @@ theme = "" # For overriding the default output directory `public`, set it to another value (e.g.: "docs") output_dir = "public" +# Whether dotfiles at the root level of the output directory are preserved when (re)building the site. +# Enabling this also prevents the deletion of the output folder itself on rebuilds. +preserve_dotfiles_in_output = false + # When set to "true", the Sass files in the `sass` directory in the site root are compiled. # Sass files in theme directories are always compiled. compile_sass = false @@ -142,6 +146,10 @@ external_level = "error" paths = "on" taxonomies = "on" anchors = "on" +# Whether to remove date prefixes for page path slugs. +# For example, content/posts/2016-10-08_a-post-with-dates.md => posts/a-post-with-dates +# When true, content/posts/2016-10-08_a-post-with-dates.md => posts/2016-10-08-a-post-with-dates +paths_keep_dates = false [search] # Whether to include the title of the page/section in the index @@ -156,6 +164,10 @@ include_content = true # become too big to load on the site. Defaults to not being set. # truncate_content_length = 100 +# Wether to produce the search index as a javascript file or as a JSON file +# Accepted value "elasticlunr_javascript" or "elasticlunr_json" +index_format = "elasticlunr_javascript" + # Optional translation object for the default language # Example: # default_language = "fr" @@ -195,48 +207,48 @@ include_content = true Zola currently has the following highlight themes available: -- [1337](https://tmtheme-editor.herokuapp.com/#!/editor/theme/1337) -- [agola-dark](https://tmtheme-editor.herokuapp.com/#!/editor/theme/Agola%20Dark) -- [ascetic-white](https://tmtheme-editor.herokuapp.com/#!/editor/theme/Ascetic%20White) -- [axar](https://tmtheme-editor.herokuapp.com/#!/editor/theme/Axar) +- [1337](https://tmtheme-editor.glitch.me/#!/editor/theme/1337) +- [agola-dark](https://tmtheme-editor.glitch.me/#!/editor/theme/Agola%20Dark) +- [ascetic-white](https://tmtheme-editor.glitch.me/#!/editor/theme/Ascetic%20White) +- [axar](https://tmtheme-editor.glitch.me/#!/editor/theme/Axar) - [ayu-dark](https://github.com/dempfi/ayu) - [ayu-light](https://github.com/dempfi/ayu) - [ayu-mirage](https://github.com/dempfi/ayu) - [base16-atelierdune-light](https://atelierbram.github.io/syntax-highlighting/atelier-schemes/dune/) -- [base16-ocean-dark](https://tmtheme-editor.herokuapp.com/#!/editor/theme/Base16%20Ocean%20Dark) -- [base16-ocean-light](https://tmtheme-editor.herokuapp.com/#!/editor/theme/Base16%20Ocean%20Light) -- [bbedit](https://tmtheme-editor.herokuapp.com/#!/editor/theme/BBEdit) -- [boron](https://tmtheme-editor.herokuapp.com/#!/editor/theme/Boron) -- [charcoal](https://tmtheme-editor.herokuapp.com/#!/editor/theme/Charcoal) -- [cheerfully-light](https://tmtheme-editor.herokuapp.com/#!/editor/theme/Cheerfully%20Light) -- [classic-modified](https://tmtheme-editor.herokuapp.com/#!/editor/theme/Classic%20Modified) -- [demain](https://tmtheme-editor.herokuapp.com/#!/editor/theme/Demain) -- [dimmed-fluid](https://tmtheme-editor.herokuapp.com/#!/editor/theme/Dimmed%20Fluid) +- [base16-ocean-dark](https://tmtheme-editor.glitch.me/#!/editor/theme/Base16%20Ocean%20Dark) +- [base16-ocean-light](https://tmtheme-editor.glitch.me/#!/editor/theme/Base16%20Ocean%20Light) +- [bbedit](https://tmtheme-editor.glitch.me/#!/editor/theme/BBEdit) +- [boron](https://tmtheme-editor.glitch.me/#!/editor/theme/Boron) +- [charcoal](https://tmtheme-editor.glitch.me/#!/editor/theme/Charcoal) +- [cheerfully-light](https://tmtheme-editor.glitch.me/#!/editor/theme/Cheerfully%20Light) +- [classic-modified](https://tmtheme-editor.glitch.me/#!/editor/theme/Classic%20Modified) +- [demain](https://tmtheme-editor.glitch.me/#!/editor/theme/Demain) +- [dimmed-fluid](https://tmtheme-editor.glitch.me/#!/editor/theme/Dimmed%20Fluid) - [dracula](https://draculatheme.com/) -- [gray-matter-dark](https://tmtheme-editor.herokuapp.com/#!/editor/theme/Gray%20Matter%20Dark) +- [gray-matter-dark](https://tmtheme-editor.glitch.me/#!/editor/theme/Gray%20Matter%20Dark) - [green](https://github.com/kristopherjohnson/MonochromeSublimeText) - [gruvbox-dark](https://github.com/morhetz/gruvbox) - [gruvbox-light](https://github.com/morhetz/gruvbox) -- [idle](https://tmtheme-editor.herokuapp.com/#!/editor/theme/IDLE) -- [inspired-github](https://tmtheme-editor.herokuapp.com/#!/editor/theme/Inspiredgithub) -- [ir-white](https://tmtheme-editor.herokuapp.com/#!/editor/theme/IR_White) -- [kronuz](https://tmtheme-editor.herokuapp.com/#!/editor/theme/Kronuz) -- [material-dark](https://tmtheme-editor.herokuapp.com/#!/editor/theme/Material%20Dark) -- [material-light](https://tmtheme-editor.herokuapp.com/#!/editor/theme/Material%20Light) -- [monokai](https://tmtheme-editor.herokuapp.com/#!/editor/theme/Monokai) +- [idle](https://tmtheme-editor.glitch.me/#!/editor/theme/IDLE) +- [inspired-github](https://tmtheme-editor.glitch.me/#!/editor/theme/Inspiredgithub) +- [ir-white](https://tmtheme-editor.glitch.me/#!/editor/theme/IR_White) +- [kronuz](https://tmtheme-editor.glitch.me/#!/editor/theme/Kronuz) +- [material-dark](https://tmtheme-editor.glitch.me/#!/editor/theme/Material%20Dark) +- [material-light](https://tmtheme-editor.glitch.me/#!/editor/theme/Material%20Light) +- [monokai](https://tmtheme-editor.glitch.me/#!/editor/theme/Monokai) - [nord](https://github.com/crabique/Nord-plist/tree/0d655b23d6b300e691676d9b90a68d92b267f7ec) - [nyx-bold](https://github.com/GalAster/vscode-theme-nyx) - [one-dark](https://github.com/andresmichel/one-dark-theme) - [OneHalfDark](https://github.com/sonph/onehalf) - [OneHalfLight](https://github.com/sonph/onehalf) - [railsbase16-green-screen-dark](https://github.com/tompave/rails_base_16) -- [solarized-dark](https://tmtheme-editor.herokuapp.com/#!/editor/theme/Solarized%20(dark)) -- [solarized-light](https://tmtheme-editor.herokuapp.com/#!/editor/theme/Solarized%20(light)) +- [solarized-dark](https://tmtheme-editor.glitch.me/#!/editor/theme/Solarized%20(dark)) +- [solarized-light](https://tmtheme-editor.glitch.me/#!/editor/theme/Solarized%20(light)) - [subway-madrid](https://github.com/idleberg/Subway.tmTheme) - [subway-moscow](https://github.com/idleberg/Subway.tmTheme) -- [Tomorrow](https://tmtheme-editor.herokuapp.com/#!/editor/theme/Tomorrow) +- [Tomorrow](https://tmtheme-editor.glitch.me/#!/editor/theme/Tomorrow) - [two-dark](https://github.com/erremauro/TwoDark) -- [visual-studio-dark](https://tmtheme-editor.herokuapp.com/#!/editor/theme/Visual%20Studio%20Dark) +- [visual-studio-dark](https://tmtheme-editor.glitch.me/#!/editor/theme/Visual%20Studio%20Dark) - [zenburn](https://github.com/colinta/zenburn) Zola uses the Sublime Text themes, making it very easy to add more. diff --git a/docs/content/documentation/getting-started/installation.md b/docs/content/documentation/getting-started/installation.md index c2e3357db1..11e481ab5d 100644 --- a/docs/content/documentation/getting-started/installation.md +++ b/docs/content/documentation/getting-started/installation.md @@ -30,10 +30,12 @@ $ pacman -S zola ### Alpine Linux -Zola is available in the official Alpine Linux repository, only on the `edge` version for now. +Zola is available in the official Alpine Linux community repository since Alpine v3.13. + +See this section of the Alpine Wiki explaining how to enable the community repository if necessary: https://wiki.alpinelinux.org/wiki/Repositories#Enabling_the_community_repository ```sh -$ apk add zola --repository http://dl-cdn.alpinelinux.org/alpine/edge/community/ +$ apk add zola ``` ### Debian @@ -47,9 +49,10 @@ $ sudo dpkg -i zola__amd64_debian_.deb ### Fedora -Zola has been available in the official repositories since Fedora 29. +On Fedora, Zola is avialable via [COPR](https://fedoraproject.org/wiki/Category:Copr). ```sh +$ sudo dnf copr enable fz0x1/zola $ sudo dnf install zola ``` @@ -77,6 +80,14 @@ Zola is available in the official package repository. $ doas pkg_add zola ``` +### pkgsrc + +Zola is available in the official package repository, with [pkgin](https://pkgin.net/). + +```sh +$ pkgin install zola +``` + ### Snapcraft Zola is available on snapcraft: @@ -99,38 +110,73 @@ To use zola: $ flatpak run org.getzola.zola [command] ``` -To avoid having to type this everytime, an alias can be created in `~/.bashrc`: +To avoid having to type this every time, an alias can be created in `~/.bashrc`: ```sh $ alias zola="flatpak run org.getzola.zola" ``` +### NixOS / Nixpkgs + +Zola is [available](https://search.nixos.org/packages?show=zola&query=zola) +in the nixpkgs repository. If you're using NixOS, you can install Zola +by adding the following to `/etc/nixos/configuration.nix`: + +``` +environment.systemPackages = [ + pkgs.zola +]; +``` + +If you're using Nix as a package manager in another OS, you can install it using: + +``` +nix-env -iA nixpkgs.zola +``` + +### Via Github Actions + +Zola can be installed in a GHA workflow with [taiki-e/install-action](https://github.com/taiki-e/install-action). +Simply add it in your CI config, for example: + +```yaml +jobs: + foo: + steps: + - uses: taiki-e/install-action@v2 + with: + tool: zola@0.17.1 + # ... +``` + +See the action repo for docs and more examples. + ### Docker Zola is available on [the GitHub registry](https://github.com/getzola/zola/pkgs/container/zola). It has no `latest` tag, you will need to specify a [specific version to pull](https://github.com/getzola/zola/pkgs/container/zola/versions). ```sh -$ docker pull ghcr.io/getzola/zola:v0.16.0 +$ docker pull ghcr.io/getzola/zola:v0.17.1 ``` #### Build ```sh -$ docker run -u "$(id -u):$(id -g)" -v $PWD:/app --workdir /app ghcr.io/getzola/zola:v0.16.0 build +$ docker run -u "$(id -u):$(id -g)" -v $PWD:/app --workdir /app ghcr.io/getzola/zola:v0.17.1 build ``` #### Serve ```sh -$ docker run -u "$(id -u):$(id -g)" -v $PWD:/app --workdir /app -p 8080:8080 ghcr.io/getzola/zola:v0.16.0 serve --interface 0.0.0.0 --port 8080 --base-url localhost +$ docker run -u "$(id -u):$(id -g)" -v $PWD:/app --workdir /app -p 8080:8080 ghcr.io/getzola/zola:v0.17.1 serve --interface 0.0.0.0 --port 8080 --base-url localhost ``` You can now browse http://localhost:8080. > To enable live browser reload, you may have to bind to port 1024. Zola searches for an open > port between 1024 and 9000 for live reload. The new docker command would be -> `$ docker run -u "$(id -u):$(id -g)" -v $PWD:/app --workdir /app -p 8080:8080 -p 1024:1024 ghcr.io/getzola/zola:v0.16.0 serve --interface 0.0.0.0 --port 8080 --base-url localhost` +> `$ docker run -u "$(id -u):$(id -g)" -v $PWD:/app --workdir /app -p 8080:8080 -p 1024:1024 ghcr.io/getzola/zola:v0.17.1 serve --interface 0.0.0.0 --port 8080 --base-url localhost` ## Windows diff --git a/docs/content/documentation/getting-started/overview.md b/docs/content/documentation/getting-started/overview.md index 3b93a06013..b71abe4048 100644 --- a/docs/content/documentation/getting-started/overview.md +++ b/docs/content/documentation/getting-started/overview.md @@ -5,7 +5,9 @@ weight = 5 ## Zola at a Glance -Zola is a static site generator (SSG), similar to [Hugo](https://gohugo.io/), [Pelican](https://blog.getpelican.com/), and [Jekyll](https://jekyllrb.com/) (for a comprehensive list of SSGs, please see [Jamstack](https://jamstack.org/generators)). It is written in [Rust](https://www.rust-lang.org/) and uses the [Tera](https://tera.netlify.com/) template engine, which is similar to [Jinja2](https://jinja.palletsprojects.com/en/2.10.x/), [Django templates](https://docs.djangoproject.com/en/2.2/topics/templates/), [Liquid](https://shopify.github.io/liquid/), and [Twig](https://twig.symfony.com/). Content is written in [CommonMark](https://commonmark.org/), a strongly defined, highly compatible specification of [Markdown](https://www.markdownguide.org/). +Zola is a static site generator (SSG), similar to [Hugo](https://gohugo.io/), [Pelican](https://blog.getpelican.com/), and [Jekyll](https://jekyllrb.com/) (for a comprehensive list of SSGs, please see [Jamstack](https://jamstack.org/generators)). It is written in [Rust](https://www.rust-lang.org/) and uses the [Tera](https://tera.netlify.com/) template engine, which is similar to [Jinja2](https://jinja.palletsprojects.com/en/2.10.x/), [Django templates](https://docs.djangoproject.com/en/2.2/topics/templates/), [Liquid](https://shopify.github.io/liquid/), and [Twig](https://twig.symfony.com/). + +Content is written in [CommonMark](https://commonmark.org/), a strongly defined, highly compatible specification of [Markdown](https://www.markdownguide.org/). Zola uses [pulldown-cmark](https://github.com/raphlinus/pulldown-cmark#pulldown-cmark) to parse markdown files. The goal of this library is 100% compliance with the CommonMark spec. It adds a few additional features such as parsing footnotes, Github flavored tables, Github flavored task lists and strikethrough. SSGs use dynamic templates to transform content into static HTML pages. Static sites are thus very fast and require no databases, making them easy to host. A comparison between static and dynamic sites, such as WordPress, Drupal, and Django, can be found [here](https://dev.to/ashenmaster/static-vs-dynamic-sites-61f). @@ -220,7 +222,7 @@ The `index.html` file inside the `templates` directory should be:

This is my blog made with Zola.

-

Click here to see my posts.

+

Click here to see my posts.

{% endblock content %} ``` diff --git a/docs/content/documentation/templates/archive.md b/docs/content/documentation/templates/archive.md index 467d12c6e3..efccfe7150 100644 --- a/docs/content/documentation/templates/archive.md +++ b/docs/content/documentation/templates/archive.md @@ -19,5 +19,16 @@ all post titles ordered by year). However, this can be accomplished directly in ``` This snippet assumes that posts are sorted by date and that you want to display the archive -in descending order. If you want to show articles in ascending order, add a `reverse` filter -after `group_by`. +in descending order. If you want to show articles in ascending order, you need to further +process the list of pages: +```jinja2 +{% set posts_by_year = section.pages | group_by(attribute="year") %} +{% set_global years = [] %} +{% for year, ignored in posts_by_year %} + {% set_global years = years | concat(with=year) %} +{% endfor %} +{% for year in years | reverse %} + {% set posts = posts_by_year[year] %} + {# (same as the previous snippet) #} +{% endfor %} +``` diff --git a/docs/content/documentation/templates/overview.md b/docs/content/documentation/templates/overview.md index 0e1f17dfaf..20c823070d 100644 --- a/docs/content/documentation/templates/overview.md +++ b/docs/content/documentation/templates/overview.md @@ -69,7 +69,7 @@ in Tera. Converts the given variable to HTML using Markdown. There are a few differences compared to page/section Markdown rendering: - shortcodes evaluated by this filter cannot access the current rendering context: `config` will be available, but accessing `section` or `page` (among others) from a shortcode called within the `markdown` filter will prevent your site from building (see [this discussion](https://github.com/getzola/zola/pull/1358)) -- `lang` in shortcodes will always be equal to the site's `default_lang` (or `en` otherwise) ; it should not be a problem, but if it is in most cases, but if you need to use language-aware shortcodes in this filter, please refer to the [Shortcode context](@/documentation/content/shortcodes.md#shortcode-context) section of the docs. +- `lang` in shortcodes will always be equal to the site's `config.default_language` (or `en` otherwise) ; it should not be a problem, but if it is in most cases, but if you need to use language-aware shortcodes in this filter, please refer to the [Shortcode context](@/documentation/content/shortcodes.md#shortcode-context) section of the docs. By default, the filter will wrap all text in a paragraph. To disable this behaviour, you can pass `true` to the inline argument: @@ -180,6 +180,23 @@ items: Array; See the [Taxonomies documentation](@/documentation/templates/taxonomies.md) for a full documentation of those types. +### `get_taxonomy_term` +Gets a single term from a taxonomy of a specific kind. + +```jinja2 +{% set categories = get_taxonomy_term(kind="categories", term="term_name") %} +``` + +The type of the output is a single `TaxonomyTerm` item. + +`lang` (optional) default to `config.default_language` in config.toml + +`include_pages` (optional) default to true. If false, the `pages` item in the `TaxonomyTerm` will be empty, regardless of what pages may actually exist for this term. `page_count` will correctly reflect the number of pages for this term in both cases. + +`required` (optional) if a taxonomy or term is not found`. + +See the [Taxonomies documentation](@/documentation/templates/taxonomies.md) for a full documentation of those types. + ### `get_url` Gets the permalink for the given path. If the path starts with `@/`, it will be treated as an internal link like the ones used in Markdown, @@ -199,35 +216,39 @@ It accepts an optional parameter `lang` in order to compute a *language-aware UR {% set url = get_url(path="@/blog/_index.md", lang="en") %} ``` -This can also be used to get the permalinks for static assets, for example if -we want to link to the file that is located at `static/css/app.css`: +This can also be used to get the permalink for a static file, for example if +you want to link to the file that is located at `static/css/app.css`: ```jinja2 {{/* get_url(path="css/app.css") */}} ``` -By default, assets will not have a trailing slash. You can force one by passing `trailing_slash=true` to the `get_url` function. +By default, the link will not have a trailing slash. You can force one by passing `trailing_slash=true` to the `get_url` function. An example is: ```jinja2 {{/* get_url(path="css/app.css", trailing_slash=true) */}} ``` -In the case of non-internal links, you can also add a cachebust of the format `?h=` at the end of a URL +In the case of a non-internal link, you can also add a cachebust of the format `?h=` at the end of a URL by passing `cachebust=true` to the `get_url` function. In this case, the path will need to resolve to an actual file. See [File Searching Logic](@/documentation/templates/overview.md#file-searching-logic) for details. -### `get_file_hash` +### `get_hash` -Returns the hash digest (SHA-256, SHA-384 or SHA-512) of a file. +Returns the hash digest (SHA-256, SHA-384 or SHA-512) of a file or a string literal. It can take the following arguments: - `path`: mandatory, see [File Searching Logic](@/documentation/templates/overview.md#file-searching-logic) for details +- **or** `literal`: mandatory, the string value to be hashed - `sha_type`: optional, one of `256`, `384` or `512`, defaults to `384` - `base64`: optional, `true` or `false`, defaults to `true`. Whether to encode the hash as base64 +Either `path` or `literal` must be given. + ```jinja2 -{{/* get_file_hash(path="static/js/app.js", sha_type=256) */}} +{{/* get_hash(literal="Hello World", sha_type=256) */}} +{{/* get_hash(path="static/js/app.js", sha_type=256) */}} ``` The function can also output a base64-encoded hash value when its `base64` @@ -236,10 +257,10 @@ integrity](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Int ```jinja2 + integrity="sha384-{{ get_hash(path="static/js/app.js", sha_type=384, base64=true) | safe }}"> ``` -Do note that subresource integrity is typically used when using external scripts, which `get_file_hash` does not support. +Do note that subresource integrity is typically used when using external scripts, which `get_hash` does not support. ### `get_image_metadata` @@ -259,7 +280,7 @@ The method returns a map containing `width`, `height` and `format` (the lowercas ### `load_data` -Loads data from a file, URL, or string literal. Supported file types include *toml*, *json*, *csv*, *bibtex*, *yaml* +Loads data from a file, URL, or string literal. Supported file types include *toml*, *json*, *csv*, *bibtex*, *yaml*/*yml*, and *xml* and only supports UTF-8 encoding. Any other file type will be loaded as plain text. diff --git a/docs/content/documentation/templates/pages-sections.md b/docs/content/documentation/templates/pages-sections.md index 8486cd6a82..1b1b15ad8b 100644 --- a/docs/content/documentation/templates/pages-sections.md +++ b/docs/content/documentation/templates/pages-sections.md @@ -34,7 +34,7 @@ toc: Array
, word_count: Number; // Based on https://help.medium.com/hc/en-us/articles/214991667-Read-time reading_time: Number; -// earlier / ligher +// earlier / lighter lower: Page?; // later / heavier higher: Page?; @@ -50,10 +50,15 @@ assets: Array; ancestors: Array; // The relative path from the `content` directory to the markdown file relative_path: String; +// The relative path from the `content` directory to the directory of a colocated index.md markdown file +// Null if the file is not colocated. +colocated_path: String?; // The language for the page if there is one. Default to the config `default_language` lang: String; // Information about all the available languages for that content, including the current page translations: Array; +// All the pages/sections linking this page: their permalink and a title if there is one +backlinks: Array<{permalink: String, title: String?}>; ``` ## Section variables @@ -75,7 +80,7 @@ path: String; components: Array; permalink: String; extra: HashMap; -// Pages directly in this section. By default, the pages are not sorted. Please set the "sorted_by" +// Pages directly in this section. By default, the pages are not sorted. Please set the "sort_by" // variable in the _index.md file of the corresponding section to "date" or "weight" for sorting by // date and weight, respectively. pages: Array; @@ -100,6 +105,10 @@ relative_path: String; lang: String; // Information about all the available languages for that content translations: Array; +// All the pages/sections linking this page: their permalink and a title if there is one +backlinks: Array<{permalink: String, title: String?}>; +// Whether this section generates a feed or not. Taken from the front-matter if set +generate_feed: bool; ``` ## Table of contents diff --git a/docs/content/documentation/templates/taxonomies.md b/docs/content/documentation/templates/taxonomies.md index 064623b71b..498f02381f 100644 --- a/docs/content/documentation/templates/taxonomies.md +++ b/docs/content/documentation/templates/taxonomies.md @@ -12,6 +12,8 @@ if they are not found, it will attempt to fall back on the following generic tem - `taxonomy_single.html` - `taxonomy_list.html` +The taxonomy templates are required only if at least one taxonomy exists with `render` set to `true`. + First, `TaxonomyTerm` has the following fields: ```ts @@ -20,6 +22,7 @@ slug: String; path: String; permalink: String; pages: Array; +page_count: Number; ``` and `TaxonomyConfig` has the following fields: diff --git a/docs/content/documentation/themes/creating-a-theme.md b/docs/content/documentation/themes/creating-a-theme.md index c6b85fe144..70bd147a69 100644 --- a/docs/content/documentation/themes/creating-a-theme.md +++ b/docs/content/documentation/themes/creating-a-theme.md @@ -54,13 +54,7 @@ to be able to build the theme from your repository. ## Submitting a theme to the gallery If you want your theme to be featured in the [themes](@/themes/_index.md) section -of this site, the theme will require two more things: - -- `screenshot.png`: a screenshot of the theme in action with a max size of around 2000x1000 -- `README.md`: a thorough README explaining how to use the theme and any other information -of importance - -The first step is to make sure that the theme meets the following three requirements: +of this site, make sure that the theme meets the following three requirements: - have a `screenshot.png` of the theme in action with a max size of around 2000x1000 - have a thorough `README.md` explaining how to use the theme and any other information diff --git a/docs/content/documentation/themes/installing-and-using-themes.md b/docs/content/documentation/themes/installing-and-using-themes.md index 674366a65a..16b5bc4a11 100644 --- a/docs/content/documentation/themes/installing-and-using-themes.md +++ b/docs/content/documentation/themes/installing-and-using-themes.md @@ -7,11 +7,11 @@ weight = 20 ## Installing a theme The easiest way to install a theme is to clone its repository in the `themes` -directory. +directory: ```bash $ cd themes -$ git clone THEME_REPO_URL +$ git clone ``` Cloning the repository using Git or another VCS will allow you to easily diff --git a/docs/content/hello/index.md b/docs/content/hello/index.md new file mode 100644 index 0000000000..008be6354a --- /dev/null +++ b/docs/content/hello/index.md @@ -0,0 +1,4 @@ ++++ ++++ + +Hello \ No newline at end of file diff --git a/docs/content/themes/DeepThought/index.md b/docs/content/themes/DeepThought/index.md index ea771220ce..bf3bde3643 100644 --- a/docs/content/themes/DeepThought/index.md +++ b/docs/content/themes/DeepThought/index.md @@ -3,11 +3,11 @@ title = "DeepThought" description = "A simple blog theme focused on writing powered by Bulma and Zola." template = "theme.html" -date = 2022-06-15T01:35:37-07:00 +date = 2022-12-04T21:40:33+02:00 [extra] -created = 2022-06-15T01:35:37-07:00 -updated = 2022-06-15T01:35:37-07:00 +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 repository = "https://github.com/RatanShreshtha/DeepThought.git" homepage = "https://github.com/RatanShreshtha/DeepThought" minimum_version = "0.14.1" diff --git a/docs/content/themes/Ergo/index.md b/docs/content/themes/Ergo/index.md index 81f5024132..9ebdcf94e4 100644 --- a/docs/content/themes/Ergo/index.md +++ b/docs/content/themes/Ergo/index.md @@ -3,11 +3,11 @@ title = "Ergo" description = "A simple blog Theme focused on writing, inspired by svbtle" template = "theme.html" -date = 2022-06-15T01:35:37-07:00 +date = 2022-12-04T21:40:33+02:00 [extra] -created = 2022-06-15T01:35:37-07:00 -updated = 2022-06-15T01:35:37-07:00 +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 repository = "https://github.com/insipx/Ergo.git" homepage = "https://github.com/insipx/Ergo" minimum_version = "0.4.1" diff --git a/docs/content/themes/Zulma/index.md b/docs/content/themes/Zulma/index.md index f0f95e1b97..8df0d7ad10 100644 --- a/docs/content/themes/Zulma/index.md +++ b/docs/content/themes/Zulma/index.md @@ -3,11 +3,11 @@ title = "Zulma" description = "A zola theme based off bulma.css" template = "theme.html" -date = 2022-06-15T01:35:37-07:00 +date = 2022-12-04T21:40:33+02:00 [extra] -created = 2022-06-15T01:35:37-07:00 -updated = 2022-06-15T01:35:37-07:00 +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 repository = "https://github.com/Worble/Zulma.git" homepage = "https://github.com/Worble/Zulma" minimum_version = "0.6.0" diff --git a/docs/content/themes/abridge/index.md b/docs/content/themes/abridge/index.md index 1a3a747ddc..7a4917e412 100644 --- a/docs/content/themes/abridge/index.md +++ b/docs/content/themes/abridge/index.md @@ -3,14 +3,14 @@ title = "abridge" description = "A fast and lightweight Zola theme using semantic html, a class-light abridge.css, and No JS." template = "theme.html" -date = 2022-06-15T01:35:37-07:00 +date = 2022-12-04T21:40:33+02:00 [extra] -created = 2022-06-15T01:35:37-07:00 -updated = 2022-06-15T01:35:37-07:00 +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 repository = "https://github.com/Jieiku/abridge.git" homepage = "https://github.com/jieiku/abridge/" -minimum_version = "0.14.1" +minimum_version = "0.16.0" license = "MIT" demo = "https://abridge.netlify.app/" @@ -28,9 +28,9 @@ Abridge is a fast and lightweight Zola theme using semantic html, only ~6kb css There is also [Abridge-minimal](https://github.com/jieiku/abridge.css) Theme which is used to showcase the [abridge.css framework](https://github.com/Jieiku/abridge.css/tree/master/dist) -Maintainence of this project is made possible by all the contributors and sponsors. If you'd like to sponsor this project and have your avatar or company logo appear below click here. 💖 +Maintenance of this project is made possible by all the contributors and sponsors. If you'd like to sponsor this project and have your avatar or company logo appear below click here. 💖 - + --- @@ -40,7 +40,7 @@ Maintainence of this project is made possible by all the You can see the [Gerson's website](https://github.com/gersonbdev/gersonbdev.github.io) repository for theme setup guide. + +For the site to work properly you need to create a `_index.md` file within the `content` path with the following structure: + +```toml ++++ +title = "Home" +description = "Home site description." +sort_by = "date" +template = "index.html" +page_template = "page.html" ++++ +``` + +You can add more markdown content inside this file if you need to. + +If you want to enable the site's blog, create a _index.md file inside the `content/blog` path then copy the following structure inside the file: + +```toml ++++ +title = "Blog" +description = "Blog site description." +sort_by = "date" +paginate_by = 5 +template = "blog.html" +page_template = "blog_page.html" ++++ +``` + +You can display the result of your website by running: + +```console +zola serve +``` + + +## Hacking + +By default, the theme comes with all the scss styles already compiled, in such a way that the installation of Bootstrap is not necessary, in order to avoid dependencies such as Node.js in the production file. + +If you want to edit the theme's styles, you'll need to have a [Node.js](https://nodejs.org/) interpreter and a [Sass compiler](https://sass-lang.com/install) installed. After that, go to the main path of the theme and execute: + +```console +npm install +``` + +```console +sass --watch scss/custom.scss:static/assets/css/custom.css +``` + +> Keep in mind that the main branch of this repository only has the stable versions of the theme, if you want to see the development status and the unstable versions, change to the corresponding branch. + +## Credits + +This theme is mainly built on [Zola](https://www.getzola.org/) and [Bootstrap](https://getbootstrap.com/), plus it makes use of [Google fonts](https://fonts.google.com/). + + +## Sponsoring +[![Liberapay](https://img.shields.io/badge/Finance%20the%20project-F6C915?style=flat&logo=liberapay&logoColor=ffffff "Finance the project")](https://liberapay.com/gersonbenavides/donate) + + +## License + +This work is published under the [MPL-2.0](https://www.mozilla.org/en-US/MPL/2.0/) license + \ No newline at end of file diff --git a/docs/content/themes/ataraxia-zola/screenshot.png b/docs/content/themes/ataraxia-zola/screenshot.png new file mode 100644 index 0000000000..514f0af3a8 Binary files /dev/null and b/docs/content/themes/ataraxia-zola/screenshot.png differ diff --git a/docs/content/themes/blow/index.md b/docs/content/themes/blow/index.md index 09d1a4c95b..56f46bfb01 100644 --- a/docs/content/themes/blow/index.md +++ b/docs/content/themes/blow/index.md @@ -3,11 +3,11 @@ title = "Blow" description = "A Zola theme made with Tailwindcss" template = "theme.html" -date = 2022-06-15T01:35:37-07:00 +date = 2022-12-04T21:40:33+02:00 [extra] -created = 2022-06-15T01:35:37-07:00 -updated = 2022-06-15T01:35:37-07:00 +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 repository = "https://github.com/tchartron/blow.git" homepage = "https://github.com/tchartron/blow" minimum_version = "0.9.0" diff --git a/docs/content/themes/book/index.md b/docs/content/themes/book/index.md index 073d33d102..4e6dcfe6bc 100644 --- a/docs/content/themes/book/index.md +++ b/docs/content/themes/book/index.md @@ -3,11 +3,11 @@ title = "book" description = "A book theme inspired from GitBook/mdBook" template = "theme.html" -date = 2022-06-15T01:35:37-07:00 +date = 2022-12-04T21:40:33+02:00 [extra] -created = 2022-06-15T01:35:37-07:00 -updated = 2022-06-15T01:35:37-07:00 +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 repository = "https://github.com/getzola/book.git" homepage = "https://github.com/getzola/book" minimum_version = "0.5.0" diff --git a/docs/content/themes/boring/index.md b/docs/content/themes/boring/index.md new file mode 100644 index 0000000000..4f8a52c822 --- /dev/null +++ b/docs/content/themes/boring/index.md @@ -0,0 +1,57 @@ + ++++ +title = "boring" +description = "A minimal theme" +template = "theme.html" +date = 2022-12-04T21:40:33+02:00 + +[extra] +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 +repository = "https://github.com/ssiyad/boring.git" +homepage = "https://github.com/ssiyad/boring" +minimum_version = "0.16.0" +license = "GPLv3" +demo = "" + +[extra.author] +name = "Sabu Siyad" +homepage = "https://ssiyad.com" ++++ + +# Boring +Minimal theme for [Zola](https://www.getzola.org/), powered by +[TailwindCSS](https://tailwindcss.com/) + +### Demo +https://boring-zola.netlify.app/ + +![sreenshot](./screenshot.png) + +### Setup +In your zola site directory +- Get theme + + ```shell + git submodule add https://github.com/ssiyad/boring themes/boring + ``` + +- Build CSS + + ```shell + cd themes/boring + yarn install --frozen-lockfile + yarn build + ``` + +- Change theme specific variables. They are listed in `extra` section of + [config.toml](./config.toml) + +Refer [Zola Docs](https://www.getzola.org/documentation/themes/installing-and-using-themes/#using-a-theme) +for further instructions + +### License +[GPLv3](./LICENSE) + + + \ No newline at end of file diff --git a/docs/content/themes/boring/screenshot.png b/docs/content/themes/boring/screenshot.png new file mode 100644 index 0000000000..320341fe69 Binary files /dev/null and b/docs/content/themes/boring/screenshot.png differ diff --git a/docs/content/themes/clean-blog/index.md b/docs/content/themes/clean-blog/index.md index c619f08ffb..24e38659f0 100644 --- a/docs/content/themes/clean-blog/index.md +++ b/docs/content/themes/clean-blog/index.md @@ -3,11 +3,11 @@ title = "Clean Blog" description = "A port of Start Bootstrap Clean Blog for Zola" template = "theme.html" -date = 2022-06-15T01:35:37-07:00 +date = 2022-12-04T21:40:33+02:00 [extra] -created = 2022-06-15T01:35:37-07:00 -updated = 2022-06-15T01:35:37-07:00 +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 repository = "https://github.com/dave-tucker/zola-clean-blog.git" homepage = "https://github.com/dave-tucker/zola-clean-blog" minimum_version = "0.4.0" diff --git a/docs/content/themes/codinfox-zola/index.md b/docs/content/themes/codinfox-zola/index.md index 04a3f5230e..0c9a5e52e1 100644 --- a/docs/content/themes/codinfox-zola/index.md +++ b/docs/content/themes/codinfox-zola/index.md @@ -3,11 +3,11 @@ title = "codinfox-zola" description = "Codinfox theme for Zola" template = "theme.html" -date = 2022-06-15T01:35:37-07:00 +date = 2022-12-04T21:40:33+02:00 [extra] -created = 2022-06-15T01:35:37-07:00 -updated = 2022-06-15T01:35:37-07:00 +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 repository = "https://github.com/svavs/codinfox-zola.git" homepage = "https://github.com/svavs/codinfox-zola" minimum_version = "0.11.0" diff --git a/docs/content/themes/d3c3nt/index.md b/docs/content/themes/d3c3nt/index.md index b98323ac52..7790b93a3e 100644 --- a/docs/content/themes/d3c3nt/index.md +++ b/docs/content/themes/d3c3nt/index.md @@ -3,11 +3,11 @@ title = "d3c3nt" description = "A simple, clean, and flexible theme for personal sites." template = "theme.html" -date = 2022-06-15T01:35:37-07:00 +date = 2022-12-04T21:40:33+02:00 [extra] -created = 2022-06-15T01:35:37-07:00 -updated = 2022-06-15T01:35:37-07:00 +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 repository = "git://git.figbert.com/d3c3nt.git" homepage = "https://git.figbert.com/d3c3nt/" minimum_version = "0.15.0" diff --git a/docs/content/themes/dinkleberg/index.md b/docs/content/themes/dinkleberg/index.md index beafc12e9e..e4d5178edd 100644 --- a/docs/content/themes/dinkleberg/index.md +++ b/docs/content/themes/dinkleberg/index.md @@ -3,11 +3,11 @@ title = "dinkleberg" description = "The Rust BR theme for Gutenberg" template = "theme.html" -date = 2022-06-15T01:35:37-07:00 +date = 2022-12-04T21:40:33+02:00 [extra] -created = 2022-06-15T01:35:37-07:00 -updated = 2022-06-15T01:35:37-07:00 +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 repository = "https://github.com/rust-br/dinkleberg.git" homepage = "https://github.com/rust-br/dinkleberg" minimum_version = "0.4.0" diff --git a/docs/content/themes/docsascode-theme/index.md b/docs/content/themes/docsascode-theme/index.md index 966c70486f..945bbd5be6 100644 --- a/docs/content/themes/docsascode-theme/index.md +++ b/docs/content/themes/docsascode-theme/index.md @@ -3,11 +3,11 @@ title = "Docsascode_theme" description = "A modern simple Zola's theme related to docs as code methodology" template = "theme.html" -date = 2022-06-15T01:35:37-07:00 +date = 2022-12-04T21:40:33+02:00 [extra] -created = 2022-06-15T01:35:37-07:00 -updated = 2022-06-15T01:35:37-07:00 +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 repository = "https://github.com/codeandmedia/zola_docsascode_theme.git" homepage = "https://github.com/codeandmedia/zola_docsascode_theme" minimum_version = "0.10.0" diff --git a/docs/content/themes/dose/index.md b/docs/content/themes/dose/index.md index 9a5235a596..00fecae9cc 100644 --- a/docs/content/themes/dose/index.md +++ b/docs/content/themes/dose/index.md @@ -3,11 +3,11 @@ title = "dose" description = "a small blog theme" template = "theme.html" -date = 2022-06-15T01:35:37-07:00 +date = 2022-12-04T21:40:33+02:00 [extra] -created = 2022-06-15T01:35:37-07:00 -updated = 2022-06-15T01:35:37-07:00 +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 repository = "https://github.com/oltdaniel/dose.git" homepage = "https://github.com/oltdaniel/dose" minimum_version = "0.14.0" diff --git a/docs/content/themes/emily/index.md b/docs/content/themes/emily/index.md index 46507f1a55..345794ac3e 100644 --- a/docs/content/themes/emily/index.md +++ b/docs/content/themes/emily/index.md @@ -3,11 +3,11 @@ title = "emily_zola_theme" description = "a KISS theme for Zola" template = "theme.html" -date = 2022-06-15T01:35:37-07:00 +date = 2022-12-04T21:40:33+02:00 [extra] -created = 2022-06-15T01:35:37-07:00 -updated = 2022-06-15T01:35:37-07:00 +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 repository = "https://github.com/kyoheiu/emily_zola_theme.git" homepage = "https://github.com/kyoheiu/emily_zola_theme" minimum_version = "0.14.1" diff --git a/docs/content/themes/even/index.md b/docs/content/themes/even/index.md index 5f787d4124..ca15ca281a 100644 --- a/docs/content/themes/even/index.md +++ b/docs/content/themes/even/index.md @@ -3,11 +3,11 @@ title = "even" description = "A robust, elegant dark theme" template = "theme.html" -date = 2022-06-15T01:35:37-07:00 +date = 2022-12-04T21:40:33+02:00 [extra] -created = 2022-06-15T01:35:37-07:00 -updated = 2022-06-15T01:35:37-07:00 +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 repository = "https://github.com/getzola/even.git" homepage = "https://github.com/getzola/even" minimum_version = "0.11.0" diff --git a/docs/content/themes/feather/index.md b/docs/content/themes/feather/index.md index 7e8ff79af5..7930c9d634 100644 --- a/docs/content/themes/feather/index.md +++ b/docs/content/themes/feather/index.md @@ -3,11 +3,11 @@ title = "feather" description = "A modern blog theme" template = "theme.html" -date = 2022-06-15T01:35:37-07:00 +date = 2022-12-04T21:40:33+02:00 [extra] -created = 2022-06-15T01:35:37-07:00 -updated = 2022-06-15T01:35:37-07:00 +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 repository = "https://github.com/piedoom/feather.git" homepage = "https://github.com/piedoom/feather" minimum_version = "0.11.0" diff --git a/docs/content/themes/float/index.md b/docs/content/themes/float/index.md index 9fc4178922..92a303d4a6 100644 --- a/docs/content/themes/float/index.md +++ b/docs/content/themes/float/index.md @@ -3,11 +3,11 @@ title = "Float" description = "An elegant blog theme" template = "theme.html" -date = 2022-06-15T01:35:37-07:00 +date = 2022-12-04T21:40:33+02:00 [extra] -created = 2022-06-15T01:35:37-07:00 -updated = 2022-06-15T01:35:37-07:00 +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 repository = "https://gitlab.com/float-theme/float.git" homepage = "https://float-theme.netlify.app/" minimum_version = "0.15.3" diff --git a/docs/content/themes/hallo/index.md b/docs/content/themes/hallo/index.md index 1c97370035..28af0b0c78 100644 --- a/docs/content/themes/hallo/index.md +++ b/docs/content/themes/hallo/index.md @@ -3,11 +3,11 @@ title = "hallo" description = "A single-page theme to introduce yourself." template = "theme.html" -date = 2022-06-15T01:35:37-07:00 +date = 2022-12-04T21:40:33+02:00 [extra] -created = 2022-06-15T01:35:37-07:00 -updated = 2022-06-15T01:35:37-07:00 +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 repository = "https://github.com/flyingP0tat0/zola-hallo.git" homepage = "https://github.com/janbaudisch/zola-hallo" minimum_version = "0.4.0" diff --git a/docs/content/themes/hephaestus/index.md b/docs/content/themes/hephaestus/index.md index 6bc95a9ee3..2042b2b558 100644 --- a/docs/content/themes/hephaestus/index.md +++ b/docs/content/themes/hephaestus/index.md @@ -3,11 +3,11 @@ title = "hephaestus" description = "A portfolio theme" template = "theme.html" -date = 2022-06-15T01:35:37-07:00 +date = 2022-12-04T21:40:33+02:00 [extra] -created = 2022-06-15T01:35:37-07:00 -updated = 2022-06-15T01:35:37-07:00 +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 repository = "https://github.com/BConquest/hephaestus.git" homepage = "https://github.com/BConquest/hephaestus" minimum_version = "0.4.0" diff --git a/docs/content/themes/hermit/index.md b/docs/content/themes/hermit/index.md index 2d5ed93569..6de805783e 100644 --- a/docs/content/themes/hermit/index.md +++ b/docs/content/themes/hermit/index.md @@ -3,11 +3,11 @@ title = "Hermit_Zola" description = "Minimal Zola theme" template = "theme.html" -date = 2022-06-15T01:35:37-07:00 +date = 2022-12-04T21:40:33+02:00 [extra] -created = 2022-06-15T01:35:37-07:00 -updated = 2022-06-15T01:35:37-07:00 +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 repository = "https://github.com/VersBinarii/hermit_zola.git" homepage = "https://github.com/VersBinarii/hermit_zola" minimum_version = "0.4.0" diff --git a/docs/content/themes/hook/index.md b/docs/content/themes/hook/index.md index 6a63ba0425..395b8d465a 100644 --- a/docs/content/themes/hook/index.md +++ b/docs/content/themes/hook/index.md @@ -3,11 +3,11 @@ title = "Hook" description = "Clean and simple personal site/blog theme" template = "theme.html" -date = 2022-06-15T01:35:37-07:00 +date = 2022-12-04T21:40:33+02:00 [extra] -created = 2022-06-15T01:35:37-07:00 -updated = 2022-06-15T01:35:37-07:00 +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 repository = "https://github.com/InputUsername/zola-hook.git" homepage = "https://github.com/InputUsername/zola-hook" minimum_version = "0.15.2" diff --git a/docs/content/themes/hyde/index.md b/docs/content/themes/hyde/index.md index 1332760380..6b35fc4ae4 100644 --- a/docs/content/themes/hyde/index.md +++ b/docs/content/themes/hyde/index.md @@ -3,11 +3,11 @@ title = "hyde" description = "A classic blog theme" template = "theme.html" -date = 2022-06-15T01:35:37-07:00 +date = 2022-12-04T21:40:33+02:00 [extra] -created = 2022-06-15T01:35:37-07:00 -updated = 2022-06-15T01:35:37-07:00 +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 repository = "https://github.com/getzola/hyde.git" homepage = "https://github.com/getzola/hyde" minimum_version = "0.11.0" diff --git a/docs/content/themes/juice/index.md b/docs/content/themes/juice/index.md index 6f8ef62b7e..f9d510bb45 100644 --- a/docs/content/themes/juice/index.md +++ b/docs/content/themes/juice/index.md @@ -3,11 +3,11 @@ title = "juice" description = "An intuitive, elegant, and lightweight Zola theme for product sites." template = "theme.html" -date = 2022-06-15T01:35:37-07:00 +date = 2022-12-04T21:40:33+02:00 [extra] -created = 2022-06-15T01:35:37-07:00 -updated = 2022-06-15T01:35:37-07:00 +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 repository = "https://github.com/huhu/juice.git" homepage = "https://github.com/huhu/juice" minimum_version = "0.11.0" diff --git a/docs/content/themes/kangae/index.md b/docs/content/themes/kangae/index.md index 9a27838ace..0b74f98671 100644 --- a/docs/content/themes/kangae/index.md +++ b/docs/content/themes/kangae/index.md @@ -3,11 +3,11 @@ title = "kangae" description = "a lightweight microblog theme for zola" template = "theme.html" -date = 2022-06-15T01:35:37-07:00 +date = 2022-12-04T21:40:33+02:00 [extra] -created = 2022-06-15T01:35:37-07:00 -updated = 2022-06-15T01:35:37-07:00 +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 repository = "https://github.com/ayushnix/kangae.git" homepage = "https://github.com/ayushnix/kangae" minimum_version = "0.15.0" @@ -178,11 +178,10 @@ If you're in India, you can also use UPI for donations. My UPI address is `ayush # Notes -I'm not really a web developer and I don't intend to be one either. However, I am interested in -learning HTML and CSS to create lightweight textual websites. You may be interested in reading [my -log about how I learned HTML and CSS][12]. However, that page is just an unorganized dump of my -thoughts and isn't a polished blog post. [Seirdy's blog post on creating textual websites][13] is -probably a better reference. +Although I'm not a web developer, I am interested in learning HTML and CSS to create lightweight +textual websites. You may be interested in reading [my log about how I learned HTML and CSS][12]. +However, that page is just an unorganized dump of my thoughts and isn't a polished blog post. +[Seirdy's blog post on creating textual websites][13] is probably a better reference. # TODO (maybe?) @@ -196,6 +195,7 @@ probably a better reference. - pagination - light and dark mode switch - content tabs +- microdata and microformats2 [1]: https://kangae.ayushnix.com/ [2]: https://www.getzola.org/ diff --git a/docs/content/themes/kangae/screenshot.png b/docs/content/themes/kangae/screenshot.png index e8faabaa16..a06eeb5955 100644 Binary files a/docs/content/themes/kangae/screenshot.png and b/docs/content/themes/kangae/screenshot.png differ diff --git a/docs/content/themes/karzok/index.md b/docs/content/themes/karzok/index.md index ce13bc77cc..42f7690f94 100644 --- a/docs/content/themes/karzok/index.md +++ b/docs/content/themes/karzok/index.md @@ -3,11 +3,11 @@ title = "karzok" description = "The theme for launching fast documentation sites" template = "theme.html" -date = 2022-06-15T01:35:37-07:00 +date = 2022-12-04T21:40:33+02:00 [extra] -created = 2022-06-15T01:35:37-07:00 -updated = 2022-06-15T01:35:37-07:00 +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 repository = "https://github.com/kogeletey/karzok.git" homepage = "https://github.com/kogeletey/karzok" minimum_version = "0.15.0" @@ -21,7 +21,6 @@ homepage = ""

github workflows status - license a repository latest release as a repository pipeline status re128 diff --git a/docs/content/themes/kodama-theme/index.md b/docs/content/themes/kodama-theme/index.md index b569749c95..5099fa1853 100644 --- a/docs/content/themes/kodama-theme/index.md +++ b/docs/content/themes/kodama-theme/index.md @@ -3,11 +3,11 @@ title = "kodama" description = "Theme insipired by wowchemy academic." template = "theme.html" -date = 2022-06-15T01:35:37-07:00 +date = 2022-12-04T21:40:33+02:00 [extra] -created = 2022-06-15T01:35:37-07:00 -updated = 2022-06-15T01:35:37-07:00 +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 repository = "https://github.com/adfaure/kodama-theme.git" homepage = "https://github.com/adfaure/kodama-theme" minimum_version = "0.15" @@ -27,7 +27,7 @@ homepage = "https://adrien-faure.fr" This theme is greatly inspired from hugo academic theme. First lets introduce some technical details: -- It relies on [zola](https://getzola.com). +- It relies on [zola](https://www.getzola.org/). - It has no javascript. - The CSS is built with [tailwindcss](https://tailwindcss.com/). - The blog articles are themed with [@tailwindcss/typography](https://tailwindcss.com/docs/typography-plugin) theme. @@ -200,6 +200,17 @@ sort_by = "date" # Here the two dedicated templates template = "publications.html" page_template = "publication-page.html" + +# If you want to show your publications under different sections +# the title will be the displayed text in your website, and the type +# should be the type of publication. +# Each individual plublication has an `extra.type` that refers to the +# publication type (example in content sub-section). +extra.publications_types = [ + { title = "Journal articles", type = "journals" }, + { title = "Thesis", type = "thesis" }, + { title = "Conferences and workshops ", type = "conferences" } +] +++ ## Content @@ -232,11 +243,20 @@ date = 2021-05-18 [extra] type = "Conference" authors = [ "Kodama Mononoke" ] -publication_types = "Conference paper" + +# Should be the type of the publication type it should appears under +# configured in the front matter of publications/_index.md +type = "conferences" + featured = true publication = "2020 IEE rainbow workshop" +# Add full url for your pdf and your presentation url_pdf = "https://your-pdf" url_slides = "path_to_slides" + +# Add a link to a local pdf inside of your paper folder (example in content/publications/paper1.index.md) +pdf = "paper.pdf" +slides = "path_to_slides.pdf" +++ ``` @@ -244,4 +264,5 @@ url_slides = "path_to_slides" The icons available in this project are stored in a dedicated macro function in `templates/macros/icons.html`. To add a new svg, you can add a case in the `if elif .. else` of the function containing the svg copied from [heroicons](https://heroicons.com/) for instance. + \ No newline at end of file diff --git a/docs/content/themes/lightspeed/index.md b/docs/content/themes/lightspeed/index.md index 7686b838eb..fc71d4d793 100644 --- a/docs/content/themes/lightspeed/index.md +++ b/docs/content/themes/lightspeed/index.md @@ -3,11 +3,11 @@ title = "lightspeed" description = "Zola theme with a perfect Lighthouse score" template = "theme.html" -date = 2022-06-15T01:35:37-07:00 +date = 2022-12-04T21:40:33+02:00 [extra] -created = 2022-06-15T01:35:37-07:00 -updated = 2022-06-15T01:35:37-07:00 +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 repository = "https://github.com/carpetscheme/lightspeed.git" homepage = "https://github.com/carpetscheme/lightspeed" minimum_version = "0.10.0" diff --git a/docs/content/themes/nasm-theme/index.md b/docs/content/themes/nasm-theme/index.md index 69bbc93cbb..99202bab64 100644 --- a/docs/content/themes/nasm-theme/index.md +++ b/docs/content/themes/nasm-theme/index.md @@ -3,11 +3,11 @@ title = "nasm-theme" description = "A robust, elegant blue theme" template = "theme.html" -date = 2022-06-15T01:35:37-07:00 +date = 2022-12-04T21:40:33+02:00 [extra] -created = 2022-06-15T01:35:37-07:00 -updated = 2022-06-15T01:35:37-07:00 +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 repository = "https://github.com/lucasnasm/nasm-theme.git" homepage = "https://github.com/lucasnasm/nasm-theme" minimum_version = "0.1.0" diff --git a/docs/content/themes/no-style-please/index.md b/docs/content/themes/no-style-please/index.md new file mode 100644 index 0000000000..611d099556 --- /dev/null +++ b/docs/content/themes/no-style-please/index.md @@ -0,0 +1,98 @@ + ++++ +title = "no style, please!" +description = "A (nearly) no-CSS, fast, minimalist Zola theme" +template = "theme.html" +date = 2022-12-04T21:40:33+02:00 + +[extra] +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 +repository = "https://gitlab.com/4bcx/no-style-please.git" +homepage = "https://gitlab.com/4bcx/no-style-please" +minimum_version = "0.4.0" +license = "MIT" +demo = "https://4bcx.gitlab.io/no-style-please" + +[extra.author] +name = "Ahmed Alaa" +homepage = "https://4b.cx" ++++ + +# no style, please! + +A (nearly) no-CSS, fast, minimalist [Zola](https://www.getzola.org/) theme. +Ported from from [riggraz](https://riggraz.dev/)'s [no style, please! Jekyll theme](https://riggraz.dev/no-style-please/), and I use it for [my site](https://4b.cx/) + +![screenshot](./screenshot.png) + +## Installation + +First download this theme to your `themes` directory: + +```bash +cd themes +git clone https://gitlab.com/4bcx/no-style-please.git +``` + +and then enable it in your `config.toml`: + +```toml +theme = "no-style-please" +``` + +## Options + +### Pages list in homepage + +To enable listing of pages in homepage add the following in `content\_index.md` frontmatter + +```toml +[exta] +list_pages = false +``` + +### Extra data + +- `author` can be set in both main config and in pages metadata +- `image` variable can be used in pages to add an image to HTML `` tags +- Same for `logo` in main config, except this one is also used as the site icon + +### Horizontal rule shortcode `hr()` + +Adds the option to insert text in the thematic break + +```html +{{/* hr(data_content="footnotes") */}} +``` + +is rendered + +![thematic break screenshot](./hr_footnotes.png) + +### Invertable image `iimg()` + +Images are not inverted in darkmode by default. To add an invertable image use the following + +```html +{{/* iimg(src="logo.png", alt="alt text") */}} +``` + +In light mode + +![image in light mode](./iimg_light.png) + +In dark mode + +![image in dark mode](./iimg_dark.png) + +## TODO + +- [ ] Add RTL support +- [ ] Write proper test pages + +## License + +The theme is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). + + \ No newline at end of file diff --git a/docs/content/themes/no-style-please/screenshot.png b/docs/content/themes/no-style-please/screenshot.png new file mode 100644 index 0000000000..7420fa5cc3 Binary files /dev/null and b/docs/content/themes/no-style-please/screenshot.png differ diff --git a/docs/content/themes/ntun/index.md b/docs/content/themes/ntun/index.md index 9f61335052..bf1d492584 100644 --- a/docs/content/themes/ntun/index.md +++ b/docs/content/themes/ntun/index.md @@ -3,11 +3,11 @@ title = "ntun-zola-theme" description = "A classic resume theme" template = "theme.html" -date = 2022-06-15T01:35:37-07:00 +date = 2022-12-04T21:40:33+02:00 [extra] -created = 2022-06-15T01:35:37-07:00 -updated = 2022-06-15T01:35:37-07:00 +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 repository = "https://github.com/Netoun/ntun.git" homepage = "https://github.com/netoun/ntun" minimum_version = "0.1.0" diff --git a/docs/content/themes/oceanic-zen/index.md b/docs/content/themes/oceanic-zen/index.md index 67928c858d..1107b15917 100644 --- a/docs/content/themes/oceanic-zen/index.md +++ b/docs/content/themes/oceanic-zen/index.md @@ -3,11 +3,11 @@ title = "Oceanic Zen" description = "Minimalistic blog theme" template = "theme.html" -date = 2022-06-15T01:35:37-07:00 +date = 2022-12-04T21:40:33+02:00 [extra] -created = 2022-06-15T01:35:37-07:00 -updated = 2022-06-15T01:35:37-07:00 +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 repository = "https://github.com/barlog-m/oceanic-zen.git" homepage = "https://github.com/barlog-m/oceanic-zen" minimum_version = "0.12.0" diff --git a/docs/content/themes/papaya/index.md b/docs/content/themes/papaya/index.md index 5dc0694889..a2d21ec84e 100644 --- a/docs/content/themes/papaya/index.md +++ b/docs/content/themes/papaya/index.md @@ -3,11 +3,11 @@ title = "Papaya" description = "A clean Zola theme for blogging and projects" template = "theme.html" -date = 2022-06-15T01:35:37-07:00 +date = 2022-12-04T21:40:33+02:00 [extra] -created = 2022-06-15T01:35:37-07:00 -updated = 2022-06-15T01:35:37-07:00 +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 repository = "https://github.com/justint/papaya.git" homepage = "https://github.com/justint/papaya" minimum_version = "0.14.0" diff --git a/docs/content/themes/particle/index.md b/docs/content/themes/particle/index.md index 180aaafd17..f7ca6e5638 100644 --- a/docs/content/themes/particle/index.md +++ b/docs/content/themes/particle/index.md @@ -3,11 +3,11 @@ title = "particle" description = "Particle theme for Zola" template = "theme.html" -date = 2022-06-15T01:35:37-07:00 +date = 2022-12-04T21:40:33+02:00 [extra] -created = 2022-06-15T01:35:37-07:00 -updated = 2022-06-15T01:35:37-07:00 +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 repository = "https://github.com/svavs/particle-zola.git" homepage = "https://github.com/svavs/particle" minimum_version = "0.11.0" diff --git a/docs/content/themes/resume/index.md b/docs/content/themes/resume/index.md index 4793ae3004..7cc4f0113a 100644 --- a/docs/content/themes/resume/index.md +++ b/docs/content/themes/resume/index.md @@ -3,11 +3,11 @@ title = "resume" description = "A resume theme" template = "theme.html" -date = 2022-06-15T01:35:37-07:00 +date = 2022-12-04T21:40:33+02:00 [extra] -created = 2022-06-15T01:35:37-07:00 -updated = 2022-06-15T01:35:37-07:00 +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 repository = "https://github.com/AlongWY/zola-resume.git" homepage = "https://github.com/alongwy/zola-resume" minimum_version = "0.11.0" diff --git a/docs/content/themes/sam/index.md b/docs/content/themes/sam/index.md index 7debb1b897..328a0d958a 100644 --- a/docs/content/themes/sam/index.md +++ b/docs/content/themes/sam/index.md @@ -3,11 +3,11 @@ title = "sam" description = "A Simple and Minimalist theme with a focus on typography and content." template = "theme.html" -date = 2022-06-15T01:35:37-07:00 +date = 2022-12-04T21:40:33+02:00 [extra] -created = 2022-06-15T01:35:37-07:00 -updated = 2022-06-15T01:35:37-07:00 +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 repository = "https://github.com/janbaudisch/zola-sam.git" homepage = "https://github.com/janbaudisch/zola-sam" minimum_version = "0.4.0" diff --git a/docs/content/themes/seje2/index.md b/docs/content/themes/seje2/index.md index c8c89727be..bf2df65150 100644 --- a/docs/content/themes/seje2/index.md +++ b/docs/content/themes/seje2/index.md @@ -3,11 +3,11 @@ title = "Seje2" description = "A beautiful zola theme" template = "theme.html" -date = 2022-06-15T01:35:37-07:00 +date = 2022-12-04T21:40:33+02:00 [extra] -created = 2022-06-15T01:35:37-07:00 -updated = 2022-06-15T01:35:37-07:00 +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 repository = "https://github.com/eatradish/seje2" homepage = "https://github.com/eatradish/Seje2" minimum_version = "0.15.0" diff --git a/docs/content/themes/serene/index.md b/docs/content/themes/serene/index.md index 781ba56376..e6bff93bf8 100644 --- a/docs/content/themes/serene/index.md +++ b/docs/content/themes/serene/index.md @@ -3,11 +3,11 @@ title = "serene" description = "A blog theme for zola, simple and clean." template = "theme.html" -date = 2022-06-15T01:35:37-07:00 +date = 2022-12-04T21:40:33+02:00 [extra] -created = 2022-06-15T01:35:37-07:00 -updated = 2022-06-15T01:35:37-07:00 +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 repository = "https://github.com/isunjn/serene.git" homepage = "https://github.com/isunjn/serene" minimum_version = "0.9.0" diff --git a/docs/content/themes/shadharon/index.md b/docs/content/themes/shadharon/index.md new file mode 100644 index 0000000000..457dbe8603 --- /dev/null +++ b/docs/content/themes/shadharon/index.md @@ -0,0 +1,76 @@ + ++++ +title = "shadharon" +description = "Simple blog theme powered by Zola" +template = "theme.html" +date = 2022-12-04T21:40:33+02:00 + +[extra] +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 +repository = "https://github.com/syedzayyan/shadharon" +homepage = "https://github.com/syedzayyan/shadharon" +minimum_version = "0.4.0" +license = "MIT" +demo = "https://syedzayyan.github.io/shadharon" + +[extra.author] +name = "Syed Zayyan Masud" +homepage = "https://syedzayyan.com" ++++ + +# Shadharon + +Simple blog theme powered by [Zola](getzola.org). See a live preview [here](https://syedzayyan.github.io/shadharon). + +> Name derived from the Bengali Word - সাধারণ which translates to "generic" + +

+ Dark theme + + ![blog-dark](https://raw.githubusercontent.com/syedzayyan/shadharon/main/screenshot.png) +
+ +
+ Light theme + + ![light-dark](https://raw.githubusercontent.com/syedzayyan/shadharon/main/screenshot-light.png) +
+ +## Features + +- [X] Themes (light, dark). Default theme is dark with a switcher in the navbar +- [X] Projects page +- [x] Social Links +- [x] Tags + +## Installation + +0. Initialize Git Repo if not initialized + +1. Download the theme +``` +git submodule add https://github.com/syedzayyan/shadharon themes/shadharon +``` + +2. Add `theme = "shadharon"` to your `config.toml` + +3. Copy the example content + +``` +cp -R themes/shadharon/content/. content +``` +4. For customization refer to config.toml files, which has comments. + +5. For customizing the banner on the homepage the content/posts/_index.md needs modification. The desc variable under `extra`, specifically. You could delete this as well to remove banner. For an about page or any aditional page an .md file in the "content" directory will do. + +## Options +These filenames are relative to the root of the site. In this example, the two CSS files would be in the `static` folder. + +## References + +This theme is takes inspiration from +- [apollo](https://github.com/not-matthias/apollo). +- [Tania's Website](https://tania.dev/) +- [Anpu Zola Theme](https://github.com/zbrox/anpu-zola-theme) + \ No newline at end of file diff --git a/docs/content/themes/shadharon/screenshot.png b/docs/content/themes/shadharon/screenshot.png new file mode 100644 index 0000000000..5784057e65 Binary files /dev/null and b/docs/content/themes/shadharon/screenshot.png differ diff --git a/docs/content/themes/simple-dev-blog/index.md b/docs/content/themes/simple-dev-blog/index.md index ad3a1fd153..1b30a55a45 100644 --- a/docs/content/themes/simple-dev-blog/index.md +++ b/docs/content/themes/simple-dev-blog/index.md @@ -3,11 +3,11 @@ title = "simple-dev-blog" description = "A simple dev blog theme with no javascript, prerendered linked pages and SEO tags." template = "theme.html" -date = 2022-06-15T01:35:37-07:00 +date = 2022-12-04T21:40:33+02:00 [extra] -created = 2022-06-15T01:35:37-07:00 -updated = 2022-06-15T01:35:37-07:00 +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 repository = "https://github.com/bennetthardwick/simple-dev-blog-zola-starter.git" homepage = "https://github.com/bennetthardwick/simple-dev-blog-zola-starter" minimum_version = "0.4.0" diff --git a/docs/content/themes/slim/index.md b/docs/content/themes/slim/index.md index d54031ae1c..897471eba9 100644 --- a/docs/content/themes/slim/index.md +++ b/docs/content/themes/slim/index.md @@ -3,11 +3,11 @@ title = "Slim" description = "Slim is a minimal, clean and beautiful theme for Zola." template = "theme.html" -date = 2022-06-15T01:35:37-07:00 +date = 2022-12-04T21:40:33+02:00 [extra] -created = 2022-06-15T01:35:37-07:00 -updated = 2022-06-15T01:35:37-07:00 +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 repository = "https://github.com/jameshclrk/zola-slim.git" homepage = "https://github.com/jameshclrk/zola-slim" minimum_version = "0.8.0" diff --git a/docs/content/themes/soapstone/index.md b/docs/content/themes/soapstone/index.md index 11c20577ec..842269f1af 100644 --- a/docs/content/themes/soapstone/index.md +++ b/docs/content/themes/soapstone/index.md @@ -3,11 +3,11 @@ title = "Soapstone" description = "A bare bones dark theme with some color tweakability" template = "theme.html" -date = 2022-06-15T01:35:37-07:00 +date = 2022-12-04T21:40:33+02:00 [extra] -created = 2022-06-15T01:35:37-07:00 -updated = 2022-06-15T01:35:37-07:00 +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 repository = "https://github.com/MattyRad/soapstone.git" homepage = "https://github.com/MattyRad/soapstone" minimum_version = "0.4.0" diff --git a/docs/content/themes/solar-theme-zola/index.md b/docs/content/themes/solar-theme-zola/index.md index 6117bc9673..ecb4b5ffbb 100644 --- a/docs/content/themes/solar-theme-zola/index.md +++ b/docs/content/themes/solar-theme-zola/index.md @@ -3,11 +3,11 @@ title = "solar-theme-zola" description = "A port of solar-theme-hugo for zola" template = "theme.html" -date = 2022-06-15T01:35:37-07:00 +date = 2022-12-04T21:40:33+02:00 [extra] -created = 2022-06-15T01:35:37-07:00 -updated = 2022-06-15T01:35:37-07:00 +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 repository = "https://github.com/hulufei/solar-theme-zola.git" homepage = "https://github.com/hulufei/solar-theme-zola" minimum_version = "0.4.0" diff --git a/docs/content/themes/tale-zola/index.md b/docs/content/themes/tale-zola/index.md index 61289d7d03..1393f6c15e 100644 --- a/docs/content/themes/tale-zola/index.md +++ b/docs/content/themes/tale-zola/index.md @@ -3,11 +3,11 @@ title = "tale-zola" description = "Tala-Zola is a minimal Zola theme helping you to build a nice and seo-ready blog." template = "theme.html" -date = 2022-06-15T01:35:37-07:00 +date = 2022-12-04T21:40:33+02:00 [extra] -created = 2022-06-15T01:35:37-07:00 -updated = 2022-06-15T01:35:37-07:00 +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 repository = "https://github.com/aaranxu/tale-zola.git" homepage = "https://github.com/aaranxu/tale-zola" minimum_version = "0.13.0" diff --git a/docs/content/themes/toucan/index.md b/docs/content/themes/toucan/index.md index 270ec198e8..53da987e75 100644 --- a/docs/content/themes/toucan/index.md +++ b/docs/content/themes/toucan/index.md @@ -3,11 +3,11 @@ title = "Toucan" description = "Inspired from Pelican default theme" template = "theme.html" -date = 2022-06-15T01:35:37-07:00 +date = 2022-12-04T21:40:33+02:00 [extra] -created = 2022-06-15T01:35:37-07:00 -updated = 2022-06-15T01:35:37-07:00 +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 repository = "https://git.42l.fr/HugoTrentesaux/toucan.git" homepage = "https://git.42l.fr/HugoTrentesaux/toucan" minimum_version = "0.8.0" diff --git a/docs/content/themes/zerm/index.md b/docs/content/themes/zerm/index.md index 2f6f1c86fe..f0677a84f9 100644 --- a/docs/content/themes/zerm/index.md +++ b/docs/content/themes/zerm/index.md @@ -3,11 +3,11 @@ title = "zerm" description = "A minimalistic and dark theme based on Radek Kozieł's theme for Hugo" template = "theme.html" -date = 2022-06-15T01:35:37-07:00 +date = 2022-12-04T21:40:33+02:00 [extra] -created = 2022-06-15T01:35:37-07:00 -updated = 2022-06-15T01:35:37-07:00 +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 repository = "https://github.com/ejmg/zerm.git" homepage = "https://github.com/ejmg/zerm" minimum_version = "0.8.0" diff --git a/docs/content/themes/zhuia/index.md b/docs/content/themes/zhuia/index.md index 68326d7368..acc1655bc2 100644 --- a/docs/content/themes/zhuia/index.md +++ b/docs/content/themes/zhuia/index.md @@ -3,11 +3,11 @@ title = "Zhuia" description = "An elegant but still playful theme for Zola." template = "theme.html" -date = 2022-06-15T01:35:37-07:00 +date = 2022-12-04T21:40:33+02:00 [extra] -created = 2022-06-15T01:35:37-07:00 -updated = 2022-06-15T01:35:37-07:00 +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 repository = "https://github.com/gicrisf/zhuia.git" homepage = "https://github.com/gicrisf/zhuia" minimum_version = "0.15.0" @@ -86,7 +86,7 @@ sort_by = "date" - [ ] Comments - [ ] Related posts (not sure about this) - [ ] Search bar -- [ ] Math rendering (WIP) +- [x] Math rendering - [ ] Other shortcodes (WIP) - [ ] Multilanguage support (WIP) - [ ] Dark mode diff --git a/docs/content/themes/zola-henry/index.md b/docs/content/themes/zola-henry/index.md index 586305bde8..3aaebb2957 100644 --- a/docs/content/themes/zola-henry/index.md +++ b/docs/content/themes/zola-henry/index.md @@ -3,11 +3,11 @@ title = "henry" description = "A timeless blog theme" template = "theme.html" -date = 2022-06-15T01:35:37-07:00 +date = 2022-12-04T21:40:33+02:00 [extra] -created = 2022-06-15T01:35:37-07:00 -updated = 2022-06-15T01:35:37-07:00 +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 repository = "https://github.com/sirodoht/zola-henry.git" homepage = "https://github.com/sirodoht/zola-henry" minimum_version = "0.4.0" diff --git a/docs/content/themes/zola-paper/index.md b/docs/content/themes/zola-paper/index.md index 249fd02bcf..9e7aae67cd 100644 --- a/docs/content/themes/zola-paper/index.md +++ b/docs/content/themes/zola-paper/index.md @@ -3,11 +3,11 @@ title = "zola-paper" description = "A clean theme inspired from hugo-paper." template = "theme.html" -date = 2022-06-15T01:35:37-07:00 +date = 2022-12-04T21:40:33+02:00 [extra] -created = 2022-06-15T01:35:37-07:00 -updated = 2022-06-15T01:35:37-07:00 +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 repository = "https://github.com/schoenenberg/zola-paper.git" homepage = "https://github.com/schoenenberg/zola-paper" minimum_version = "0.11.0" diff --git a/docs/content/themes/zola-pickles/index.md b/docs/content/themes/zola-pickles/index.md index 0f8c2c28fb..274fb6bc27 100644 --- a/docs/content/themes/zola-pickles/index.md +++ b/docs/content/themes/zola-pickles/index.md @@ -3,11 +3,11 @@ title = "pickles" description = "A modern, simple, clean blog theme for Zola." template = "theme.html" -date = 2022-06-15T01:35:37-07:00 +date = 2022-12-04T21:40:33+02:00 [extra] -created = 2022-06-15T01:35:37-07:00 -updated = 2022-06-15T01:35:37-07:00 +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 repository = "https://github.com/lukehsiao/zola-pickles.git" homepage = "https://github.com/lukehsiao/zola-pickles" minimum_version = "0.13.0" diff --git a/docs/content/themes/zola-theme-course/index.md b/docs/content/themes/zola-theme-course/index.md index c7dcca269c..dea1b3c6f4 100644 --- a/docs/content/themes/zola-theme-course/index.md +++ b/docs/content/themes/zola-theme-course/index.md @@ -3,11 +3,11 @@ title = "Course" description = "A zola theme designed for online courses or tutorials" template = "theme.html" -date = 2022-06-15T01:35:37-07:00 +date = 2022-12-04T21:40:33+02:00 [extra] -created = 2022-06-15T01:35:37-07:00 -updated = 2022-06-15T01:35:37-07:00 +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 repository = "https://github.com/elegaanz/zola-theme-course.git" homepage = "https://github.com/elegaanz/zola-theme-course" minimum_version = "0.15.0" diff --git a/docs/content/themes/zola-theme-hikari/index.md b/docs/content/themes/zola-theme-hikari/index.md index 0ef6579b84..5c9aebf6bf 100644 --- a/docs/content/themes/zola-theme-hikari/index.md +++ b/docs/content/themes/zola-theme-hikari/index.md @@ -3,11 +3,11 @@ title = "Hikari" description = "Fluid, responsive blog theme for Zola" template = "theme.html" -date = 2022-06-15T01:35:37-07:00 +date = 2022-12-04T21:40:33+02:00 [extra] -created = 2022-06-15T01:35:37-07:00 -updated = 2022-06-15T01:35:37-07:00 +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 repository = "https://github.com/waynee95/zola-theme-hikari.git" homepage = "https://github.com/waynee95/zola-theme-hikari" minimum_version = "0.5.1" diff --git a/docs/content/themes/zola-theme-terminimal/index.md b/docs/content/themes/zola-theme-terminimal/index.md index cfa8c5c413..59ffdde536 100644 --- a/docs/content/themes/zola-theme-terminimal/index.md +++ b/docs/content/themes/zola-theme-terminimal/index.md @@ -3,11 +3,11 @@ title = "terminimal" description = "A simple, minimal retro theme" template = "theme.html" -date = 2022-06-15T01:35:37-07:00 +date = 2022-12-04T21:40:33+02:00 [extra] -created = 2022-06-15T01:35:37-07:00 -updated = 2022-06-15T01:35:37-07:00 +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 repository = "https://github.com/pawroman/zola-theme-terminimal.git" homepage = "https://github.com/pawroman/zola-theme-terminimal" minimum_version = "0.11.0" diff --git a/docs/content/themes/zola.386/index.md b/docs/content/themes/zola.386/index.md index 0052c54059..4e9d871a14 100644 --- a/docs/content/themes/zola.386/index.md +++ b/docs/content/themes/zola.386/index.md @@ -3,11 +3,11 @@ title = "zola.386" description = "Zola port of the BOOTSTRA.386 theme." template = "theme.html" -date = 2022-06-15T01:35:37-07:00 +date = 2022-12-04T21:40:33+02:00 [extra] -created = 2022-06-15T01:35:37-07:00 -updated = 2022-06-15T01:35:37-07:00 +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 repository = "https://github.com/lopes/zola.386.git" homepage = "https://github.com/lopes/zola.386" minimum_version = "0.10.1" diff --git a/docs/content/themes/zola_easydocs_theme/index.md b/docs/content/themes/zola_easydocs_theme/index.md index f294d26789..8c6cf058b7 100644 --- a/docs/content/themes/zola_easydocs_theme/index.md +++ b/docs/content/themes/zola_easydocs_theme/index.md @@ -3,11 +3,11 @@ title = "EasyDocs" description = "An easy way to create docs for your project" template = "theme.html" -date = 2022-06-15T01:35:37-07:00 +date = 2022-12-04T21:40:33+02:00 [extra] -created = 2022-06-15T01:35:37-07:00 -updated = 2022-06-15T01:35:37-07:00 +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 repository = "https://github.com/codeandmedia/zola_easydocs_theme.git" homepage = "https://github.com/codeandmedia/zola_easydocs_theme" minimum_version = "0.13.0" diff --git a/docs/content/themes/zolastrap/index.md b/docs/content/themes/zolastrap/index.md index 36ba8107c3..5d4f57e751 100644 --- a/docs/content/themes/zolastrap/index.md +++ b/docs/content/themes/zolastrap/index.md @@ -3,11 +3,11 @@ title = "zolastrap" description = "A bootstrap theme for zola" template = "theme.html" -date = 2022-06-15T01:35:37-07:00 +date = 2022-12-04T21:40:33+02:00 [extra] -created = 2022-06-15T01:35:37-07:00 -updated = 2022-06-15T01:35:37-07:00 +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 repository = "https://github.com/marcodpt/zolastrap.git" homepage = "https://github.com/marcodpt/zolastrap" minimum_version = "0.14.1" diff --git a/docs/content/themes/zplit/index.md b/docs/content/themes/zplit/index.md new file mode 100644 index 0000000000..504c365772 --- /dev/null +++ b/docs/content/themes/zplit/index.md @@ -0,0 +1,162 @@ + ++++ +title = "Zplit" +description = "A single page theme for a professional online presence." +template = "theme.html" +date = 2022-12-04T21:40:33+02:00 + +[extra] +created = 2022-12-04T21:40:33+02:00 +updated = 2022-12-04T21:40:33+02:00 +repository = "https://github.com/gicrisf/zplit" +homepage = "https://github.com/gicrisf/zplit" +minimum_version = "0.15.0" +license = "Creative Commons Attribution 3.0 License" +demo = "https://zplit.netlify.app" + +[extra.author] +name = "Giovanni Crisalfi" +homepage = "https://github.com/gicrisf" ++++ + +# Zplit + +Zplit is a single page, centrally-divided layout for a professional online presence with a big image or video left with alongside content. It is a port of [Split](//onepagelove.com/split) by [One Page Love](//onepagelove.com) for [Zola](https://www.getzola.org/). + +![Zola Zplit Theme screenshot](screenshot.png) + +**DEMO**: [https://zplit.netlify.app/](https://zplit.netlify.app/) + +## Installation + +Download this theme to your `themes` directory: + +```bash +$ cd themes +$ git clone https://github.com/gicrisf/zplit.git +``` + +Then, enable the theme editing your `config.toml`: + +```toml +theme = "zhuia" +``` + +## Getting started + +You can find the most important file of the theme in the root directory. It's called `config.toml`. +Edit it, specifying your personal preferences. Go through this (small) file to set some self-explaining variable, such as `author` under `[extra]` or `intro_tagline` under `[extra.content]`. + +If something appears not that evident, maybe you missed the ["configuration" paragraph of the Zola official documentation](https://www.getzola.org/documentation/getting-started/configuration/). Even if this is your first time with a static site generator, don't be scared and go through the docs, because it's very basic stuff. + +Here, we'll see in more detail two more section, that are peculiar for this theme: +- Background image +- Lists (of links) + +### Background image + +Edit the `[extra.visual]` section to set your background image of choice. + +```toml +[extra.visual] + +background = "" +``` + +You can find this example already written as the default: + +```toml +[extra.visual] + +background = "images/background.jpg" +position = "center center" +``` + +As you can see, you can edit the relative position of the image, which is centered by default. + +### Lists + +You can set up to 3 lists of links in the `[extra.lists]` section of the `config.toml` file: +- connect +- social +- network + +Manipulating them is very easy: just add/remove elements in the TOML list, as showed in this example (also already present in the default file): + +``` toml +social = [ + {url = "https://t.me/zwitterio", text = "Telegram"}, + {url = "https://twitter.com/gicrisf", text = "Twitter"}, + {url = "https://github.com/gicrisf", text = "Github"}, +] +``` + +Do you want another item? Just throw it up to the pile. You have no limits. +Remember to set the `url` field with the link itself you want to direct your user at and a `text` to show in the page for the corrisponding URL. + +## Posts + +You could add new posts, by adding markdown files to the `content` directory. +To sort the post index by date, enable sort in your index section `content/_index.md`: + +```toml +sort_by = "date" +``` + +Showing the posts in the main page could need some tweaking of the code, because it's not an officially supported feature. + +## Custom CSS + +Add a `custom.css` file in `static` directory and add all the changes you want to the original stylesheets. + +## Custom colors + +If you want to tweak the colors or grid dimensions, though, it could be easier to directly edit the `_01-content.scss` file frontmatter, where the variables are easily exposed at the top of the file: + +``` scss +//------------------------------------------------------------------------------- +// Variables +//------------------------------------------------------------------------------- + +// Colors +$color-background : #061C30; +$color-text : #848d96; +$color-link : #848d96; +$color-link-hover : #CA486d; +$color-maverick : #47bec7; +$color-tagline : #CCCCCC; + +// Breakpoints +$bp-smallish : 1200px; +$bp-tablet : 800px; +$bp-mobile : 500px; +``` + +If you choose this way, you don't have to care about anything else in that file. Just look at the variables. + +## Features + +- [x] Lightweight and minimal +- [x] Responsive (mobile support) +- [x] Social links +- [x] Deploy via Netlify (config already included) +- [x] Easily extendable menus +- [x] De-googled (local assets are faster and more secure) +- [x] Netlify support +- [x] Custom CSS +- [x] Custom colors +- [x] 404 page +- [ ] Open Graph and Twitter Cards support +- [ ] Multilanguage support + +## Support me! + +Do you love this theme? Was it useful to you? Make a donation and support new features! + +[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/V7V425BFU) + +## License + +The original template is released under the [Creative Commons Attribution 3.0 License](//github.com/escalate/hugo-split-theme/blob/master/LICENSE.md). Please keep the original attribution link when using for your own project. If you'd like to use the template without the attribution, you can check out the license option via the template [author's website](//onepagelove.com/split). + + \ No newline at end of file diff --git a/docs/content/themes/zplit/screenshot.png b/docs/content/themes/zplit/screenshot.png new file mode 100644 index 0000000000..27cef891d4 Binary files /dev/null and b/docs/content/themes/zplit/screenshot.png differ diff --git a/docs/static/processed_images/01-zola.1a8b81cf026f45cb.png b/docs/static/processed_images/01-zola.1a8b81cf026f45cb.png new file mode 100644 index 0000000000..a32742aba7 Binary files /dev/null and b/docs/static/processed_images/01-zola.1a8b81cf026f45cb.png differ diff --git a/docs/static/processed_images/01-zola.415a7ae280b04f3a.png b/docs/static/processed_images/01-zola.415a7ae280b04f3a.png new file mode 100644 index 0000000000..e161ab9c89 Binary files /dev/null and b/docs/static/processed_images/01-zola.415a7ae280b04f3a.png differ diff --git a/docs/static/processed_images/01-zola.88a4046ce11eac5c.png b/docs/static/processed_images/01-zola.88a4046ce11eac5c.png new file mode 100644 index 0000000000..e161ab9c89 Binary files /dev/null and b/docs/static/processed_images/01-zola.88a4046ce11eac5c.png differ diff --git a/docs/static/processed_images/01-zola.8b09d4ac023bf17f.png b/docs/static/processed_images/01-zola.8b09d4ac023bf17f.png new file mode 100644 index 0000000000..8a987e72ec Binary files /dev/null and b/docs/static/processed_images/01-zola.8b09d4ac023bf17f.png differ diff --git a/docs/static/processed_images/01-zola.a7f6fb4842538499.png b/docs/static/processed_images/01-zola.a7f6fb4842538499.png new file mode 100644 index 0000000000..22ff33a5cd Binary files /dev/null and b/docs/static/processed_images/01-zola.a7f6fb4842538499.png differ diff --git a/docs/static/processed_images/01-zola.aa5c9741e1f54677.png b/docs/static/processed_images/01-zola.aa5c9741e1f54677.png new file mode 100644 index 0000000000..ec483367fe Binary files /dev/null and b/docs/static/processed_images/01-zola.aa5c9741e1f54677.png differ diff --git a/docs/static/processed_images/01-zola.c31346a8ceb47990.png b/docs/static/processed_images/01-zola.c31346a8ceb47990.png new file mode 100644 index 0000000000..d9bd0c48ae Binary files /dev/null and b/docs/static/processed_images/01-zola.c31346a8ceb47990.png differ diff --git a/docs/static/processed_images/02-zola-manet.f247a1c1a09dea92.png b/docs/static/processed_images/02-zola-manet.f247a1c1a09dea92.png new file mode 100644 index 0000000000..1bbcfaf99e Binary files /dev/null and b/docs/static/processed_images/02-zola-manet.f247a1c1a09dea92.png differ diff --git a/docs/static/processed_images/03-zola-cezanne.54f3edb977adbe2f.png b/docs/static/processed_images/03-zola-cezanne.54f3edb977adbe2f.png new file mode 100644 index 0000000000..26d94952e4 Binary files /dev/null and b/docs/static/processed_images/03-zola-cezanne.54f3edb977adbe2f.png differ diff --git a/docs/static/processed_images/04-gutenberg.6b23c36e66378f24.jpg b/docs/static/processed_images/04-gutenberg.6b23c36e66378f24.jpg new file mode 100644 index 0000000000..e4595c176e Binary files /dev/null and b/docs/static/processed_images/04-gutenberg.6b23c36e66378f24.jpg differ diff --git a/docs/static/processed_images/05-example.67dc3b46cdb5d5d4.jpg b/docs/static/processed_images/05-example.67dc3b46cdb5d5d4.jpg new file mode 100644 index 0000000000..b50a4638cc Binary files /dev/null and b/docs/static/processed_images/05-example.67dc3b46cdb5d5d4.jpg differ diff --git a/docs/static/processed_images/06-example.2c54491c2daefe2f.jpg b/docs/static/processed_images/06-example.2c54491c2daefe2f.jpg new file mode 100644 index 0000000000..d5eb7c0b74 Binary files /dev/null and b/docs/static/processed_images/06-example.2c54491c2daefe2f.jpg differ diff --git a/docs/static/processed_images/07-example.3143e7a66ae6fd02.jpg b/docs/static/processed_images/07-example.3143e7a66ae6fd02.jpg new file mode 100644 index 0000000000..9c37769a02 Binary files /dev/null and b/docs/static/processed_images/07-example.3143e7a66ae6fd02.jpg differ diff --git a/docs/static/processed_images/08-example.684e6a6497b4e859.jpg b/docs/static/processed_images/08-example.684e6a6497b4e859.jpg new file mode 100644 index 0000000000..c71858f71a Binary files /dev/null and b/docs/static/processed_images/08-example.684e6a6497b4e859.jpg differ diff --git a/docs/static/processed_images/0b751f5aa0aeb49e00.png b/docs/static/processed_images/0b751f5aa0aeb49e00.png deleted file mode 100644 index 59b4811d98..0000000000 Binary files a/docs/static/processed_images/0b751f5aa0aeb49e00.png and /dev/null differ diff --git a/docs/static/processed_images/10743d39eadb4f5500.png b/docs/static/processed_images/10743d39eadb4f5500.png deleted file mode 100644 index 790583d08d..0000000000 Binary files a/docs/static/processed_images/10743d39eadb4f5500.png and /dev/null differ diff --git a/docs/static/processed_images/1b3166afe3c05c8100.png b/docs/static/processed_images/1b3166afe3c05c8100.png deleted file mode 100644 index 68895ecc72..0000000000 Binary files a/docs/static/processed_images/1b3166afe3c05c8100.png and /dev/null differ diff --git a/docs/static/processed_images/347f63dafce8976a00.jpg b/docs/static/processed_images/347f63dafce8976a00.jpg deleted file mode 100644 index 0a6f625afa..0000000000 Binary files a/docs/static/processed_images/347f63dafce8976a00.jpg and /dev/null differ diff --git a/docs/static/processed_images/42c8c04e2cbdedc000.png b/docs/static/processed_images/42c8c04e2cbdedc000.png deleted file mode 100644 index 49ebe9cbea..0000000000 Binary files a/docs/static/processed_images/42c8c04e2cbdedc000.png and /dev/null differ diff --git a/docs/static/processed_images/6398117c46046a4a00.png b/docs/static/processed_images/6398117c46046a4a00.png deleted file mode 100644 index 4cf656b6cd..0000000000 Binary files a/docs/static/processed_images/6398117c46046a4a00.png and /dev/null differ diff --git a/docs/static/processed_images/68cc2a54764edf4500.png b/docs/static/processed_images/68cc2a54764edf4500.png deleted file mode 100644 index 792fb55012..0000000000 Binary files a/docs/static/processed_images/68cc2a54764edf4500.png and /dev/null differ diff --git a/docs/static/processed_images/6ba688a3fb0d202b00.jpg b/docs/static/processed_images/6ba688a3fb0d202b00.jpg deleted file mode 100644 index f3e78efe79..0000000000 Binary files a/docs/static/processed_images/6ba688a3fb0d202b00.jpg and /dev/null differ diff --git a/docs/static/processed_images/7f400da1e92ca8a400.jpg b/docs/static/processed_images/7f400da1e92ca8a400.jpg deleted file mode 100644 index e265b8039d..0000000000 Binary files a/docs/static/processed_images/7f400da1e92ca8a400.jpg and /dev/null differ diff --git a/docs/static/processed_images/9df3b2448c50878a00.jpg b/docs/static/processed_images/9df3b2448c50878a00.jpg deleted file mode 100644 index b51159a0f7..0000000000 Binary files a/docs/static/processed_images/9df3b2448c50878a00.jpg and /dev/null differ diff --git a/docs/static/processed_images/d2f2746ebaaa9e6200.png b/docs/static/processed_images/d2f2746ebaaa9e6200.png deleted file mode 100644 index d01ce6cdfb..0000000000 Binary files a/docs/static/processed_images/d2f2746ebaaa9e6200.png and /dev/null differ diff --git a/docs/static/processed_images/ea6a03d035169dbd00.jpg b/docs/static/processed_images/ea6a03d035169dbd00.jpg deleted file mode 100644 index 4340a65daf..0000000000 Binary files a/docs/static/processed_images/ea6a03d035169dbd00.jpg and /dev/null differ diff --git a/docs/static/processed_images/fd56f23df12683fc00.png b/docs/static/processed_images/fd56f23df12683fc00.png deleted file mode 100644 index ea199cbbb4..0000000000 Binary files a/docs/static/processed_images/fd56f23df12683fc00.png and /dev/null differ diff --git a/docs/static/search.js b/docs/static/search.js index 408156555d..553b1de368 100644 --- a/docs/static/search.js +++ b/docs/static/search.js @@ -142,11 +142,24 @@ function initSearch() { } }; var currentTerm = ""; - var index = elasticlunr.Index.load(window.searchIndex); + var index; + + var initIndex = async function () { + if (index === undefined) { + index = fetch("/search_index.en.json") + .then( + async function(response) { + return await elasticlunr.Index.load(await response.json()); + } + ); + } + let res = await index; + return res; + } - $searchInput.addEventListener("keyup", debounce(function() { + $searchInput.addEventListener("keyup", debounce(async function() { var term = $searchInput.value.trim(); - if (term === currentTerm || !index) { + if (term === currentTerm) { return; } $searchResults.style.display = term === "" ? "none" : "block"; @@ -156,7 +169,7 @@ function initSearch() { return; } - var results = index.search(term, options); + var results = (await initIndex()).search(term, options); if (results.length === 0) { $searchResults.style.display = "none"; return; diff --git a/docs/templates/index.html b/docs/templates/index.html index 2efb0373b4..de9134f306 100644 --- a/docs/templates/index.html +++ b/docs/templates/index.html @@ -103,7 +103,6 @@

Augmented Markdown

- diff --git a/netlify.toml b/netlify.toml index 4169c02cc7..c3a5a39855 100644 --- a/netlify.toml +++ b/netlify.toml @@ -4,7 +4,7 @@ command = "zola build" [build.environment] - ZOLA_VERSION = "0.15.3" + ZOLA_VERSION = "0.17.1" [context.deploy-preview] command = "zola build --base-url $DEPLOY_PRIME_URL" diff --git a/snapcraft.yaml b/snapcraft.yaml index a59ba432ec..95a81fd071 100644 --- a/snapcraft.yaml +++ b/snapcraft.yaml @@ -1,5 +1,5 @@ name: zola -version: 0.16.0 +version: 0.17.1 summary: A fast static site generator in a single binary with everything built-in. description: | A fast static site generator in a single binary with everything built-in. @@ -21,7 +21,7 @@ parts: zola: source-type: git source: https://github.com/getzola/zola.git - source-tag: v0.16.0 + source-tag: v0.17.1 plugin: rust rust-channel: stable build-packages: diff --git a/src/cli.rs b/src/cli.rs index 82b1f9084e..8155fefad8 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,6 +1,7 @@ use std::path::PathBuf; use clap::{Parser, Subcommand}; +use clap_complete::Shell; #[derive(Parser)] #[clap(version, author, about)] @@ -40,6 +41,10 @@ pub enum Command { #[clap(short = 'o', long)] output_dir: Option, + /// Force building the site even if output directory is non-empty + #[clap(short = 'f', long)] + force: bool, + /// Include drafts when loading the site #[clap(long)] drafts: bool, @@ -75,6 +80,10 @@ pub enum Command { /// Only rebuild the minimum on change - useful when working on a specific page/section #[clap(short = 'f', long)] fast: bool, + + /// Default append port to the base url. + #[clap(long)] + no_port_append: bool, }, /// Try to build the project without rendering it. Checks links @@ -83,4 +92,11 @@ pub enum Command { #[clap(long)] drafts: bool, }, + + /// Generate shell completion + Completion { + /// Shell to generate completion for + #[clap(value_enum)] + shell: Shell, + }, } diff --git a/src/cmd/build.rs b/src/cmd/build.rs index 6bfe603d88..d088743687 100644 --- a/src/cmd/build.rs +++ b/src/cmd/build.rs @@ -4,38 +4,22 @@ use errors::{Error, Result}; use site::Site; use crate::messages; -use crate::prompt::ask_bool_timeout; - -const BUILD_PROMPT_TIMEOUT_MILLIS: u64 = 10_000; pub fn build( root_dir: &Path, config_file: &Path, base_url: Option<&str>, output_dir: Option<&Path>, + force: bool, include_drafts: bool, ) -> Result<()> { let mut site = Site::new(root_dir, config_file)?; if let Some(output_dir) = output_dir { - // Check whether output directory exists or not - // This way we don't replace already existing files. - if output_dir.exists() { - console::warn(&format!("The directory '{}' already exists. Building to this directory will delete files contained within this directory.", output_dir.display())); - - // Prompt the user to ask whether they want to continue. - let clear_dir = tokio::runtime::Runtime::new() - .expect("Tokio runtime failed to instantiate") - .block_on(ask_bool_timeout( - "Are you sure you want to continue?", - false, - std::time::Duration::from_millis(BUILD_PROMPT_TIMEOUT_MILLIS), - ))?; - - if !clear_dir { - return Err(Error::msg( - "Cancelled build process because output directory already exists.", - )); - } + if !force && output_dir.exists() { + return Err(Error::msg(format!( + "Directory '{}' already exists. Use --force to overwrite.", + output_dir.display(), + ))); } site.set_output_path(output_dir); diff --git a/src/cmd/serve.rs b/src/cmd/serve.rs index 0242a9a075..52b1d585b9 100644 --- a/src/cmd/serve.rs +++ b/src/cmd/serve.rs @@ -21,7 +21,7 @@ // 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::fs::{read_dir, remove_dir_all}; +use std::fs::read_dir; use std::net::{SocketAddrV4, TcpListener}; use std::path::{Path, PathBuf, MAIN_SEPARATOR}; use std::sync::mpsc::channel; @@ -36,18 +36,18 @@ use mime_guess::from_path as mimetype_from_path; use time::macros::format_description; use time::{OffsetDateTime, UtcOffset}; +use libs::globset::GlobSet; use libs::percent_encoding; +use libs::relative_path::{RelativePath, RelativePathBuf}; use libs::serde_json; use notify::{watcher, RecursiveMode, Watcher}; use ws::{Message, Sender, WebSocket}; use errors::{anyhow, Context, Result}; -use libs::globset::GlobSet; -use libs::relative_path::{RelativePath, RelativePathBuf}; use pathdiff::diff_paths; use site::sass::compile_sass; use site::{Site, SITE_CONTENT}; -use utils::fs::copy_file; +use utils::fs::{clean_site_output_folder, copy_file, is_temp_file}; use crate::messages; use std::ffi::OsStr; @@ -241,6 +241,7 @@ fn create_new_site( base_url: &str, config_file: &Path, include_drafts: bool, + no_port_append: bool, ws_port: Option, ) -> Result<(Site, String)> { SITE_CONTENT.write().unwrap().clear(); @@ -251,7 +252,11 @@ fn create_new_site( let base_url = if base_url == "/" { String::from("/") } else { - let base_address = format!("{}:{}", base_url, interface_port); + let base_address = if no_port_append { + base_url.to_string() + } else { + format!("{}:{}", base_url, interface_port) + }; if site.config.base_url.ends_with('/') { format!("http://{}/", base_address) @@ -291,6 +296,7 @@ pub fn serve( open: bool, include_drafts: bool, fast_rebuild: bool, + no_port_append: bool, utc_offset: UtcOffset, ) -> Result<()> { let start = Instant::now(); @@ -302,6 +308,7 @@ pub fn serve( base_url, config_file, include_drafts, + no_port_append, None, )?; messages::report_elapsed_time(start); @@ -311,13 +318,12 @@ pub fn serve( Ok(a) => a, Err(_) => return Err(anyhow!("Invalid address: {}.", address)), }; - if (TcpListener::bind(&bind_address)).is_err() { + if (TcpListener::bind(bind_address)).is_err() { return Err(anyhow!("Cannot start server on address {}.", address)); } let config_path = PathBuf::from(config_file); - let config_path_rel = - diff_paths(&config_path, &root_dir).unwrap_or_else(|| config_path.clone()); + let config_path_rel = diff_paths(&config_path, root_dir).unwrap_or_else(|| config_path.clone()); // An array of (path, WatchMode) where the path should be watched for changes, // and the WatchMode value indicates whether this file/folder must exist for @@ -435,12 +441,14 @@ pub fn serve( watchers.join(",") ); + let preserve_dotfiles_in_output = site.config.preserve_dotfiles_in_output; + println!("Press Ctrl+C to stop\n"); - // Delete the output folder on ctrl+C + // Clean the output folder on ctrl+C ctrlc::set_handler(move || { - match remove_dir_all(&output_path) { + match clean_site_output_folder(&output_path, preserve_dotfiles_in_output) { Ok(()) => (), - Err(e) => println!("Errored while deleting output folder: {}", e), + Err(e) => println!("Errored while cleaning output folder: {}", e), } ::std::process::exit(0); }) @@ -502,6 +510,7 @@ pub fn serve( base_url, config_file, include_drafts, + no_port_append, ws_port, ) { Ok((s, _)) => { @@ -645,33 +654,6 @@ fn is_ignored_file(ignored_content_globset: &Option, path: &Path) -> bo } } -/// Returns whether the path we received corresponds to a temp file created -/// by an editor or the OS -fn is_temp_file(path: &Path) -> bool { - let ext = path.extension(); - match ext { - Some(ex) => match ex.to_str().unwrap() { - "swp" | "swx" | "tmp" | ".DS_STORE" => true, - // jetbrains IDE - x if x.ends_with("jb_old___") => true, - x if x.ends_with("jb_tmp___") => true, - x if x.ends_with("jb_bak___") => true, - // vim & jetbrains - x if x.ends_with('~') => true, - _ => { - if let Some(filename) = path.file_stem() { - // emacs - let name = filename.to_str().unwrap(); - name.starts_with('#') || name.starts_with(".#") - } else { - false - } - } - }, - None => true, - } -} - /// Detect what changed from the given path so we have an idea what needs /// to be reloaded fn detect_change_kind(pwd: &Path, path: &Path, config_path: &Path) -> (ChangeKind, PathBuf) { diff --git a/src/main.rs b/src/main.rs index 22fa101cec..6f3fcdfeb7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,7 @@ use std::time::Instant; use cli::{Cli, Command}; use utils::net::{get_available_port, port_is_available}; -use clap::Parser; +use clap::{CommandFactory, Parser}; use time::UtcOffset; mod cli; @@ -15,12 +15,12 @@ mod prompt; fn get_config_file_path(dir: &Path, config_path: &Path) -> (PathBuf, PathBuf) { let root_dir = dir .ancestors() - .find(|a| a.join(&config_path).exists()) + .find(|a| a.join(config_path).exists()) .unwrap_or_else(|| panic!("could not find directory containing config file")); // if we got here we found root_dir so config file should exist so we can unwrap safely let config_file = root_dir - .join(&config_path) + .join(config_path) .canonicalize() .unwrap_or_else(|_| panic!("could not find directory containing config file")); (root_dir.to_path_buf(), config_file) @@ -39,7 +39,7 @@ fn main() { std::process::exit(1); } } - Command::Build { base_url, output_dir, drafts } => { + Command::Build { base_url, output_dir, force, drafts } => { console::info("Building site..."); let start = Instant::now(); let (root_dir, config_file) = get_config_file_path(&cli_dir, &cli.config); @@ -48,6 +48,7 @@ fn main() { &config_file, base_url.as_deref(), output_dir.as_deref(), + force, drafts, ) { Ok(()) => messages::report_elapsed_time(start), @@ -57,7 +58,16 @@ fn main() { } } } - Command::Serve { interface, mut port, output_dir, base_url, drafts, open, fast } => { + Command::Serve { + interface, + mut port, + output_dir, + base_url, + drafts, + open, + fast, + no_port_append, + } => { if port != 1111 && !port_is_available(port) { console::error("The requested port is not available"); std::process::exit(1); @@ -82,6 +92,7 @@ fn main() { open, drafts, fast, + no_port_append, UtcOffset::current_local_offset().unwrap_or(UtcOffset::UTC), ) { messages::unravel_errors("Failed to serve the site", &e); @@ -100,5 +111,9 @@ fn main() { } } } + Command::Completion { shell } => { + let cmd = &mut Cli::command(); + clap_complete::generate(shell, cmd, cmd.get_name().to_string(), &mut std::io::stdout()); + } } } diff --git a/src/prompt.rs b/src/prompt.rs index 2820f48f79..9083efcd3f 100644 --- a/src/prompt.rs +++ b/src/prompt.rs @@ -1,5 +1,4 @@ use std::io::{self, BufRead, Write}; -use std::time::Duration; use libs::url::Url; @@ -33,24 +32,6 @@ pub fn ask_bool(question: &str, default: bool) -> Result { } } -/// Ask a yes/no question to the user with a timeout -pub async fn ask_bool_timeout(question: &str, default: bool, timeout: Duration) -> Result { - let (tx, rx) = tokio::sync::oneshot::channel(); - - let q = question.to_string(); - std::thread::spawn(move || { - tx.send(ask_bool(&q, default)).unwrap(); - }); - - match tokio::time::timeout(timeout, rx).await { - Err(_) => { - console::warn("\nWaited too long for response."); - Ok(default) - } - Ok(val) => val.expect("Tokio failed to properly execute"), - } -} - /// Ask a question to the user where they can write a URL pub fn ask_url(question: &str, default: &str) -> Result { print!("{} ({}): ", question, default); diff --git a/test_site/config.toml b/test_site/config.toml index 21b9e74949..e3fcbfd64d 100644 --- a/test_site/config.toml +++ b/test_site/config.toml @@ -11,6 +11,8 @@ taxonomies = [ ignored_content = ["*/ignored.md"] +author = "config@example.com (Config Author)" + [markdown] highlight_code = true highlight_theme = "custom_gruvbox" diff --git a/test_site/content/colocated-assets/index.md b/test_site/content/colocated-assets/index.md new file mode 100644 index 0000000000..528f72b72b --- /dev/null +++ b/test_site/content/colocated-assets/index.md @@ -0,0 +1,3 @@ ++++ +title = "Assets in root content directory" ++++ \ No newline at end of file diff --git a/test_site/content/posts/2018/transparent-page.md b/test_site/content/posts/2018/transparent-page.md index 21f207facb..767c997958 100644 --- a/test_site/content/posts/2018/transparent-page.md +++ b/test_site/content/posts/2018/transparent-page.md @@ -2,4 +2,5 @@ title = "A transparent page" description = "" date = 2018-10-10 +authors = ["page@example.com (Page Author)"] +++ diff --git a/test_site/content/posts/simple.md b/test_site/content/posts/simple.md index 3593cecd97..7d28ecf423 100644 --- a/test_site/content/posts/simple.md +++ b/test_site/content/posts/simple.md @@ -8,9 +8,3 @@ A simple page {{ youtube(id="e1C9kpMV2e8") }} {{ youtube(id="e1C9kpMV2e8", autoplay=true) }} - -{{ vimeo(id="210073083") }} - -{{ streamable(id="c0ic") }} - -{{ gist(url="https://gist.github.com/Keats/32d26f699dcc13ebd41b") }} diff --git a/test_site/templates/index.html b/test_site/templates/index.html index 35a24be4b7..0c19bb0867 100644 --- a/test_site/templates/index.html +++ b/test_site/templates/index.html @@ -14,5 +14,5 @@

{{ page.title }}

{% block script %} + integrity="sha384-{{ get_hash(path="scripts/hello.js", base64=true) | safe }}"> {% endblock script %} diff --git a/test_site/templates/page.html b/test_site/templates/page.html index e8cf68d683..b60bcfd07b 100644 --- a/test_site/templates/page.html +++ b/test_site/templates/page.html @@ -1,6 +1,7 @@ {% extends "index.html" %} {% block content %} + {{ page.title | safe }} {{ page.content | safe }} {{ page.relative_path | safe }} {{ page.toc }} diff --git a/components/templates/src/builtins/shortcodes/youtube.html b/test_site/templates/shortcodes/youtube.html similarity index 100% rename from components/templates/src/builtins/shortcodes/youtube.html rename to test_site/templates/shortcodes/youtube.html diff --git a/test_sites_invalid/indexmd/config.toml b/test_sites_invalid/indexmd/config.toml new file mode 100644 index 0000000000..33d4d7d78f --- /dev/null +++ b/test_sites_invalid/indexmd/config.toml @@ -0,0 +1,3 @@ +title = "My Integration Testing site" +base_url = "https://replace-this-with-your-url.com" + diff --git a/test_sites_invalid/indexmd/content/_index.md b/test_sites_invalid/indexmd/content/_index.md new file mode 100644 index 0000000000..f7ba421c9a --- /dev/null +++ b/test_sites_invalid/indexmd/content/_index.md @@ -0,0 +1,2 @@ ++++ ++++ \ No newline at end of file diff --git a/test_sites_invalid/indexmd/content/index.md b/test_sites_invalid/indexmd/content/index.md new file mode 100644 index 0000000000..ac36e06227 --- /dev/null +++ b/test_sites_invalid/indexmd/content/index.md @@ -0,0 +1,2 @@ ++++ ++++ diff --git a/zola.metainfo.xml b/zola.metainfo.xml index 85d2fa3ee2..0d3fd322e8 100644 --- a/zola.metainfo.xml +++ b/zola.metainfo.xml @@ -52,6 +52,15 @@ + + https://github.com/getzola/zola/releases/tag/v0.17.1 + + + https://github.com/getzola/zola/releases/tag/v0.17.0 + + + https://github.com/getzola/zola/releases/tag/v0.16.1 + https://github.com/getzola/zola/releases/tag/v0.16.0