From e948df1028cf292dd40baeec3dd732f5d800437c Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Fri, 3 Mar 2017 17:12:40 +0900 Subject: [PATCH 01/26] Add a Site struct that knows how to parse/build a site --- Cargo.lock | 197 ++++++++++++++++++++++++-------------------- Cargo.toml | 6 +- src/cmd/build.rs | 88 +------------------- src/cmd/mod.rs | 2 + src/cmd/serve.rs | 5 ++ src/config.rs | 27 +++++- src/front_matter.rs | 8 +- src/main.rs | 50 +++++------ src/page.rs | 47 +++++++---- src/site.rs | 107 ++++++++++++++++++++++++ 10 files changed, 321 insertions(+), 216 deletions(-) create mode 100644 src/cmd/serve.rs create mode 100644 src/site.rs diff --git a/Cargo.lock b/Cargo.lock index 78e0b8c9..b076a63f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,17 +3,18 @@ name = "gutenberg" version = "0.1.0" dependencies = [ "clap 2.20.5 (registry+https://github.com/rust-lang/crates.io-index)", - "error-chain 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "error-chain 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "pulldown-cmark 0.0.8 (registry+https://github.com/rust-lang/crates.io-index)", "regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 0.9.8 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 0.9.8 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 0.9.10 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 0.9.10 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 0.9.8 (registry+https://github.com/rust-lang/crates.io-index)", - "syntect 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "tera 0.7.2 (git+https://github.com/Keats/tera?branch=next)", - "toml 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "slug 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syntect 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tera 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "toml 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -39,8 +40,8 @@ dependencies = [ "cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "dbghelp-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc-demangle 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-demangle 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -50,7 +51,7 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "gcc 0.3.43 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -59,7 +60,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", "serde 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -74,11 +75,6 @@ name = "bitflags" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "bitflags" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "byteorder" version = "0.5.3" @@ -99,7 +95,7 @@ name = "chrono" version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "num 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)", + "num 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -108,7 +104,7 @@ name = "chrono" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "num 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)", + "num 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -119,7 +115,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", "strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "term_size 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-segmentation 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -129,7 +125,7 @@ dependencies = [ [[package]] name = "cmake" -version = "0.1.20" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "gcc 0.3.43 (registry+https://github.com/rust-lang/crates.io-index)", @@ -151,7 +147,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "error-chain" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "backtrace 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -162,7 +158,7 @@ name = "flate2" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", "miniz-sys 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -222,7 +218,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "libc" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -235,7 +231,7 @@ name = "memchr" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -244,39 +240,39 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "gcc 0.3.43 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "num" -version = "0.1.36" +version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "num-integer 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", - "num-iter 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)", + "num-integer 0.1.33 (registry+https://github.com/rust-lang/crates.io-index)", + "num-iter 0.1.33 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "num-integer" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "num-traits 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "num-iter" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "num-integer 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)", + "num-integer 0.1.33 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "num-traits" -version = "0.1.36" +version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -286,7 +282,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", "onig_sys 61.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -295,8 +291,8 @@ name = "onig_sys" version = "61.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cmake 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)", + "cmake 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -312,14 +308,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "plist" -version = "0.1.1" +version = "0.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", "chrono 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 0.9.8 (registry+https://github.com/rust-lang/crates.io-index)", - "xml-rs 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 0.7.15 (registry+https://github.com/rust-lang/crates.io-index)", + "xml-rs 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -333,7 +329,7 @@ dependencies = [ [[package]] name = "quote" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -353,6 +349,11 @@ dependencies = [ "utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "regex-syntax" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "regex-syntax" version = "0.4.0" @@ -360,7 +361,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "rustc-demangle" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -377,6 +378,11 @@ dependencies = [ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "serde" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "serde" version = "0.8.23" @@ -384,25 +390,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "serde" -version = "0.9.8" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "serde_codegen_internals" -version = "0.14.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "syn 0.11.6 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.11.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "serde_derive" -version = "0.9.8" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "quote 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_codegen_internals 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.11.6 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_codegen_internals 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.11.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -412,8 +418,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "dtoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "itoa 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 0.9.8 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 0.9.10 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -431,17 +437,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "syn" -version = "0.11.6" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "quote 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)", - "synom 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", + "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "synom" -version = "0.11.0" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", @@ -449,35 +455,35 @@ dependencies = [ [[package]] name = "syntect" -version = "1.1.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bincode 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", - "bitflags 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "flate2 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)", "fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "onig 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "plist 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "regex-syntax 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "plist 0.0.14 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", - "walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "walkdir 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "yaml-rust 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "tera" -version = "0.7.2" -source = "git+https://github.com/Keats/tera?branch=next#cd13a801b61e8c11ef829943bd3f34ae7d9d4bab" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "chrono 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "error-chain 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "error-chain 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "humansize 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "pest 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 0.9.8 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 0.9.10 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 0.9.8 (registry+https://github.com/rust-lang/crates.io-index)", "slug 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "url 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -489,7 +495,7 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -499,7 +505,7 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -517,17 +523,17 @@ version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", "redox_syscall 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "toml" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "serde 0.9.8 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 0.9.10 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -595,6 +601,15 @@ name = "void" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "walkdir" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "walkdir" version = "1.0.7" @@ -617,7 +632,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "xml-rs" -version = "0.3.8" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -636,17 +651,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum bincode 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "55eb0b7fd108527b0c77860f75eca70214e11a8b4c6ef05148c54c05a25d48ad" "checksum bitflags 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4f67931368edf3a9a51d29886d245f1c3db2f1ef0dcc9e35ff70341b78c10d23" "checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" -"checksum bitflags 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e1ab483fc81a8143faa7203c4a3c02888ebd1a782e37e41fa34753ba9a162" "checksum byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0fc10e8cc6b2580fda3f36eb6dc5316657f812a3df879a44a66fc9f0fdbc4855" "checksum byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c40977b0ee6b9885c9013cd41d9feffdd22deb3bb4dc3a71d901cc7a77de18c8" "checksum cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de1e760d7b6535af4241fca8bd8adf68e2e7edacc6b29f5d399050c5e48cf88c" "checksum chrono 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)" = "9213f7cd7c27e95c2b57c49f0e69b1ea65b27138da84a170133fd21b07659c00" "checksum chrono 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "158b0bd7d75cbb6bf9c25967a48a2e9f77da95876b858eadfabaa99cd069de6e" "checksum clap 2.20.5 (registry+https://github.com/rust-lang/crates.io-index)" = "7db281b0520e97fbd15cd615dcd8f8bcad0c26f5f7d5effe705f090f39e9a758" -"checksum cmake 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)" = "a3a6805df695087e7c1bcd9a82e03ad6fb864c8e67ac41b1348229ce5b7f0407" +"checksum cmake 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)" = "e1acc68a3f714627af38f9f5d09706a28584ba60dfe2cca68f40bf779f941b25" "checksum dbghelp-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "97590ba53bcb8ac28279161ca943a924d1fd4a8fb3fa63302591647c4fc5b850" "checksum dtoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "80c8b71fd71146990a9742fc06dcbbde19161a267e0ad4e572c35162f4578c90" -"checksum error-chain 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e92ecf0a508c8e074c0e6fa8fe0fa38414848ad4dfc4db6f74c5e9753330b248" +"checksum error-chain 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9435d864e017c3c6afeac1654189b06cdb491cf2ff73dbf0d73b0f292f42ff8" "checksum flate2 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)" = "d4e4d0c15ef829cbc1b7cda651746be19cceeb238be7b1049227b14891df9e25" "checksum fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6cc484842f1e2884faf56f529f960cc12ad8c71ce96cc7abba0a067c98fee344" "checksum gcc 0.3.43 (registry+https://github.com/rust-lang/crates.io-index)" = "c07c758b972368e703a562686adb39125707cc1ef3399da8c019fc6c2498a75d" @@ -657,43 +671,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum itoa 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eb2f404fbc66fd9aac13e998248505e7ecb2ad8e44ab6388684c5fb11c6c251c" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6abe0ee2e758cd6bc8a2cd56726359007748fbf4128da998b65d0b70f881e19b" -"checksum libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)" = "684f330624d8c3784fb9558ca46c4ce488073a8d22450415c5eb4f4cfb0d11b5" +"checksum libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)" = "88ee81885f9f04bff991e306fea7c1c60a5f0f9e409e99f6b40e3311a3363135" "checksum matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "efd7622e3022e1a6eaa602c4cea8912254e5582c9c692e9167714182244801b1" "checksum memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1dbccc0e46f1ea47b9f17e6d67c5a96bd27030519c519c9c91327e31275a47b4" "checksum miniz-sys 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "28eaee17666671fa872e567547e8428e83308ebe5808cdf6a0e28397dbe2c726" -"checksum num 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)" = "bde7c03b09e7c6a301ee81f6ddf66d7a28ec305699e3d3b056d2fc56470e3120" -"checksum num-integer 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "fb24d9bfb3f222010df27995441ded1e954f8f69cd35021f6bef02ca9552fb92" -"checksum num-iter 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "287a1c9969a847055e1122ec0ea7a5c5d6f72aad97934e131c83d5c08ab4e45c" -"checksum num-traits 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)" = "a16a42856a256b39c6d3484f097f6713e14feacd9bfb02290917904fae46c81c" +"checksum num 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "98b15ba84e910ea7a1973bccd3df7b31ae282bf9d8bd2897779950c9b8303d40" +"checksum num-integer 0.1.33 (registry+https://github.com/rust-lang/crates.io-index)" = "21e4df1098d1d797d27ef0c69c178c3fab64941559b290fcae198e0825c9c8b5" +"checksum num-iter 0.1.33 (registry+https://github.com/rust-lang/crates.io-index)" = "f7d1891bd7b936f12349b7d1403761c8a0b85a18b148e9da4429d5d102c1a41e" +"checksum num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "e1cbfa3781f3fe73dc05321bed52a06d2d491eaa764c52335cf4399f046ece99" "checksum onig 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0c5f586e53fa11ead18582956ea282c30baea1f25d3ee4c5fb85803f98727cb7" "checksum onig_sys 61.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a35f2cca300f0945538564da6052a449db55e65870cf0e9d443c1bce3d5dda47" "checksum pest 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3e2e823a5967bb4cdc6d3e46f47baaf4ecfeae44413a642b74ad44e59e49c7f6" "checksum pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "3a8b4c6b8165cd1a1cd4b9b120978131389f64bdaf456435caa41e630edba903" -"checksum plist 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c201fd99d98798fce524e3ea5dd91883c7def66d9ef6467ce014a05329de0799" +"checksum plist 0.0.14 (registry+https://github.com/rust-lang/crates.io-index)" = "29f7f7deddf1244a97e2771e47edb01e5b5603d133bd4a0e516ea0e6817adc64" "checksum pulldown-cmark 0.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "1058d7bb927ca067656537eec4e02c2b4b70eaaa129664c5b90c111e20326f41" -"checksum quote 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)" = "08de3f12e670f83f61e450443cbae34496a35b665691fd8e99b24ec662f75865" +"checksum quote 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7375cf7ad34a92e8fd18dd9c42f58b9a11def59ab48bec955bf359a788335592" "checksum redox_syscall 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "8dd35cc9a8bdec562c757e3d43c1526b5c6d2653e23e2315065bc25556550753" "checksum regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4278c17d0f6d62dfef0ab00028feb45bd7d2102843f80763474eeb1be8a10c01" +"checksum regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "f9ec002c35e86791825ed294b50008eea9ddfc8def4420124fbc6b08db834957" "checksum regex-syntax 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9191b1f57603095f105d317e375d19b1c9c5c3185ea9633a99a6dcbed04457" -"checksum rustc-demangle 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1430d286cadb237c17c885e25447c982c97113926bb579f4379c0eca8d9586dc" +"checksum rustc-demangle 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3058a43ada2c2d0b92b3ae38007a2d0fa5e9db971be260e0171408a4ff471c95" "checksum rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)" = "237546c689f20bb44980270c73c3b9edd0891c1be49cc1274406134a66d3957b" "checksum same-file 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d931a44fdaa43b8637009e7632a02adc4f2b2e0733c08caa4cf00e8da4a117a7" +"checksum serde 0.7.15 (registry+https://github.com/rust-lang/crates.io-index)" = "1b0e0732aa8ec4267f61815a396a942ba3525062e3bd5520aa8419927cfc0a92" "checksum serde 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)" = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8" -"checksum serde 0.9.8 (registry+https://github.com/rust-lang/crates.io-index)" = "204db0f2a5335be7313fd4453132fd56d2085aed081c673140a256772903e116" -"checksum serde_codegen_internals 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a5113d5bd16471b183803b374f0fe4877ad9658b95e33b11f4a004d73aacc74a" -"checksum serde_derive 0.9.8 (registry+https://github.com/rust-lang/crates.io-index)" = "e88ec062a02cbebfd6276044a305d665a9919b497aa6acb2e12c070d1a50d32d" +"checksum serde 0.9.10 (registry+https://github.com/rust-lang/crates.io-index)" = "a78def33a828eb05eb7f0167499f19cca368faf27601f6c43bc70316825d9adf" +"checksum serde_codegen_internals 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4d52006899f910528a10631e5b727973fe668f3228109d1707ccf5bad5490b6e" +"checksum serde_derive 0.9.10 (registry+https://github.com/rust-lang/crates.io-index)" = "789ee9f3cd78c850948b94121020147f5220b47dafbf230d7098a93a58f726cf" "checksum serde_json 0.9.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6501ac6f8b74f9b1033f7ddf79a08edfa0f58d6f8e3190cb8dc97736afa257a8" "checksum slug 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f5ff4b43cb07b86c5f9236c92714a22cdf9e5a27a7d85e398e2c9403328cb8" "checksum strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b4d15c810519a91cf877e7e36e63fe068815c678181439f2f29e2562147c3694" -"checksum syn 0.11.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0e28da8d02d75d1e58b89258e0741128f0b0d8a8309fb5c627be0fbd37a76c67" -"checksum synom 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8fece1853fb872b0acdc3ff88f37c474018e125ef81cd4cb8c0ca515746b62ed" -"checksum syntect 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3f79be04af68d5fa09e71b3274159a955a25f25a5cbfba9a6ff64139b71d848a" -"checksum tera 0.7.2 (git+https://github.com/Keats/tera?branch=next)" = "" +"checksum syn 0.11.8 (registry+https://github.com/rust-lang/crates.io-index)" = "37c279fb816210c9bb28b2c292664581e7b87b4561e86b94df462664d8620bb8" +"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" +"checksum syntect 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e9d678dfa7f7f9c90535caaa81778c971765942fcd57aceffc3acd34564fc0fd" +"checksum tera 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb775e4a7318e080e9c3000b150f720caf5825b66504f56f358df35b74fd0c14" "checksum term_size 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "07b6c1ac5b3fffd75073276bca1ceed01f67a28537097a2a9539e116e50fb21a" "checksum thread-id 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4437c97558c70d129e40629a5b385b3fb1ffac301e63941335e4d354081ec14a" "checksum thread_local 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c85048c6260d17cf486ceae3282d9fb6b90be220bf5b28c400f5485ffc29f0c7" "checksum time 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)" = "211b63c112206356ef1ff9b19355f43740fc3f85960c598a93d3a3d3ba7beade" -"checksum toml 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "08272367dd2e766db3fa38f068067d17aa6a9dfd7259af24b3927db92f1e0c2f" +"checksum toml 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3474f3c6eaf32eedb4f4a66a26214f020f828a6d96c37e38a35e3a379bbcfd11" "checksum unicode-bidi 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d3a078ebdd62c0e71a709c3d53d2af693fe09fe93fbff8344aebe289b78f9032" "checksum unicode-normalization 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "e28fa37426fceeb5cf8f41ee273faa7c82c47dc8fba5853402841e665fcd86ff" "checksum unicode-segmentation 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "18127285758f0e2c6cf325bb3f3d138a12fee27de4f23e146cd6a179f26c2cf3" @@ -705,8 +721,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122" "checksum vec_map 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cac5efe5cb0fa14ec2f84f83c701c562ee63f6dcc680861b21d65c682adfb05f" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +"checksum walkdir 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "c66c0b9792f0a765345452775f3adbd28dde9d33f30d13e5dcc5ae17cf6f3780" "checksum walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "bb08f9e670fab86099470b97cd2b252d6527f0b3cc1401acdb595ffc9dd288ff" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" -"checksum xml-rs 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "729264a98260c6469f7a7d7162baaf5869da5573f69ee08ccf3f3d9110cafe3b" +"checksum xml-rs 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7ec6c39eaa68382c8e31e35239402c0a9489d4141a8ceb0c716099a0b515b562" "checksum yaml-rust 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e66366e18dc58b46801afbf2ca7661a9f59cc8c5962c29892b6039b4f86fa992" diff --git a/Cargo.toml b/Cargo.toml index 8963a220..f8f1fdb5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ repository = "https://github.com/Keats/gutenberg" keywords = ["static", "site", "generator", "blog"] [dependencies] -error-chain = "0.9" +error-chain = "0.10" clap = "2.19" walkdir = "1" pulldown-cmark = "0" @@ -20,7 +20,9 @@ glob = "0.2" serde = "0.9" serde_json = "0.9" serde_derive = "0.9" -tera = { git = "https://github.com/Keats/tera", branch = "next" } +# tera = { git = "https://github.com/Keats/tera", branch = "next" } +tera = "0.8" +slug = "0.1" syntect = "1" [dependencies.toml] diff --git a/src/cmd/build.rs b/src/cmd/build.rs index 85e6aadc..f5fc79fe 100644 --- a/src/cmd/build.rs +++ b/src/cmd/build.rs @@ -1,87 +1,7 @@ -use std::collections::HashMap; -use std::fs::{create_dir, remove_dir_all}; -use std::path::Path; - -use glob::glob; -use tera::{Tera, Context}; - -use config:: Config; -use errors::{Result, ResultExt}; -use page::{Page, order_pages}; -use utils::create_file; +use errors::Result; +use site::Site; - -pub fn build(config: Config) -> Result<()> { - if Path::new("public").exists() { - // Delete current `public` directory so we can start fresh - remove_dir_all("public").chain_err(|| "Couldn't delete `public` directory")?; - } - - let tera = Tera::new("templates/**/*").chain_err(|| "Error parsing templates")?; - - // ok we got all the pages HTML, time to write them down to disk - create_dir("public")?; - let public = Path::new("public"); - let mut pages: Vec = vec![]; - let mut sections: HashMap> = HashMap::new(); - - // First step: do all the articles and group article by sections - // hardcoded pattern so can't error - for entry in glob("content/**/*.md").unwrap().filter_map(|e| e.ok()) { - let path = entry.as_path(); - let mut page = Page::from_file(&path)?; - - let mut current_path = public.to_path_buf(); - - for section in &page.sections { - current_path.push(section); - - if !current_path.exists() { - create_dir(¤t_path)?; - } - - let str_path = current_path.as_path().to_string_lossy().to_string(); - sections.entry(str_path).or_insert_with(|| vec![page.clone()]); - } - - if let Some(ref url) = page.meta.url { - println!("URL: {:?}", url); - current_path.push(url); - } else { - println!("REMOVE ME IF YOU DONT SEE ME"); - current_path.push(&page.get_slug()); - } - - create_dir(¤t_path)?; - create_file(current_path.join("index.html"), &page.render_html(&tera, &config)?)?; - pages.push(page); - } - - for (section, pages) in sections { - render_section_index(section, pages, &tera, &config)?; - } - - // and now the index page - let mut context = Context::new(); - context.add("pages", &order_pages(pages)); - context.add("config", &config); - create_file(public.join("index.html"), &tera.render("index.html", &context)?)?; - - Ok(()) -} - - -fn render_section_index(section: String, pages: Vec, tera: &Tera, config: &Config) -> Result<()> { - let path = Path::new(§ion); - let mut context = Context::new(); - context.add("pages", &order_pages(pages)); - context.add("config", &config); - - let section_name = match path.components().into_iter().last() { - Some(s) => s.as_ref().to_string_lossy().to_string(), - None => bail!("Couldn't find a section name in {:?}", path.display()) - }; - - create_file(path.join("index.html"), &tera.render(&format!("{}.html", section_name), &context)?) +pub fn build() -> Result<()> { + Site::new()?.build() } diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index 1af65e3f..e3c62b5e 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -1,5 +1,7 @@ mod init; mod build; +mod serve; pub use self::init::create_new_project; pub use self::build::build; +pub use self::serve::serve; diff --git a/src/cmd/serve.rs b/src/cmd/serve.rs new file mode 100644 index 00000000..d19eec1f --- /dev/null +++ b/src/cmd/serve.rs @@ -0,0 +1,5 @@ +use errors::{Result, ResultExt}; + +pub fn serve(interface: &str, port: &str) -> Result<()> { + Ok(()) +} diff --git a/src/config.rs b/src/config.rs index 96696dbd..3808da7f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -14,11 +14,13 @@ pub struct Config { pub title: String, /// Base URL of the site pub base_url: String, + /// Whether to highlight all code found in markdown files. Defaults to true + pub highlight_code: Option, /// Description of the site pub description: Option, /// The language used in the site. Defaults to "en" pub language_code: Option, - /// Whether to disable RSS generation, defaults to None (== generate RSS) + /// Whether to disable RSS generation, defaults to false (== generate RSS) pub disable_rss: Option, /// All user params set in [extra] in the config pub extra: Option>, @@ -32,10 +34,19 @@ impl Config { Ok(c) => c, Err(e) => bail!(e) }; + if config.language_code.is_none() { config.language_code = Some("en".to_string()); } + if config.highlight_code.is_none() { + config.highlight_code = Some(true); + } + + if config.disable_rss.is_none() { + config.disable_rss = Some(false); + } + Ok(config) } @@ -51,6 +62,20 @@ impl Config { } +/// Get and parse the config. +/// If it doesn't succeed, exit +pub fn get_config() -> Config { + match Config::from_file("config.toml") { + Ok(c) => c, + Err(e) => { + println!("Failed to load config.toml"); + println!("Error: {}", e); + ::std::process::exit(1); + } + } +} + + #[cfg(test)] mod tests { use super::{Config}; diff --git a/src/front_matter.rs b/src/front_matter.rs index 384d19c9..e83f4c73 100644 --- a/src/front_matter.rs +++ b/src/front_matter.rs @@ -11,11 +11,15 @@ use errors::{Result}; /// The front matter of every page #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct FrontMatter { - // of the page + // Mandatory fields + + /// <title> of the page pub title: String, /// Description that appears when linked, e.g. on twitter pub description: String, + // Optional stuff + /// Date if we want to order pages (ie blog post) pub date: Option<String>, /// The page slug. Will be used instead of the filename if present @@ -44,7 +48,7 @@ impl FrontMatter { bail!("Front matter of file is missing"); } - let mut f: FrontMatter = match toml::from_str(toml) { + let f: FrontMatter = match toml::from_str(toml) { Ok(d) => d, Err(e) => bail!(e), }; diff --git a/src/main.rs b/src/main.rs index b71b4c80..4a52e6ea 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,34 +14,18 @@ extern crate regex; extern crate tera; extern crate glob; extern crate syntect; +extern crate slug; +use std::time::Instant; + mod utils; mod config; mod errors; mod cmd; mod page; mod front_matter; - - -use config::Config; - - -// Get and parse the config. -// If it doesn't succeed, exit -fn get_config() -> Config { - match Config::from_file("config.toml") { - Ok(c) => c, - Err(e) => { - println!("Failed to load config.toml"); - println!("Error: {}", e); - for e in e.iter().skip(1) { - println!("Reason: {}", e) - } - ::std::process::exit(1); - } - } -} +mod site; fn main() { @@ -57,6 +41,11 @@ fn main() { (@subcommand build => (about: "Builds the site") ) + (@subcommand serve => + (about: "Serve the site. Rebuild and reload on change automatically") + (@arg interface: "Interface to bind on (default to 127.0.0.1)") + (@arg port: "Which port to use (default to 1111)") + ) ).get_matches(); match matches.subcommand() { @@ -64,7 +53,6 @@ fn main() { match cmd::create_new_project(matches.value_of("name").unwrap()) { Ok(()) => { println!("Project created"); - println!("You will now need to set a theme in `config.toml`"); }, Err(e) => { println!("Error: {}", e); @@ -73,9 +61,11 @@ fn main() { }; }, ("build", Some(_)) => { - match cmd::build(get_config()) { + let start = Instant::now(); + match cmd::build() { Ok(()) => { - println!("Project built."); + let duration = start.elapsed(); + println!("Site built in {}s.", duration.as_secs()); }, Err(e) => { println!("Failed to build the site"); @@ -87,6 +77,20 @@ fn main() { }, }; }, + ("serve", Some(matches)) => { + let interface = matches.value_of("interface").unwrap_or("127.0.0.1"); + let port = matches.value_of("port").unwrap_or("1111"); + match cmd::serve(interface, port) { + Ok(()) => { + println!("Project created"); + println!("You will now need to set a theme in `config.toml`"); + }, + Err(e) => { + println!("Error: {}", e); + ::std::process::exit(1); + }, + }; + }, _ => unreachable!(), } } diff --git a/src/page.rs b/src/page.rs index 0efb2695..b763ec06 100644 --- a/src/page.rs +++ b/src/page.rs @@ -9,6 +9,7 @@ use pulldown_cmark as cmark; use regex::Regex; use tera::{Tera, Context}; use serde::ser::{SerializeStruct, self}; +use slug::slugify; use errors::{Result, ResultExt}; use config::Config; @@ -67,10 +68,22 @@ impl Page { if let Some(ref slug) = self.meta.slug { slug.to_string() } else { - self.filename.clone() + slugify(self.filename.clone()) } } + pub fn get_url(&self) -> String { + if let Some(ref u) = self.meta.url { + return u.to_string(); + } + + if !self.sections.is_empty() { + return format!("/{}/{}", self.sections.join("/"), self.get_slug()); + } + + format!("/{}", self.get_slug()) + } + // Parse a page given the content of the .md file // Files without front matter or with invalid front matter are considered // erroneous @@ -110,14 +123,6 @@ impl Page { for section in path.parent().unwrap().components() { page.sections.push(section.as_ref().to_string_lossy().to_string()); } - - // now the url - // We get it from a combination of sections + slug - if !page.sections.is_empty() { - page.meta.url = Some(format!("/{}/{}", page.sections.join("/"), page.get_slug())); - } else { - page.meta.url = Some(format!("/{}", page.get_slug())); - }; } Ok(page) @@ -143,7 +148,7 @@ impl Page { } } - pub fn render_html(&mut self, tera: &Tera, config: &Config) -> Result<String> { + pub fn render_html(&self, tera: &Tera, config: &Config) -> Result<String> { let tpl = self.get_layout_name(); let mut context = Context::new(); context.add("site", config); @@ -161,8 +166,8 @@ impl ser::Serialize for Page { state.serialize_field("title", &self.meta.title)?; state.serialize_field("description", &self.meta.description)?; state.serialize_field("date", &self.meta.date)?; - state.serialize_field("slug", &self.meta.slug)?; - state.serialize_field("url", &self.meta.url)?; + state.serialize_field("slug", &self.get_slug())?; + state.serialize_field("url", &self.get_url())?; state.serialize_field("tags", &self.meta.tags)?; state.serialize_field("draft", &self.meta.draft)?; state.serialize_field("category", &self.meta.category)?; @@ -244,7 +249,7 @@ Hello world"#; let res = Page::parse("posts/intro/start.md", content); assert!(res.is_ok()); let page = res.unwrap(); - assert_eq!(page.meta.url.unwrap(), "/posts/intro/hello-world"); + assert_eq!(page.get_url(), "/posts/intro/hello-world"); } #[test] @@ -259,7 +264,7 @@ Hello world"#; let res = Page::parse("start.md", content); assert!(res.is_ok()); let page = res.unwrap(); - assert_eq!(page.meta.url.unwrap(), "/hello-world"); + assert_eq!(page.get_url(), "/hello-world"); } #[test] @@ -273,4 +278,18 @@ Hello world"#; let res = Page::parse("start.md", content); assert!(res.is_err()); } + + #[test] + fn test_can_make_slug_from_non_slug_filename() { + let content = r#" ++++ +title = "Hello" +description = "hey there" ++++ +Hello world"#; + let res = Page::parse("file with space.md", content); + assert!(res.is_ok()); + let page = res.unwrap(); + assert_eq!(page.get_slug(), "file-with-space"); + } } diff --git a/src/site.rs b/src/site.rs new file mode 100644 index 00000000..fe0d229b --- /dev/null +++ b/src/site.rs @@ -0,0 +1,107 @@ +use std::collections::HashMap; +use std::fs::{create_dir, remove_dir_all}; +use std::path::Path; + +use glob::glob; +use tera::{Tera, Context}; + +use errors::{Result, ResultExt}; +use config::{Config, get_config}; +use page::Page; +use utils::create_file; + + +#[derive(Debug)] +pub struct Site { + config: Config, + pages: HashMap<String, Page>, + sections: HashMap<String, Vec<String>>, + templates: Tera, +} + +impl Site { + pub fn new() -> Result<Site> { + let tera = Tera::new("templates/**/*").chain_err(|| "Error parsing templates")?; + let mut site = Site { + config: get_config(), + pages: HashMap::new(), + sections: HashMap::new(), + templates: tera, + }; + site.parse_site()?; + + Ok(site) + } + + /// Reads all .md files in the `content` directory and create pages + /// out of them + fn parse_site(&mut self) -> Result<()> { + // First step: do all the articles and group article by sections + // hardcoded pattern so can't error + for entry in glob("content/**/*.md").unwrap().filter_map(|e| e.ok()) { + let page = Page::from_file(&entry.as_path())?; + + for section in &page.sections { + self.sections.entry(section.clone()).or_insert(vec![]).push(page.get_slug()); + } + + self.pages.insert(page.get_slug(), page); + } + + Ok(()) + } + + /// Builds the site to the `public` directory after deleting it + pub fn build(&self) -> Result<()> { + if Path::new("public").exists() { + // Delete current `public` directory so we can start fresh + remove_dir_all("public").chain_err(|| "Couldn't delete `public` directory")?; + } + + // Start from scratch + create_dir("public")?; + let public = Path::new("public"); + + let mut pages = vec![]; + // First we render the pages themselves + for page in self.pages.values() { + // Copy the nesting of the content directory if we have sections for that page + let mut current_path = public.to_path_buf(); + + // This loop happens when the page doesn't have a set URL + for section in &page.sections { + current_path.push(section); + + if !current_path.exists() { + create_dir(¤t_path)?; + } + } + + // if we have a url already set, use that as base + if let Some(ref url) = page.meta.url { + current_path.push(url); + } + + // Make sure the folder exists + create_dir(¤t_path)?; + // Finally, create a index.html file there with the page rendered + let output = page.render_html(&self.templates, &self.config)?; + create_file(current_path.join("index.html"), &output)?; + pages.push(page); + } + + // Then the section pages + // The folders have already been created in the page loop so no need to `create_dir` here +// for (section, slugs) in &self.sections { +// // TODO +// } + + // And finally the index page + let mut context = Context::new(); + context.add("pages", &pages); + context.add("config", &self.config); + create_file(public.join("index.html"), &self.templates.render("index.html", &context)?)?; + + Ok(()) + } +} From f57912f712b03c792670022818695361eb49cd10 Mon Sep 17 00:00:00 2001 From: Vincent Prouillet <vincent@wearewizards.io> Date: Mon, 6 Mar 2017 19:35:56 +0900 Subject: [PATCH 02/26] Basic livereload --- Cargo.lock | 481 +++++++++++++++-- Cargo.toml | 7 + src/cmd/build.rs | 2 +- src/cmd/livereload.js | 1175 +++++++++++++++++++++++++++++++++++++++++ src/cmd/serve.rs | 134 ++++- src/main.rs | 11 +- src/site.rs | 27 +- 7 files changed, 1784 insertions(+), 53 deletions(-) create mode 100644 src/cmd/livereload.js diff --git a/Cargo.lock b/Cargo.lock index b076a63f..9d42702f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5,17 +5,22 @@ dependencies = [ "clap 2.20.5 (registry+https://github.com/rust-lang/crates.io-index)", "error-chain 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "iron 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", + "mount 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "notify 4.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "pulldown-cmark 0.0.8 (registry+https://github.com/rust-lang/crates.io-index)", "regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 0.9.10 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 0.9.10 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 0.9.8 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 0.9.9 (registry+https://github.com/rust-lang/crates.io-index)", "slug 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syntect 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "staticfile 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "syntect 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "tera 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "ws 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -65,6 +70,11 @@ dependencies = [ "serde 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "bitflags" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "bitflags" version = "0.5.0" @@ -75,6 +85,11 @@ name = "bitflags" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "bitflags" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "byteorder" version = "0.5.3" @@ -85,6 +100,11 @@ name = "byteorder" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "bytes" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "cfg-if" version = "0.1.0" @@ -131,6 +151,14 @@ dependencies = [ "gcc 0.3.43 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "conduit-mime-types" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "dbghelp-sys" version = "0.2.0" @@ -145,6 +173,15 @@ name = "dtoa" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "error" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "error-chain" version = "0.10.0" @@ -153,6 +190,14 @@ dependencies = [ "backtrace 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "filetime" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "flate2" version = "0.2.17" @@ -167,6 +212,24 @@ name = "fnv" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "fsevent" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "fsevent-sys 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "fsevent-sys" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "gcc" version = "0.3.43" @@ -182,11 +245,35 @@ name = "glob" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "httparse" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "humansize" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "hyper" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "httparse 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "mime 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)", + "traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "idna" version = "0.1.0" @@ -197,6 +284,31 @@ dependencies = [ "unicode-normalization 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "inotify" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "iron" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "conduit-mime-types 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "error 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "modifier 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "plugin 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "typemap 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "itoa" version = "0.3.1" @@ -212,15 +324,30 @@ dependencies = [ ] [[package]] -name = "lazy_static" +name = "language-tags" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "lazy_static" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "lazycell" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "libc" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "log" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "matches" version = "0.1.4" @@ -234,6 +361,14 @@ dependencies = [ "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "mime" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "miniz-sys" version = "0.1.9" @@ -243,6 +378,112 @@ dependencies = [ "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "mio" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "miow 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "net2 0.2.26 (registry+https://github.com/rust-lang/crates.io-index)", + "nix 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "mio" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazycell 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "miow 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "net2 0.2.26 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "miow" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "net2 0.2.26 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "miow" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "net2 0.2.26 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "modifier" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "mount" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "iron 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "sequence_trie 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "net2" +version = "0.2.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "nix" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "notify" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "filetime 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "fsevent 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)", + "fsevent-sys 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "inotify 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)", + "walkdir 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "num" version = "0.1.37" @@ -275,13 +516,21 @@ name = "num-traits" version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "num_cpus" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "onig" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", "onig_sys 61.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -308,16 +557,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "plist" -version = "0.0.14" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", "chrono 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 0.7.15 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)", "xml-rs 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "plugin" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "typemap 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "pulldown-cmark" version = "0.0.8" @@ -329,9 +586,17 @@ dependencies = [ [[package]] name = "quote" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "rand" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "redox_syscall" version = "0.1.16" @@ -349,11 +614,6 @@ dependencies = [ "utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "regex-syntax" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "regex-syntax" version = "0.4.0" @@ -369,6 +629,14 @@ name = "rustc-serialize" version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "rustc_version" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "same-file" version = "0.1.3" @@ -379,8 +647,13 @@ dependencies = [ ] [[package]] -name = "serde" -version = "0.7.15" +name = "semver" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "sequence_trie" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -390,7 +663,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "serde" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -403,25 +676,40 @@ dependencies = [ [[package]] name = "serde_derive" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "quote 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", "serde_codegen_internals 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)", "syn 0.11.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "serde_json" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "dtoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "itoa 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 0.9.10 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "sha1" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "slab" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "slab" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "slug" version = "0.1.2" @@ -430,6 +718,17 @@ dependencies = [ "unidecode 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "staticfile" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "iron 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "mount 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "strsim" version = "0.6.0" @@ -440,7 +739,7 @@ name = "syn" version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "quote 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -455,19 +754,19 @@ dependencies = [ [[package]] name = "syntect" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bincode 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", - "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "flate2 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)", "fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", "onig 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "plist 0.0.14 (registry+https://github.com/rust-lang/crates.io-index)", - "regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "plist 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", - "walkdir 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", "yaml-rust 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -480,11 +779,11 @@ dependencies = [ "error-chain 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "humansize 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", "pest 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 0.9.10 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 0.9.8 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 0.9.9 (registry+https://github.com/rust-lang/crates.io-index)", "slug 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "url 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -533,7 +832,33 @@ name = "toml" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "serde 0.9.10 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "traitobject" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "typeable" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "typemap" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unsafe-any 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicase" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -577,6 +902,14 @@ dependencies = [ "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "unsafe-any" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "url" version = "1.4.0" @@ -630,6 +963,30 @@ name = "winapi-build" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "ws" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "httparse 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "sha1 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ws2_32-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "xml-rs" version = "0.3.6" @@ -649,67 +1006,104 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum backtrace 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f551bc2ddd53aea015d453ef0b635af89444afa5ed2405dd0b2062ad5d600d80" "checksum backtrace-sys 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d192fd129132fbc97497c1f2ec2c2c5174e376b95f535199ef4fe0a293d33842" "checksum bincode 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "55eb0b7fd108527b0c77860f75eca70214e11a8b4c6ef05148c54c05a25d48ad" +"checksum bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8dead7461c1127cf637931a1e50934eb6eee8bff2f74433ac7909e9afcee04a3" "checksum bitflags 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4f67931368edf3a9a51d29886d245f1c3db2f1ef0dcc9e35ff70341b78c10d23" "checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" +"checksum bitflags 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e1ab483fc81a8143faa7203c4a3c02888ebd1a782e37e41fa34753ba9a162" "checksum byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0fc10e8cc6b2580fda3f36eb6dc5316657f812a3df879a44a66fc9f0fdbc4855" "checksum byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c40977b0ee6b9885c9013cd41d9feffdd22deb3bb4dc3a71d901cc7a77de18c8" +"checksum bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c129aff112dcc562970abb69e2508b40850dd24c274761bb50fb8a0067ba6c27" "checksum cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de1e760d7b6535af4241fca8bd8adf68e2e7edacc6b29f5d399050c5e48cf88c" "checksum chrono 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)" = "9213f7cd7c27e95c2b57c49f0e69b1ea65b27138da84a170133fd21b07659c00" "checksum chrono 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "158b0bd7d75cbb6bf9c25967a48a2e9f77da95876b858eadfabaa99cd069de6e" "checksum clap 2.20.5 (registry+https://github.com/rust-lang/crates.io-index)" = "7db281b0520e97fbd15cd615dcd8f8bcad0c26f5f7d5effe705f090f39e9a758" "checksum cmake 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)" = "e1acc68a3f714627af38f9f5d09706a28584ba60dfe2cca68f40bf779f941b25" +"checksum conduit-mime-types 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "95ca30253581af809925ef68c2641cc140d6183f43e12e0af4992d53768bd7b8" "checksum dbghelp-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "97590ba53bcb8ac28279161ca943a924d1fd4a8fb3fa63302591647c4fc5b850" "checksum dtoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "80c8b71fd71146990a9742fc06dcbbde19161a267e0ad4e572c35162f4578c90" +"checksum error 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "a6e606f14042bb87cc02ef6a14db6c90ab92ed6f62d87e69377bc759fd7987cc" "checksum error-chain 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9435d864e017c3c6afeac1654189b06cdb491cf2ff73dbf0d73b0f292f42ff8" +"checksum filetime 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "5363ab8e4139b8568a6237db5248646e5a8a2f89bd5ccb02092182b11fd3e922" "checksum flate2 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)" = "d4e4d0c15ef829cbc1b7cda651746be19cceeb238be7b1049227b14891df9e25" "checksum fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6cc484842f1e2884faf56f529f960cc12ad8c71ce96cc7abba0a067c98fee344" +"checksum fsevent 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)" = "dfe593ebcfc76884138b25426999890b10da8e6a46d01b499d7c54c604672c38" +"checksum fsevent-sys 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "1a772d36c338d07a032d5375a36f15f9a7043bf0cb8ce7cee658e037c6032874" "checksum gcc 0.3.43 (registry+https://github.com/rust-lang/crates.io-index)" = "c07c758b972368e703a562686adb39125707cc1ef3399da8c019fc6c2498a75d" "checksum getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9047cfbd08a437050b363d35ef160452c5fe8ea5187ae0a624708c91581d685" "checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" +"checksum httparse 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a6e7a63e511f9edffbab707141fbb8707d1a3098615fb2adbd5769cdfcc9b17d" "checksum humansize 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9b963e0c0a5149e12a9cab4d889404e4935e3484db7c4d9681e8bbdbcb9dfd80" +"checksum hyper 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)" = "43a15e3273b2133aaac0150478ab443fb89f15c3de41d8d93d8f3bb14bf560f6" "checksum idna 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1053236e00ce4f668aeca4a769a09b3bf5a682d802abd6f3cb39374f6b162c11" +"checksum inotify 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "887fcc180136e77a85e6a6128579a719027b1bab9b1c38ea4444244fe262c20c" +"checksum iron 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2440ae846e7a8c7f9b401db8f6e31b4ea5e7d3688b91761337da7e054520c75b" "checksum itoa 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eb2f404fbc66fd9aac13e998248505e7ecb2ad8e44ab6388684c5fb11c6c251c" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" -"checksum lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6abe0ee2e758cd6bc8a2cd56726359007748fbf4128da998b65d0b70f881e19b" +"checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" +"checksum lazy_static 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7291b1dd97d331f752620b02dfdbc231df7fc01bf282a00769e1cdb963c460dc" +"checksum lazycell 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ce12306c4739d86ee97c23139f3a34ddf0387bbf181bc7929d287025a8c3ef6b" "checksum libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)" = "88ee81885f9f04bff991e306fea7c1c60a5f0f9e409e99f6b40e3311a3363135" +"checksum log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ab83497bf8bf4ed2a74259c1c802351fcd67a65baa86394b6ba73c36f4838054" "checksum matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "efd7622e3022e1a6eaa602c4cea8912254e5582c9c692e9167714182244801b1" "checksum memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1dbccc0e46f1ea47b9f17e6d67c5a96bd27030519c519c9c91327e31275a47b4" +"checksum mime 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b5c93a4bd787ddc6e7833c519b73a50883deb5863d76d9b71eb8216fb7f94e66" "checksum miniz-sys 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "28eaee17666671fa872e567547e8428e83308ebe5808cdf6a0e28397dbe2c726" +"checksum mio 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a637d1ca14eacae06296a008fa7ad955347e34efcb5891cfd8ba05491a37907e" +"checksum mio 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "eecdbdd49a849336e77b453f021c89972a2cfb5b51931a0026ae0ac4602de681" +"checksum miow 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3e690c5df6b2f60acd45d56378981e827ff8295562fc8d34f573deb267a59cd1" +"checksum miow 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3a78d2605eb97302c10cf944b8d96b0a2a890c52957caf92fcd1f24f69049579" +"checksum modifier 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "41f5c9112cb662acd3b204077e0de5bc66305fa8df65c8019d5adb10e9ab6e58" +"checksum mount 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "32245731923cd096899502fc4c4317cfd09f121e80e73f7f576cf3777a824256" +"checksum net2 0.2.26 (registry+https://github.com/rust-lang/crates.io-index)" = "5edf9cb6be97212423aed9413dd4729d62b370b5e1c571750e882cebbbc1e3e2" +"checksum nix 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bfb3ddedaa14746434a02041940495bf11325c22f6d36125d3bdd56090d50a79" +"checksum notify 4.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "87c628262cc8a99209596ec610dcf199d6876a10cfa22e355acc5936d6f1fa63" "checksum num 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "98b15ba84e910ea7a1973bccd3df7b31ae282bf9d8bd2897779950c9b8303d40" "checksum num-integer 0.1.33 (registry+https://github.com/rust-lang/crates.io-index)" = "21e4df1098d1d797d27ef0c69c178c3fab64941559b290fcae198e0825c9c8b5" "checksum num-iter 0.1.33 (registry+https://github.com/rust-lang/crates.io-index)" = "f7d1891bd7b936f12349b7d1403761c8a0b85a18b148e9da4429d5d102c1a41e" "checksum num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "e1cbfa3781f3fe73dc05321bed52a06d2d491eaa764c52335cf4399f046ece99" +"checksum num_cpus 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a18c392466409c50b87369414a2680c93e739aedeb498eb2bff7d7eb569744e2" "checksum onig 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0c5f586e53fa11ead18582956ea282c30baea1f25d3ee4c5fb85803f98727cb7" "checksum onig_sys 61.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a35f2cca300f0945538564da6052a449db55e65870cf0e9d443c1bce3d5dda47" "checksum pest 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3e2e823a5967bb4cdc6d3e46f47baaf4ecfeae44413a642b74ad44e59e49c7f6" "checksum pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "3a8b4c6b8165cd1a1cd4b9b120978131389f64bdaf456435caa41e630edba903" -"checksum plist 0.0.14 (registry+https://github.com/rust-lang/crates.io-index)" = "29f7f7deddf1244a97e2771e47edb01e5b5603d133bd4a0e516ea0e6817adc64" +"checksum plist 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6ab9bef2781bcdac1baf3e29eb297344cd24263e22fd9436d3a21215b7d8aa" +"checksum plugin 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "1a6a0dc3910bc8db877ffed8e457763b317cf880df4ae19109b9f77d277cf6e0" "checksum pulldown-cmark 0.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "1058d7bb927ca067656537eec4e02c2b4b70eaaa129664c5b90c111e20326f41" -"checksum quote 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7375cf7ad34a92e8fd18dd9c42f58b9a11def59ab48bec955bf359a788335592" +"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" +"checksum rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "022e0636ec2519ddae48154b028864bdce4eaf7d35226ab8e65c611be97b189d" "checksum redox_syscall 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "8dd35cc9a8bdec562c757e3d43c1526b5c6d2653e23e2315065bc25556550753" "checksum regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4278c17d0f6d62dfef0ab00028feb45bd7d2102843f80763474eeb1be8a10c01" -"checksum regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "f9ec002c35e86791825ed294b50008eea9ddfc8def4420124fbc6b08db834957" "checksum regex-syntax 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9191b1f57603095f105d317e375d19b1c9c5c3185ea9633a99a6dcbed04457" "checksum rustc-demangle 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3058a43ada2c2d0b92b3ae38007a2d0fa5e9db971be260e0171408a4ff471c95" "checksum rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)" = "237546c689f20bb44980270c73c3b9edd0891c1be49cc1274406134a66d3957b" +"checksum rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c5f5376ea5e30ce23c03eb77cbe4962b988deead10910c372b226388b594c084" "checksum same-file 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d931a44fdaa43b8637009e7632a02adc4f2b2e0733c08caa4cf00e8da4a117a7" -"checksum serde 0.7.15 (registry+https://github.com/rust-lang/crates.io-index)" = "1b0e0732aa8ec4267f61815a396a942ba3525062e3bd5520aa8419927cfc0a92" +"checksum semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)" = "d4f410fedcf71af0345d7607d246e7ad15faaadd49d240ee3b24e5dc21a820ac" +"checksum sequence_trie 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c915714ca833b1d4d6b8f6a9d72a3ff632fe45b40a8d184ef79c81bec6327eed" "checksum serde 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)" = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8" -"checksum serde 0.9.10 (registry+https://github.com/rust-lang/crates.io-index)" = "a78def33a828eb05eb7f0167499f19cca368faf27601f6c43bc70316825d9adf" +"checksum serde 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)" = "a702319c807c016e51f672e5c77d6f0b46afddd744b5e437d6b8436b888b458f" "checksum serde_codegen_internals 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4d52006899f910528a10631e5b727973fe668f3228109d1707ccf5bad5490b6e" -"checksum serde_derive 0.9.10 (registry+https://github.com/rust-lang/crates.io-index)" = "789ee9f3cd78c850948b94121020147f5220b47dafbf230d7098a93a58f726cf" -"checksum serde_json 0.9.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6501ac6f8b74f9b1033f7ddf79a08edfa0f58d6f8e3190cb8dc97736afa257a8" +"checksum serde_derive 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)" = "f15ea24bd037b2d64646b4d934fa99c649be66e3f7b29fb595a5543b212b1452" +"checksum serde_json 0.9.9 (registry+https://github.com/rust-lang/crates.io-index)" = "dbc45439552eb8fb86907a2c41c1fd0ef97458efb87ff7f878db466eb581824e" +"checksum sha1 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cc30b1e1e8c40c121ca33b86c23308a090d19974ef001b4bf6e61fd1a0fb095c" +"checksum slab 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d807fd58c4181bbabed77cb3b891ba9748241a552bcc5be698faaebefc54f46e" +"checksum slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "17b4fcaed89ab08ef143da37bc52adbcc04d4a69014f4c1208d6b51f0c47bc23" "checksum slug 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f5ff4b43cb07b86c5f9236c92714a22cdf9e5a27a7d85e398e2c9403328cb8" +"checksum staticfile 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "31493480e073d52522a94cdf56269dd8eb05f99549effd1826b0271690608878" "checksum strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b4d15c810519a91cf877e7e36e63fe068815c678181439f2f29e2562147c3694" "checksum syn 0.11.8 (registry+https://github.com/rust-lang/crates.io-index)" = "37c279fb816210c9bb28b2c292664581e7b87b4561e86b94df462664d8620bb8" "checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" -"checksum syntect 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e9d678dfa7f7f9c90535caaa81778c971765942fcd57aceffc3acd34564fc0fd" +"checksum syntect 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3f79be04af68d5fa09e71b3274159a955a25f25a5cbfba9a6ff64139b71d848a" "checksum tera 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb775e4a7318e080e9c3000b150f720caf5825b66504f56f358df35b74fd0c14" "checksum term_size 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "07b6c1ac5b3fffd75073276bca1ceed01f67a28537097a2a9539e116e50fb21a" "checksum thread-id 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4437c97558c70d129e40629a5b385b3fb1ffac301e63941335e4d354081ec14a" "checksum thread_local 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c85048c6260d17cf486ceae3282d9fb6b90be220bf5b28c400f5485ffc29f0c7" "checksum time 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)" = "211b63c112206356ef1ff9b19355f43740fc3f85960c598a93d3a3d3ba7beade" "checksum toml 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3474f3c6eaf32eedb4f4a66a26214f020f828a6d96c37e38a35e3a379bbcfd11" +"checksum traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079" +"checksum typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887" +"checksum typemap 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "653be63c80a3296da5551e1bfd2cca35227e13cdd08c6668903ae2f4f77aa1f6" +"checksum unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "13a5906ca2b98c799f4b1ab4557b76367ebd6ae5ef14930ec841c74aed5f3764" "checksum unicode-bidi 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d3a078ebdd62c0e71a709c3d53d2af693fe09fe93fbff8344aebe289b78f9032" "checksum unicode-normalization 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "e28fa37426fceeb5cf8f41ee273faa7c82c47dc8fba5853402841e665fcd86ff" "checksum unicode-segmentation 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "18127285758f0e2c6cf325bb3f3d138a12fee27de4f23e146cd6a179f26c2cf3" @@ -717,6 +1111,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" "checksum unidecode 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d2adb95ee07cd579ed18131f2d9e7a17c25a4b76022935c7f2460d2bfae89fd2" "checksum unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1f2ae5ddb18e1c92664717616dd9549dde73f539f01bd7b77c2edb2446bdff91" +"checksum unsafe-any 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b351086021ebc264aea3ab4f94d61d889d98e5e9ec2d985d993f50133537fd3a" "checksum url 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f5ba8a749fb4479b043733416c244fa9d1d3af3d7c23804944651c8a448cb87e" "checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122" "checksum vec_map 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cac5efe5cb0fa14ec2f84f83c701c562ee63f6dcc680861b21d65c682adfb05f" @@ -725,5 +1120,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "bb08f9e670fab86099470b97cd2b252d6527f0b3cc1401acdb595ffc9dd288ff" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" +"checksum ws 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "04614a58714f3fd4a8b1da4bcae9f031c532d35988c3d39627619248113f8be8" +"checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" "checksum xml-rs 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7ec6c39eaa68382c8e31e35239402c0a9489d4141a8ceb0c716099a0b515b562" "checksum yaml-rust 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e66366e18dc58b46801afbf2ca7661a9f59cc8c5962c29892b6039b4f86fa992" diff --git a/Cargo.toml b/Cargo.toml index f8f1fdb5..d68253fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,13 @@ tera = "0.8" slug = "0.1" syntect = "1" +# Below is for the serve cmd +staticfile = "0.4" +iron = "0.5" +mount = "0.3" +notify = "4" +ws = "0.6" + [dependencies.toml] version = "0.3" default-features = false diff --git a/src/cmd/build.rs b/src/cmd/build.rs index f5fc79fe..50a1bc66 100644 --- a/src/cmd/build.rs +++ b/src/cmd/build.rs @@ -3,5 +3,5 @@ use site::Site; pub fn build() -> Result<()> { - Site::new()?.build() + Site::new(false)?.build() } diff --git a/src/cmd/livereload.js b/src/cmd/livereload.js new file mode 100644 index 00000000..fd221102 --- /dev/null +++ b/src/cmd/livereload.js @@ -0,0 +1,1175 @@ +(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ +(function() { + var Connector, PROTOCOL_6, PROTOCOL_7, Parser, Version, _ref; + + _ref = require('./protocol'), Parser = _ref.Parser, PROTOCOL_6 = _ref.PROTOCOL_6, PROTOCOL_7 = _ref.PROTOCOL_7; + + Version = '2.2.2'; + + exports.Connector = Connector = (function() { + function Connector(options, WebSocket, Timer, handlers) { + this.options = options; + this.WebSocket = WebSocket; + this.Timer = Timer; + this.handlers = handlers; + this._uri = "ws" + (this.options.https ? "s" : "") + "://" + this.options.host + ":" + this.options.port + "/livereload"; + this._nextDelay = this.options.mindelay; + this._connectionDesired = false; + this.protocol = 0; + this.protocolParser = new Parser({ + connected: (function(_this) { + return function(protocol) { + _this.protocol = protocol; + _this._handshakeTimeout.stop(); + _this._nextDelay = _this.options.mindelay; + _this._disconnectionReason = 'broken'; + return _this.handlers.connected(protocol); + }; + })(this), + error: (function(_this) { + return function(e) { + _this.handlers.error(e); + return _this._closeOnError(); + }; + })(this), + message: (function(_this) { + return function(message) { + return _this.handlers.message(message); + }; + })(this) + }); + this._handshakeTimeout = new Timer((function(_this) { + return function() { + if (!_this._isSocketConnected()) { + return; + } + _this._disconnectionReason = 'handshake-timeout'; + return _this.socket.close(); + }; + })(this)); + this._reconnectTimer = new Timer((function(_this) { + return function() { + if (!_this._connectionDesired) { + return; + } + return _this.connect(); + }; + })(this)); + this.connect(); + } + + Connector.prototype._isSocketConnected = function() { + return this.socket && this.socket.readyState === this.WebSocket.OPEN; + }; + + Connector.prototype.connect = function() { + this._connectionDesired = true; + if (this._isSocketConnected()) { + return; + } + this._reconnectTimer.stop(); + this._disconnectionReason = 'cannot-connect'; + this.protocolParser.reset(); + this.handlers.connecting(); + this.socket = new this.WebSocket(this._uri); + this.socket.onopen = (function(_this) { + return function(e) { + return _this._onopen(e); + }; + })(this); + this.socket.onclose = (function(_this) { + return function(e) { + return _this._onclose(e); + }; + })(this); + this.socket.onmessage = (function(_this) { + return function(e) { + return _this._onmessage(e); + }; + })(this); + return this.socket.onerror = (function(_this) { + return function(e) { + return _this._onerror(e); + }; + })(this); + }; + + Connector.prototype.disconnect = function() { + this._connectionDesired = false; + this._reconnectTimer.stop(); + if (!this._isSocketConnected()) { + return; + } + this._disconnectionReason = 'manual'; + return this.socket.close(); + }; + + Connector.prototype._scheduleReconnection = function() { + if (!this._connectionDesired) { + return; + } + if (!this._reconnectTimer.running) { + this._reconnectTimer.start(this._nextDelay); + return this._nextDelay = Math.min(this.options.maxdelay, this._nextDelay * 2); + } + }; + + Connector.prototype.sendCommand = function(command) { + if (this.protocol == null) { + return; + } + return this._sendCommand(command); + }; + + Connector.prototype._sendCommand = function(command) { + return this.socket.send(JSON.stringify(command)); + }; + + Connector.prototype._closeOnError = function() { + this._handshakeTimeout.stop(); + this._disconnectionReason = 'error'; + return this.socket.close(); + }; + + Connector.prototype._onopen = function(e) { + var hello; + this.handlers.socketConnected(); + this._disconnectionReason = 'handshake-failed'; + hello = { + command: 'hello', + protocols: [PROTOCOL_6, PROTOCOL_7] + }; + hello.ver = Version; + if (this.options.ext) { + hello.ext = this.options.ext; + } + if (this.options.extver) { + hello.extver = this.options.extver; + } + if (this.options.snipver) { + hello.snipver = this.options.snipver; + } + this._sendCommand(hello); + return this._handshakeTimeout.start(this.options.handshake_timeout); + }; + + Connector.prototype._onclose = function(e) { + this.protocol = 0; + this.handlers.disconnected(this._disconnectionReason, this._nextDelay); + return this._scheduleReconnection(); + }; + + Connector.prototype._onerror = function(e) {}; + + Connector.prototype._onmessage = function(e) { + return this.protocolParser.process(e.data); + }; + + return Connector; + + })(); + +}).call(this); + +},{"./protocol":6}],2:[function(require,module,exports){ +(function() { + var CustomEvents; + + CustomEvents = { + bind: function(element, eventName, handler) { + if (element.addEventListener) { + return element.addEventListener(eventName, handler, false); + } else if (element.attachEvent) { + element[eventName] = 1; + return element.attachEvent('onpropertychange', function(event) { + if (event.propertyName === eventName) { + return handler(); + } + }); + } else { + throw new Error("Attempt to attach custom event " + eventName + " to something which isn't a DOMElement"); + } + }, + fire: function(element, eventName) { + var event; + if (element.addEventListener) { + event = document.createEvent('HTMLEvents'); + event.initEvent(eventName, true, true); + return document.dispatchEvent(event); + } else if (element.attachEvent) { + if (element[eventName]) { + return element[eventName]++; + } + } else { + throw new Error("Attempt to fire custom event " + eventName + " on something which isn't a DOMElement"); + } + } + }; + + exports.bind = CustomEvents.bind; + + exports.fire = CustomEvents.fire; + +}).call(this); + +},{}],3:[function(require,module,exports){ +(function() { + var LessPlugin; + + module.exports = LessPlugin = (function() { + LessPlugin.identifier = 'less'; + + LessPlugin.version = '1.0'; + + function LessPlugin(window, host) { + this.window = window; + this.host = host; + } + + LessPlugin.prototype.reload = function(path, options) { + if (this.window.less && this.window.less.refresh) { + if (path.match(/\.less$/i)) { + return this.reloadLess(path); + } + if (options.originalPath.match(/\.less$/i)) { + return this.reloadLess(options.originalPath); + } + } + return false; + }; + + LessPlugin.prototype.reloadLess = function(path) { + var link, links, _i, _len; + links = (function() { + var _i, _len, _ref, _results; + _ref = document.getElementsByTagName('link'); + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + link = _ref[_i]; + if (link.href && link.rel.match(/^stylesheet\/less$/i) || (link.rel.match(/stylesheet/i) && link.type.match(/^text\/(x-)?less$/i))) { + _results.push(link); + } + } + return _results; + })(); + if (links.length === 0) { + return false; + } + for (_i = 0, _len = links.length; _i < _len; _i++) { + link = links[_i]; + link.href = this.host.generateCacheBustUrl(link.href); + } + this.host.console.log("LiveReload is asking LESS to recompile all stylesheets"); + this.window.less.refresh(true); + return true; + }; + + LessPlugin.prototype.analyze = function() { + return { + disable: !!(this.window.less && this.window.less.refresh) + }; + }; + + return LessPlugin; + + })(); + +}).call(this); + +},{}],4:[function(require,module,exports){ +(function() { + var Connector, LiveReload, Options, Reloader, Timer, + __hasProp = {}.hasOwnProperty; + + Connector = require('./connector').Connector; + + Timer = require('./timer').Timer; + + Options = require('./options').Options; + + Reloader = require('./reloader').Reloader; + + exports.LiveReload = LiveReload = (function() { + function LiveReload(window) { + var k, v, _ref; + this.window = window; + this.listeners = {}; + this.plugins = []; + this.pluginIdentifiers = {}; + this.console = this.window.console && this.window.console.log && this.window.console.error ? this.window.location.href.match(/LR-verbose/) ? this.window.console : { + log: function() {}, + error: this.window.console.error.bind(this.window.console) + } : { + log: function() {}, + error: function() {} + }; + if (!(this.WebSocket = this.window.WebSocket || this.window.MozWebSocket)) { + this.console.error("LiveReload disabled because the browser does not seem to support web sockets"); + return; + } + if ('LiveReloadOptions' in window) { + this.options = new Options(); + _ref = window['LiveReloadOptions']; + for (k in _ref) { + if (!__hasProp.call(_ref, k)) continue; + v = _ref[k]; + this.options.set(k, v); + } + } else { + this.options = Options.extract(this.window.document); + if (!this.options) { + this.console.error("LiveReload disabled because it could not find its own <SCRIPT> tag"); + return; + } + } + this.reloader = new Reloader(this.window, this.console, Timer); + this.connector = new Connector(this.options, this.WebSocket, Timer, { + connecting: (function(_this) { + return function() {}; + })(this), + socketConnected: (function(_this) { + return function() {}; + })(this), + connected: (function(_this) { + return function(protocol) { + var _base; + if (typeof (_base = _this.listeners).connect === "function") { + _base.connect(); + } + _this.log("LiveReload is connected to " + _this.options.host + ":" + _this.options.port + " (protocol v" + protocol + ")."); + return _this.analyze(); + }; + })(this), + error: (function(_this) { + return function(e) { + return console.log("" + e.message + "."); + }; + })(this), + disconnected: (function(_this) { + return function(reason, nextDelay) { + var _base; + if (typeof (_base = _this.listeners).disconnect === "function") { + _base.disconnect(); + } + switch (reason) { + case 'cannot-connect': + return _this.log("LiveReload cannot connect to " + _this.options.host + ":" + _this.options.port + ", will retry in " + nextDelay + " sec."); + case 'broken': + return _this.log("LiveReload disconnected from " + _this.options.host + ":" + _this.options.port + ", reconnecting in " + nextDelay + " sec."); + case 'handshake-timeout': + return _this.log("LiveReload cannot connect to " + _this.options.host + ":" + _this.options.port + " (handshake timeout), will retry in " + nextDelay + " sec."); + case 'handshake-failed': + return _this.log("LiveReload cannot connect to " + _this.options.host + ":" + _this.options.port + " (handshake failed), will retry in " + nextDelay + " sec."); + case 'manual': + break; + case 'error': + break; + default: + return _this.log("LiveReload disconnected from " + _this.options.host + ":" + _this.options.port + " (" + reason + "), reconnecting in " + nextDelay + " sec."); + } + }; + })(this), + message: (function(_this) { + return function(message) { + switch (message.command) { + case 'reload': + return _this.performReload(message); + case 'alert': + return _this.performAlert(message); + } + }; + })(this) + }); + this.initialized = true; + } + + LiveReload.prototype.on = function(eventName, handler) { + return this.listeners[eventName] = handler; + }; + + LiveReload.prototype.log = function(message) { + return this.console.log("" + message); + }; + + LiveReload.prototype.performReload = function(message) { + var _ref, _ref1; + this.log("LiveReload received reload request: " + (JSON.stringify(message, null, 2))); + return this.reloader.reload(message.path, { + liveCSS: (_ref = message.liveCSS) != null ? _ref : true, + liveImg: (_ref1 = message.liveImg) != null ? _ref1 : true, + originalPath: message.originalPath || '', + overrideURL: message.overrideURL || '', + serverURL: "http://" + this.options.host + ":" + this.options.port + }); + }; + + LiveReload.prototype.performAlert = function(message) { + return alert(message.message); + }; + + LiveReload.prototype.shutDown = function() { + var _base; + if (!this.initialized) { + return; + } + this.connector.disconnect(); + this.log("LiveReload disconnected."); + return typeof (_base = this.listeners).shutdown === "function" ? _base.shutdown() : void 0; + }; + + LiveReload.prototype.hasPlugin = function(identifier) { + return !!this.pluginIdentifiers[identifier]; + }; + + LiveReload.prototype.addPlugin = function(pluginClass) { + var plugin; + if (!this.initialized) { + return; + } + if (this.hasPlugin(pluginClass.identifier)) { + return; + } + this.pluginIdentifiers[pluginClass.identifier] = true; + plugin = new pluginClass(this.window, { + _livereload: this, + _reloader: this.reloader, + _connector: this.connector, + console: this.console, + Timer: Timer, + generateCacheBustUrl: (function(_this) { + return function(url) { + return _this.reloader.generateCacheBustUrl(url); + }; + })(this) + }); + this.plugins.push(plugin); + this.reloader.addPlugin(plugin); + }; + + LiveReload.prototype.analyze = function() { + var plugin, pluginData, pluginsData, _i, _len, _ref; + if (!this.initialized) { + return; + } + if (!(this.connector.protocol >= 7)) { + return; + } + pluginsData = {}; + _ref = this.plugins; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + plugin = _ref[_i]; + pluginsData[plugin.constructor.identifier] = pluginData = (typeof plugin.analyze === "function" ? plugin.analyze() : void 0) || {}; + pluginData.version = plugin.constructor.version; + } + this.connector.sendCommand({ + command: 'info', + plugins: pluginsData, + url: this.window.location.href + }); + }; + + return LiveReload; + + })(); + +}).call(this); + +},{"./connector":1,"./options":5,"./reloader":7,"./timer":9}],5:[function(require,module,exports){ +(function() { + var Options; + + exports.Options = Options = (function() { + function Options() { + this.https = false; + this.host = null; + this.port = 35729; + this.snipver = null; + this.ext = null; + this.extver = null; + this.mindelay = 1000; + this.maxdelay = 60000; + this.handshake_timeout = 5000; + } + + Options.prototype.set = function(name, value) { + if (typeof value === 'undefined') { + return; + } + if (!isNaN(+value)) { + value = +value; + } + return this[name] = value; + }; + + return Options; + + })(); + + Options.extract = function(document) { + var element, keyAndValue, m, mm, options, pair, src, _i, _j, _len, _len1, _ref, _ref1; + _ref = document.getElementsByTagName('script'); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + element = _ref[_i]; + if ((src = element.src) && (m = src.match(/^[^:]+:\/\/(.*)\/z?livereload\.js(?:\?(.*))?$/))) { + options = new Options(); + options.https = src.indexOf("https") === 0; + if (mm = m[1].match(/^([^\/:]+)(?::(\d+))?$/)) { + options.host = mm[1]; + if (mm[2]) { + options.port = parseInt(mm[2], 10); + } + } + if (m[2]) { + _ref1 = m[2].split('&'); + for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { + pair = _ref1[_j]; + if ((keyAndValue = pair.split('=')).length > 1) { + options.set(keyAndValue[0].replace(/-/g, '_'), keyAndValue.slice(1).join('=')); + } + } + } + return options; + } + } + return null; + }; + +}).call(this); + +},{}],6:[function(require,module,exports){ +(function() { + var PROTOCOL_6, PROTOCOL_7, Parser, ProtocolError, + __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + + exports.PROTOCOL_6 = PROTOCOL_6 = 'http://livereload.com/protocols/official-6'; + + exports.PROTOCOL_7 = PROTOCOL_7 = 'http://livereload.com/protocols/official-7'; + + exports.ProtocolError = ProtocolError = (function() { + function ProtocolError(reason, data) { + this.message = "LiveReload protocol error (" + reason + ") after receiving data: \"" + data + "\"."; + } + + return ProtocolError; + + })(); + + exports.Parser = Parser = (function() { + function Parser(handlers) { + this.handlers = handlers; + this.reset(); + } + + Parser.prototype.reset = function() { + return this.protocol = 7; + }; + + Parser.prototype.process = function(data) { + var command, e, message, options, _ref; + try { + if (this.protocol == null) { + if (data.match(/^!!ver:([\d.]+)$/)) { + this.protocol = 6; + } else if (message = this._parseMessage(data, ['hello'])) { + if (!message.protocols.length) { + throw new ProtocolError("no protocols specified in handshake message"); + } else if (__indexOf.call(message.protocols, PROTOCOL_7) >= 0) { + this.protocol = 7; + } else if (__indexOf.call(message.protocols, PROTOCOL_6) >= 0) { + this.protocol = 6; + } else { + throw new ProtocolError("no supported protocols found"); + } + } + return this.handlers.connected(this.protocol); + } else if (this.protocol === 6) { + message = JSON.parse(data); + if (!message.length) { + throw new ProtocolError("protocol 6 messages must be arrays"); + } + command = message[0], options = message[1]; + if (command !== 'refresh') { + throw new ProtocolError("unknown protocol 6 command"); + } + return this.handlers.message({ + command: 'reload', + path: options.path, + liveCSS: (_ref = options.apply_css_live) != null ? _ref : true + }); + } else { + message = this._parseMessage(data, ['reload', 'alert']); + return this.handlers.message(message); + } + } catch (_error) { + e = _error; + if (e instanceof ProtocolError) { + return this.handlers.error(e); + } else { + throw e; + } + } + }; + + Parser.prototype._parseMessage = function(data, validCommands) { + var e, message, _ref; + try { + message = JSON.parse(data); + } catch (_error) { + e = _error; + throw new ProtocolError('unparsable JSON', data); + } + if (!message.command) { + throw new ProtocolError('missing "command" key', data); + } + if (_ref = message.command, __indexOf.call(validCommands, _ref) < 0) { + throw new ProtocolError("invalid command '" + message.command + "', only valid commands are: " + (validCommands.join(', ')) + ")", data); + } + return message; + }; + + return Parser; + + })(); + +}).call(this); + +},{}],7:[function(require,module,exports){ +(function() { + var IMAGE_STYLES, Reloader, numberOfMatchingSegments, pathFromUrl, pathsMatch, pickBestMatch, splitUrl; + + splitUrl = function(url) { + var hash, index, params; + if ((index = url.indexOf('#')) >= 0) { + hash = url.slice(index); + url = url.slice(0, index); + } else { + hash = ''; + } + if ((index = url.indexOf('?')) >= 0) { + params = url.slice(index); + url = url.slice(0, index); + } else { + params = ''; + } + return { + url: url, + params: params, + hash: hash + }; + }; + + pathFromUrl = function(url) { + var path; + url = splitUrl(url).url; + if (url.indexOf('file://') === 0) { + path = url.replace(/^file:\/\/(localhost)?/, ''); + } else { + path = url.replace(/^([^:]+:)?\/\/([^:\/]+)(:\d*)?\//, '/'); + } + return decodeURIComponent(path); + }; + + pickBestMatch = function(path, objects, pathFunc) { + var bestMatch, object, score, _i, _len; + bestMatch = { + score: 0 + }; + for (_i = 0, _len = objects.length; _i < _len; _i++) { + object = objects[_i]; + score = numberOfMatchingSegments(path, pathFunc(object)); + if (score > bestMatch.score) { + bestMatch = { + object: object, + score: score + }; + } + } + if (bestMatch.score > 0) { + return bestMatch; + } else { + return null; + } + }; + + numberOfMatchingSegments = function(path1, path2) { + var comps1, comps2, eqCount, len; + path1 = path1.replace(/^\/+/, '').toLowerCase(); + path2 = path2.replace(/^\/+/, '').toLowerCase(); + if (path1 === path2) { + return 10000; + } + comps1 = path1.split('/').reverse(); + comps2 = path2.split('/').reverse(); + len = Math.min(comps1.length, comps2.length); + eqCount = 0; + while (eqCount < len && comps1[eqCount] === comps2[eqCount]) { + ++eqCount; + } + return eqCount; + }; + + pathsMatch = function(path1, path2) { + return numberOfMatchingSegments(path1, path2) > 0; + }; + + IMAGE_STYLES = [ + { + selector: 'background', + styleNames: ['backgroundImage'] + }, { + selector: 'border', + styleNames: ['borderImage', 'webkitBorderImage', 'MozBorderImage'] + } + ]; + + exports.Reloader = Reloader = (function() { + function Reloader(window, console, Timer) { + this.window = window; + this.console = console; + this.Timer = Timer; + this.document = this.window.document; + this.importCacheWaitPeriod = 200; + this.plugins = []; + } + + Reloader.prototype.addPlugin = function(plugin) { + return this.plugins.push(plugin); + }; + + Reloader.prototype.analyze = function(callback) { + return results; + }; + + Reloader.prototype.reload = function(path, options) { + var plugin, _base, _i, _len, _ref; + this.options = options; + if ((_base = this.options).stylesheetReloadTimeout == null) { + _base.stylesheetReloadTimeout = 15000; + } + _ref = this.plugins; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + plugin = _ref[_i]; + if (plugin.reload && plugin.reload(path, options)) { + return; + } + } + if (options.liveCSS) { + if (path.match(/\.css$/i)) { + if (this.reloadStylesheet(path)) { + return; + } + } + } + if (options.liveImg) { + if (path.match(/\.(jpe?g|png|gif)$/i)) { + this.reloadImages(path); + return; + } + } + return this.reloadPage(); + }; + + Reloader.prototype.reloadPage = function() { + return this.window.document.location.reload(); + }; + + Reloader.prototype.reloadImages = function(path) { + var expando, img, selector, styleNames, styleSheet, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref, _ref1, _ref2, _ref3, _results; + expando = this.generateUniqueString(); + _ref = this.document.images; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + img = _ref[_i]; + if (pathsMatch(path, pathFromUrl(img.src))) { + img.src = this.generateCacheBustUrl(img.src, expando); + } + } + if (this.document.querySelectorAll) { + for (_j = 0, _len1 = IMAGE_STYLES.length; _j < _len1; _j++) { + _ref1 = IMAGE_STYLES[_j], selector = _ref1.selector, styleNames = _ref1.styleNames; + _ref2 = this.document.querySelectorAll("[style*=" + selector + "]"); + for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) { + img = _ref2[_k]; + this.reloadStyleImages(img.style, styleNames, path, expando); + } + } + } + if (this.document.styleSheets) { + _ref3 = this.document.styleSheets; + _results = []; + for (_l = 0, _len3 = _ref3.length; _l < _len3; _l++) { + styleSheet = _ref3[_l]; + _results.push(this.reloadStylesheetImages(styleSheet, path, expando)); + } + return _results; + } + }; + + Reloader.prototype.reloadStylesheetImages = function(styleSheet, path, expando) { + var e, rule, rules, styleNames, _i, _j, _len, _len1; + try { + rules = styleSheet != null ? styleSheet.cssRules : void 0; + } catch (_error) { + e = _error; + } + if (!rules) { + return; + } + for (_i = 0, _len = rules.length; _i < _len; _i++) { + rule = rules[_i]; + switch (rule.type) { + case CSSRule.IMPORT_RULE: + this.reloadStylesheetImages(rule.styleSheet, path, expando); + break; + case CSSRule.STYLE_RULE: + for (_j = 0, _len1 = IMAGE_STYLES.length; _j < _len1; _j++) { + styleNames = IMAGE_STYLES[_j].styleNames; + this.reloadStyleImages(rule.style, styleNames, path, expando); + } + break; + case CSSRule.MEDIA_RULE: + this.reloadStylesheetImages(rule, path, expando); + } + } + }; + + Reloader.prototype.reloadStyleImages = function(style, styleNames, path, expando) { + var newValue, styleName, value, _i, _len; + for (_i = 0, _len = styleNames.length; _i < _len; _i++) { + styleName = styleNames[_i]; + value = style[styleName]; + if (typeof value === 'string') { + newValue = value.replace(/\burl\s*\(([^)]*)\)/, (function(_this) { + return function(match, src) { + if (pathsMatch(path, pathFromUrl(src))) { + return "url(" + (_this.generateCacheBustUrl(src, expando)) + ")"; + } else { + return match; + } + }; + })(this)); + if (newValue !== value) { + style[styleName] = newValue; + } + } + } + }; + + Reloader.prototype.reloadStylesheet = function(path) { + var imported, link, links, match, style, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref, _ref1; + links = (function() { + var _i, _len, _ref, _results; + _ref = this.document.getElementsByTagName('link'); + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + link = _ref[_i]; + if (link.rel.match(/^stylesheet$/i) && !link.__LiveReload_pendingRemoval) { + _results.push(link); + } + } + return _results; + }).call(this); + imported = []; + _ref = this.document.getElementsByTagName('style'); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + style = _ref[_i]; + if (style.sheet) { + this.collectImportedStylesheets(style, style.sheet, imported); + } + } + for (_j = 0, _len1 = links.length; _j < _len1; _j++) { + link = links[_j]; + this.collectImportedStylesheets(link, link.sheet, imported); + } + if (this.window.StyleFix && this.document.querySelectorAll) { + _ref1 = this.document.querySelectorAll('style[data-href]'); + for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) { + style = _ref1[_k]; + links.push(style); + } + } + this.console.log("LiveReload found " + links.length + " LINKed stylesheets, " + imported.length + " @imported stylesheets"); + match = pickBestMatch(path, links.concat(imported), (function(_this) { + return function(l) { + return pathFromUrl(_this.linkHref(l)); + }; + })(this)); + if (match) { + if (match.object.rule) { + this.console.log("LiveReload is reloading imported stylesheet: " + match.object.href); + this.reattachImportedRule(match.object); + } else { + this.console.log("LiveReload is reloading stylesheet: " + (this.linkHref(match.object))); + this.reattachStylesheetLink(match.object); + } + } else { + this.console.log("LiveReload will reload all stylesheets because path '" + path + "' did not match any specific one"); + for (_l = 0, _len3 = links.length; _l < _len3; _l++) { + link = links[_l]; + this.reattachStylesheetLink(link); + } + } + return true; + }; + + Reloader.prototype.collectImportedStylesheets = function(link, styleSheet, result) { + var e, index, rule, rules, _i, _len; + try { + rules = styleSheet != null ? styleSheet.cssRules : void 0; + } catch (_error) { + e = _error; + } + if (rules && rules.length) { + for (index = _i = 0, _len = rules.length; _i < _len; index = ++_i) { + rule = rules[index]; + switch (rule.type) { + case CSSRule.CHARSET_RULE: + continue; + case CSSRule.IMPORT_RULE: + result.push({ + link: link, + rule: rule, + index: index, + href: rule.href + }); + this.collectImportedStylesheets(link, rule.styleSheet, result); + break; + default: + break; + } + } + } + }; + + Reloader.prototype.waitUntilCssLoads = function(clone, func) { + var callbackExecuted, executeCallback, poll; + callbackExecuted = false; + executeCallback = (function(_this) { + return function() { + if (callbackExecuted) { + return; + } + callbackExecuted = true; + return func(); + }; + })(this); + clone.onload = (function(_this) { + return function() { + _this.console.log("LiveReload: the new stylesheet has finished loading"); + _this.knownToSupportCssOnLoad = true; + return executeCallback(); + }; + })(this); + if (!this.knownToSupportCssOnLoad) { + (poll = (function(_this) { + return function() { + if (clone.sheet) { + _this.console.log("LiveReload is polling until the new CSS finishes loading..."); + return executeCallback(); + } else { + return _this.Timer.start(50, poll); + } + }; + })(this))(); + } + return this.Timer.start(this.options.stylesheetReloadTimeout, executeCallback); + }; + + Reloader.prototype.linkHref = function(link) { + return link.href || link.getAttribute('data-href'); + }; + + Reloader.prototype.reattachStylesheetLink = function(link) { + var clone, parent; + if (link.__LiveReload_pendingRemoval) { + return; + } + link.__LiveReload_pendingRemoval = true; + if (link.tagName === 'STYLE') { + clone = this.document.createElement('link'); + clone.rel = 'stylesheet'; + clone.media = link.media; + clone.disabled = link.disabled; + } else { + clone = link.cloneNode(false); + } + clone.href = this.generateCacheBustUrl(this.linkHref(link)); + parent = link.parentNode; + if (parent.lastChild === link) { + parent.appendChild(clone); + } else { + parent.insertBefore(clone, link.nextSibling); + } + return this.waitUntilCssLoads(clone, (function(_this) { + return function() { + var additionalWaitingTime; + if (/AppleWebKit/.test(navigator.userAgent)) { + additionalWaitingTime = 5; + } else { + additionalWaitingTime = 200; + } + return _this.Timer.start(additionalWaitingTime, function() { + var _ref; + if (!link.parentNode) { + return; + } + link.parentNode.removeChild(link); + clone.onreadystatechange = null; + return (_ref = _this.window.StyleFix) != null ? _ref.link(clone) : void 0; + }); + }; + })(this)); + }; + + Reloader.prototype.reattachImportedRule = function(_arg) { + var href, index, link, media, newRule, parent, rule, tempLink; + rule = _arg.rule, index = _arg.index, link = _arg.link; + parent = rule.parentStyleSheet; + href = this.generateCacheBustUrl(rule.href); + media = rule.media.length ? [].join.call(rule.media, ', ') : ''; + newRule = "@import url(\"" + href + "\") " + media + ";"; + rule.__LiveReload_newHref = href; + tempLink = this.document.createElement("link"); + tempLink.rel = 'stylesheet'; + tempLink.href = href; + tempLink.__LiveReload_pendingRemoval = true; + if (link.parentNode) { + link.parentNode.insertBefore(tempLink, link); + } + return this.Timer.start(this.importCacheWaitPeriod, (function(_this) { + return function() { + if (tempLink.parentNode) { + tempLink.parentNode.removeChild(tempLink); + } + if (rule.__LiveReload_newHref !== href) { + return; + } + parent.insertRule(newRule, index); + parent.deleteRule(index + 1); + rule = parent.cssRules[index]; + rule.__LiveReload_newHref = href; + return _this.Timer.start(_this.importCacheWaitPeriod, function() { + if (rule.__LiveReload_newHref !== href) { + return; + } + parent.insertRule(newRule, index); + return parent.deleteRule(index + 1); + }); + }; + })(this)); + }; + + Reloader.prototype.generateUniqueString = function() { + return 'livereload=' + Date.now(); + }; + + Reloader.prototype.generateCacheBustUrl = function(url, expando) { + var hash, oldParams, originalUrl, params, _ref; + if (expando == null) { + expando = this.generateUniqueString(); + } + _ref = splitUrl(url), url = _ref.url, hash = _ref.hash, oldParams = _ref.params; + if (this.options.overrideURL) { + if (url.indexOf(this.options.serverURL) < 0) { + originalUrl = url; + url = this.options.serverURL + this.options.overrideURL + "?url=" + encodeURIComponent(url); + this.console.log("LiveReload is overriding source URL " + originalUrl + " with " + url); + } + } + params = oldParams.replace(/(\?|&)livereload=(\d+)/, function(match, sep) { + return "" + sep + expando; + }); + if (params === oldParams) { + if (oldParams.length === 0) { + params = "?" + expando; + } else { + params = "" + oldParams + "&" + expando; + } + } + return url + params + hash; + }; + + return Reloader; + + })(); + +}).call(this); + +},{}],8:[function(require,module,exports){ +(function() { + var CustomEvents, LiveReload, k; + + CustomEvents = require('./customevents'); + + LiveReload = window.LiveReload = new (require('./livereload').LiveReload)(window); + + for (k in window) { + if (k.match(/^LiveReloadPlugin/)) { + LiveReload.addPlugin(window[k]); + } + } + + LiveReload.addPlugin(require('./less')); + + LiveReload.on('shutdown', function() { + return delete window.LiveReload; + }); + + LiveReload.on('connect', function() { + return CustomEvents.fire(document, 'LiveReloadConnect'); + }); + + LiveReload.on('disconnect', function() { + return CustomEvents.fire(document, 'LiveReloadDisconnect'); + }); + + CustomEvents.bind(document, 'LiveReloadShutDown', function() { + return LiveReload.shutDown(); + }); + +}).call(this); + +},{"./customevents":2,"./less":3,"./livereload":4}],9:[function(require,module,exports){ +(function() { + var Timer; + + exports.Timer = Timer = (function() { + function Timer(func) { + this.func = func; + this.running = false; + this.id = null; + this._handler = (function(_this) { + return function() { + _this.running = false; + _this.id = null; + return _this.func(); + }; + })(this); + } + + Timer.prototype.start = function(timeout) { + if (this.running) { + clearTimeout(this.id); + } + this.id = setTimeout(this._handler, timeout); + return this.running = true; + }; + + Timer.prototype.stop = function() { + if (this.running) { + clearTimeout(this.id); + this.running = false; + return this.id = null; + } + }; + + return Timer; + + })(); + + Timer.start = function(timeout, func) { + return setTimeout(func, timeout); + }; + +}).call(this); + +},{}]},{},[8]); diff --git a/src/cmd/serve.rs b/src/cmd/serve.rs index d19eec1f..cb3aeb54 100644 --- a/src/cmd/serve.rs +++ b/src/cmd/serve.rs @@ -1,5 +1,133 @@ -use errors::{Result, ResultExt}; +use std::env; +use std::path::Path; +use std::sync::mpsc::channel; +use std::time::Duration; +use std::thread; -pub fn serve(interface: &str, port: &str) -> Result<()> { - Ok(()) +use iron::{Iron, Request, IronResult, Response, status}; +use iron::modifiers::Header; +use mount::Mount; +use staticfile::Static; +use notify::{Watcher, RecursiveMode, watcher}; +use ws::{WebSocket}; + +use site::Site; +use errors::{Result}; + +const LIVE_RELOAD: &'static [u8; 37809] = include_bytes!("livereload.js"); + + +fn livereload_handler(req: &mut Request) -> IronResult<Response> { + Ok(Response::with((status::Ok, String::from_utf8(LIVE_RELOAD.to_vec()).unwrap()))) +} + + +// Most of it taken from mdbook +pub fn serve(interface: &str, port: &str) -> Result<()> { + let mut site = Site::new(true)?; + site.build()?; + + let address = format!("{}:{}", interface, port); + let ws_address = format!("{}:{}", interface, "1112"); + + // Start a webserver that serves the `public` directory + let mut mount = Mount::new(); + mount.mount("/", Static::new(Path::new("public/"))); + mount.mount("/livereload.js", livereload_handler); + let server = Iron::new(mount).http(address.clone()).unwrap(); + println!("Web server is available at http://{}", address); + println!("Press CTRL+C to stop"); + + // The websocket for livereload + let ws_server = WebSocket::new(|_| { + |_| { + Ok(()) + } + }).unwrap(); + let broadcaster = ws_server.broadcaster(); + thread::spawn(move || { + ws_server.listen(&*ws_address).unwrap(); + }); + + // And finally watching/reacting on file changes + let (tx, rx) = channel(); + let mut watcher = watcher(tx, Duration::from_secs(2)).unwrap(); + watcher.watch("content/", RecursiveMode::Recursive).unwrap(); + watcher.watch("static/", RecursiveMode::Recursive).unwrap(); + watcher.watch("templates/", RecursiveMode::Recursive).unwrap(); + let pwd = env::current_dir().unwrap(); + println!("Listening for changes in {}/{{content, static, templates}}", pwd.display()); + + use notify::DebouncedEvent::*; + + loop { + // See https://github.com/spf13/hugo/blob/master/commands/hugo.go + // for a more complete version of that + match rx.recv() { + Ok(event) => match event { + NoticeWrite(path) | + NoticeRemove(path) | + Create(path) | + Write(path) | + Remove(path) | + Rename(_, path) => { + if !is_temp_file(&path) { + println!("Change detected in {}", path.display()); + match site.rebuild() { + Ok(_) => { + println!("Site rebuilt"); + broadcaster.send(r#" + { + "command": "reload", + "path": "", + "originalPath": "", + "liveCSS": true, + "liveImg": true, + "protocol": ["http://livereload.com/protocols/official-7"] + }"#).unwrap(); + }, + Err(e) => { + println!("Failed to build the site"); + println!("Error: {}", e); + for e in e.iter().skip(1) { + println!("Reason: {}", e) + } + } + } + } + } + _ => {} + }, + Err(e) => println!("Watch error: {:?}", e), + }; + } +} + + +/// Returns whether the path we received corresponds to a temp file create +/// by an editor +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, + // byword + x if x.starts_with("sb-") => true, + // gnome + x if x.starts_with(".gooutputstream") => true, + _ => { + if let Some(filename) = path.file_stem() { + // emacs + filename.to_str().unwrap().starts_with("#") + } else { + false + } + } + }, + None => false, + } } diff --git a/src/main.rs b/src/main.rs index 4a52e6ea..80c4ce38 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,6 +16,12 @@ extern crate glob; extern crate syntect; extern crate slug; +extern crate staticfile; +extern crate iron; +extern crate mount; +extern crate notify; +extern crate ws; + use std::time::Instant; @@ -81,10 +87,7 @@ fn main() { let interface = matches.value_of("interface").unwrap_or("127.0.0.1"); let port = matches.value_of("port").unwrap_or("1111"); match cmd::serve(interface, port) { - Ok(()) => { - println!("Project created"); - println!("You will now need to set a theme in `config.toml`"); - }, + Ok(()) => (), Err(e) => { println!("Error: {}", e); ::std::process::exit(1); diff --git a/src/site.rs b/src/site.rs index fe0d229b..79e023ba 100644 --- a/src/site.rs +++ b/src/site.rs @@ -17,16 +17,18 @@ pub struct Site { pages: HashMap<String, Page>, sections: HashMap<String, Vec<String>>, templates: Tera, + live_reload: bool, } impl Site { - pub fn new() -> Result<Site> { + pub fn new(livereload: bool) -> Result<Site> { let tera = Tera::new("templates/**/*").chain_err(|| "Error parsing templates")?; let mut site = Site { config: get_config(), pages: HashMap::new(), sections: HashMap::new(), templates: tera, + live_reload: livereload, }; site.parse_site()?; @@ -51,6 +53,23 @@ impl Site { Ok(()) } + // Inject live reload script tag if in live reload mode + fn inject_livereload(&self, html: String) -> String { + if self.live_reload { + return html.replace( + "</body>", + r#"<script src="/livereload.js?port=1112&mindelay=10"></script></body>"# + ); + } + + html + } + + pub fn rebuild(&mut self) -> Result<()> { + self.parse_site()?; + self.build() + } + /// Builds the site to the `public` directory after deleting it pub fn build(&self) -> Result<()> { if Path::new("public").exists() { @@ -85,8 +104,9 @@ impl Site { // Make sure the folder exists create_dir(¤t_path)?; // Finally, create a index.html file there with the page rendered + let output = page.render_html(&self.templates, &self.config)?; - create_file(current_path.join("index.html"), &output)?; + create_file(current_path.join("index.html"), &self.inject_livereload(output))?; pages.push(page); } @@ -100,7 +120,8 @@ impl Site { let mut context = Context::new(); context.add("pages", &pages); context.add("config", &self.config); - create_file(public.join("index.html"), &self.templates.render("index.html", &context)?)?; + let index = self.templates.render("index.html", &context)?; + create_file(public.join("index.html"), &self.inject_livereload(index))?; Ok(()) } From 4acce865b45e811213b9d7285a4f283bbbf334db Mon Sep 17 00:00:00 2001 From: Vincent Prouillet <vincent@wearewizards.io> Date: Mon, 6 Mar 2017 20:58:31 +0900 Subject: [PATCH 03/26] Sort by date --- Cargo.lock | 1 + Cargo.toml | 1 + src/cmd/serve.rs | 3 +-- src/front_matter.rs | 45 +++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 1 + src/page.rs | 29 +++++++++++++++++++++++++---- src/site.rs | 1 + 7 files changed, 75 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9d42702f..eb9a42a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,7 @@ name = "gutenberg" version = "0.1.0" dependencies = [ + "chrono 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.20.5 (registry+https://github.com/rust-lang/crates.io-index)", "error-chain 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index d68253fb..8ebd7137 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ serde_derive = "0.9" tera = "0.8" slug = "0.1" syntect = "1" +chrono = "0.3" # Below is for the serve cmd staticfile = "0.4" diff --git a/src/cmd/serve.rs b/src/cmd/serve.rs index cb3aeb54..6bca8e30 100644 --- a/src/cmd/serve.rs +++ b/src/cmd/serve.rs @@ -5,7 +5,6 @@ use std::time::Duration; use std::thread; use iron::{Iron, Request, IronResult, Response, status}; -use iron::modifiers::Header; use mount::Mount; use staticfile::Static; use notify::{Watcher, RecursiveMode, watcher}; @@ -17,7 +16,7 @@ use errors::{Result}; const LIVE_RELOAD: &'static [u8; 37809] = include_bytes!("livereload.js"); -fn livereload_handler(req: &mut Request) -> IronResult<Response> { +fn livereload_handler(_: &mut Request) -> IronResult<Response> { Ok(Response::with((status::Ok, String::from_utf8(LIVE_RELOAD.to_vec()).unwrap()))) } diff --git a/src/front_matter.rs b/src/front_matter.rs index e83f4c73..1bf9411c 100644 --- a/src/front_matter.rs +++ b/src/front_matter.rs @@ -3,6 +3,7 @@ use std::collections::HashMap; use toml; use tera::Value; +use chrono::prelude::*; use errors::{Result}; @@ -67,6 +68,19 @@ impl FrontMatter { Ok(f) } + + pub fn parse_date(&self) -> Option<NaiveDateTime> { + match self.date { + Some(ref d) => { + if d.contains("T") { + DateTime::parse_from_rfc3339(d).ok().and_then(|s| Some(s.naive_local())) + } else { + NaiveDate::parse_from_str(d, "%Y-%m-%d").ok().and_then(|s| Some(s.and_hms(0,0,0))) + } + }, + None => None, + } + } } @@ -183,6 +197,7 @@ slug = """#; let res = FrontMatter::parse(content); assert!(res.is_err()); } + #[test] fn test_errors_on_present_but_empty_url() { let content = r#" @@ -192,4 +207,34 @@ url = """#; let res = FrontMatter::parse(content); assert!(res.is_err()); } + + #[test] + fn test_parse_date_yyyy_mm_dd() { + let content = r#" +title = "Hello" +description = "hey there" +date = "2016-10-10""#; + let res = FrontMatter::parse(content).unwrap(); + assert!(res.parse_date().is_some()); + } + + #[test] + fn test_parse_date_rfc3339() { + let content = r#" +title = "Hello" +description = "hey there" +date = "2002-10-02T15:00:00Z""#; + let res = FrontMatter::parse(content).unwrap(); + assert!(res.parse_date().is_some()); + } + + #[test] + fn test_cant_parse_random_date_format() { + let content = r#" +title = "Hello" +description = "hey there" +date = "2002/10/12""#; + let res = FrontMatter::parse(content).unwrap(); + assert!(res.parse_date().is_none()); + } } diff --git a/src/main.rs b/src/main.rs index 80c4ce38..37e8f0c0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,6 +15,7 @@ extern crate tera; extern crate glob; extern crate syntect; extern crate slug; +extern crate chrono; extern crate staticfile; extern crate iron; diff --git a/src/page.rs b/src/page.rs index b763ec06..2d2beb2b 100644 --- a/src/page.rs +++ b/src/page.rs @@ -1,4 +1,5 @@ /// A page, can be a blog post or a basic page +use std::cmp::Ordering; use std::fs::File; use std::io::prelude::*; use std::path::Path; @@ -176,10 +177,30 @@ impl ser::Serialize for Page { } } -// Order pages by date, no-op for now -// TODO: impl PartialOrd on Vec<Page> so we can use sort()? -pub fn order_pages(pages: Vec<Page>) -> Vec<Page> { - pages +impl PartialOrd for Page { + fn partial_cmp(&self, other: &Page) -> Option<Ordering> { + if self.meta.date.is_none() { + println!("No self data"); + return Some(Ordering::Less); + } + + if other.meta.date.is_none() { + println!("No other date"); + return Some(Ordering::Greater); + } + + let this_date = self.meta.parse_date().unwrap(); + let other_date = other.meta.parse_date().unwrap(); + + if this_date > other_date { + return Some(Ordering::Less); + } + if this_date < other_date { + return Some(Ordering::Greater); + } + + Some(Ordering::Equal) + } } diff --git a/src/site.rs b/src/site.rs index 79e023ba..f133e63d 100644 --- a/src/site.rs +++ b/src/site.rs @@ -118,6 +118,7 @@ impl Site { // And finally the index page let mut context = Context::new(); + pages.sort_by(|a, b| a.partial_cmp(b).unwrap()); context.add("pages", &pages); context.add("config", &self.config); let index = self.templates.render("index.html", &context)?; From e5367453d7aa3f1193d16541bbca7911fc71dcea Mon Sep 17 00:00:00 2001 From: Vincent Prouillet <vincent@wearewizards.io> Date: Mon, 6 Mar 2017 22:45:33 +0900 Subject: [PATCH 04/26] Wordcount + reading time --- src/page.rs | 52 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/src/page.rs b/src/page.rs index 2d2beb2b..3227f688 100644 --- a/src/page.rs +++ b/src/page.rs @@ -73,6 +73,7 @@ impl Page { } } + /// Get the URL (without the base URL) to that page pub fn get_url(&self) -> String { if let Some(ref u) = self.meta.url { return u.to_string(); @@ -85,6 +86,16 @@ impl Page { format!("/{}", self.get_slug()) } + // Get word count and estimated reading time + pub fn get_reading_analytics(&self) -> (usize, usize) { + // Only works for latin language but good enough for a start + let word_count: usize = self.raw_content.split_whitespace().count(); + + // https://help.medium.com/hc/en-us/articles/214991667-Read-time + // 275 seems a bit too high though + (word_count, (word_count / 200)) + } + // Parse a page given the content of the .md file // Files without front matter or with invalid front matter are considered // erroneous @@ -149,6 +160,7 @@ impl Page { } } + /// Renders the page using the default layout, unless specified in front-matter pub fn render_html(&self, tera: &Tera, config: &Config) -> Result<String> { let tpl = self.get_layout_name(); let mut context = Context::new(); @@ -162,7 +174,7 @@ impl Page { impl ser::Serialize for Page { fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error> where S: ser::Serializer { - let mut state = serializer.serialize_struct("page", 10)?; + let mut state = serializer.serialize_struct("page", 12)?; state.serialize_field("content", &self.content)?; state.serialize_field("title", &self.meta.title)?; state.serialize_field("description", &self.meta.description)?; @@ -173,6 +185,9 @@ impl ser::Serialize for Page { state.serialize_field("draft", &self.meta.draft)?; state.serialize_field("category", &self.meta.category)?; state.serialize_field("extra", &self.meta.extra)?; + let (word_count, reading_time) = self.get_reading_analytics(); + state.serialize_field("word_count", &word_count)?; + state.serialize_field("reading_time", &reading_time)?; state.end() } } @@ -313,4 +328,39 @@ Hello world"#; let page = res.unwrap(); assert_eq!(page.get_slug(), "file-with-space"); } + + #[test] + fn test_reading_analytics_short() { + let content = r#" ++++ +title = "Hello" +description = "hey there" ++++ +Hello world"#; + let res = Page::parse("file with space.md", content); + assert!(res.is_ok()); + let page = res.unwrap(); + let (word_count, reading_time) = page.get_reading_analytics(); + assert_eq!(word_count, 2); + assert_eq!(reading_time, 0); + } + + #[test] + fn test_reading_analytics_long() { + let mut content = r#" ++++ +title = "Hello" +description = "hey there" ++++ +Hello world"#.to_string(); + for _ in 0..1000 { + content.push_str(" Hello world"); + } + let res = Page::parse("hello.md", &content); + assert!(res.is_ok()); + let page = res.unwrap(); + let (word_count, reading_time) = page.get_reading_analytics(); + assert_eq!(word_count, 2002); + assert_eq!(reading_time, 10); + } } From 434a7fcde8f818e4fc1eae78ebdb55ef163ece4a Mon Sep 17 00:00:00 2001 From: Vincent Prouillet <vincent@wearewizards.io> Date: Mon, 6 Mar 2017 23:45:57 +0900 Subject: [PATCH 05/26] Permalinks and sitemap --- src/config.rs | 15 +++++ src/page.rs | 127 ++++++++++++++++++++++++-------------- src/site.rs | 22 ++++++- src/templates/sitemap.xml | 10 +++ 4 files changed, 125 insertions(+), 49 deletions(-) create mode 100644 src/templates/sitemap.xml diff --git a/src/config.rs b/src/config.rs index 3808da7f..6f514825 100644 --- a/src/config.rs +++ b/src/config.rs @@ -61,6 +61,21 @@ impl Config { } } +impl Default for Config { + /// Exists for testing purposes + fn default() -> Config { + Config { + title: "".to_string(), + base_url: "http://a-website.com/".to_string(), + highlight_code: Some(true), + description: None, + language_code: Some("en".to_string()), + disable_rss: Some(false), + extra: None, + } + } +} + /// Get and parse the config. /// If it doesn't succeed, exit diff --git a/src/page.rs b/src/page.rs index 3227f688..e6c36785 100644 --- a/src/page.rs +++ b/src/page.rs @@ -42,6 +42,15 @@ pub struct Page { pub content: String, /// The front matter meta-data pub meta: FrontMatter, + + /// The slug of that page. + /// First tries to find the slug in the meta and defaults to filename otherwise + pub slug: String, + /// The relative URL of the page + pub url: String, + /// The full URL for that page + pub permalink: String, + /// The previous page, by date pub previous: Option<Box<Page>>, /// The next page, by date @@ -57,35 +66,15 @@ impl Page { sections: vec![], raw_content: "".to_string(), content: "".to_string(), + slug: "".to_string(), + url: "".to_string(), + permalink: "".to_string(), meta: meta, previous: None, next: None, } } - /// Get the slug for the page. - /// First tries to find the slug in the meta and defaults to filename otherwise - pub fn get_slug(&self) -> String { - if let Some(ref slug) = self.meta.slug { - slug.to_string() - } else { - slugify(self.filename.clone()) - } - } - - /// Get the URL (without the base URL) to that page - pub fn get_url(&self) -> String { - if let Some(ref u) = self.meta.url { - return u.to_string(); - } - - if !self.sections.is_empty() { - return format!("/{}/{}", self.sections.join("/"), self.get_slug()); - } - - format!("/{}", self.get_slug()) - } - // Get word count and estimated reading time pub fn get_reading_analytics(&self) -> (usize, usize) { // Only works for latin language but good enough for a start @@ -99,7 +88,7 @@ impl Page { // Parse a page given the content of the .md file // Files without front matter or with invalid front matter are considered // erroneous - pub fn parse(filepath: &str, content: &str) -> Result<Page> { + pub fn parse(filepath: &str, content: &str, config: &Config) -> Result<Page> { // 1. separate front matter from content if !PAGE_RE.is_match(content) { bail!("Couldn't find front matter in `{}`. Did you forget to add `+++`?", filepath); @@ -124,23 +113,43 @@ impl Page { cmark::html::push_html(&mut html, parser); html }; + let path = Path::new(filepath); + page.filename = path.file_stem().expect("Couldn't get filename").to_string_lossy().to_string(); + page.slug = { + if let Some(ref slug) = page.meta.slug { + slug.to_string() + } else { + slugify(page.filename.clone()) + } + }; + // 4. Find sections // Pages with custom urls exists outside of sections - if page.meta.url.is_none() { - let path = Path::new(filepath); - page.filename = path.file_stem().expect("Couldn't get filename").to_string_lossy().to_string(); - + if let Some(ref u) = page.meta.url { + page.url = u.to_string(); + } else { // find out if we have sections for section in path.parent().unwrap().components() { page.sections.push(section.as_ref().to_string_lossy().to_string()); } + + if !page.sections.is_empty() { + page.url = format!("{}/{}", page.sections.join("/"), page.slug); + } else { + page.url = format!("{}", page.slug); + } } + page.permalink = if config.base_url.ends_with("/") { + format!("{}{}", config.base_url, page.url) + } else { + format!("{}/{}", config.base_url, page.url) + }; Ok(page) } - pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Page> { + pub fn from_file<P: AsRef<Path>>(path: P, config: &Config) -> Result<Page> { let path = path.as_ref(); let mut content = String::new(); @@ -150,7 +159,7 @@ impl Page { // Remove the content string from name // Maybe get a path as an arg instead and use strip_prefix? - Page::parse(&path.strip_prefix("content").unwrap().to_string_lossy(), &content) + Page::parse(&path.strip_prefix("content").unwrap().to_string_lossy(), &content, config) } fn get_layout_name(&self) -> String { @@ -174,13 +183,14 @@ impl Page { impl ser::Serialize for Page { fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error> where S: ser::Serializer { - let mut state = serializer.serialize_struct("page", 12)?; + let mut state = serializer.serialize_struct("page", 13)?; state.serialize_field("content", &self.content)?; state.serialize_field("title", &self.meta.title)?; state.serialize_field("description", &self.meta.description)?; state.serialize_field("date", &self.meta.date)?; - state.serialize_field("slug", &self.get_slug())?; - state.serialize_field("url", &self.get_url())?; + state.serialize_field("slug", &self.slug)?; + state.serialize_field("url", &format!("/{}", self.url))?; + state.serialize_field("permalink", &self.permalink)?; state.serialize_field("tags", &self.meta.tags)?; state.serialize_field("draft", &self.meta.draft)?; state.serialize_field("category", &self.meta.category)?; @@ -222,6 +232,7 @@ impl PartialOrd for Page { #[cfg(test)] mod tests { use super::{Page}; + use config::Config; #[test] @@ -233,7 +244,7 @@ description = "hey there" slug = "hello-world" +++ Hello world"#; - let res = Page::parse("post.md", content); + let res = Page::parse("post.md", content, &Config::default()); assert!(res.is_ok()); let page = res.unwrap(); @@ -252,7 +263,7 @@ description = "hey there" slug = "hello-world" +++ Hello world"#; - let res = Page::parse("posts/intro.md", content); + let res = Page::parse("posts/intro.md", content, &Config::default()); assert!(res.is_ok()); let page = res.unwrap(); assert_eq!(page.sections, vec!["posts".to_string()]); @@ -267,7 +278,7 @@ description = "hey there" slug = "hello-world" +++ Hello world"#; - let res = Page::parse("posts/intro/start.md", content); + let res = Page::parse("posts/intro/start.md", content, &Config::default()); assert!(res.is_ok()); let page = res.unwrap(); assert_eq!(page.sections, vec!["posts".to_string(), "intro".to_string()]); @@ -282,14 +293,17 @@ description = "hey there" slug = "hello-world" +++ Hello world"#; - let res = Page::parse("posts/intro/start.md", content); + let mut conf = Config::default(); + conf.base_url = "http://hello.com/".to_string(); + let res = Page::parse("posts/intro/start.md", content, &conf); assert!(res.is_ok()); let page = res.unwrap(); - assert_eq!(page.get_url(), "/posts/intro/hello-world"); + assert_eq!(page.url, "posts/intro/hello-world"); + assert_eq!(page.permalink, "http://hello.com/posts/intro/hello-world"); } #[test] - fn test_can_make_url_from_sections_and_slug_root() { + fn test_can_make_permalink_with_non_trailing_slash_base_url() { let content = r#" +++ title = "Hello" @@ -297,10 +311,30 @@ description = "hey there" slug = "hello-world" +++ Hello world"#; - let res = Page::parse("start.md", content); + let mut conf = Config::default(); + conf.base_url = "http://hello.com".to_string(); + let res = Page::parse("posts/intro/start.md", content, &conf); assert!(res.is_ok()); let page = res.unwrap(); - assert_eq!(page.get_url(), "/hello-world"); + assert_eq!(page.url, "posts/intro/hello-world"); + println!("{}", page.permalink); + assert_eq!(page.permalink, format!("{}{}", conf.base_url, "/posts/intro/hello-world")); + } + + #[test] + fn test_can_make_url_from_slug_only() { + let content = r#" ++++ +title = "Hello" +description = "hey there" +slug = "hello-world" ++++ +Hello world"#; + let res = Page::parse("start.md", content, &Config::default()); + assert!(res.is_ok()); + let page = res.unwrap(); + assert_eq!(page.url, "hello-world"); + assert_eq!(page.permalink, format!("{}{}", Config::default().base_url, "hello-world")); } #[test] @@ -311,7 +345,7 @@ description = "hey there" slug = "hello-world" +++ Hello world"#; - let res = Page::parse("start.md", content); + let res = Page::parse("start.md", content, &Config::default()); assert!(res.is_err()); } @@ -323,10 +357,11 @@ title = "Hello" description = "hey there" +++ Hello world"#; - let res = Page::parse("file with space.md", content); + let res = Page::parse("file with space.md", content, &Config::default()); assert!(res.is_ok()); let page = res.unwrap(); - assert_eq!(page.get_slug(), "file-with-space"); + assert_eq!(page.slug, "file-with-space"); + assert_eq!(page.permalink, format!("{}{}", Config::default().base_url, "file-with-space")); } #[test] @@ -337,7 +372,7 @@ title = "Hello" description = "hey there" +++ Hello world"#; - let res = Page::parse("file with space.md", content); + let res = Page::parse("file with space.md", content, &Config::default()); assert!(res.is_ok()); let page = res.unwrap(); let (word_count, reading_time) = page.get_reading_analytics(); @@ -356,7 +391,7 @@ Hello world"#.to_string(); for _ in 0..1000 { content.push_str(" Hello world"); } - let res = Page::parse("hello.md", &content); + let res = Page::parse("hello.md", &content, &Config::default()); assert!(res.is_ok()); let page = res.unwrap(); let (word_count, reading_time) = page.get_reading_analytics(); diff --git a/src/site.rs b/src/site.rs index f133e63d..a3a26eeb 100644 --- a/src/site.rs +++ b/src/site.rs @@ -41,13 +41,13 @@ impl Site { // First step: do all the articles and group article by sections // hardcoded pattern so can't error for entry in glob("content/**/*.md").unwrap().filter_map(|e| e.ok()) { - let page = Page::from_file(&entry.as_path())?; + let page = Page::from_file(&entry.as_path(), &self.config)?; for section in &page.sections { - self.sections.entry(section.clone()).or_insert(vec![]).push(page.get_slug()); + self.sections.entry(section.clone()).or_insert(vec![]).push(page.slug.clone()); } - self.pages.insert(page.get_slug(), page); + self.pages.insert(page.slug.clone(), page); } Ok(()) @@ -65,6 +65,8 @@ impl Site { html } + /// Re-parse and re-generate the site + /// Very dumb for now, ideally it would only rebuild what changed pub fn rebuild(&mut self) -> Result<()> { self.parse_site()?; self.build() @@ -124,6 +126,20 @@ impl Site { let index = self.templates.render("index.html", &context)?; create_file(public.join("index.html"), &self.inject_livereload(index))?; + self.render_sitemap()?; + + Ok(()) + } + + pub fn render_sitemap(&self) -> Result<()> { + let tpl = String::from_utf8(include_bytes!("templates/sitemap.xml").to_vec()).unwrap(); + let mut context = Context::new(); + context.add("pages", &self.pages.values().collect::<Vec<&Page>>()); + let sitemap = Tera::one_off(&tpl, &context, false)?; + + let public = Path::new("public"); + create_file(public.join("sitemap.xml"), &sitemap)?; + Ok(()) } } diff --git a/src/templates/sitemap.xml b/src/templates/sitemap.xml new file mode 100644 index 00000000..0dadd5c4 --- /dev/null +++ b/src/templates/sitemap.xml @@ -0,0 +1,10 @@ +<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> + {% for page in pages %} + <url> + <loc>{{ page.permalink }}</loc> + {% if page.date %} + <lastmod>{{ page.date }}</lastmod> + {% endif %} + </url> + {% endfor %} +</urlset> From 4f1f73fe563b9253adbca15df5da876c0dca74b5 Mon Sep 17 00:00:00 2001 From: Vincent Prouillet <vincent@wearewizards.io> Date: Tue, 7 Mar 2017 12:42:14 +0900 Subject: [PATCH 06/26] Add summary --- src/page.rs | 59 +++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 53 insertions(+), 6 deletions(-) diff --git a/src/page.rs b/src/page.rs index e6c36785..52da0a72 100644 --- a/src/page.rs +++ b/src/page.rs @@ -19,6 +19,14 @@ use front_matter::{FrontMatter}; lazy_static! { static ref PAGE_RE: Regex = Regex::new(r"^\n?\+\+\+\n((?s).*(?-s))\+\+\+\n((?s).*(?-s))$").unwrap(); + static ref SUMMARY_RE: Regex = Regex::new(r"<!-- more -->").unwrap(); +} + +fn markdown_to_html(content: &str) -> String { + let mut html = String::new(); + let parser = cmark::Parser::new(content); + cmark::html::push_html(&mut html, parser); + html } @@ -50,6 +58,10 @@ pub struct Page { pub url: String, /// The full URL for that page pub permalink: String, + /// The summary for the article, defaults to empty string + /// When <!-- more --> is found in the text, will take the content up to that part + /// as summary + pub summary: String, /// The previous page, by date pub previous: Option<Box<Page>>, @@ -69,6 +81,7 @@ impl Page { slug: "".to_string(), url: "".to_string(), permalink: "".to_string(), + summary: "".to_string(), meta: meta, previous: None, next: None, @@ -107,12 +120,16 @@ impl Page { let mut page = Page::new(meta); page.filepath = filepath.to_string(); page.raw_content = content.to_string(); - page.content = { - let mut html = String::new(); - let parser = cmark::Parser::new(&page.raw_content); - cmark::html::push_html(&mut html, parser); - html - }; + page.content = markdown_to_html(&page.raw_content); + + + if page.raw_content.contains("<!-- more -->") { + page.summary = { + let summary = SUMMARY_RE.split(&page.raw_content).collect::<Vec<&str>>()[0]; + markdown_to_html(summary) + } + } + let path = Path::new(filepath); page.filename = path.file_stem().expect("Couldn't get filename").to_string_lossy().to_string(); page.slug = { @@ -398,4 +415,34 @@ Hello world"#.to_string(); assert_eq!(word_count, 2002); assert_eq!(reading_time, 10); } + + #[test] + fn test_automatic_summary_is_empty_string() { + let content = r#" ++++ +title = "Hello" +description = "hey there" ++++ +Hello world"#.to_string(); + let res = Page::parse("hello.md", &content, &Config::default()); + assert!(res.is_ok()); + let page = res.unwrap(); + assert_eq!(page.summary, ""); + } + + #[test] + fn test_can_specify_summary() { + let content = r#" ++++ +title = "Hello" +description = "hey there" ++++ +Hello world +<!-- more --> +"#.to_string(); + let res = Page::parse("hello.md", &content, &Config::default()); + assert!(res.is_ok()); + let page = res.unwrap(); + assert_eq!(page.summary, "<p>Hello world</p>\n"); + } } From fa7fc6e49b3cc0bda084c9aa6cbc819090c04f43 Mon Sep 17 00:00:00 2001 From: Vincent Prouillet <vincent@wearewizards.io> Date: Tue, 7 Mar 2017 15:01:20 +0900 Subject: [PATCH 07/26] Render categories and tags --- src/site.rs | 99 +++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 93 insertions(+), 6 deletions(-) diff --git a/src/site.rs b/src/site.rs index a3a26eeb..b562b883 100644 --- a/src/site.rs +++ b/src/site.rs @@ -1,9 +1,11 @@ use std::collections::HashMap; +use std::iter::FromIterator; use std::fs::{create_dir, remove_dir_all}; use std::path::Path; use glob::glob; use tera::{Tera, Context}; +use slug::slugify; use errors::{Result, ResultExt}; use config::{Config, get_config}; @@ -11,6 +13,31 @@ use page::Page; use utils::create_file; +#[derive(Debug, PartialEq)] +enum RenderList { + Tags, + Categories, +} + +/// A tag or category +#[derive(Debug, Serialize, PartialEq)] +struct ListItem { + name: String, + slug: String, + count: usize, +} + +impl ListItem { + pub fn new(name: &str, count: usize) -> ListItem { + ListItem { + name: name.to_string(), + slug: slugify(name), + count: count, + } + } +} + + #[derive(Debug)] pub struct Site { config: Config, @@ -84,6 +111,8 @@ impl Site { let public = Path::new("public"); let mut pages = vec![]; + let mut category_pages: HashMap<String, Vec<&Page>> = HashMap::new(); + let mut tag_pages: HashMap<String, Vec<&Page>> = HashMap::new(); // First we render the pages themselves for page in self.pages.values() { // Copy the nesting of the content directory if we have sections for that page @@ -106,17 +135,23 @@ impl Site { // Make sure the folder exists create_dir(¤t_path)?; // Finally, create a index.html file there with the page rendered - let output = page.render_html(&self.templates, &self.config)?; create_file(current_path.join("index.html"), &self.inject_livereload(output))?; pages.push(page); + + if let Some(ref category) = page.meta.category { + category_pages.entry(category.to_string()).or_insert(vec![]).push(page); + } + if let Some(ref tags) = page.meta.tags { + for tag in tags { + tag_pages.entry(tag.to_string()).or_insert(vec![]).push(page); + } + } } - // Then the section pages - // The folders have already been created in the page loop so no need to `create_dir` here -// for (section, slugs) in &self.sections { -// // TODO -// } + // Outputting categories and pages + self.render_categories_and_tags(RenderList::Categories, &category_pages)?; + self.render_categories_and_tags(RenderList::Tags, &tag_pages)?; // And finally the index page let mut context = Context::new(); @@ -127,6 +162,58 @@ impl Site { create_file(public.join("index.html"), &self.inject_livereload(index))?; self.render_sitemap()?; + // TODO: render rss feed + + Ok(()) + } + /// Render the /{categories, list} pages and each individual category/tag page + fn render_categories_and_tags(&self, kind: RenderList, container: &HashMap<String, Vec<&Page>>) -> Result<()> { + if container.is_empty() { + return Ok(()); + } + + let (name, list_tpl_name, single_tpl_name, var_name) = if kind == RenderList::Categories { + ("categories", "categories.html", "category.html", "category") + } else { + ("tags", "tags.html", "tag.html", "tag") + }; + + let public = Path::new("public"); + let mut output_path = public.to_path_buf(); + output_path.push(name); + create_dir(&output_path)?; + + // First we render the list of categories/tags page + let mut sorted_container = vec![]; + for (item, count) in Vec::from_iter(container).into_iter().map(|(a, b)| (a, b.len())) { + sorted_container.push(ListItem::new(item, count)); + } + sorted_container.sort_by(|a, b| b.count.cmp(&a.count)); + + let mut context = Context::new(); + context.add(name, &sorted_container); + context.add("config", &self.config); + + let list_output = self.templates.render(list_tpl_name, &context)?; + create_file(output_path.join("index.html"), &self.inject_livereload(list_output))?; + + // and then each individual item + for (item_name, mut pages) in container.clone() { + let mut context = Context::new(); + pages.sort_by(|a, b| a.partial_cmp(b).unwrap()); + let slug = slugify(&item_name); + context.add(var_name, &item_name); + context.add(&format!("{}_slug", var_name), &slug); + context.add("pages", &pages); + context.add("config", &self.config); + let single_output = self.templates.render(single_tpl_name, &context)?; + + create_dir(&output_path.join(&slug))?; + create_file( + output_path.join(&slug).join("index.html"), + &self.inject_livereload(single_output) + )?; + } Ok(()) } From d43d738a4faf7fa146532b1a31830a07803a7e87 Mon Sep 17 00:00:00 2001 From: Vincent Prouillet <vincent@wearewizards.io> Date: Tue, 7 Mar 2017 16:43:27 +0900 Subject: [PATCH 08/26] RSS feed generation --- Cargo.lock | 26 +++++++++++++------------- Cargo.toml | 4 ++-- src/page.rs | 19 +++++++++++++++++-- src/site.rs | 41 ++++++++++++++++++++++++++++++++++++++--- src/templates/rss.xml | 20 ++++++++++++++++++++ 5 files changed, 90 insertions(+), 20 deletions(-) create mode 100644 src/templates/rss.xml diff --git a/Cargo.lock b/Cargo.lock index eb9a42a5..15104770 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,7 +18,7 @@ dependencies = [ "slug 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "staticfile 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "syntect 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "tera 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tera 0.8.0 (git+https://github.com/Keats/tera)", "toml 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", "ws 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -388,7 +388,7 @@ dependencies = [ "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "miow 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "net2 0.2.26 (registry+https://github.com/rust-lang/crates.io-index)", + "net2 0.2.27 (registry+https://github.com/rust-lang/crates.io-index)", "nix 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "slab 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)", @@ -405,7 +405,7 @@ dependencies = [ "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "miow 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "net2 0.2.26 (registry+https://github.com/rust-lang/crates.io-index)", + "net2 0.2.27 (registry+https://github.com/rust-lang/crates.io-index)", "slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -416,7 +416,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "net2 0.2.26 (registry+https://github.com/rust-lang/crates.io-index)", + "net2 0.2.27 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -427,7 +427,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "net2 0.2.26 (registry+https://github.com/rust-lang/crates.io-index)", + "net2 0.2.27 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -448,7 +448,7 @@ dependencies = [ [[package]] name = "net2" -version = "0.2.26" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -672,7 +672,7 @@ name = "serde_codegen_internals" version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "syn 0.11.8 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.11.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -682,7 +682,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", "serde_codegen_internals 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.11.8 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.11.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -737,7 +737,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "syn" -version = "0.11.8" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", @@ -774,7 +774,7 @@ dependencies = [ [[package]] name = "tera" version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" +source = "git+https://github.com/Keats/tera#2eb55de231e08da50ca32a443499fbce8d9003e0" dependencies = [ "chrono 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "error-chain 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1055,7 +1055,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum miow 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3a78d2605eb97302c10cf944b8d96b0a2a890c52957caf92fcd1f24f69049579" "checksum modifier 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "41f5c9112cb662acd3b204077e0de5bc66305fa8df65c8019d5adb10e9ab6e58" "checksum mount 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "32245731923cd096899502fc4c4317cfd09f121e80e73f7f576cf3777a824256" -"checksum net2 0.2.26 (registry+https://github.com/rust-lang/crates.io-index)" = "5edf9cb6be97212423aed9413dd4729d62b370b5e1c571750e882cebbbc1e3e2" +"checksum net2 0.2.27 (registry+https://github.com/rust-lang/crates.io-index)" = "18b9642ad6222faf5ce46f6966f59b71b9775ad5758c9e09fcf0a6c8061972b4" "checksum nix 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bfb3ddedaa14746434a02041940495bf11325c22f6d36125d3bdd56090d50a79" "checksum notify 4.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "87c628262cc8a99209596ec610dcf199d6876a10cfa22e355acc5936d6f1fa63" "checksum num 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "98b15ba84e910ea7a1973bccd3df7b31ae282bf9d8bd2897779950c9b8303d40" @@ -1092,10 +1092,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum slug 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f5ff4b43cb07b86c5f9236c92714a22cdf9e5a27a7d85e398e2c9403328cb8" "checksum staticfile 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "31493480e073d52522a94cdf56269dd8eb05f99549effd1826b0271690608878" "checksum strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b4d15c810519a91cf877e7e36e63fe068815c678181439f2f29e2562147c3694" -"checksum syn 0.11.8 (registry+https://github.com/rust-lang/crates.io-index)" = "37c279fb816210c9bb28b2c292664581e7b87b4561e86b94df462664d8620bb8" +"checksum syn 0.11.9 (registry+https://github.com/rust-lang/crates.io-index)" = "480c834701caba3548aa991e54677281be3a5414a9d09ddbdf4ed74a569a9d19" "checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" "checksum syntect 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3f79be04af68d5fa09e71b3274159a955a25f25a5cbfba9a6ff64139b71d848a" -"checksum tera 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb775e4a7318e080e9c3000b150f720caf5825b66504f56f358df35b74fd0c14" +"checksum tera 0.8.0 (git+https://github.com/Keats/tera)" = "<none>" "checksum term_size 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "07b6c1ac5b3fffd75073276bca1ceed01f67a28537097a2a9539e116e50fb21a" "checksum thread-id 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4437c97558c70d129e40629a5b385b3fb1ffac301e63941335e4d354081ec14a" "checksum thread_local 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c85048c6260d17cf486ceae3282d9fb6b90be220bf5b28c400f5485ffc29f0c7" diff --git a/Cargo.toml b/Cargo.toml index 8ebd7137..f381c61e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,8 +20,8 @@ glob = "0.2" serde = "0.9" serde_json = "0.9" serde_derive = "0.9" -# tera = { git = "https://github.com/Keats/tera", branch = "next" } -tera = "0.8" +tera = { git = "https://github.com/Keats/tera", branch = "master" } +# tera = "0.8" slug = "0.1" syntect = "1" chrono = "0.3" diff --git a/src/page.rs b/src/page.rs index 52da0a72..a4c4d9c1 100644 --- a/src/page.rs +++ b/src/page.rs @@ -134,7 +134,7 @@ impl Page { page.filename = path.file_stem().expect("Couldn't get filename").to_string_lossy().to_string(); page.slug = { if let Some(ref slug) = page.meta.slug { - slug.to_string() + slug.trim().to_string() } else { slugify(page.filename.clone()) } @@ -144,7 +144,7 @@ impl Page { // 4. Find sections // Pages with custom urls exists outside of sections if let Some(ref u) = page.meta.url { - page.url = u.to_string(); + page.url = u.trim().to_string(); } else { // find out if we have sections for section in path.parent().unwrap().components() { @@ -381,6 +381,21 @@ Hello world"#; assert_eq!(page.permalink, format!("{}{}", Config::default().base_url, "file-with-space")); } + #[test] + fn test_trim_slug_if_needed() { + let content = r#" ++++ +title = "Hello" +description = "hey there" ++++ +Hello world"#; + let res = Page::parse(" file with space.md", content, &Config::default()); + assert!(res.is_ok()); + let page = res.unwrap(); + assert_eq!(page.slug, "file-with-space"); + assert_eq!(page.permalink, format!("{}{}", Config::default().base_url, "file-with-space")); + } + #[test] fn test_reading_analytics_short() { let content = r#" diff --git a/src/site.rs b/src/site.rs index b562b883..499ae54d 100644 --- a/src/site.rs +++ b/src/site.rs @@ -153,6 +153,9 @@ impl Site { self.render_categories_and_tags(RenderList::Categories, &category_pages)?; self.render_categories_and_tags(RenderList::Tags, &tag_pages)?; + self.render_sitemap()?; + self.render_rss_feed()?; + // And finally the index page let mut context = Context::new(); pages.sort_by(|a, b| a.partial_cmp(b).unwrap()); @@ -161,8 +164,6 @@ impl Site { let index = self.templates.render("index.html", &context)?; create_file(public.join("index.html"), &self.inject_livereload(index))?; - self.render_sitemap()?; - // TODO: render rss feed Ok(()) } @@ -218,7 +219,7 @@ impl Site { Ok(()) } - pub fn render_sitemap(&self) -> Result<()> { + fn render_sitemap(&self) -> Result<()> { let tpl = String::from_utf8(include_bytes!("templates/sitemap.xml").to_vec()).unwrap(); let mut context = Context::new(); context.add("pages", &self.pages.values().collect::<Vec<&Page>>()); @@ -229,4 +230,38 @@ impl Site { Ok(()) } + + fn get_rss_feed_url(&self) -> String { + if self.config.base_url.ends_with("/") { + format!("{}{}", self.config.base_url, "feed.xml") + } else { + format!("{}/{}", self.config.base_url, "feed.xml") + } + } + + fn render_rss_feed(&self) -> Result<()> { + let tpl = String::from_utf8(include_bytes!("templates/rss.xml").to_vec()).unwrap(); + let mut context = Context::new(); + let mut pages = self.pages.values() + .filter(|p| p.meta.date.is_some()) + .take(15) // limit to the last 15 elements + .collect::<Vec<&Page>>(); + + // Don't generate a RSS feed if none of the pages has a date + if pages.is_empty() { + return Ok(()); + } + pages.sort_by(|a, b| a.partial_cmp(b).unwrap()); + context.add("pages", &pages); + context.add("last_build_date", &pages[0].meta.date); + context.add("config", &self.config); + context.add("feed_url", &self.get_rss_feed_url()); + + let sitemap = Tera::one_off(&tpl, &context, false)?; + + let public = Path::new("public"); + create_file(public.join("rss.xml"), &sitemap)?; + + Ok(()) + } } diff --git a/src/templates/rss.xml b/src/templates/rss.xml new file mode 100644 index 00000000..53aae2f5 --- /dev/null +++ b/src/templates/rss.xml @@ -0,0 +1,20 @@ +<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"> + <channel> + <title>{{ config.title }} + {{ config.base_url }} + {{ config.description }} + Gutenberg + {{ config.language_code }} + + {{ last_build_date | 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 }} + {{ page.permalink }} + "{{ page.content | escape }}" + + {% endfor %} + + From a99f084ee29cb411a429bbfe1149d89fb5f19865 Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Tue, 7 Mar 2017 21:34:31 +0900 Subject: [PATCH 09/26] Code highlighting --- src/cmd/serve.rs | 4 +- src/config.rs | 2 +- src/main.rs | 1 + src/markdown.rs | 150 +++++++++++++++++++++++++++++++++++++++++++++++ src/page.rs | 13 +--- 5 files changed, 158 insertions(+), 12 deletions(-) create mode 100644 src/markdown.rs diff --git a/src/cmd/serve.rs b/src/cmd/serve.rs index 6bca8e30..9c8c15a5 100644 --- a/src/cmd/serve.rs +++ b/src/cmd/serve.rs @@ -33,7 +33,9 @@ pub fn serve(interface: &str, port: &str) -> Result<()> { let mut mount = Mount::new(); mount.mount("/", Static::new(Path::new("public/"))); mount.mount("/livereload.js", livereload_handler); - let server = Iron::new(mount).http(address.clone()).unwrap(); + // Starts with a _ to not trigger the unused lint + // we need to assign to a variable otherwise it will block + let _iron = Iron::new(mount).http(address.clone()).unwrap(); println!("Web server is available at http://{}", address); println!("Press CTRL+C to stop"); diff --git a/src/config.rs b/src/config.rs index 6f514825..d7e0c257 100644 --- a/src/config.rs +++ b/src/config.rs @@ -7,7 +7,7 @@ use toml::{Value as Toml, self}; use errors::{Result, ResultExt}; -// TODO: disable tag(s)/category(ies) page generation + #[derive(Debug, PartialEq, Serialize, Deserialize)] pub struct Config { /// Title of the site diff --git a/src/main.rs b/src/main.rs index 37e8f0c0..bf4f79f6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,6 +33,7 @@ mod cmd; mod page; mod front_matter; mod site; +mod markdown; fn main() { diff --git a/src/markdown.rs b/src/markdown.rs new file mode 100644 index 00000000..f1fe3bbf --- /dev/null +++ b/src/markdown.rs @@ -0,0 +1,150 @@ +use std::borrow::Cow::Owned; + +use pulldown_cmark as cmark; +use self::cmark::{Parser, Event, Tag}; + +use syntect::easy::HighlightLines; +use syntect::parsing::SyntaxSet; +use syntect::highlighting::ThemeSet; +use syntect::html::{start_coloured_html_snippet, styles_to_coloured_html, IncludeBackground}; + + +// We need to put those in a struct to impl Send and sync +struct Setup { + syntax_set: SyntaxSet, + theme_set: ThemeSet, +} + +unsafe impl Send for Setup {} +unsafe impl Sync for Setup {} + +lazy_static!{ + static ref SETUP: Setup = Setup { + syntax_set: SyntaxSet::load_defaults_newlines(), + theme_set: ThemeSet::load_defaults() + }; +} + + +struct CodeHighlightingParser<'a> { + // The block we're currently highlighting + highlighter: Option>, + parser: Parser<'a>, +} + +impl<'a> CodeHighlightingParser<'a> { + pub fn new(parser: Parser<'a>) -> CodeHighlightingParser<'a> { + CodeHighlightingParser { + highlighter: None, + parser: parser, + } + } +} + +impl<'a> Iterator for CodeHighlightingParser<'a> { + type Item = Event<'a>; + + fn next(&mut self) -> Option> { + // Not using pattern matching to reduce indentation levels + let next_opt = self.parser.next(); + if next_opt.is_none() { + return None; + } + + let item = next_opt.unwrap(); + // Below we just look for the start of a code block and highlight everything + // until we see the end of a code block. + // Everything else happens as normal in pulldown_cmark + match item { + Event::Text(text) => { + // if we are in the middle of a code block + if let Some(ref mut highlighter) = self.highlighter { + let highlighted = &highlighter.highlight(&text); + let html = styles_to_coloured_html(highlighted, IncludeBackground::Yes); + Some(Event::Html(Owned(html))) + } else { + Some(Event::Text(text)) + } + }, + Event::Start(Tag::CodeBlock(ref info)) => { + let syntax = info + .split(' ') + .next() + .and_then(|lang| SETUP.syntax_set.find_syntax_by_token(lang)) + .unwrap_or_else(|| SETUP.syntax_set.find_syntax_plain_text()); + self.highlighter = Some( + HighlightLines::new(&syntax, &SETUP.theme_set.themes["base16-ocean.dark"]) + ); + let snippet = start_coloured_html_snippet(&SETUP.theme_set.themes["base16-ocean.dark"]); + Some(Event::Html(Owned(snippet))) + }, + Event::End(Tag::CodeBlock(_)) => { + // reset highlight and close the code block + self.highlighter = None; + Some(Event::Html(Owned("".to_owned()))) + }, + _ => Some(item) + } + + } +} + +pub fn markdown_to_html(content: &str, highlight_code: bool) -> String { + let mut html = String::new(); + if highlight_code { + let parser = CodeHighlightingParser::new(Parser::new(content)); + cmark::html::push_html(&mut html, parser); + } else { + let parser = Parser::new(content); + cmark::html::push_html(&mut html, parser); + }; + html +} + + +#[cfg(test)] +mod tests { + use super::{markdown_to_html}; + + #[test] + fn test_markdown_to_html_simple() { + let res = markdown_to_html("# hello", true); + assert_eq!(res, "

hello

\n"); + } + + #[test] + fn test_markdown_to_html_code_block_highlighting_off() { + let res = markdown_to_html("```\n$ gutenberg server\n```", false); + assert_eq!( + res, + "
$ gutenberg server\n
\n" + ); + } + + #[test] + fn test_markdown_to_html_code_block_no_lang() { + let res = markdown_to_html("```\n$ gutenberg server\n$ ping\n```", true); + assert_eq!( + res, + "
\n$ gutenberg server\n$ ping\n
" + ); + } + + #[test] + fn test_markdown_to_html_code_block_with_lang() { + let res = markdown_to_html("```python\nlist.append(1)\n```", true); + assert_eq!( + res, + "
\nlist.append(1)\n
" + ); + } + #[test] + fn test_markdown_to_html_code_block_with_unknown_lang() { + let res = markdown_to_html("```yolo\nlist.append(1)\n```", true); + // defaults to plain text + assert_eq!( + res, + "
\nlist.append(1)\n
" + ); + } +} diff --git a/src/page.rs b/src/page.rs index a4c4d9c1..0722c82b 100644 --- a/src/page.rs +++ b/src/page.rs @@ -6,7 +6,6 @@ use std::path::Path; use std::result::Result as StdResult; -use pulldown_cmark as cmark; use regex::Regex; use tera::{Tera, Context}; use serde::ser::{SerializeStruct, self}; @@ -15,6 +14,7 @@ use slug::slugify; use errors::{Result, ResultExt}; use config::Config; use front_matter::{FrontMatter}; +use markdown::markdown_to_html; lazy_static! { @@ -22,13 +22,6 @@ lazy_static! { static ref SUMMARY_RE: Regex = Regex::new(r"").unwrap(); } -fn markdown_to_html(content: &str) -> String { - let mut html = String::new(); - let parser = cmark::Parser::new(content); - cmark::html::push_html(&mut html, parser); - html -} - #[derive(Clone, Debug, PartialEq, Deserialize)] pub struct Page { @@ -120,13 +113,13 @@ impl Page { let mut page = Page::new(meta); page.filepath = filepath.to_string(); page.raw_content = content.to_string(); - page.content = markdown_to_html(&page.raw_content); + page.content = markdown_to_html(&page.raw_content, config.highlight_code.unwrap()); if page.raw_content.contains("") { page.summary = { let summary = SUMMARY_RE.split(&page.raw_content).collect::>()[0]; - markdown_to_html(summary) + markdown_to_html(summary, config.highlight_code.unwrap()) } } From e68af48eb6ac0403f7cc73d0be52a81054aa0441 Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Wed, 8 Mar 2017 09:13:50 +0900 Subject: [PATCH 10/26] Make gutenberg a lib as well --- README.md | 3 + src/cmd/build.rs | 4 +- src/cmd/init.rs | 4 +- src/cmd/serve.rs | 4 +- src/lib.rs | 30 + src/main.rs | 22 +- sublime_themes/base16-ocean-dark.tmTheme | 593 +++++++ sublime_themes/base16-ocean-light.tmTheme | 589 +++++++ sublime_themes/gruvbox-dark.tmTheme | 766 +++++++++ sublime_themes/gruvbox-light.tmTheme | 774 +++++++++ sublime_themes/inspired-github.tmTheme | 1725 +++++++++++++++++++ sublime_themes/kronuz.tmTheme | 1843 +++++++++++++++++++++ sublime_themes/material-dark.tmTheme | 1011 +++++++++++ sublime_themes/material-light.tmTheme | 1011 +++++++++++ sublime_themes/monokai.tmTheme | 297 ++++ sublime_themes/solarized-dark.tmTheme | 1189 +++++++++++++ sublime_themes/solarized-light.tmTheme | 1189 +++++++++++++ 17 files changed, 11027 insertions(+), 27 deletions(-) create mode 100644 src/lib.rs create mode 100644 sublime_themes/base16-ocean-dark.tmTheme create mode 100644 sublime_themes/base16-ocean-light.tmTheme create mode 100644 sublime_themes/gruvbox-dark.tmTheme create mode 100644 sublime_themes/gruvbox-light.tmTheme create mode 100644 sublime_themes/inspired-github.tmTheme create mode 100644 sublime_themes/kronuz.tmTheme create mode 100644 sublime_themes/material-dark.tmTheme create mode 100644 sublime_themes/material-light.tmTheme create mode 100644 sublime_themes/monokai.tmTheme create mode 100644 sublime_themes/solarized-dark.tmTheme create mode 100644 sublime_themes/solarized-light.tmTheme diff --git a/README.md b/README.md index 4656336d..be241c9d 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,9 @@ Split the file between front matter and content Parse the front matter markdown -> HTML for the content +### Themes +Gallery at https://tmtheme-editor.herokuapp.com/#!/editor/theme/Agola%20Dark + # TODO: - syntax highlighting diff --git a/src/cmd/build.rs b/src/cmd/build.rs index 50a1bc66..a20459ca 100644 --- a/src/cmd/build.rs +++ b/src/cmd/build.rs @@ -1,5 +1,5 @@ -use errors::Result; -use site::Site; +use gutenberg::errors::Result; +use gutenberg::Site; pub fn build() -> Result<()> { diff --git a/src/cmd/init.rs b/src/cmd/init.rs index 6b69133d..210f06e2 100644 --- a/src/cmd/init.rs +++ b/src/cmd/init.rs @@ -2,8 +2,8 @@ use std::fs::{create_dir}; use std::path::Path; -use errors::Result; -use utils::create_file; +use gutenberg::errors::Result; +use gutenberg::create_file; const CONFIG: &'static str = r#" diff --git a/src/cmd/serve.rs b/src/cmd/serve.rs index 9c8c15a5..00b82e7b 100644 --- a/src/cmd/serve.rs +++ b/src/cmd/serve.rs @@ -10,8 +10,8 @@ use staticfile::Static; use notify::{Watcher, RecursiveMode, watcher}; use ws::{WebSocket}; -use site::Site; -use errors::{Result}; +use gutenberg::Site; +use gutenberg::errors::{Result}; const LIVE_RELOAD: &'static [u8; 37809] = include_bytes!("livereload.js"); diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 00000000..81d1df9b --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,30 @@ +#[macro_use] +extern crate error_chain; +#[macro_use] +extern crate lazy_static; +#[macro_use] +extern crate serde_derive; +extern crate serde; +extern crate toml; +extern crate walkdir; +extern crate pulldown_cmark; +extern crate regex; +extern crate tera; +extern crate glob; +extern crate syntect; +extern crate slug; +extern crate chrono; + +mod utils; +mod config; +pub mod errors; +mod page; +mod front_matter; +mod site; +mod markdown; + +pub use site::Site; +pub use config::Config; +pub use front_matter::FrontMatter; +pub use page::Page; +pub use utils::create_file; diff --git a/src/main.rs b/src/main.rs index bf4f79f6..ca1bded6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,20 +2,7 @@ extern crate clap; #[macro_use] extern crate error_chain; -#[macro_use] -extern crate lazy_static; -#[macro_use] -extern crate serde_derive; -extern crate serde; -extern crate toml; -extern crate walkdir; -extern crate pulldown_cmark; -extern crate regex; -extern crate tera; -extern crate glob; -extern crate syntect; -extern crate slug; -extern crate chrono; +extern crate gutenberg; extern crate staticfile; extern crate iron; @@ -26,14 +13,7 @@ extern crate ws; use std::time::Instant; -mod utils; -mod config; -mod errors; mod cmd; -mod page; -mod front_matter; -mod site; -mod markdown; fn main() { diff --git a/sublime_themes/base16-ocean-dark.tmTheme b/sublime_themes/base16-ocean-dark.tmTheme new file mode 100644 index 00000000..19b34166 --- /dev/null +++ b/sublime_themes/base16-ocean-dark.tmTheme @@ -0,0 +1,593 @@ + + + + + author + Chris Kempson (http://chriskempson.com) + name + Base16 Ocean Dark + semanticClass + base16.ocean.dark + colorSpaceName + sRGB + gutterSettings + + background + #343d46 + divider + #343d46 + foreground + #65737e + selectionBackground + #4f5b66 + selectionForeground + #a7adba + + settings + + + settings + + background + #2b303b + caret + #c0c5ce + foreground + #c0c5ce + invisibles + #65737e + lineHighlight + #65737e30 + selection + #4f5b66 + guide + #3b5364 + activeGuide + #96b5b4 + stackGuide + #343d46 + + + + name + Text + scope + variable.parameter.function + settings + + foreground + #c0c5ce + + + + name + Comments + scope + comment, punctuation.definition.comment + settings + + foreground + #65737e + + + + name + Punctuation + scope + punctuation.definition.string, punctuation.definition.variable, punctuation.definition.string, punctuation.definition.parameters, punctuation.definition.string, punctuation.definition.array + settings + + foreground + #c0c5ce + + + + name + Delimiters + scope + none + settings + + foreground + #c0c5ce + + + + name + Operators + scope + keyword.operator + settings + + foreground + #c0c5ce + + + + name + Keywords + scope + keyword + settings + + foreground + #b48ead + + + + name + Variables + scope + variable + settings + + foreground + #bf616a + + + + name + Functions + scope + entity.name.function, meta.require, support.function.any-method + settings + + foreground + #8fa1b3 + + + + name + Classes + scope + support.class, entity.name.class, entity.name.type.class + settings + + foreground + #ebcb8b + + + + name + Classes + scope + meta.class + settings + + foreground + #eff1f5 + + + + name + Methods + scope + keyword.other.special-method + settings + + foreground + #8fa1b3 + + + + name + Storage + scope + storage + settings + + foreground + #b48ead + + + + name + Support + scope + support.function + settings + + foreground + #96b5b4 + + + + name + Strings, Inherited Class + scope + string, constant.other.symbol, entity.other.inherited-class + settings + + foreground + #a3be8c + + + + name + Integers + scope + constant.numeric + settings + + foreground + #d08770 + + + + name + Floats + scope + none + settings + + foreground + #d08770 + + + + name + Boolean + scope + none + settings + + foreground + #d08770 + + + + name + Constants + scope + constant + settings + + foreground + #d08770 + + + + name + Tags + scope + entity.name.tag + settings + + foreground + #bf616a + + + + name + Attributes + scope + entity.other.attribute-name + settings + + foreground + #d08770 + + + + name + Attribute IDs + scope + entity.other.attribute-name.id, punctuation.definition.entity + settings + + foreground + #8fa1b3 + + + + name + Selector + scope + meta.selector + settings + + foreground + #b48ead + + + + name + Values + scope + none + settings + + foreground + #d08770 + + + + name + Headings + scope + markup.heading punctuation.definition.heading, entity.name.section + settings + + fontStyle + + foreground + #8fa1b3 + + + + name + Units + scope + keyword.other.unit + settings + + foreground + #d08770 + + + + name + Bold + scope + markup.bold, punctuation.definition.bold + settings + + fontStyle + bold + foreground + #ebcb8b + + + + name + Italic + scope + markup.italic, punctuation.definition.italic + settings + + fontStyle + italic + foreground + #b48ead + + + + name + Code + scope + markup.raw.inline + settings + + foreground + #a3be8c + + + + name + Link Text + scope + string.other.link + settings + + foreground + #bf616a + + + + name + Link Url + scope + meta.link + settings + + foreground + #d08770 + + + + name + Lists + scope + markup.list + settings + + foreground + #bf616a + + + + name + Quotes + scope + markup.quote + settings + + foreground + #d08770 + + + + name + Separator + scope + meta.separator + settings + + background + #4f5b66 + foreground + #c0c5ce + + + + name + Inserted + scope + markup.inserted, markup.inserted.git_gutter + settings + + foreground + #a3be8c + + + + name + Deleted + scope + markup.deleted, markup.deleted.git_gutter + settings + + foreground + #bf616a + + + + name + Changed + scope + markup.changed, markup.changed.git_gutter + settings + + foreground + #b48ead + + + + name + Ignored + scope + markup.ignored, markup.ignored.git_gutter + settings + + foreground + #4f5b66 + + + + name + Untracked + scope + markup.untracked, markup.untracked.git_gutter + settings + + foreground + #4f5b66 + + + + name + Colors + scope + constant.other.color + settings + + foreground + #96b5b4 + + + + name + Regular Expressions + scope + string.regexp + settings + + foreground + #96b5b4 + + + + name + Escape Characters + scope + constant.character.escape + settings + + foreground + #96b5b4 + + + + name + Embedded + scope + punctuation.section.embedded, variable.interpolation + settings + + foreground + #ab7967 + + + + name + Invalid + scope + invalid.illegal + settings + + background + #bf616a + foreground + #2b303b + + + + name + GitGutter deleted + scope + markup.deleted.git_gutter + settings + + foreground + #F92672 + + + + name + GitGutter inserted + scope + markup.inserted.git_gutter + settings + + foreground + #A6E22E + + + + name + GitGutter changed + scope + markup.changed.git_gutter + settings + + foreground + #967EFB + + + + name + GitGutter ignored + scope + markup.ignored.git_gutter + settings + + foreground + #565656 + + + + name + GitGutter untracked + scope + markup.untracked.git_gutter + settings + + foreground + #565656 + + + + uuid + 59c1e2f2-7b41-46f9-91f2-1b4c6f5866f7 + + diff --git a/sublime_themes/base16-ocean-light.tmTheme b/sublime_themes/base16-ocean-light.tmTheme new file mode 100644 index 00000000..8d416e54 --- /dev/null +++ b/sublime_themes/base16-ocean-light.tmTheme @@ -0,0 +1,589 @@ + + + + + author + Chris Kempson (http://chriskempson.com) + name + Base16 Ocean Light + semanticClass + base16.ocean.light + colorSpaceName + sRGB + gutterSettings + + background + #eff1f5 + divider + #eff1f5 + foreground + #4f5b66 + selectionBackground + #eff1f5 + selectionForeground + #c0c5ce + + settings + + + settings + + background + #eff1f5 + caret + #4f5b66 + foreground + #4f5b66 + invisibles + #dfe1e8 + lineHighlight + #a7adba30 + selection + #dfe1e8 + shadow + #dfe1e8 + + + + name + Text + scope + variable.parameter.function + settings + + foreground + #4f5b66 + + + + name + Comments + scope + comment, punctuation.definition.comment + settings + + foreground + #a7adba + + + + name + Punctuation + scope + punctuation.definition.string, punctuation.definition.variable, punctuation.definition.string, punctuation.definition.parameters, punctuation.definition.string, punctuation.definition.array + settings + + foreground + #4f5b66 + + + + name + Delimiters + scope + none + settings + + foreground + #4f5b66 + + + + name + Operators + scope + keyword.operator + settings + + foreground + #4f5b66 + + + + name + Keywords + scope + keyword + settings + + foreground + #b48ead + + + + name + Variables + scope + variable + settings + + foreground + #bf616a + + + + name + Functions + scope + entity.name.function, meta.require, support.function.any-method + settings + + foreground + #8fa1b3 + + + + name + Classes + scope + support.class, entity.name.class, entity.name.type.class + settings + + foreground + #d08770 + + + + name + Classes + scope + meta.class + settings + + foreground + #343d46 + + + + name + Methods + scope + keyword.other.special-method + settings + + foreground + #8fa1b3 + + + + name + Storage + scope + storage + settings + + foreground + #b48ead + + + + name + Support + scope + support.function + settings + + foreground + #96b5b4 + + + + name + Strings, Inherited Class + scope + string, constant.other.symbol, entity.other.inherited-class + settings + + foreground + #a3be8c + + + + name + Integers + scope + constant.numeric + settings + + foreground + #d08770 + + + + name + Floats + scope + none + settings + + foreground + #d08770 + + + + name + Boolean + scope + none + settings + + foreground + #d08770 + + + + name + Constants + scope + constant + settings + + foreground + #d08770 + + + + name + Tags + scope + entity.name.tag + settings + + foreground + #bf616a + + + + name + Attributes + scope + entity.other.attribute-name + settings + + foreground + #d08770 + + + + name + Attribute IDs + scope + entity.other.attribute-name.id, punctuation.definition.entity + settings + + foreground + #8fa1b3 + + + + name + Selector + scope + meta.selector + settings + + foreground + #b48ead + + + + name + Values + scope + none + settings + + foreground + #d08770 + + + + name + Headings + scope + markup.heading punctuation.definition.heading, entity.name.section + settings + + fontStyle + + foreground + #8fa1b3 + + + + name + Units + scope + keyword.other.unit + settings + + foreground + #d08770 + + + + name + Bold + scope + markup.bold, punctuation.definition.bold + settings + + fontStyle + bold + foreground + #d08770 + + + + name + Italic + scope + markup.italic, punctuation.definition.italic + settings + + fontStyle + italic + foreground + #b48ead + + + + name + Code + scope + markup.raw.inline + settings + + foreground + #a3be8c + + + + name + Link Text + scope + string.other.link + settings + + foreground + #bf616a + + + + name + Link Url + scope + meta.link + settings + + foreground + #d08770 + + + + name + Lists + scope + markup.list + settings + + foreground + #bf616a + + + + name + Quotes + scope + markup.quote + settings + + foreground + #d08770 + + + + name + Separator + scope + meta.separator + settings + + background + #dfe1e8 + foreground + #4f5b66 + + + + name + Inserted + scope + markup.inserted, markup.inserted.git_gutter + settings + + foreground + #a3be8c + + + + name + Deleted + scope + markup.deleted, markup.deleted.git_gutter + settings + + foreground + #bf616a + + + + name + Changed + scope + markup.changed, markup.changed.git_gutter + settings + + foreground + #b48ead + + + + name + Ignored + scope + markup.ignored, markup.ignored.git_gutter + settings + + foreground + #c0c5ce + + + + name + Untracked + scope + markup.untracked, markup.untracked.git_gutter + settings + + foreground + #c0c5ce + + + + name + Colors + scope + constant.other.color + settings + + foreground + #96b5b4 + + + + name + Regular Expressions + scope + string.regexp + settings + + foreground + #96b5b4 + + + + name + Escape Characters + scope + constant.character.escape + settings + + foreground + #96b5b4 + + + + name + Embedded + scope + punctuation.section.embedded, variable.interpolation + settings + + foreground + #ab7967 + + + + name + Invalid + scope + invalid.illegal + settings + + background + #bf616a + foreground + #eff1f5 + + + + name + GitGutter deleted + scope + markup.deleted.git_gutter + settings + + foreground + #F92672 + + + + name + GitGutter inserted + scope + markup.inserted.git_gutter + settings + + foreground + #A6E22E + + + + name + GitGutter changed + scope + markup.changed.git_gutter + settings + + foreground + #967EFB + + + + name + GitGutter ignored + scope + markup.ignored.git_gutter + settings + + foreground + #565656 + + + + name + GitGutter untracked + scope + markup.untracked.git_gutter + settings + + foreground + #565656 + + + + uuid + 52997033-52ea-4534-af9f-7572613947d8 + + diff --git a/sublime_themes/gruvbox-dark.tmTheme b/sublime_themes/gruvbox-dark.tmTheme new file mode 100644 index 00000000..b068fc96 --- /dev/null +++ b/sublime_themes/gruvbox-dark.tmTheme @@ -0,0 +1,766 @@ + + + + + comment + Based on original gruvbox color scheme. + author + peaceant + name + gruvbox + settings + + + settings + + background + #282828 + + caret + #fcf9e3 + + foreground + #fdf4c1aa + + invisibles + #fabd2f + + lineHighlight + #3c3836 + + selection + #504945 + + bracketContentsForeground + #928374 + + bracketsForeground + #d5c4a1 + + guide + #3c3836 + + activeGuide + #a89984 + + stackGuide + #665c54 + + + + name + Punctuation + scope + punctuation.definition.tag + settings + + fontStyle + + foreground + #83a598 + + + + name + Punctuation + scope + punctuation.definition.entity + settings + + fontStyle + + foreground + #d3869b + + + + name + Constant + scope + constant + settings + + fontStyle + + foreground + #d3869b + + + + name + Constant escape + scope + constant.character.escape + settings + + fontStyle + + foreground + #b8bb26 + + + + name + Constant other + scope + constant.other + settings + + fontStyle + + foreground + #fdf4c1 + + + + name + Entity + scope + entity + settings + + fontStyle + + foreground + #8ec07c + + + + name + Keyword + scope + keyword.operator.comparison, keyword.operator, keyword.operator.symbolic, keyword.operator.string, keyword.operator.assignment, keyword.operator.arithmetic, keyword.operator.class, keyword.operator.key, keyword.operator.logical + settings + + fontStyle + + foreground + #fe8019 + + + + name + Keyword + scope + keyword, keyword.operator.new, keyword.other, keyword.control + settings + + fontStyle + + foreground + #fa5c4b + + + + name + Storage + scope + storage + settings + + fontStyle + + foreground + #fa5c4b + + + + name + String + scope + string -string.unquoted.old-plist -string.unquoted.heredoc, string.unquoted.heredoc string + settings + + fontStyle + + foreground + #b8bb26 + + + + name + Comment + scope + comment + settings + + fontStyle + italic + foreground + #928374 + + + + name + Regexp + scope + string.regexp constant.character.escape + settings + + foreground + #b8bb26 + + + + name + Support + scope + support + settings + + fontStyle + + foreground + #fabd2f + + + + name + Variable + scope + variable + settings + + fontStyle + + foreground + #fdf4c1 + + + + name + Lang Variable + scope + variable.language + settings + + fontStyle + + foreground + #fdf4c1 + + + + name + Function Call + scope + meta.function-call + settings + + foreground + #fdf4c1 + + + + name + Invalid + scope + invalid + settings + + background + #932b1e + foreground + #fdf4c1 + + + + name + Embedded Source + scope + text source, string.unquoted.heredoc, source source + settings + + fontStyle + + foreground + #fdf4c1 + + + + name + String embedded-source + scope + string.quoted source + settings + + fontStyle + + foreground + #b8bb26 + + + + name + String constant + scope + string + settings + + foreground + #b8bb26 + + + + + name + Support.constant + scope + support.constant + settings + + fontStyle + + foreground + #fabd2f + + + + name + Support.class + scope + support.class + settings + + fontStyle + + foreground + #8ec07c + + + + name + Meta.tag.A + scope + entity.name.tag + settings + + fontStyle + bold + foreground + #8ec07c + + + + name + Inner tag + scope + meta.tag, meta.tag entity + settings + + foreground + #8ec07c + + + + name + css colors + scope + constant.other.color.rgb-value + settings + + foreground + #83a598 + + + + name + css tag-name + scope + meta.selector.css entity.name.tag + settings + + foreground + #fa5c4b + + + + name + css#id + scope + meta.selector.css, entity.other.attribute-name.id + settings + + foreground + #b8bb26 + + + + name + css.class + scope + meta.selector.css entity.other.attribute-name.class + settings + + foreground + #b8bb26 + + + + name + css property-name: + scope + support.type.property-name.css + settings + + foreground + #8ec07c + + + + name + css @at-rule + scope + meta.preprocessor.at-rule keyword.control.at-rule + settings + + foreground + #fabd2f + + + + name + css additional-constants + scope + meta.property-value constant + settings + + foreground + #fabd2f + + + + name + css additional-constants + scope + meta.property-value support.constant.named-color.css + settings + + foreground + #fe8019 + + + + name + css constructor.argument + scope + meta.constructor.argument.css + settings + + foreground + #fabd2f + + + + + + + name + diff.header + scope + meta.diff, meta.diff.header + settings + + foreground + #83a598 + + + + name + diff.deleted + scope + markup.deleted + settings + + foreground + #fa5c4b + + + + name + diff.changed + scope + markup.changed + settings + + foreground + #fabd2f + + + + name + diff.inserted + scope + markup.inserted + settings + + foreground + #8ec07c + + + + + + + name + Bold Markup + scope + markup.bold + settings + + fontStyle + bold + + + + name + Italic Markup + scope + markup.italic + settings + + fontStyle + italic + + + + name + Heading Markup + scope + markup.heading + settings + + fontStyle + bold + foreground + #8ec07c + + + + + + + + + name + PHP: class name + scope + entity.name.type.class.php + settings + + foreground + #8ec07c + + + + name + PHP: Comment + scope + keyword.other.phpdoc + settings + + fontStyle + + foreground + #928374 + + + + + + name + CSS: numbers + scope + constant.numeric.css, keyword.other.unit.css + settings + + foreground + #d3869b + + + + name + CSS: entity dot, hash, comma, etc. + scope + punctuation.definition.entity.css + settings + + foreground + #b8bb26 + + + + + name + JS: variable + scope + variable.language.js + settings + + foreground + #fabd2f + + + + name + JS: unquoted labe + scope + string.unquoted.label.js + settings + + foreground + #fdf4c1 + + + + + name + Constant other sql + scope + constant.other.table-name.sql + settings + + fontStyle + + foreground + #b8bb26 + + + + name + Constant other sql + scope + constant.other.database-name.sql + settings + + fontStyle + + foreground + #b8bb26 + + + + + + + + name + dired directory + scope + storage.type.dired.item.directory, dired.item.directory + settings + + foreground + #8ec07c + + + + + + name + orgmode link + scope + orgmode.link + settings + + foreground + #fabd2f + fontStyle + underline + + + + name + orgmode page + scope + orgmode.page + settings + + foreground + #b8bb26 + + + + name + orgmode break + scope + orgmode.break + settings + + foreground + #d3869b + + + + name + orgmode headline + scope + orgmode.headline + settings + + foreground + #8ec07c + + + + name + orgmode tack + scope + orgmode.tack + settings + + foreground + #fabd2f + + + + name + orgmode follow up + scope + orgmode.follow_up + settings + + foreground + #fabd2f + + + + name + orgmode checkbox + scope + orgmode.checkbox + settings + + foreground + #fabd2f + + + + name + orgmode checkbox summary + scope + orgmode.checkbox.summary + settings + + foreground + #fabd2f + + + + name + orgmode tags + scope + orgmode.tags + settings + + foreground + #fa5c4b + + + + + uuid + 06CD1FB2-A00A-4F8C-97B2-60E131980454 + + + diff --git a/sublime_themes/gruvbox-light.tmTheme b/sublime_themes/gruvbox-light.tmTheme new file mode 100644 index 00000000..4bfc3a45 --- /dev/null +++ b/sublime_themes/gruvbox-light.tmTheme @@ -0,0 +1,774 @@ + + + + + comment + Based on original gruvbox color scheme. + author + Martin Radimec + name + gruvbox + settings + + + settings + + background + #FCF0CA + + caret + #3C3836 + + foreground + #282828aa + + invisibles + #b57614 + + lineHighlight + #EDDAB5 + + selection + #D6C3A3 + + bracketContentsForeground + #928374 + + bracketsForeground + #d5c4a1 + + guide + #EDDAB5 + + activeGuide + #7c6f64 + + stackGuide + #BEAD95 + + + + name + Punctuation + scope + punctuation.definition.tag + settings + + fontStyle + + foreground + #076678 + + + + name + Punctuation + scope + punctuation.definition.entity + settings + + fontStyle + + foreground + #8f3f71 + + + + name + Constant + scope + constant + settings + + fontStyle + + foreground + #8f3f71 + + + + name + Constant escape + scope + constant.character.escape + settings + + fontStyle + + foreground + #79740e + + + + name + Constant other + scope + constant.other + settings + + fontStyle + + foreground + #282828 + + + + name + Entity + scope + entity + settings + + fontStyle + + foreground + #407959 + + + + name + Keyword + scope + keyword.operator.comparison, keyword.operator, keyword.operator.symbolic, keyword.operator.string, keyword.operator.assignment, keyword.operator.arithmetic, keyword.operator.class, keyword.operator.key, keyword.operator.logical + settings + + fontStyle + + foreground + #B23C15 + + + + name + Keyword + scope + keyword, keyword.operator.new, keyword.other, keyword.control + settings + + fontStyle + + foreground + #9d0006 + + + + name + Storage + scope + storage + settings + + fontStyle + + foreground + #9d0006 + + + + name + String + scope + string -string.unquoted.old-plist -string.unquoted.heredoc, string.unquoted.heredoc string + settings + + fontStyle + + foreground + #79740e + + + + name + Comment + scope + comment + settings + + fontStyle + italic + foreground + #928374 + + + + name + Regexp + scope + string.regexp constant.character.escape + settings + + foreground + #79740e + + + + name + Support + scope + support + settings + + fontStyle + + foreground + #b57614 + + + + name + Variable + scope + variable + settings + + fontStyle + + foreground + #282828 + + + + name + Lang Variable + scope + variable.language + settings + + fontStyle + + foreground + #282828 + + + + name + Function Call + scope + meta.function-call + settings + + foreground + #282828 + + + + name + Invalid + scope + invalid + settings + + background + #932b1e + foreground + #282828 + + + + name + Embedded Source + scope + text source, string.unquoted.heredoc, source source + settings + + fontStyle + + foreground + #282828 + + + + name + String embedded-source + scope + string.quoted source + settings + + fontStyle + + foreground + #79740e + + + + name + String constant + scope + string + settings + + foreground + #79740e + + + + + name + Support.constant + scope + support.constant + settings + + fontStyle + + foreground + #b57614 + + + + name + Support.class + scope + support.class + settings + + fontStyle + + foreground + #407959 + + + + name + Meta.tag.A + scope + entity.name.tag + settings + + fontStyle + bold + foreground + #407959 + + + + name + Inner tag + scope + meta.tag, meta.tag entity + settings + + foreground + #407959 + + + + name + css colors + scope + constant.other.color.rgb-value + settings + + foreground + #076678 + + + + name + css tag-name + scope + meta.selector.css entity.name.tag + settings + + foreground + #9d0006 + + + + name + css#id + scope + meta.selector.css, entity.other.attribute-name.id + settings + + foreground + #79740e + + + + name + css.class + scope + meta.selector.css entity.other.attribute-name.class + settings + + foreground + #79740e + + + + name + css property-name: + scope + support.type.property-name.css + settings + + foreground + #407959 + + + + name + css @at-rule + scope + meta.preprocessor.at-rule keyword.control.at-rule + settings + + foreground + #b57614 + + + + name + css additional-constants + scope + meta.property-value constant + settings + + foreground + #b57614 + + + + name + css additional-constants + scope + meta.property-value support.constant.named-color.css + settings + + foreground + #B23C15 + + + + name + css constructor.argument + scope + meta.constructor.argument.css + settings + + foreground + #b57614 + + + + + + + name + diff.header + scope + meta.diff, meta.diff.header + settings + + background + #076678 + foreground + #282828 + + + + name + diff.deleted + scope + markup.deleted + settings + + background + #9d0006 + foreground + #282828 + + + + name + diff.changed + scope + markup.changed + settings + + background + #b57614 + foreground + #282828 + + + + name + diff.inserted + scope + markup.inserted + settings + + background + #407959 + foreground + #282828 + + + + + + + name + Bold Markup + scope + markup.bold + settings + + fontStyle + bold + + + + name + Italic Markup + scope + markup.italic + settings + + fontStyle + italic + + + + name + Heading Markup + scope + markup.heading + settings + + fontStyle + bold + foreground + #407959 + + + + + + + + + name + PHP: class name + scope + entity.name.type.class.php + settings + + foreground + #407959 + + + + name + PHP: Comment + scope + keyword.other.phpdoc + settings + + fontStyle + + foreground + #928374 + + + + + + name + CSS: numbers + scope + constant.numeric.css, keyword.other.unit.css + settings + + foreground + #8f3f71 + + + + name + CSS: entity dot, hash, comma, etc. + scope + punctuation.definition.entity.css + settings + + foreground + #79740e + + + + + name + JS: variable + scope + variable.language.js + settings + + foreground + #b57614 + + + + name + JS: unquoted labe + scope + string.unquoted.label.js + settings + + foreground + #282828 + + + + + name + Constant other sql + scope + constant.other.table-name.sql + settings + + fontStyle + + foreground + #79740e + + + + name + Constant other sql + scope + constant.other.database-name.sql + settings + + fontStyle + + foreground + #79740e + + + + + + + + name + dired directory + scope + storage.type.dired.item.directory, dired.item.directory + settings + + foreground + #407959 + + + + + + name + orgmode link + scope + orgmode.link + settings + + foreground + #b57614 + fontStyle + underline + + + + name + orgmode page + scope + orgmode.page + settings + + foreground + #79740e + + + + name + orgmode break + scope + orgmode.break + settings + + foreground + #8f3f71 + + + + name + orgmode headline + scope + orgmode.headline + settings + + foreground + #407959 + + + + name + orgmode tack + scope + orgmode.tack + settings + + foreground + #b57614 + + + + name + orgmode follow up + scope + orgmode.follow_up + settings + + foreground + #b57614 + + + + name + orgmode checkbox + scope + orgmode.checkbox + settings + + foreground + #b57614 + + + + name + orgmode checkbox summary + scope + orgmode.checkbox.summary + settings + + foreground + #b57614 + + + + name + orgmode tags + scope + orgmode.tags + settings + + foreground + #9d0006 + + + + + uuid + 06CD1FB2-A00A-4F8C-97B2-60E131980454 + + + diff --git a/sublime_themes/inspired-github.tmTheme b/sublime_themes/inspired-github.tmTheme new file mode 100644 index 00000000..c009512e --- /dev/null +++ b/sublime_themes/inspired-github.tmTheme @@ -0,0 +1,1725 @@ + + + + + + name + GitHub + settings + + + + + settings + + + background + #ffffff + foreground + #323232 + invisibles + #000000 + caret + #323232 + + + gutter + #ffffff + gutterForeground + #b3b3b3 + + + guide + #e8e8e8 + stackGuide + #e8e8e8 + activeGuide + #b3b3b3 + + + lineHighlight + #f5f5f5 + findHighlight + #f8eec7 + findHighlightForeground + #323232 + selection + #f8eec7 + selectionBorder + #ffffff + + + bracketsForeground + #63a35c + bracketsOptions + underline + bracketContentsForeground + #63a35c + bracketContentsOptions + underline + + + tagsForeground + #63a35c + tagsOptions + underline + + + + + + name + Comment + scope + comment + settings + + foreground + #969896 + fontStyle + italic + + + + + + name + String + scope + string + settings + + foreground + #183691 + + + + + + name + RegExp Operator + scope + regexp-operator + settings + + foreground + #a71d5d + + + + + + name + RegExp Character Class + scope + string.regexp.characterclass punctuation.definition.string.begin, string.regexp.characterclass punctuation.definition.string.end + settings + + foreground + #a71d5d + + + + + + name + Number + scope + constant.numeric + settings + + foreground + #0086b3 + + + + + + name + Built-in Constant + scope + constant.language + settings + + foreground + #0086b3 + + + + + + name + User-defined Constant + scope + constant.character, constant.other, variable.other.constant + settings + + foreground + #0086b3 + + + + + + name + Variable + scope + variable + settings + + foreground + #323232 + + + + + + name + Keyword + scope + keyword + settings + + foreground + #a71d5d + fontStyle + bold + + + + + + name + Bitwise + scope + bitwise-operator + settings + + foreground + #a71d5d + fontStyle + bold + + + + + + name + Storage + scope + storage + settings + + foreground + #a71d5d + fontStyle + bold + + + + + + name + Storage Type + scope + storage.type + settings + + foreground + #a71d5d + fontStyle + bold + + + + + + name + Class Name + scope + entity.name.class + settings + + foreground + #0086b3 + + + + + + name + Inherited Class + scope + entity.other.inherited-class + settings + + foreground + #0086b3 + + + + + + name + Function Name + scope + entity.name.function + settings + + foreground + #795da3 + fontStyle + bold + + + + + + name + Function Argument + scope + variable.parameter + settings + + foreground + #323232 + + + + + + name + Tag Name + scope + entity.name.tag + settings + + foreground + #63a35c + + + + + + name + Tag Attribute + scope + entity.other.attribute-name + settings + + foreground + #795da3 + + + + + + name + Library Function + scope + support.function + settings + + foreground + #62a35c + + + + + + name + Library Constant + scope + support.constant + settings + + foreground + #0086b3 + + + + + + name + Library Class + scope + support.type, support.class + settings + + foreground + #0086b3 + + + + + + name + Library Variable + scope + support.other.variable + settings + + foreground + #323232 + + + + + + name + Invalid + scope + invalid, invalid.illegal, invalid.deprecated + settings + + background + #f5f5f5 + foreground + #b52a1d + fontStyle + bold + + + + + + + + + name + Find-in-files Filename + scope + entity.name.filename.find-in-files + settings + + foreground + #323232 + fontStyle + bold + + + + name + Find-in-files Line Numbers + scope + constant.numeric.line-number.find-in-files, constant.numeric.line-number.match.find-in-files + settings + + foreground + #b3b3b3 + + + + + + + + + + name + Diff Header + scope + meta.diff.header + settings + + foreground + #969896 + background + #ffffff + fontStyle + italic + + + + + name + Diff Header + scope + meta.diff.header punctuation.definition.from-file.diff + settings + + foreground + #bd2c00 + background + #ffecec + fontStyle + italic bold + + + + + name + Diff Header + scope + meta.diff.header punctuation.definition.to-file.diff + settings + + foreground + #55a532 + background + #eaffea + fontStyle + italic bold + + + + + name + Diff Range + scope + meta.diff.range + settings + + foreground + #969896 + fontStyle + italic bold + + + + + name + Diff Deleted + scope + markup.deleted + settings + + background + #ffecec + + + + + name + Diff Deleted Punctuation + scope + markup.deleted punctuation.definition.inserted + settings + + foreground + #bd2c00 + fontStyle + bold + + + + + name + Diff Inserted + scope + markup.inserted + settings + + background + #eaffea + + + + + name + Diff Inserted Punctuation + scope + markup.inserted punctuation.definition.inserted + settings + + foreground + #55a532 + fontStyle + bold + + + + + + + + + + name + GitGutter Deleted + scope + markup.deleted.git_gutter + settings + + foreground + #bd2c00 + + + + + name + GitGutter Inserted + scope + markup.inserted.git_gutter + settings + + foreground + #55a532 + + + + + name + GitGutter Modified + scope + markup.changed.git_gutter + settings + + foreground + #0086B3 + + + + + name + GitGutter Ignored + scope + markup.ignored.git_gutter + settings + + foreground + #b3b3b3 + + + + + name + GitGutter Untracked + scope + markup.untracked.git_gutter + settings + + foreground + #b3b3b3 + + + + + + + + + + name + Entity Punctuation + scope + source.css punctuation.definition.entity + settings + + foreground + #323232 + + + + + name + Pseudo Selector + scope + source.css entity.other.attribute-name.pseudo-class, source.css entity.other.attribute-name.pseudo-element + settings + + foreground + #a71d5d + + + + + name + Property Value + scope + source.css meta.value, source.css support.constant, source.css support.function + settings + + foreground + #323232 + + + + + name + Color + scope + source.css constant.other.color + settings + + foreground + #ed6a43 + + + + + + + + + + name + Entity Punctuation + scope + source.scss punctuation.definition.entity + settings + + foreground + #323232 + + + + + name + Pseudo Selector + scope + source.scss entity.other.attribute-name.pseudo-class, source.scss entity.other.attribute-name.pseudo-element + settings + + foreground + #a71d5d + + + + + name + Color + scope + source.scss support.constant.property-value, source.scss support.function + settings + + foreground + #323232 + + + + + name + Variable + scope + source.scss variable + settings + + foreground + #a71d5d + + + + + + + + + + name + this + scope + variable.language.this.js + settings + + foreground + #ed6a43 + + + + + name + Function + scope + source.js entity.name.function + settings + + foreground + #323232 + fontStyle + regular + + + + + name + Function Definition + scope + source.js meta.function entity.name.function, source.js entity.name.function meta.function + settings + + foreground + #795da3 + fontStyle + bold + + + + + name + New Function + scope + entity.name.type.new.js + settings + + foreground + #795da3 + + + + + name + Function Prototype + scope + variable.language.prototype.js + settings + + foreground + #0086b3 + + + + + name + Support Function + scope + source.js support.function + settings + + foreground + #0086b3 + + + + + name + Function Prototype + scope + support.type.object.console.js + settings + + foreground + #795da3 + + + + + + + + + + name + JSON Property - 20 Deep + scope + meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json string.quoted.double.json + settings + + foreground + #183691 + fontStyle + bold + + + + name + JSON Value - 20 Deep + scope + meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json + settings + + foreground + #323232 + fontStyle + regular + + + + + name + JSON Property - 19 Deep + scope + meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json string.quoted.double.json + settings + + foreground + #183691 + fontStyle + bold + + + + name + JSON Value - 19 Deep + scope + meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json + settings + + foreground + #323232 + fontStyle + regular + + + + + name + JSON Property - 18 Deep + scope + meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json string.quoted.double.json + settings + + foreground + #183691 + fontStyle + bold + + + + name + JSON Value - 18 Deep + scope + meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json + settings + + foreground + #323232 + fontStyle + regular + + + + + name + JSON Property - 17 Deep + scope + meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json string.quoted.double.json + settings + + foreground + #183691 + fontStyle + bold + + + + name + JSON Value - 17 Deep + scope + meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json + settings + + foreground + #323232 + fontStyle + regular + + + + + name + JSON Property - 16 Deep + scope + meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json string.quoted.double.json + settings + + foreground + #183691 + fontStyle + bold + + + + name + JSON Value - 16 Deep + scope + meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json + settings + + foreground + #323232 + fontStyle + regular + + + + + name + JSON Property - 15 Deep + scope + meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json string.quoted.double.json + settings + + foreground + #183691 + fontStyle + bold + + + + name + JSON Value - 15 Deep + scope + meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json + settings + + foreground + #323232 + fontStyle + regular + + + + + name + JSON Property - 14 Deep + scope + meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json string.quoted.double.json + settings + + foreground + #183691 + fontStyle + bold + + + + name + JSON Value - 14 Deep + scope + meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json + settings + + foreground + #323232 + fontStyle + regular + + + + + name + JSON Property - 13 Deep + scope + meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json string.quoted.double.json + settings + + foreground + #183691 + fontStyle + bold + + + + name + JSON Value - 13 Deep + scope + meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json + settings + + foreground + #323232 + fontStyle + regular + + + + + name + JSON Property - 12 Deep + scope + meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json string.quoted.double.json + settings + + foreground + #183691 + fontStyle + bold + + + + name + JSON Value - 12 Deep + scope + meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json + settings + + foreground + #323232 + fontStyle + regular + + + + + name + JSON Property - 11 Deep + scope + meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json string.quoted.double.json + settings + + foreground + #183691 + fontStyle + bold + + + + name + JSON Value - 11 Deep + scope + meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json + settings + + foreground + #323232 + fontStyle + regular + + + + + name + JSON Property - 10 Deep + scope + meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json string.quoted.double.json + settings + + foreground + #183691 + fontStyle + bold + + + + name + JSON Value - 10 Deep + scope + meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json + settings + + foreground + #323232 + fontStyle + regular + + + + + name + JSON Property - 9 Deep + scope + meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json string.quoted.double.json + settings + + foreground + #183691 + fontStyle + bold + + + + name + JSON Value - 9 Deep + scope + meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json + settings + + foreground + #323232 + fontStyle + regular + + + + + name + JSON Property - 8 Deep + scope + meta meta meta meta meta meta meta meta meta.structure.dictionary.json string.quoted.double.json + settings + + foreground + #183691 + fontStyle + bold + + + + name + JSON Value - 8 Deep + scope + meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json + settings + + foreground + #323232 + fontStyle + regular + + + + + name + JSON Property - 7 Deep + scope + meta meta meta meta meta meta meta meta.structure.dictionary.json string.quoted.double.json + settings + + foreground + #183691 + fontStyle + bold + + + + name + JSON Value - 7 Deep + scope + meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json + settings + + foreground + #323232 + fontStyle + regular + + + + + name + JSON Property - 6 Deep + scope + meta meta meta meta meta meta meta.structure.dictionary.json string.quoted.double.json + settings + + foreground + #183691 + fontStyle + bold + + + + name + JSON Value - 6 Deep + scope + meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json + settings + + foreground + #323232 + fontStyle + regular + + + + + name + JSON Property - 5 Deep + scope + meta meta meta meta meta meta.structure.dictionary.json string.quoted.double.json + settings + + foreground + #183691 + fontStyle + bold + + + + name + JSON Value - 5 Deep + scope + meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json + settings + + foreground + #323232 + fontStyle + regular + + + + + name + JSON Property - 4 Deep + scope + meta meta meta meta meta.structure.dictionary.json string.quoted.double.json + settings + + foreground + #183691 + fontStyle + bold + + + + name + JSON Value - 4 Deep + scope + meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json + settings + + foreground + #323232 + fontStyle + regular + + + + + name + JSON Property - 3 Deep + scope + meta meta meta meta.structure.dictionary.json string.quoted.double.json + settings + + foreground + #183691 + fontStyle + bold + + + + name + JSON Value - 3 Deep + scope + meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json + settings + + foreground + #323232 + fontStyle + regular + + + + + name + JSON Property - 2 Deep + scope + meta meta meta.structure.dictionary.json string.quoted.double.json + settings + + foreground + #183691 + fontStyle + bold + + + + name + JSON Value - 2 Deep + scope + meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json + settings + + foreground + #323232 + fontStyle + regular + + + + + name + JSON Property - 1 Deep + scope + meta meta.structure.dictionary.json string.quoted.double.json + settings + + foreground + #183691 + fontStyle + bold + + + + name + JSON Value - 1 Deep + scope + meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json + settings + + foreground + #323232 + fontStyle + regular + + + + + name + JSON Property + scope + meta.structure.dictionary.json string.quoted.double.json + settings + + foreground + #183691 + fontStyle + bold + + + + name + JSON Value + scope + meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json + settings + + foreground + #323232 + fontStyle + regular + + + + + + + + + + name + Keyword + scope + source.python keyword + settings + + fontStyle + bold + + + + + name + Storage + scope + source.python storage + settings + + fontStyle + bold + + + + + name + Storage Type + scope + source.python storage.type + settings + + fontStyle + bold + + + + + name + Function + scope + source.python entity.name.function + settings + + foreground + #323232 + fontStyle + bold + + + + + + + + + + name + Class + scope + source.php entity.name.type.class + settings + + foreground + #323232 + fontStyle + bold + + + + + + + + + + name + Language Variable + scope + variable.language.ruby + settings + + foreground + #ed6a43 + + + + + name + Module Name + scope + entity.name.type.module.ruby + settings + + foreground + #795da3 + fontStyle + bold + + + + + name + Class Name + scope + entity.name.type.class.ruby + settings + + foreground + #795da3 + fontStyle + bold + + + + + name + Inherited Class + scope + entity.other.inherited-class.ruby + settings + + foreground + #795da3 + fontStyle + bold + + + + + + + + + + name + Punctuation + scope + punctuation.definition.heading.markdown, punctuation.definition.italic.markdown, punctuation.definition.bold.markdown, punctuation.definition.string.begin.markdown, punctuation.definition.string.end.markdown, punctuation.definition.metadata.markdown, punctuation.definition.link.markdown, punctuation.definition.blockquote.markdown, punctuation.definition.raw.markdown + settings + + foreground + #a71d5d + + + + + name + Separator + scope + text.html.markdown meta.separator + settings + + foreground + #b3b3b3 + + + + + name + Heading + scope + text.html.markdown markup.heading + settings + + fontStyle + bold + + + + + name + Code Block + scope + text.html.markdown markup.raw.block + settings + + foreground + #323232 + + + + + name + Inline Code + scope + text.html.markdown markup.raw.inline + settings + + foreground + #323232 + + + + + name + Link and Image + scope + text.html.markdown meta.link, text.html.markdown meta.image + settings + + foreground + #4183c4 + + + + name + Link URL + scope + text.html.markdown markup.underline.link, text.html.markdown constant.other.reference + settings + + fontStyle + italic + + + + + name + List + scope + text.html.markdown markup.list + settings + + foreground + #ed6a43 + + + + + name + Bold + scope + text.html.markdown markup.bold + settings + + fontStyle + bold + + + + + name + Italic + scope + text.html.markdown markup.italic + settings + + fontStyle + italic + + + + + name + Bold Italic + scope + text.html.markdown markup.bold markup.italic + settings + + fontStyle + bold italic + + + + + name + Italic Bold + scope + text.html.markdown markup.italic markup.bold + settings + + fontStyle + italic bold + + + + + diff --git a/sublime_themes/kronuz.tmTheme b/sublime_themes/kronuz.tmTheme new file mode 100644 index 00000000..b0e33528 --- /dev/null +++ b/sublime_themes/kronuz.tmTheme @@ -0,0 +1,1843 @@ + + + + + author + github.com/Kronuz + colorSpaceName + sRGB + gutterSettings + + background + #073642 + divider + #586e75 + foreground + #839496 + selectionBackground + #586e75 + selectionForeground + #86c20e + + name + Kronuz + semanticClass + theme.kronuz + settings + + + settings + + activeGuide + #df940088 + background + #383838 + bracketContentsForeground + #f8f8f2a5 + bracketContentsOptions + underline + bracketsForeground + #ff9400 + bracketsOptions + foreground underline + caret + #ffffff + findHighlight + #000000 + findHighlightForeground + #cccccc + foreground + #e6e1dc + invisibles + #404040 + lineHighlight + #60606080 + selection + #3366ff66 + selectionBorder + #1c1c1c + stackGuide + #df940044 + tagsOptions + stippled_underline + + + + name + Comment + scope + comment + settings + + foreground + #95815e + + + + name + String + scope + string + settings + + foreground + #a5c261 + + + + name + String + scope + string.quoted.double + settings + + foreground + #c1be91 + + + + name + String.regexp + scope + string.regexp + settings + + foreground + #a5c261 + + + + name + Number + scope + constant.numeric + settings + + foreground + #a5c261 + + + + name + Constant (built-in) + scope + constant.language, meta.preprocessor + settings + + foreground + #6e9cbe + + + + name + Constant: (user-defined) + scope + constant.character, constant.other + settings + + foreground + #6d9cbe + + + + name + Constant (other variable) + scope + variable.other.constant + settings + + foreground + #da4939 + + + + name + Variable + scope + variable.language, variable.other + settings + + foreground + #d0d0ff + + + + name + Keyword + scope + keyword + settings + + foreground + #cc7833 + + + + name + Storage + scope + storage + settings + + foreground + #cc7833 + + + + name + Storage (type) + scope + storage.type + settings + + fontStyle + italic + foreground + #6e9cbe + + + + name + Function (definition) + scope + entity.name.function, keyword.other.name-of-parameter.objc + settings + + foreground + #ffc66d + + + + name + Function (argument) + scope + variable.parameter + settings + + fontStyle + italic + foreground + #fd971f + + + + name + Class (definition) + scope + entity.name.class + settings + + fontStyle + underline + foreground + #ffc66d + + + + name + Class (inherited) + scope + entity.other.inherited-class + settings + + fontStyle + italic underline + foreground + #ffc66d + + + + name + Block Parameter + scope + variable.other.block + settings + + foreground + #e6e1dc + + + + name + Library function + scope + support.function + settings + + foreground + #da4939 + + + + name + Library constant + scope + support.constant + settings + + foreground + #86c20e + + + + name + Library type + scope + support.type, support.class + settings + + fontStyle + italic + foreground + #6e9cbe + + + + name + Markup tag + scope + meta.tag, declaration.tag, entity.name.tag, entity.other.attribute-name + settings + + foreground + #e8bf6a + + + + name + Invalid + scope + invalid + settings + + background + #b90622 + foreground + #f8f8f0 + + + + name + Invalid deprecated + scope + invalid.deprecated + settings + + background + #b90682 + foreground + #f8f8f0 + + + + name + String interpolation + scope + constant.character.escaped, constant.character.escape, string source, string source.ruby + settings + + foreground + #519f50 + + + + name + Guide + scope + guide + settings + + foreground + #4c4a4988 + + + + name + Source + scope + source.console + settings + + foreground + #c5c8c6 + + + + name + Diff Header + scope + meta.diff, meta.diff.header, meta.separator.diff, meta.diff.index, meta.diff.range + settings + + foreground + #95815e + + + + name + Diff Remove + scope + markup.deleted + settings + + foreground + #f92640 + + + + name + Diff Add + scope + markup.inserted + settings + + foreground + #86c20e + + + + name + Diff Change + scope + markup.changed + settings + + foreground + #e6db74 + + + + name + Diff Range + scope + meta.diff, meta.diff.range + settings + + foreground + #3bc0f0 + + + + name + TODO: Category Name + scope + todo.category.name + settings + + foreground + #e8bf6a + + + + name + TODO: Urgent + scope + todo.task.urgent + settings + + foreground + #feff00 + + + + name + TODO: Category Extras + scope + todo.category.extras + settings + + foreground + #bc9458 + + + + name + TODO: Task Dormant + scope + todo.task.dormant + settings + + foreground + #807c79 + + + + name + TODO: Task Complete + scope + todo.task.complete + settings + + foreground + #da4939 + + + + name + TODO: Task Question + scope + todo.task.question + settings + + foreground + #82a7e2 + + + + name + TODO: Task Large + scope + todo.task.large + settings + + foreground + #a5c261 + + + + name + HTML: Doctype/XML Processing + scope + meta.tag.sgml.doctype.xml, declaration.sgml.html declaration.doctype, declaration.sgml.html declaration.doctype entity, declaration.sgml.html declaration.doctype string, declaration.xml-processing, declaration.xml-processing entity, declaration.xml-processing string, doctype + settings + + foreground + #c8cecc + + + + name + HTML: Comment Block + scope + comment.block.html + settings + + foreground + #95815e + + + + name + HTML: Script + scope + entity.name.tag.script.html + settings + + fontStyle + italic + + + + name + HTML: Attribute punctuation + scope + text.html.basic meta.tag.other.html, text.html.basic meta.tag.any.html, text.html.basic meta.tag.block.any, text.html.basic meta.tag.inline.any, text.html.basic meta.tag.structure.any.html, text.html.basic source.js.embedded.html, punctuation.separator.key-value.html + settings + + foreground + #86c20e + + + + name + HTML: Attributes + scope + text.html.basic entity.other.attribute-name.html + settings + + foreground + #86c20e + + + + name + HTML: Quotation Marks + scope + text.html.basic meta.tag.structure.any.html punctuation.definition.string.begin.html, punctuation.definition.string.begin.html, punctuation.definition.string.end.html + settings + + foreground + #e6e1dc + + + + name + HTML: Tags punctuation + scope + punctuation.definition.tag.end, punctuation.definition.tag.begin, punctuation.definition.tag + settings + + foreground + #e6e1dc + + + + name + Handlebars: Variable + scope + variable.parameter.handlebars + settings + + foreground + #f6aa11 + + + + name + Handlebars: Constant + scope + support.constant.handlebars, meta.function.block.start.handlebars + settings + + foreground + #6e9cbe + + + + name + CSS: @at-rule + scope + meta.preprocessor.at-rule keyword.control.at-rule + settings + + foreground + #f6aa11 + + + + name + CSS: #Id + scope + meta.selector.css entity.other.attribute-name.id + settings + + foreground + #f6aa11 + + + + name + CSS: .class + scope + meta.selector.css entity.other.attribute-name.class + settings + + foreground + #86c20e + + + + name + CSS: Property Name + scope + support.type.property-name.css + settings + + foreground + #6e9cbe + + + + name + CSS: Constructor Argument + scope + meta.constructor.argument.css + settings + + foreground + #f6aa11 + + + + name + CSS: {} + scope + punctuation.section.property-list.css + settings + + foreground + #e6e1dc + + + + name + CSS: Tag Punctuation + scope + punctuation.definition.tag.css + settings + + foreground + #f92672 + + + + name + CSS: : , + scope + punctuation.separator.key-value.css, punctuation.terminator.rule.css + settings + + foreground + #e6e1dc + + + + name + CSS :pseudo + scope + entity.other.attribute-name.pseudo-element.css, entity.other.attribute-name.pseudo-class.css, entity.other.attribute-name.pseudo-selector.css + settings + + foreground + #86c20e + + + + name + LESS variables + scope + variable.other.less + settings + + foreground + #e6e1dc + + + + name + LESS mixins + scope + entity.other.less.mixin + settings + + fontStyle + italic + foreground + #e0fdce + + + + name + LESS: Extend + scope + entity.other.attribute-name.pseudo-element.less + settings + + foreground + #ff9117 + + + + name + JS: Function Name + scope + meta.function.js, entity.name.function.js, support.function.dom.js + settings + + foreground + #86c20e + + + + name + JS: Storage Type + scope + storage.type.js + settings + + fontStyle + italic + foreground + #6e9cbe + + + + name + JS: Source + scope + text.html.basic source.js.embedded.html + settings + + foreground + #e6e1dc + + + + name + JS: Function + scope + storage.type.function.js + settings + + fontStyle + italic + foreground + #6e9cbe + + + + name + JS: Numeric Constant + scope + constant.numeric.js + settings + + foreground + #ae81ff + + + + name + JS: Literal language variable + scope + variable.language.arguments.js, variable.language.super.js, variable.language.this.js, variable.language.self.js, variable.language.proto.js, variable.language.constructor.js, variable.language.prototype.js + settings + + fontStyle + italic + foreground + #6e9cbe + + + + name + JS: [] + scope + meta.brace.square.js + settings + + foreground + #e6e1dc + + + + name + JS: () + scope + meta.brace.round, punctuation.definition.parameters.begin.js, punctuation.definition.parameters.end.js + settings + + foreground + #e6e1dc + + + + name + JS: {} + scope + meta.brace.curly.js + settings + + foreground + #e6e1dc + + + + name + JSON String + scope + meta.structure.dictionary.json string.quoted.double.json + settings + + foreground + #cfcfc2 + + + + name + CoffeeScript String Interpolated + scope + punctuation.section.embedded.coffee + settings + + foreground + #e69f66 + + + + name + PHP: [] + scope + keyword.operator.index-start.php, keyword.operator.index-end.php + settings + + foreground + #e6e1dc + + + + name + PHP: Array + scope + meta.array.php + settings + + foreground + #e6e1dc + + + + name + PHP: Array() + scope + meta.array.php support.function.construct.php, meta.array.empty.php support.function.construct.php + settings + + foreground + #e42e70 + + + + name + PHP: Array Construct + scope + support.function.construct.php + settings + + foreground + #e42e70 + + + + name + PHP: Storage Type Function + scope + storage.type.function.php + settings + + foreground + #f92672dd + + + + name + PHP: Numeric Constant + scope + constant.numeric.php + settings + + foreground + #be84ff + + + + name + PHP: New + scope + keyword.other.new.php + settings + + foreground + #f6aa11 + + + + name + PHP: :: + scope + support.class.php + settings + + foreground + #e6e1dc + + + + name + PHP: Other Property + scope + variable.other.property.php + settings + + foreground + #f6aa11 + + + + name + PHP: Class + scope + storage.modifier.extends.php, storage.type.class.php, keyword.operator.class.php + settings + + foreground + #86c20e + + + + name + PHP: Inherited Class + scope + meta.other.inherited-class.php + settings + + foreground + #86c20e + + + + name + PHP: Storage Type + scope + storage.type.php + settings + + foreground + #6e9cbe + + + + name + PHP: Function + scope + entity.name.function.php + settings + + foreground + #6e9cbe + + + + name + PHP: Function Construct + scope + support.function.construct.php + settings + + foreground + #86c20e + + + + name + PHP: Function Call + scope + entity.name.type.class.php, meta.function-call.php, meta.function-call.static.php, meta.function-call.object.php + settings + + foreground + #e6e1dc + + + + name + PHP: Comment + scope + keyword.other.phpdoc + settings + + foreground + #7c7865 + + + + name + PHP: Source Emebedded + scope + source.php.embedded.block.html + settings + + foreground + #e6e1dc + + + + name + Python: storage + scope + storage.type.class.python, storage.type.function.python, storage.modifier.global.python + settings + + foreground + #3bc0f0 + + + + name + Python: import + scope + keyword.control.import.python, keyword.control.import.from.python + settings + + foreground + #f92672dd + + + + name + Python: Support.exception + scope + support.type.exception.python + settings + + foreground + #6e9cbe + + + + name + Perl: variables + scope + punctuation.definition.variable.perl, variable.other.readwrite.global.perl, variable.other.predefined.perl, keyword.operator.comparison.perl + settings + + foreground + #e42e70 + + + + name + Perl: functions + scope + support.function.perl + settings + + foreground + #6e9cbe + + + + name + Perl: comments + scope + comment.line.number-sign.perl + settings + + fontStyle + italic + foreground + #95815e + + + + name + Perl: quotes + scope + punctuation.definition.string.begin.perl, punctuation.definition.string.end.perl + settings + + foreground + #e6e1dc + + + + name + Perl: char + scope + constant.character.escape.perl + settings + + foreground + #dc322f + + + + name + Ruby: Constant + scope + constant.language.ruby, constant.numeric.ruby + settings + + foreground + #ae81ff + + + + name + Ruby: Variable definition + scope + punctuation.definition.variable.ruby + settings + + foreground + #f6aa11 + + + + name + Ruby: Function Name + scope + meta.function.method.with-arguments.ruby + settings + + foreground + #86c20e + + + + name + Ruby: Variable + scope + variable.language.ruby + settings + + foreground + #e6e1dc + + + + name + Ruby: Function + scope + entity.name.function.ruby + settings + + foreground + #f6aa11 + + + + name + Ruby: Keyword Control + scope + keyword.control.ruby, keyword.control.def.ruby + settings + + fontStyle + bold + foreground + #86c20e + + + + name + Ruby: Class + scope + keyword.control.class.ruby, meta.class.ruby + settings + + foreground + #86c20e + + + + name + Ruby: Class Name + scope + entity.name.type.class.ruby + settings + + foreground + #6e9cbe + + + + name + Ruby: Keyword + scope + keyword.control.ruby + settings + + foreground + #86c20e + + + + name + Ruby: Support Class + scope + support.class.ruby + settings + + foreground + #6e9cbe + + + + name + Ruby: Special Method + scope + keyword.other.special-method.ruby + settings + + foreground + #86c20e + + + + name + Ruby: Constant Other + scope + variable.other.constant.ruby + settings + + foreground + #6e9cbe + + + + name + Ruby: :symbol + scope + constant.other.symbol.ruby + settings + + foreground + #f6f080 + + + + name + Ruby: Punctuation Section + scope + punctuation.section.embedded.ruby, punctuation.definition.string.begin.ruby, punctuation.definition.string.end.ruby + settings + + foreground + #f92672 + + + + name + Ruby: Special Method + scope + keyword.other.special-method.ruby + settings + + foreground + #e42e70 + + + + name + Markdown: plain + scope + text.html.markdown + settings + + foreground + #e6e1dc + + + + name + Markdown: linebreak + scope + text.html.markdown meta.dummy.line-break + settings + + foreground + #e0eddd + + + + name + Markdown: heading + scope + markdown.heading, markup.heading | markup.heading entity.name, markup.heading.markdown punctuation.definition.heading.markdown + settings + + foreground + #fd971f + + + + name + Markup: raw inline + scope + text.html.markdown markup.raw.inline + settings + + foreground + #ec3533 + + + + name + Markup: italic + scope + markup.italic + settings + + fontStyle + italic + foreground + #e42e70 + + + + name + Markup: bold + scope + markup.bold + settings + + fontStyle + bold + foreground + #f92672 + + + + name + Markup: underline + scope + markup.underline + settings + + fontStyle + underline + foreground + #86c20e + + + + name + Markup: strike + scope + markup.strike + settings + + fontStyle + + foreground + #cc4273 + + + + name + Markdown: Blockquote + scope + markup.quote, punctuation.definition.blockquote.markdown + settings + + fontStyle + italic + foreground + #6e9cbe + + + + name + Markup: Quote + scope + markup.quote + settings + + fontStyle + italic + foreground + #6e9cbe + + + + name + Markdown: Link + scope + string.other.link.title.markdown + settings + + fontStyle + underline + foreground + #6e9cbe + + + + name + Markup: Raw block + scope + markup.raw.block + settings + + foreground + #ae81ff + + + + name + Markdown: List Items Punctuation + scope + punctuation.definition.list_item.markdown + settings + + foreground + #e6e1dc + + + + name + Markdown: Raw Block fenced + scope + markup.raw.block.fenced.markdown + settings + + background + #20202060 + foreground + #e6e1dc + + + + name + Markdown: Fenced Bode Block + scope + punctuation.definition.fenced.markdown, variable.language.fenced.markdown + settings + + background + #20202060 + foreground + #636050 + + + + name + Markdown: Fenced Language + scope + variable.language.fenced.markdown + settings + + foreground + #7c7865 + + + + name + Markdown: Separator + scope + meta.separator + settings + + background + #ffffff0f + fontStyle + bold + foreground + #ffffff33 + + + + name + Markup: table + scope + markup.table + settings + + background + #ff3a281a + foreground + #b42a1d + + + + name + Shell: DOTFILES + scope + source.shell + settings + + foreground + #e6e1dc + + + + name + Shell: meta scope in loop + scope + meta.scope.for-in-loop.shell, meta.scope.while-loop.shell, variable.other.loop.shell + settings + + background + #20202020 + foreground + #e6e1dc + + + + name + Shell: meta scope in if + scope + meta.scope.if-block.shell + settings + + background + #20202020 + foreground + #e6e1dc + + + + name + Shell: meta scope in case + scope + meta.scope.case-block.shell + settings + + background + #20202020 + foreground + #e6e1dc + + + + name + Shell: Meta Block + scope + meta.scope.case-block.shell, meta.scope.case-body.shell + settings + + foreground + #fd971f + + + + name + Shell: Comment + scope + comment.line.number-sign.shell + settings + + fontStyle + italic + foreground + #7c7865 + + + + name + Shell: variable + scope + variable.other.normal.shell + settings + + foreground + #6e9cbe + + + + name + Shell: builtin + scope + support.function.builtin.shell + settings + + foreground + #6e9cbe + + + + name + Shell: Function name + scope + entity.name.function.shell + settings + + foreground + #86c20e + + + + name + Shell: keyword + scope + keyword.other.shell + settings + + foreground + #cc7833 + + + + name + Shell: [] + scope + punctuation.definition.logical-expression.shell + settings + + foreground + #e6e1dc + + + + name + Makefile: Comment + scope + comment.line.number-sign.makefile + settings + + foreground + #7c7865 + + + + name + Makefile: Comment punctuation + scope + punctuation.definition.comment.makefile + settings + + foreground + #7c7865 + + + + name + Makefile: Variables + scope + variable.other.makefile + settings + + foreground + #f92672 + + + + name + Makefile: Function name + scope + entity.name.function.makefile + settings + + foreground + #86c20e + + + + name + Makefile: Function + scope + meta.function.makefile + settings + + foreground + #6e9cbe + + + + name + GitGutter deleted + scope + markup.deleted.git_gutter + settings + + foreground + #F92672 + + + + name + GitGutter inserted + scope + markup.inserted.git_gutter + settings + + foreground + #A6E22E + + + + name + GitGutter changed + scope + markup.changed.git_gutter + settings + + foreground + #967EFB + + + + name + GitGutter ignored + scope + markup.ignored.git_gutter + settings + + foreground + #565656 + + + + name + GitGutter untracked + scope + markup.untracked.git_gutter + settings + + foreground + #565656 + + + + name + Nginx path + scope + string.other.path.nginx + settings + + foreground + #fc951e + + + + name + Django: Template Tag + scope + meta.scope.django.template.tag + settings + + background + #00000010 + + + + name + Django: Template Variable + scope + variable.other.django.template + settings + + background + #00000010 + + + + name + Django: Template Braces + scope + entity.other.django.tagbraces + settings + + foreground + #cc7833 + + + + name + Other: Removal + scope + other.package.exclude, other.remove + settings + + foreground + #d3201f + + + + name + Raw New Line: Carriage Rerturn + scope + glyph.carriage-return + settings + + foreground + #66cccc + + + + name + Raw New Line: New Line Glyph + scope + glyph.new-line + settings + + foreground + #f2777a + + + + uuid + 7126a769-fb8c-4004-9141-bd2f620fd3f4 + + diff --git a/sublime_themes/material-dark.tmTheme b/sublime_themes/material-dark.tmTheme new file mode 100644 index 00000000..4467f2c6 --- /dev/null +++ b/sublime_themes/material-dark.tmTheme @@ -0,0 +1,1011 @@ + + + + + author + Mattia Astorino + colorSpaceName + sRGB + name + Material-Theme-Darker + semanticClass + material.theme.darker + settings + + + settings + + activeGuide + #FFFFFF50 + background + #212121 + caret + #FFCC00 + findHighlight + #F8E71C + foreground + #eeffffff + guide + #42424270 + gutterForeground + #424242 + invisibles + #65737e + lineHighlight + #00000050 + popupCss + + selection + #61616150 + selectionBorder + #61616150 + shadow + #00000010 + stackGuide + #42424280 + + + + name + Comments + scope + comment, punctuation.definition.comment + settings + + fontStyle + italic + foreground + #4A4A4A + + + + name + Variable + scope + variable, string constant.other.placeholder + settings + + foreground + #eeffffff + + + + name + Colors + scope + constant.other.color + settings + + foreground + #ffffff + + + + name + Invalid + scope + invalid, invalid.illegal, invalid.broken + settings + + background + #FF5370 + foreground + #ffffff + + + + name + Unimplemented + scope + invalid.unimplemented + settings + + background + #C3E88D + foreground + #ffffff + + + + name + Invalid deprecated + scope + invalid.deprecated + settings + + background + #C792EA + foreground + #ffffff + + + + name + Keyword, Storage + scope + keyword, storage.type, storage.modifier + settings + + foreground + #C792EA + + + + name + Keyword, Storage + scope + storage.type, keyword.control + settings + + fontStyle + italic + + + + name + Operator, Misc + scope + keyword.operator, constant.other.color, punctuation, meta.tag, punctuation.definition.tag, punctuation.separator.inheritance.php, punctuation.definition.tag.html, punctuation.definition.tag.begin.html, punctuation.definition.tag.end.html, punctuation.section.embedded, keyword.other.template, keyword.other.substitution + settings + + foreground + #89DDFF + + + + name + Tag + scope + entity.name.tag, meta.tag.sgml, markup.deleted.git_gutter + settings + + foreground + #f07178 + + + + name + Function, Special Method, Block Level + scope + entity.name.function, meta.function-call, variable.function, support.function, keyword.other.special-method, meta.block-level + settings + + foreground + #82AAFF + + + + name + Other Variable, String Link + scope + support.other.variable, string.other.link + settings + + foreground + #f07178 + + + + name + Number, Constant, Function Argument, Tag Attribute, Embedded + scope + constant.numeric, constant.language, support.constant, constant.character, variable.parameter, keyword.other.unit + settings + + foreground + #F78C6C + + + + name + String, Symbols, Inherited Class, Markup Heading + scope + string, constant.other.symbol, constant.other.key, entity.other.inherited-class, markup.heading, markup.inserted.git_gutter, meta.group.braces.curly constant.other.object.key.js string.unquoted.label.js + settings + + fontStyle + normal + foreground + #C3E88D + + + + name + Class, Support + scope + entity.name.class, entity.name.type.class, support.type, support.class, support.orther.namespace.use.php, meta.use.php, support.other.namespace.php, markup.changed.git_gutter, support.type.sys-types + settings + + foreground + #FFCB6B + + + + name + CSS Class and Support + scope + source.css support.type, source.sass support.type, source.scss support.type, source.less support.type, source.stylus support.type + settings + + foreground + #B2CCD6 + + + + name + Sub-methods + scope + entity.name.module.js, variable.import.parameter.js, variable.other.class.js + settings + + foreground + #FF5370 + + + + name + Language methods + scope + variable.language + settings + + fontStyle + italic + foreground + #FF5370 + + + + name + entity.name.method.js + scope + entity.name.method.js + settings + + foreground + #82AAFF + + + + name + meta.method.js + scope + meta.class-method.js entity.name.function.js, variable.function.constructor + settings + + foreground + #82AAFF + + + + name + Attributes + scope + entity.other.attribute-name + settings + + foreground + #C792EA + + + + name + HTML Attributes + scope + text.html.basic entity.other.attribute-name.html, text.html.basic entity.other.attribute-name + settings + + fontStyle + italic + foreground + #FFCB6B + + + + name + CSS Classes + scope + entity.other.attribute-name.class + settings + + foreground + #FFCB6B + + + + name + CSS Id + scope + source.sass keyword.control + settings + + foreground + #82AAFF + + + + name + Inserted + scope + markup.inserted + settings + + foreground + #C3E88D + + + + name + Deleted + scope + markup.deleted + settings + + foreground + #FF5370 + + + + name + Changed + scope + markup.changed + settings + + foreground + #C792EA + + + + name + Regular Expressions + scope + string.regexp + settings + + foreground + #89DDFF + + + + name + Escape Characters + scope + constant.character.escape + settings + + foreground + #89DDFF + + + + name + URL + scope + *url*, *link*, *uri* + settings + + fontStyle + underline + + + + name + Search Results Nums + scope + constant.numeric.line-number.find-in-files - match + settings + + foreground + #C17E70 + + + + name + Search Results Lines + scope + entity.name.filename.find-in-files + settings + + foreground + #C3E88D + + + + name + Decorators + scope + tag.decorator.js entity.name.tag.js, tag.decorator.js punctuation.definition.tag.js + settings + + fontStyle + italic + foreground + #82AAFF + + + + name + ES7 Bind Operator + scope + source.js constant.other.object.key.js string.unquoted.label.js + settings + + fontStyle + italic + foreground + #FF5370 + + + + name + JSON Key - Level 8 + scope + source.json meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json string.quoted.double.json - meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json, source.json meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json punctuation.definition.string - meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json punctuation.definition.string + settings + + foreground + #C3E88D + + + + name + JSON Key - Level 7 + scope + source.json meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json string.quoted.double.json - meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json, source.json meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json punctuation.definition.string - meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json punctuation.definition.string + settings + + foreground + #C792EA + + + + name + JSON Key - Level 6 + scope + source.json meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json string.quoted.double.json - meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json, source.json meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json punctuation.definition.string - meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json punctuation.definition.string + settings + + foreground + #f07178 + + + + name + JSON Key - Level 5 + scope + source.json meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json string.quoted.double.json - meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json, source.json meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json punctuation.definition.string - meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json punctuation.definition.string + settings + + foreground + #82AAFF + + + + name + JSON Key - Level 4 + scope + source.json meta meta meta meta meta meta meta meta.structure.dictionary.json string.quoted.double.json - meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json, source.json meta meta meta meta meta meta meta meta.structure.dictionary.json punctuation.definition.string - meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json punctuation.definition.string + settings + + foreground + #C17E70 + + + + name + JSON Key - Level 3 + scope + source.json meta meta meta meta meta meta.structure.dictionary.json string.quoted.double.json - meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json, source.json meta meta meta meta meta meta.structure.dictionary.json punctuation.definition.string - meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json punctuation.definition.string + settings + + foreground + #FF5370 + + + + name + JSON Key - Level 2 + scope + source.json meta meta meta meta.structure.dictionary.json string.quoted.double.json - meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json, source.json meta meta meta meta.structure.dictionary.json punctuation.definition.string - meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json punctuation.definition.string + settings + + foreground + #F78C6C + + + + name + JSON Key - Level 1 + scope + source.json meta meta.structure.dictionary.json string.quoted.double.json - meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json, source.json meta meta.structure.dictionary.json punctuation.definition.string - meta meta.structure.dictionary.json meta.structure.dictionary.value.json punctuation.definition.string + settings + + foreground + #FFCB6B + + + + name + JSON Key - Level 0 + scope + source.json meta.structure.dictionary.json string.quoted.double.json - meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json, source.json meta.structure.dictionary.json punctuation.definition.string - meta.structure.dictionary.json meta.structure.dictionary.value.json punctuation.definition.string + settings + + foreground + #C792EA + + + + name + Markdown - Plain + scope + text.html.markdown, punctuation.definition.list_item.markdown + settings + + foreground + #eeffffff + + + + name + Markdown - Markup Raw Inline + scope + text.html.markdown markup.raw.inline + settings + + foreground + #C792EA + + + + name + Markdown - Markup Raw Inline Punctuation + scope + text.html.markdown punctuation.definition.raw.markdown + settings + + foreground + #65737e + + + + name + Markdown - Line Break + scope + text.html.markdown meta.dummy.line-break + settings + + foreground + + + + + name + Markdown - Heading + scope + markdown.heading, markup.heading | markup.heading entity.name, markup.heading.markdown punctuation.definition.heading.markdown + settings + + foreground + #C3E88D + + + + name + Markup - Italic + scope + markup.italic + settings + + fontStyle + italic + foreground + #f07178 + + + + name + Markup - Bold + scope + markup.bold, markup.bold string + settings + + fontStyle + bold + foreground + #f07178 + + + + name + Markup - Bold & Italic + scope + markup.bold markup.italic, markup.italic markup.bold, markup.quote markup.bold, markup.bold markup.italic string, markup.italic markup.bold string, markup.quote markup.bold string + settings + + fontStyle + bold italic + + + + name + Markup - Underline + scope + markup.underline + settings + + fontStyle + underline + foreground + #F78C6C + + + + name + Markup - Strike + scope + markup.strike + settings + + fontStyle + strike + foreground + + + + + name + Markdown - Blockquote + scope + markup.quote punctuation.definition.blockquote.markdown + settings + + background + #65737e + foreground + #65737e + + + + name + Markup - Quote + scope + markup.quote + settings + + fontStyle + italic + foreground + + + + + name + Markdown - Link + scope + string.other.link.title.markdown + settings + + foreground + #82AAFF + + + + name + Markdown - Link Description + scope + string.other.link.description.title.markdown + settings + + foreground + #C792EA + + + + name + Markdown - Link Anchor + scope + constant.other.reference.link.markdown + settings + + foreground + #FFCB6B + + + + name + Markup - Raw Block + scope + markup.raw.block + settings + + foreground + #C792EA + + + + name + Markdown - Raw Block Fenced + scope + markup.raw.block.fenced.markdown + settings + + background + #00000050 + + + + name + Markdown - Fenced Bode Block + scope + punctuation.definition.fenced.markdown + settings + + background + #00000050 + + + + name + Markdown - Fenced Bode Block Variable + scope + markup.raw.block.fenced.markdown, variable.language.fenced.markdown, punctuation.section.class.end + settings + + foreground + #eeffffff + + + + name + Markdown - Fenced Language + scope + variable.language.fenced.markdown + settings + + fontStyle + + foreground + #65737e + + + + name + Markdown - Punctuation Definition + scope + text.html.markdown punctuation.definition + settings + + foreground + #4A4A4A + + + + name + Markdown HTML - Punctuation Definition + scope + text.html.markdown meta.disable-markdown punctuation.definition + settings + + foreground + #89DDFF + + + + name + Markdown - Separator + scope + meta.separator + settings + + background + #00000050 + fontStyle + bold + foreground + #65737e + + + + name + Markup - Table + scope + markup.table + settings + + background + + foreground + #eeffffff + + + + name + AceJump Label - Blue + scope + acejump.label.blue + settings + + background + #82AAFF + foreground + #ffffff + + + + name + AceJump Label - Green + scope + acejump.label.green + settings + + background + #C3E88D + foreground + #ffffff + + + + name + AceJump Label - Orange + scope + acejump.label.orange + settings + + background + #F78C6C + foreground + #ffffff + + + + name + AceJump Label - Purple + scope + acejump.label.purple + settings + + background + #C792EA + foreground + #ffffff + + + + name + SublimeLinter Warning + scope + sublimelinter.mark.warning + settings + + foreground + #FFCB6B + + + + name + SublimeLinter Gutter Mark + scope + sublimelinter.gutter-mark + settings + + foreground + #ffffff + + + + name + SublimeLinter Error + scope + sublimelinter.mark.error + settings + + foreground + #FF5370 + + + + name + SublimeLinter Annotation + scope + sublimelinter.annotations + settings + + background + #C17E70 + + + + name + GitGutter Ignored + scope + markup.ignored.git_gutter + settings + + foreground + #65737e + + + + name + GitGutter Untracked + scope + markup.untracked.git_gutter + settings + + foreground + #65737e + + + + name + GitGutter Inserted + scope + markup.inserted.git_gutter + settings + + foreground + #C3E88D + + + + name + GitGutter Changed + scope + markup.changed.git_gutter + settings + + foreground + #FFCB6B + + + + name + GitGutter Deleted + scope + markup.deleted.git_gutter + settings + + foreground + #FF5370 + + + + name + Bracket Curly + scope + brackethighlighter.default + settings + + foreground + #B2CCD6 + + + + name + Bracket Quote + scope + brackethighlighter.quote + settings + + foreground + #C3E88D + + + + name + Bracket Unmatched + scope + brackethighlighter.unmatched + settings + + foreground + #FF5370 + + + + uuid + 4F44C0F5-1F8D-4C52-8BAB-F0951904C1EC + + diff --git a/sublime_themes/material-light.tmTheme b/sublime_themes/material-light.tmTheme new file mode 100644 index 00000000..fa85508a --- /dev/null +++ b/sublime_themes/material-light.tmTheme @@ -0,0 +1,1011 @@ + + + + + author + Mattia Astorino + colorSpaceName + sRGB + name + Material-Theme-Lighter + semanticClass + material.theme.lighter + settings + + + settings + + activeGuide + #00000070 + background + #fafafa + caret + #27272790 + findHighlight + #F8E71C + foreground + #80CBC4 + guide + #B0BEC570 + gutterForeground + #CFD8DC + invisibles + #E7EAEC + lineHighlight + #90A4AE20 + popupCss + + selection + #80CBC440 + selectionBorder + #80CBC440 + shadow + #90A4AE50 + stackGuide + #B0BEC580 + + + + name + Comments + scope + comment, punctuation.definition.comment + settings + + fontStyle + italic + foreground + #CCD7DA + + + + name + Variable + scope + variable, string constant.other.placeholder + settings + + foreground + #80CBC4 + + + + name + Colors + scope + constant.other.color + settings + + foreground + #ffffff + + + + name + Invalid + scope + invalid, invalid.illegal, invalid.broken + settings + + background + #E53935 + foreground + #ffffff + + + + name + Unimplemented + scope + invalid.unimplemented + settings + + background + #91B859 + foreground + #ffffff + + + + name + Invalid deprecated + scope + invalid.deprecated + settings + + background + #7C4DFF + foreground + #ffffff + + + + name + Keyword, Storage + scope + keyword, storage.type, storage.modifier + settings + + foreground + #7C4DFF + + + + name + Keyword, Storage + scope + storage.type, keyword.control + settings + + fontStyle + italic + + + + name + Operator, Misc + scope + keyword.operator, constant.other.color, punctuation, meta.tag, punctuation.definition.tag, punctuation.separator.inheritance.php, punctuation.definition.tag.html, punctuation.definition.tag.begin.html, punctuation.definition.tag.end.html, punctuation.section.embedded, keyword.other.template, keyword.other.substitution + settings + + foreground + #39ADB5 + + + + name + Tag + scope + entity.name.tag, meta.tag.sgml, markup.deleted.git_gutter + settings + + foreground + #FF5370 + + + + name + Function, Special Method, Block Level + scope + entity.name.function, meta.function-call, variable.function, support.function, keyword.other.special-method, meta.block-level + settings + + foreground + #6182B8 + + + + name + Other Variable, String Link + scope + support.other.variable, string.other.link + settings + + foreground + #FF5370 + + + + name + Number, Constant, Function Argument, Tag Attribute, Embedded + scope + constant.numeric, constant.language, support.constant, constant.character, variable.parameter, keyword.other.unit + settings + + foreground + #F76D47 + + + + name + String, Symbols, Inherited Class, Markup Heading + scope + string, constant.other.symbol, constant.other.key, entity.other.inherited-class, markup.heading, markup.inserted.git_gutter, meta.group.braces.curly constant.other.object.key.js string.unquoted.label.js + settings + + fontStyle + normal + foreground + #91B859 + + + + name + Class, Support + scope + entity.name.class, entity.name.type.class, support.type, support.class, support.orther.namespace.use.php, meta.use.php, support.other.namespace.php, markup.changed.git_gutter, support.type.sys-types + settings + + foreground + #FFB62C + + + + name + CSS Class and Support + scope + source.css support.type, source.sass support.type, source.scss support.type, source.less support.type, source.stylus support.type + settings + + foreground + #8796B0 + + + + name + Sub-methods + scope + entity.name.module.js, variable.import.parameter.js, variable.other.class.js + settings + + foreground + #E53935 + + + + name + Language methods + scope + variable.language + settings + + fontStyle + italic + foreground + #E53935 + + + + name + entity.name.method.js + scope + entity.name.method.js + settings + + foreground + #6182B8 + + + + name + meta.method.js + scope + meta.class-method.js entity.name.function.js, variable.function.constructor + settings + + foreground + #6182B8 + + + + name + Attributes + scope + entity.other.attribute-name + settings + + foreground + #7C4DFF + + + + name + HTML Attributes + scope + text.html.basic entity.other.attribute-name.html, text.html.basic entity.other.attribute-name + settings + + fontStyle + italic + foreground + #FFB62C + + + + name + CSS Classes + scope + entity.other.attribute-name.class + settings + + foreground + #FFB62C + + + + name + CSS Id + scope + source.sass keyword.control + settings + + foreground + #6182B8 + + + + name + Inserted + scope + markup.inserted + settings + + foreground + #91B859 + + + + name + Deleted + scope + markup.deleted + settings + + foreground + #E53935 + + + + name + Changed + scope + markup.changed + settings + + foreground + #7C4DFF + + + + name + Regular Expressions + scope + string.regexp + settings + + foreground + #39ADB5 + + + + name + Escape Characters + scope + constant.character.escape + settings + + foreground + #39ADB5 + + + + name + URL + scope + *url*, *link*, *uri* + settings + + fontStyle + underline + + + + name + Search Results Nums + scope + constant.numeric.line-number.find-in-files - match + settings + + foreground + #C17E70 + + + + name + Search Results Lines + scope + entity.name.filename.find-in-files + settings + + foreground + #91B859 + + + + name + Decorators + scope + tag.decorator.js entity.name.tag.js, tag.decorator.js punctuation.definition.tag.js + settings + + fontStyle + italic + foreground + #6182B8 + + + + name + ES7 Bind Operator + scope + source.js constant.other.object.key.js string.unquoted.label.js + settings + + fontStyle + italic + foreground + #E53935 + + + + name + JSON Key - Level 8 + scope + source.json meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json string.quoted.double.json - meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json, source.json meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json punctuation.definition.string - meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json punctuation.definition.string + settings + + foreground + #91B859 + + + + name + JSON Key - Level 7 + scope + source.json meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json string.quoted.double.json - meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json, source.json meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json punctuation.definition.string - meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json punctuation.definition.string + settings + + foreground + #7C4DFF + + + + name + JSON Key - Level 6 + scope + source.json meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json string.quoted.double.json - meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json, source.json meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json punctuation.definition.string - meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json punctuation.definition.string + settings + + foreground + #FF5370 + + + + name + JSON Key - Level 5 + scope + source.json meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json string.quoted.double.json - meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json, source.json meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json punctuation.definition.string - meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json punctuation.definition.string + settings + + foreground + #6182B8 + + + + name + JSON Key - Level 4 + scope + source.json meta meta meta meta meta meta meta meta.structure.dictionary.json string.quoted.double.json - meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json, source.json meta meta meta meta meta meta meta meta.structure.dictionary.json punctuation.definition.string - meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json punctuation.definition.string + settings + + foreground + #C17E70 + + + + name + JSON Key - Level 3 + scope + source.json meta meta meta meta meta meta.structure.dictionary.json string.quoted.double.json - meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json, source.json meta meta meta meta meta meta.structure.dictionary.json punctuation.definition.string - meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json punctuation.definition.string + settings + + foreground + #E53935 + + + + name + JSON Key - Level 2 + scope + source.json meta meta meta meta.structure.dictionary.json string.quoted.double.json - meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json, source.json meta meta meta meta.structure.dictionary.json punctuation.definition.string - meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json punctuation.definition.string + settings + + foreground + #F76D47 + + + + name + JSON Key - Level 1 + scope + source.json meta meta.structure.dictionary.json string.quoted.double.json - meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json, source.json meta meta.structure.dictionary.json punctuation.definition.string - meta meta.structure.dictionary.json meta.structure.dictionary.value.json punctuation.definition.string + settings + + foreground + #FFB62C + + + + name + JSON Key - Level 0 + scope + source.json meta.structure.dictionary.json string.quoted.double.json - meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json, source.json meta.structure.dictionary.json punctuation.definition.string - meta.structure.dictionary.json meta.structure.dictionary.value.json punctuation.definition.string + settings + + foreground + #7C4DFF + + + + name + Markdown - Plain + scope + text.html.markdown, punctuation.definition.list_item.markdown + settings + + foreground + #80CBC4 + + + + name + Markdown - Markup Raw Inline + scope + text.html.markdown markup.raw.inline + settings + + foreground + #7C4DFF + + + + name + Markdown - Markup Raw Inline Punctuation + scope + text.html.markdown punctuation.definition.raw.markdown + settings + + foreground + #E7EAEC + + + + name + Markdown - Line Break + scope + text.html.markdown meta.dummy.line-break + settings + + foreground + + + + + name + Markdown - Heading + scope + markdown.heading, markup.heading | markup.heading entity.name, markup.heading.markdown punctuation.definition.heading.markdown + settings + + foreground + #91B859 + + + + name + Markup - Italic + scope + markup.italic + settings + + fontStyle + italic + foreground + #FF5370 + + + + name + Markup - Bold + scope + markup.bold, markup.bold string + settings + + fontStyle + bold + foreground + #FF5370 + + + + name + Markup - Bold & Italic + scope + markup.bold markup.italic, markup.italic markup.bold, markup.quote markup.bold, markup.bold markup.italic string, markup.italic markup.bold string, markup.quote markup.bold string + settings + + fontStyle + bold italic + + + + name + Markup - Underline + scope + markup.underline + settings + + fontStyle + underline + foreground + #F76D47 + + + + name + Markup - Strike + scope + markup.strike + settings + + fontStyle + strike + foreground + + + + + name + Markdown - Blockquote + scope + markup.quote punctuation.definition.blockquote.markdown + settings + + background + #E7EAEC + foreground + #E7EAEC + + + + name + Markup - Quote + scope + markup.quote + settings + + fontStyle + italic + foreground + + + + + name + Markdown - Link + scope + string.other.link.title.markdown + settings + + foreground + #6182B8 + + + + name + Markdown - Link Description + scope + string.other.link.description.title.markdown + settings + + foreground + #7C4DFF + + + + name + Markdown - Link Anchor + scope + constant.other.reference.link.markdown + settings + + foreground + #FFB62C + + + + name + Markup - Raw Block + scope + markup.raw.block + settings + + foreground + #7C4DFF + + + + name + Markdown - Raw Block Fenced + scope + markup.raw.block.fenced.markdown + settings + + background + #90A4AE20 + + + + name + Markdown - Fenced Bode Block + scope + punctuation.definition.fenced.markdown + settings + + background + #90A4AE20 + + + + name + Markdown - Fenced Bode Block Variable + scope + markup.raw.block.fenced.markdown, variable.language.fenced.markdown, punctuation.section.class.end + settings + + foreground + #80CBC4 + + + + name + Markdown - Fenced Language + scope + variable.language.fenced.markdown + settings + + fontStyle + + foreground + #E7EAEC + + + + name + Markdown - Punctuation Definition + scope + text.html.markdown punctuation.definition + settings + + foreground + #CCD7DA + + + + name + Markdown HTML - Punctuation Definition + scope + text.html.markdown meta.disable-markdown punctuation.definition + settings + + foreground + #39ADB5 + + + + name + Markdown - Separator + scope + meta.separator + settings + + background + #90A4AE20 + fontStyle + bold + foreground + #E7EAEC + + + + name + Markup - Table + scope + markup.table + settings + + background + + foreground + #80CBC4 + + + + name + AceJump Label - Blue + scope + acejump.label.blue + settings + + background + #6182B8 + foreground + #ffffff + + + + name + AceJump Label - Green + scope + acejump.label.green + settings + + background + #91B859 + foreground + #ffffff + + + + name + AceJump Label - Orange + scope + acejump.label.orange + settings + + background + #F76D47 + foreground + #ffffff + + + + name + AceJump Label - Purple + scope + acejump.label.purple + settings + + background + #7C4DFF + foreground + #ffffff + + + + name + SublimeLinter Warning + scope + sublimelinter.mark.warning + settings + + foreground + #FFB62C + + + + name + SublimeLinter Gutter Mark + scope + sublimelinter.gutter-mark + settings + + foreground + #ffffff + + + + name + SublimeLinter Error + scope + sublimelinter.mark.error + settings + + foreground + #E53935 + + + + name + SublimeLinter Annotation + scope + sublimelinter.annotations + settings + + background + #C17E70 + + + + name + GitGutter Ignored + scope + markup.ignored.git_gutter + settings + + foreground + #E7EAEC + + + + name + GitGutter Untracked + scope + markup.untracked.git_gutter + settings + + foreground + #E7EAEC + + + + name + GitGutter Inserted + scope + markup.inserted.git_gutter + settings + + foreground + #91B859 + + + + name + GitGutter Changed + scope + markup.changed.git_gutter + settings + + foreground + #FFB62C + + + + name + GitGutter Deleted + scope + markup.deleted.git_gutter + settings + + foreground + #E53935 + + + + name + Bracket Curly + scope + brackethighlighter.default + settings + + foreground + #8796B0 + + + + name + Bracket Quote + scope + brackethighlighter.quote + settings + + foreground + #91B859 + + + + name + Bracket Unmatched + scope + brackethighlighter.unmatched + settings + + foreground + #E53935 + + + + uuid + 133d1250-19c6-4565-bc93-b37fd36f7fc9 + + diff --git a/sublime_themes/monokai.tmTheme b/sublime_themes/monokai.tmTheme new file mode 100644 index 00000000..edf20142 --- /dev/null +++ b/sublime_themes/monokai.tmTheme @@ -0,0 +1,297 @@ + + + + + + + + + name + Monokai + settings + + + settings + + background + #272822 + caret + #F8F8F0 + foreground + #F8F8F2 + invisibles + #3B3A32 + lineHighlight + #3E3D32 + selection + #49483E + + + + name + Comment + scope + comment + settings + + foreground + #75715E + + + + name + String + scope + string + settings + + foreground + #E6DB74 + + + + name + Number + scope + constant.numeric + settings + + foreground + #AE81FF + + + + name + Built-in constant + scope + constant.language + settings + + foreground + #AE81FF + + + + name + User-defined constant + scope + constant.character, constant.other + settings + + foreground + #AE81FF + + + + name + Variable + scope + variable + settings + + fontStyle + + + + + name + Keyword + scope + keyword + settings + + foreground + #F92672 + + + + name + Storage + scope + storage + settings + + fontStyle + + foreground + #F92672 + + + + name + Storage type + scope + storage.type + settings + + fontStyle + italic + foreground + #66D9EF + + + + name + Class name + scope + entity.name.class + settings + + fontStyle + underline + foreground + #A6E22E + + + + name + Inherited class + scope + entity.other.inherited-class + settings + + fontStyle + italic underline + foreground + #A6E22E + + + + name + Function name + scope + entity.name.function + settings + + fontStyle + + foreground + #A6E22E + + + + name + Function argument + scope + variable.parameter + settings + + fontStyle + italic + foreground + #FD971F + + + + name + Tag name + scope + entity.name.tag + settings + + fontStyle + + foreground + #F92672 + + + + name + Tag attribute + scope + entity.other.attribute-name + settings + + fontStyle + + foreground + #A6E22E + + + + name + Library function + scope + support.function + settings + + fontStyle + + foreground + #66D9EF + + + + name + Library constant + scope + support.constant + settings + + fontStyle + + foreground + #66D9EF + + + + name + Library class/type + scope + support.type, support.class + settings + + fontStyle + italic + foreground + #66D9EF + + + + name + Library variable + scope + support.other.variable + settings + + fontStyle + + + + + name + Invalid + scope + invalid + settings + + background + #F92672 + fontStyle + + foreground + #F8F8F0 + + + + name + Invalid deprecated + scope + invalid.deprecated + settings + + background + #AE81FF + foreground + #F8F8F0 + + + + uuid + D8D5E82E-3D5B-46B5-B38E-8C841C21347D + colorSpaceName + sRGB + semanticClass + theme.dark.monokai + + \ No newline at end of file diff --git a/sublime_themes/solarized-dark.tmTheme b/sublime_themes/solarized-dark.tmTheme new file mode 100644 index 00000000..b39a1395 --- /dev/null +++ b/sublime_themes/solarized-dark.tmTheme @@ -0,0 +1,1189 @@ + + + + + name + Solarized (dark) + settings + + + + + settings + + background + #002b36 + caret + #eee8d5 + foreground + #839496 + gutter + #073642 + invisibles + #586e75 + lineHighlight + #073642 + selection + #2c4c55 + selectionBorder + #586e75 + + + + + + + + + + + name + Comment + scope + comment, meta.documentation + settings + + foreground + #586e75 + + + + name + String + scope + string + settings + + foreground + #2aa198 + + + + name + Regexp + scope + string.regexp + settings + + foreground + #2aa198 + + + + name + Escape \ char + scope + constant.character.escape + settings + + foreground + #dc322f + + + + name + Number + scope + constant.numeric + settings + + foreground + #6c71c4 + + + + name + Variable + scope + variable + settings + + foreground + #268bd2 + + + + name + Keyword + scope + keyword + settings + + foreground + #859900 + + + + name + Import + scope + meta.import keyword, keyword.control.import, keyword.control.import.from, keyword.other.import, keyword.control.at-rule.include, keyword.control.at-rule.import + settings + + foreground + #cb4b16 + + + + name + Arithmetical, Assignment, Comparision Operators + scope + keyword.operator.comparison, keyword.operator.assignment, keyword.operator.arithmetic + settings + + foreground + #657b83 + + + + name + Storage + scope + storage + settings + + foreground + #859900 + + + + name + Class + scope + keyword.control.class, meta.class, entity.name.class, entity.name.type.class + settings + + foreground + #b58900 + + + + name + Library class + scope + support.type, support.class + settings + + foreground + #859900 + + + + name + Function name + scope + entity.name.function + settings + + foreground + #b58900 + + + + name + Variable start + scope + punctuation.definition.variable + settings + + foreground + #859900 + + + + name + Built-in constant + scope + constant.language, meta.preprocessor + settings + + foreground + #b58900 + + + + name + Support.construct + scope + support.function.construct, keyword.other.new + settings + + foreground + #dc322f + + + + name + User-defined constant + scope + constant.character, constant.other + settings + + foreground + #cb4b16 + + + + name + Tag name + scope + entity.name.tag + settings + + foreground + #268bd2 + + + + name + Tag start/end + scope + punctuation.definition.tag.html, punctuation.definition.tag.begin, punctuation.definition.tag.end + settings + + foreground + #586e75 + + + + name + Library function + scope + support.function + settings + + foreground + #859900 + + + + name + Continuation + scope + punctuation.separator.continuation + settings + + foreground + #dc322f + + + + name + Storage Type + scope + storage.type + settings + + foreground + #268bd2 + + + + name + Exception + scope + support.type.exception + settings + + foreground + #cb4b16 + + + + name + Special + scope + keyword.other.special-method + settings + + foreground + #cb4b16 + + + + name + Invalid + scope + invalid + settings + + background + #6e2e32 + + + + name + Quoted String + scope + string.quoted.double, string.quoted.single + settings + + foreground + #2aa198 + + + + name + Quoted Single + scope + punctuation.definition.string.begin, punctuation.definition.string.end + settings + + foreground + #839496 + + + + name + [] + scope + meta.brace.square + settings + + foreground + #268bd2 + + + + name + () + scope + meta.brace.round, punctuation.definition.parameters.begin, punctuation.definition.parameters.end + settings + + foreground + #657b83 + + + + name + {} + scope + meta.brace.curly + settings + + foreground + #657b83 + + + + + + + + name + CSS: Standard color value + scope + support.constant.color, invalid.deprecated.color.w3c-non-standard-color-name.scss + settings + + foreground + #b58900 + + + + name + CSS: Selector > [] and non-spec tags + scope + meta.selector.css + settings + + foreground + #657b83 + + + + name + CSS: Tag + scope + entity.name.tag.css, entity.name.tag.scss, source.less keyword.control.html.elements, source.sass keyword.control.untitled + settings + + foreground + #b58900 + + + + name + CSS .class + scope + entity.other.attribute-name.class.css, entity.other.attribute-name.class.sass + settings + + foreground + #b58900 + + + + name + CSS: #id + scope + source.css entity.other.attribute-name.id, source.less entity.other.attribute-name.id, source.scss entity.other.attribute-name.id, source.sass entity.other.attribute-name.id + settings + + foreground + #b58900 + + + + name + CSS :pseudo + scope + entity.other.attribute-name.pseudo-element.css, entity.other.attribute-name.pseudo-class, entity.other.attribute-name.tag.pseudo-class + settings + + foreground + #268bd2 + + + + + + name + HTML: = + scope + text.html.basic meta.tag.other.html, text.html.basic meta.tag.any.html, text.html.basic meta.tag.block.any, text.html.basic meta.tag.inline.any, text.html.basic meta.tag.structure.any.html, text.html.basic source.js.embedded.html, punctuation.separator.key-value.html + settings + + foreground + #657b83 + + + + name + HTML: something= + scope + text.html.basic entity.other.attribute-name.html, meta.tag.xml entity.other.attribute-name + settings + + foreground + #b58900 + + + + + + name + Ruby: Special Method + scope + keyword.other.special-method.ruby + settings + + foreground + #859900 + + + + name + Ruby: Constant Other + scope + variable.other.constant.ruby + settings + + foreground + #b58900 + + + + name + Ruby: :symbol + scope + constant.other.symbol.ruby + settings + + foreground + #2aa198 + + + + name + Ruby: Special Method + scope + keyword.other.special-method.ruby + settings + + foreground + #cb4b16 + + + + + + name + PHP: Array() + scope + meta.array support.function.construct.php + settings + + foreground + #b58900 + + + + + + name + C: Preprocessor + scope + entity.name.function.preprocessor.c, meta.preprocessor.c.include, meta.preprocessor.macro.c + settings + + foreground + #cb4b16 + + + + name + C: include + scope + meta.preprocessor.c.include string, meta.preprocessor.c.include punctuation.definition.string.begin, meta.preprocessor.c.include punctuation.definition.string.end + settings + + foreground + #2aa198 + + + + + + name + Other: Removal + scope + other.package.exclude, other.remove + settings + + foreground + #dc322f + + + + name + Other: Add + scope + other.add + settings + + foreground + #2aa198 + + + + + + name + Tex: {} + scope + punctuation.section.group.tex , punctuation.definition.arguments.begin.latex, punctuation.definition.arguments.end.latex, punctuation.definition.arguments.latex + settings + + foreground + #dc322f + + + + name + Tex: {text} + scope + meta.group.braces.tex + settings + + foreground + #b58900 + + + + name + Tex: Other Math + scope + string.other.math.tex + settings + + foreground + #b58900 + + + + name + Tex: {var} + scope + variable.parameter.function.latex + settings + + foreground + #cb4b16 + + + + name + Tex: Math \\ + scope + punctuation.definition.constant.math.tex + settings + + foreground + #dc322f + + + + name + Tex: Constant Math + scope + text.tex.latex constant.other.math.tex, constant.other.general.math.tex, constant.other.general.math.tex, constant.character.math.tex + settings + + foreground + #2aa198 + + + + name + Tex: Other Math String + scope + string.other.math.tex + settings + + foreground + #b58900 + + + + name + Tex: $ + scope + punctuation.definition.string.begin.tex, punctuation.definition.string.end.tex + settings + + foreground + #dc322f + + + + name + Tex: \label + scope + keyword.control.label.latex, text.tex.latex constant.other.general.math.tex + settings + + foreground + #2aa198 + + + + name + Tex: \label { } + scope + variable.parameter.definition.label.latex + settings + + foreground + #dc322f + + + + name + Tex: Function + scope + support.function.be.latex + settings + + foreground + #859900 + + + + name + Tex: Support Function Section + scope + support.function.section.latex + settings + + foreground + #cb4b16 + + + + name + Tex: Support Function + scope + support.function.general.tex + settings + + foreground + #2aa198 + + + + name + Tex: Reference Label + scope + keyword.control.ref.latex + settings + + foreground + #2aa198 + + + + + + name + Python: storage + scope + storage.type.class.python, storage.type.function.python, storage.modifier.global.python + settings + + foreground + #859900 + + + + name + Python: Support.exception + scope + support.type.exception.python + settings + + foreground + #b58900 + + + + + + name + Shell: meta scope in loop + scope + meta.scope.for-in-loop.shell, variable.other.loop.shell + settings + + foreground + #586e75 + + + + name + Shell: Meta Block + scope + meta.scope.case-block.shell, meta.scope.case-body.shell + settings + + foreground + #586e75 + + + + name + Shell: [] + scope + punctuation.definition.logical-expression.shell + settings + + foreground + #dc322f + + + + + + name + Java: storage import + scope + storage.modifier.import.java + settings + + foreground + #93a1a1 + + + + + + name + Perl: functions + scope + support.function.perl + settings + + foreground + #268bd2 + + + + + + name + diff: header + scope + meta.diff, meta.diff.header + settings + + foreground + #586e75 + + + + name + diff: range + scope + meta.diff.range + settings + + foreground + #268bd2 + + + + name + diff: deleted + scope + markup.deleted + settings + + foreground + #dc322f + + + + name + diff: changed + scope + markup.changed + settings + + foreground + #2aa198 + + + + name + diff: inserted + scope + markup.inserted + settings + + foreground + #859900 + + + + + + Name + Markdown heading + scope + markup.heading, punctuation.definition.heading.markdown + settings + + foreground + #b58900 + + + + name + Markdown quote + scope + markup.quote + settings + + foreground + #859900 + + + + name + Markdown em + scope + markup.italic + settings + + fontStyle + italic + + + + Name + Markdown strong + scope + markup.bold + settings + + fontStyle + bold + + + + Name + Markdown reference + scope + markup.underline.link.markdown, meta.link.reference constant.other.reference.link.markdown + settings + + foreground + #2aa198 + + + + Name + Markdown reference list + scope + constant.other.reference.link.markdown + settings + + foreground + #6c71c4 + + + + Name + Markdown linebreak + scope + meta.paragraph.markdown meta.dummy.line-break + settings + + background + #586e75 + + + + + + name + SublimeLinter Annotations + scope + sublimelinter.notes + settings + + background + #586e75 + foreground + #586e75 + + + + name + SublimeLinter Error Outline + scope + sublimelinter.outline.illegal + settings + + background + #586e75 + foreground + #586e75 + + + + name + SublimeLinter Error Underline + scope + sublimelinter.underline.illegal + settings + + background + #dc322f + + + + name + SublimeLinter Warning Outline + scope + sublimelinter.outline.warning + settings + + background + #839496 + foreground + #839496 + + + + name + SublimeLinter Warning Underline + scope + sublimelinter.underline.warning + settings + + background + #b58900 + + + + name + SublimeLinter Violation Outline + scope + sublimelinter.outline.violation + settings + + background + #657b83 + foreground + #657b83 + + + + name + SublimeLinter Violation Underline + scope + sublimelinter.underline.violation + settings + + background + #cb4b16 + + + + name + SublimeLinter Warning + scope + sublimelinter.mark.warning + settings + + foreground + #b58900 + + + + name + SublimeLinter Error + scope + sublimelinter.mark.error + settings + + foreground + #dc322f + + + + name + SublimeLinter Gutter Mark + scope + sublimelinter.gutter-mark + settings + + foreground + #657b83 + + + + + + name + SublimeBracketHighlighter + scope + brackethighlighter.all + settings + + foreground + #586e75 + + + + + + name + Find In Files: File Name + scope + entity.name.filename.find-in-files + settings + + foreground + #2aa198 + + + + name + Find In Files: Line numbers + scope + constant.numeric.line-number.find-in-files + settings + + foreground + #586e75 + + + + + + name + GitGutter deleted + scope + markup.deleted.git_gutter + settings + + foreground + #dc322f + + + + name + GitGutter inserted + scope + markup.inserted.git_gutter + settings + + foreground + #859900 + + + + name + GitGutter changed + scope + markup.changed.git_gutter + settings + + foreground + #b58900 + + + + + + name + JavaScript Variables + scope + variable.other.readwrite.js, variable.other.object.js, variable.other.constant.js + settings + + foreground + #839496 + + + + name + JavaScript Functions + scope + variable.function.js + settings + + foreground + #b58900 + + + + name + JavaScript punctation inside class + scope + meta.class punctuation + settings + + foreground + #839496 + + + + name + JavaScript Classes + scope + variable.language.this.js, variable.language.super.js + settings + + foreground + #d33682 + + + + uuid + 5815b34d-fb7d-4593-bf0c-4f41f8b1f0a9 + + diff --git a/sublime_themes/solarized-light.tmTheme b/sublime_themes/solarized-light.tmTheme new file mode 100644 index 00000000..ed7956bc --- /dev/null +++ b/sublime_themes/solarized-light.tmTheme @@ -0,0 +1,1189 @@ + + + + + name + Solarized (light) + settings + + + + + settings + + background + #fdf6e3 + caret + #073642 + foreground + #657b83 + gutter + #eee8d5 + invisibles + #eee8d5 + lineHighlight + #eee8d5 + selection + #eee8d5 + selectionBorder + #c0c4bb + + + + + + + + + + + name + Comment + scope + comment, meta.documentation + settings + + foreground + #93a1a1 + + + + name + String + scope + string + settings + + foreground + #2aa198 + + + + name + Regexp + scope + string.regexp + settings + + foreground + #2aa198 + + + + name + Escape \ char + scope + constant.character.escape + settings + + foreground + #dc322f + + + + name + Number + scope + constant.numeric + settings + + foreground + #6c71c4 + + + + name + Variable + scope + variable + settings + + foreground + #268bd2 + + + + name + Keyword + scope + keyword + settings + + foreground + #859900 + + + + name + Import + scope + meta.import keyword, keyword.control.import, keyword.control.import.from, keyword.other.import, keyword.control.at-rule.include, keyword.control.at-rule.import + settings + + foreground + #cb4b16 + + + + name + Arithmetical, Assignment, Comparision Operators + scope + keyword.operator.comparison, keyword.operator.assignment, keyword.operator.arithmetic + settings + + foreground + #657b83 + + + + name + Storage + scope + storage + settings + + foreground + #859900 + + + + name + Class + scope + keyword.control.class, meta.class, entity.name.class, entity.name.type.class + settings + + foreground + #b58900 + + + + name + Library class + scope + support.type, support.class + settings + + foreground + #859900 + + + + name + Function name + scope + entity.name.function + settings + + foreground + #b58900 + + + + name + Variable start + scope + punctuation.definition.variable + settings + + foreground + #859900 + + + + name + Built-in constant + scope + constant.language, meta.preprocessor + settings + + foreground + #b58900 + + + + name + Support.construct + scope + support.function.construct, keyword.other.new + settings + + foreground + #dc322f + + + + name + User-defined constant + scope + constant.character, constant.other + settings + + foreground + #cb4b16 + + + + name + Tag name + scope + entity.name.tag + settings + + foreground + #268bd2 + + + + name + Tag start/end + scope + punctuation.definition.tag.html, punctuation.definition.tag.begin, punctuation.definition.tag.end + settings + + foreground + #93a1a1 + + + + name + Library function + scope + support.function + settings + + foreground + #859900 + + + + name + Continuation + scope + punctuation.separator.continuation + settings + + foreground + #dc322f + + + + name + Storage Type + scope + storage.type + settings + + foreground + #268bd2 + + + + name + Exception + scope + support.type.exception + settings + + foreground + #cb4b16 + + + + name + Special + scope + keyword.other.special-method + settings + + foreground + #cb4b16 + + + + name + Invalid + scope + invalid + settings + + background + #ec9489 + + + + name + Quoted String + scope + string.quoted.double, string.quoted.single + settings + + foreground + #2aa198 + + + + name + Quoted Single + scope + punctuation.definition.string.begin, punctuation.definition.string.end + settings + + foreground + #839496 + + + + name + [] + scope + meta.brace.square + settings + + foreground + #268bd2 + + + + name + () + scope + meta.brace.round, punctuation.definition.parameters.begin, punctuation.definition.parameters.end + settings + + foreground + #657b83 + + + + name + {} + scope + meta.brace.curly + settings + + foreground + #657b83 + + + + + + + + name + CSS: Standard color value + scope + support.constant.color, invalid.deprecated.color.w3c-non-standard-color-name.scss + settings + + foreground + #b58900 + + + + name + CSS: Selector > [] and non-spec tags + scope + meta.selector.css + settings + + foreground + #657b83 + + + + name + CSS: Tag + scope + entity.name.tag.css, entity.name.tag.scss, source.less keyword.control.html.elements, source.sass keyword.control.untitled + settings + + foreground + #b58900 + + + + name + CSS .class + scope + entity.other.attribute-name.class.css, entity.other.attribute-name.class.sass + settings + + foreground + #b58900 + + + + name + CSS: #id + scope + source.css entity.other.attribute-name.id, source.less entity.other.attribute-name.id, source.scss entity.other.attribute-name.id, source.sass entity.other.attribute-name.id + settings + + foreground + #b58900 + + + + name + CSS :pseudo + scope + entity.other.attribute-name.pseudo-element.css, entity.other.attribute-name.pseudo-class, entity.other.attribute-name.tag.pseudo-class + settings + + foreground + #268bd2 + + + + + + name + HTML: = + scope + text.html.basic meta.tag.other.html, text.html.basic meta.tag.any.html, text.html.basic meta.tag.block.any, text.html.basic meta.tag.inline.any, text.html.basic meta.tag.structure.any.html, text.html.basic source.js.embedded.html, punctuation.separator.key-value.html + settings + + foreground + #657b83 + + + + name + HTML: something= + scope + text.html.basic entity.other.attribute-name.html, meta.tag.xml entity.other.attribute-name + settings + + foreground + #b58900 + + + + + + name + Ruby: Special Method + scope + keyword.other.special-method.ruby + settings + + foreground + #859900 + + + + name + Ruby: Constant Other + scope + variable.other.constant.ruby + settings + + foreground + #b58900 + + + + name + Ruby: :symbol + scope + constant.other.symbol.ruby + settings + + foreground + #2aa198 + + + + name + Ruby: Special Method + scope + keyword.other.special-method.ruby + settings + + foreground + #cb4b16 + + + + + + name + PHP: Array() + scope + meta.array support.function.construct.php + settings + + foreground + #b58900 + + + + + + name + C: Preprocessor + scope + entity.name.function.preprocessor.c, meta.preprocessor.c.include, meta.preprocessor.macro.c + settings + + foreground + #cb4b16 + + + + name + C: include + scope + meta.preprocessor.c.include string.quoted.other.lt-gt.include.c, meta.preprocessor.c.include punctuation.definition.string.begin.c, meta.preprocessor.c.include punctuation.definition.string.end.c + settings + + foreground + #2aa198 + + + + + + name + Other: Removal + scope + other.package.exclude, other.remove + settings + + foreground + #dc322f + + + + name + Other: Add + scope + other.add + settings + + foreground + #2aa198 + + + + + + name + Tex: {} + scope + punctuation.section.group.tex , punctuation.definition.arguments.begin.latex, punctuation.definition.arguments.end.latex, punctuation.definition.arguments.latex + settings + + foreground + #dc322f + + + + name + Tex: {text} + scope + meta.group.braces.tex + settings + + foreground + #b58900 + + + + name + Tex: Other Math + scope + string.other.math.tex + settings + + foreground + #b58900 + + + + name + Tex: {var} + scope + variable.parameter.function.latex + settings + + foreground + #cb4b16 + + + + name + Tex: Math \\ + scope + punctuation.definition.constant.math.tex + settings + + foreground + #dc322f + + + + name + Tex: Constant Math + scope + text.tex.latex constant.other.math.tex, constant.other.general.math.tex, constant.other.general.math.tex, constant.character.math.tex + settings + + foreground + #2aa198 + + + + name + Tex: Other Math String + scope + string.other.math.tex + settings + + foreground + #b58900 + + + + name + Tex: $ + scope + punctuation.definition.string.begin.tex, punctuation.definition.string.end.tex + settings + + foreground + #dc322f + + + + name + Tex: \label + scope + keyword.control.label.latex, text.tex.latex constant.other.general.math.tex + settings + + foreground + #2aa198 + + + + name + Tex: \label { } + scope + variable.parameter.definition.label.latex + settings + + foreground + #dc322f + + + + name + Tex: Function + scope + support.function.be.latex + settings + + foreground + #859900 + + + + name + Tex: Support Function Section + scope + support.function.section.latex + settings + + foreground + #cb4b16 + + + + name + Tex: Support Function + scope + support.function.general.tex + settings + + foreground + #2aa198 + + + + name + Tex: Reference Label + scope + keyword.control.ref.latex + settings + + foreground + #2aa198 + + + + + + name + Python: storage + scope + storage.type.class.python, storage.type.function.python, storage.modifier.global.python + settings + + foreground + #859900 + + + + name + Python: Support.exception + scope + support.type.exception.python + settings + + foreground + #b58900 + + + + + + name + Shell: meta scope in loop + scope + meta.scope.for-in-loop.shell, variable.other.loop.shell + settings + + foreground + #586e75 + + + + name + Shell: Meta Block + scope + meta.scope.case-block.shell, meta.scope.case-body.shell + settings + + foreground + #586e75 + + + + name + Shell: [] + scope + punctuation.definition.logical-expression.shell + settings + + foreground + #dc322f + + + + + + name + Java: storage import + scope + storage.modifier.import.java + settings + + foreground + #586e75 + + + + + + name + Perl: functions + scope + support.function.perl + settings + + foreground + #268bd2 + + + + + + name + diff: header + scope + meta.diff, meta.diff.header + settings + + foreground + #93a1a1 + + + + name + diff: range + scope + meta.diff.range + settings + + foreground + #268bd2 + + + + name + diff: deleted + scope + markup.deleted + settings + + foreground + #dc322f + + + + name + diff: changed + scope + markup.changed + settings + + foreground + #2aa198 + + + + name + diff: inserted + scope + markup.inserted + settings + + foreground + #859900 + + + + + + Name + Markdown heading + scope + markup.heading, punctuation.definition.heading.markdown + settings + + foreground + #b58900 + + + + name + Markdown quote + scope + markup.quote + settings + + foreground + #859900 + + + + name + Markdown em + scope + markup.italic + settings + + fontStyle + italic + + + + Name + Markdown strong + scope + markup.bold + settings + + fontStyle + bold + + + + Name + Markdown reference + scope + markup.underline.link.markdown, meta.link.reference constant.other.reference.link.markdown + settings + + foreground + #2aa198 + + + + Name + Markdown reference list + scope + constant.other.reference.link.markdown + settings + + foreground + #6c71c4 + + + + Name + Markdown linebreak + scope + meta.paragraph.markdown meta.dummy.line-break + settings + + background + #eee8d5 + + + + + + name + SublimeLinter Annotations + scope + sublimelinter.notes + settings + + background + #eee8d5 + foreground + #eee8d5 + + + + name + SublimeLinter Error Outline + scope + sublimelinter.outline.illegal + settings + + background + #93a1a1 + foreground + #93a1a1 + + + + name + SublimeLinter Error Underline + scope + sublimelinter.underline.illegal + settings + + background + #dc322f + + + + name + SublimeLinter Warning Outline + scope + sublimelinter.outline.warning + settings + + background + #839496 + foreground + #839496 + + + + name + SublimeLinter Warning Underline + scope + sublimelinter.underline.warning + settings + + background + #b58900 + + + + name + SublimeLinter Violation Outline + scope + sublimelinter.outline.violation + settings + + background + #657b83 + foreground + #657b83 + + + + name + SublimeLinter Violation Underline + scope + sublimelinter.underline.violation + settings + + background + #cb4b16 + + + + name + SublimeLinter Warning + scope + sublimelinter.mark.warning + settings + + foreground + #b58900 + + + + name + SublimeLinter Error + scope + sublimelinter.mark.error + settings + + foreground + #dc322f + + + + name + SublimeLinter Gutter Mark + scope + sublimelinter.gutter-mark + settings + + foreground + #657b83 + + + + + + name + SublimeBracketHighlighter + scope + brackethighlighter.all + settings + + foreground + #93a1a1 + + + + + + name + Find In Files: File Name + scope + entity.name.filename.find-in-files + settings + + foreground + #2aa198 + + + + name + Find In Files: Line numbers + scope + constant.numeric.line-number.find-in-files + settings + + foreground + #93a1a1 + + + + + + name + GitGutter deleted + scope + markup.deleted.git_gutter + settings + + foreground + #dc322f + + + + name + GitGutter inserted + scope + markup.inserted.git_gutter + settings + + foreground + #859900 + + + + name + GitGutter changed + scope + markup.changed.git_gutter + settings + + foreground + #b58900 + + + + + + name + JavaScript Variables + scope + variable.other.readwrite.js, variable.other.object.js, variable.other.constant.js + settings + + foreground + #657b83 + + + + name + JavaScript Functions + scope + variable.function.js + settings + + foreground + #b58900 + + + + name + JavaScript punctation inside class + scope + meta.class punctuation + settings + + foreground + #657b83 + + + + name + JavaScript Classes + scope + variable.language.this.js, variable.language.super.js + settings + + foreground + #d33682 + + + + uuid + ca238b9c-28f7-42d4-9670-bd0829ef864e + + From 478e7054deeb0508c879a62e14ef345a92d74400 Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Wed, 8 Mar 2017 13:21:45 +0900 Subject: [PATCH 11/26] More precise time and show what changed --- .travis.yml | 10 ++++ src/cmd/serve.rs | 149 +++++++++++++++++++++++++++++++++++++---------- src/main.rs | 12 +++- src/site.rs | 10 ++++ 4 files changed, 148 insertions(+), 33 deletions(-) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..79ef3140 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,10 @@ +language: rust +cache: cargo + +rust: + - nightly + - beta + - stable + +notifications: + email: false diff --git a/src/cmd/serve.rs b/src/cmd/serve.rs index 00b82e7b..7e4ce311 100644 --- a/src/cmd/serve.rs +++ b/src/cmd/serve.rs @@ -1,7 +1,7 @@ use std::env; use std::path::Path; use std::sync::mpsc::channel; -use std::time::Duration; +use std::time::{Instant, Duration}; use std::thread; use iron::{Iron, Request, IronResult, Response, status}; @@ -9,15 +9,25 @@ use mount::Mount; use staticfile::Static; use notify::{Watcher, RecursiveMode, watcher}; use ws::{WebSocket}; - use gutenberg::Site; use gutenberg::errors::{Result}; -const LIVE_RELOAD: &'static [u8; 37809] = include_bytes!("livereload.js"); + +use ::time_elapsed; + + +#[derive(Debug, PartialEq)] +enum ChangeKind { + Content, + Templates, + StaticFiles, +} + +const LIVE_RELOAD: &'static str = include_str!("livereload.js"); fn livereload_handler(_: &mut Request) -> IronResult { - Ok(Response::with((status::Ok, String::from_utf8(LIVE_RELOAD.to_vec()).unwrap()))) + Ok(Response::with((status::Ok, LIVE_RELOAD.to_string()))) } @@ -37,7 +47,6 @@ pub fn serve(interface: &str, port: &str) -> Result<()> { // we need to assign to a variable otherwise it will block let _iron = Iron::new(mount).http(address.clone()).unwrap(); println!("Web server is available at http://{}", address); - println!("Press CTRL+C to stop"); // The websocket for livereload let ws_server = WebSocket::new(|_| { @@ -56,8 +65,9 @@ pub fn serve(interface: &str, port: &str) -> Result<()> { watcher.watch("content/", RecursiveMode::Recursive).unwrap(); watcher.watch("static/", RecursiveMode::Recursive).unwrap(); watcher.watch("templates/", RecursiveMode::Recursive).unwrap(); - let pwd = env::current_dir().unwrap(); - println!("Listening for changes in {}/{{content, static, templates}}", pwd.display()); + let pwd = format!("{}", env::current_dir().unwrap().display()); + println!("Listening for changes in {}/{{content, static, templates}}", pwd); + println!("Press CTRL+C to stop"); use notify::DebouncedEvent::*; @@ -65,27 +75,36 @@ pub fn serve(interface: &str, port: &str) -> Result<()> { // See https://github.com/spf13/hugo/blob/master/commands/hugo.go // for a more complete version of that match rx.recv() { - Ok(event) => match event { - NoticeWrite(path) | - NoticeRemove(path) | - Create(path) | - Write(path) | - Remove(path) | - Rename(_, path) => { - if !is_temp_file(&path) { - println!("Change detected in {}", path.display()); + Ok(event) => { + match event { + Create(path) | + Write(path) | + Remove(path) | + Rename(_, path) => { + if is_temp_file(&path) { + continue; + } + + println!("Change detected, rebuilding site"); + let what_changed = detect_change_kind(&pwd, &path); + match what_changed { + ChangeKind::Content => println!("Content changed {}", path.display()), + ChangeKind::Templates => println!("Template changed {}", path.display()), + ChangeKind::StaticFiles => println!("Static file changes detected {}", path.display()), + }; + let start = Instant::now(); match site.rebuild() { Ok(_) => { - println!("Site rebuilt"); + println!("Done in {:.1}s.", time_elapsed(start)); broadcaster.send(r#" - { - "command": "reload", - "path": "", - "originalPath": "", - "liveCSS": true, - "liveImg": true, - "protocol": ["http://livereload.com/protocols/official-7"] - }"#).unwrap(); + { + "command": "reload", + "path": "", + "originalPath": "", + "liveCSS": true, + "liveImg": true, + "protocol": ["http://livereload.com/protocols/official-7"] + }"#).unwrap(); }, Err(e) => { println!("Failed to build the site"); @@ -95,9 +114,10 @@ pub fn serve(interface: &str, port: &str) -> Result<()> { } } } + } + _ => {} } - _ => {} }, Err(e) => println!("Watch error: {:?}", e), }; @@ -116,10 +136,8 @@ fn is_temp_file(path: &Path) -> bool { x if x.ends_with("jb_old___") => true, x if x.ends_with("jb_tmp___") => true, x if x.ends_with("jb_bak___") => true, - // byword - x if x.starts_with("sb-") => true, - // gnome - x if x.starts_with(".gooutputstream") => true, + // vim + x if x.ends_with("~") => true, _ => { if let Some(filename) = path.file_stem() { // emacs @@ -129,6 +147,75 @@ fn is_temp_file(path: &Path) -> bool { } } }, - None => false, + None => { + if path.ends_with(".DS_STORE") { + true + } else { + false + } + }, } } + + +/// Detect what changed from the given path so we have an idea what needs +/// to be reloaded +fn detect_change_kind(pwd: &str, path: &Path) -> ChangeKind { + let path_str = format!("{}", path.display()) + .replace(pwd, "") + .replace("\\", "/"); + let change_kind = if path_str.starts_with("/templates") { + ChangeKind::Templates + } else if path_str.starts_with("/content") { + ChangeKind::Content + } else if path_str.starts_with("/static") { + ChangeKind::StaticFiles + } else { + panic!("Got a change in an unexpected path: {}", path_str); + }; + + change_kind +} + +#[cfg(test)] +mod tests { + use std::path::Path; + + use super::{is_temp_file, detect_change_kind, ChangeKind}; + + #[test] + fn test_can_recognize_temp_files() { + let testcases = vec![ + Path::new("hello.swp"), + Path::new("hello.swx"), + Path::new(".DS_STORE"), + Path::new("hello.tmp"), + Path::new("hello.html.__jb_old___"), + Path::new("hello.html.__jb_tmp___"), + Path::new("hello.html.__jb_bak___"), + Path::new("hello.html~"), + Path::new("#hello.html"), + ]; + + for t in testcases { + println!("{:?}", t.display()); + assert!(is_temp_file(&t)); + } + } + + #[test] + fn test_can_detect_kind_of_changes() { + let testcases = vec![ + (ChangeKind::Templates, "/home/vincent/site", Path::new("/home/vincent/site/templates/hello.html")), + (ChangeKind::StaticFiles, "/home/vincent/site", Path::new("/home/vincent/site/static/site.css")), + (ChangeKind::Content, "/home/vincent/site", Path::new("/home/vincent/site/content/posts/hello.md")), + ]; + + for (expected, pwd, path) in testcases { + println!("{:?}", path.display()); + assert_eq!(expected, detect_change_kind(&pwd, &path)); + } + } + + +} diff --git a/src/main.rs b/src/main.rs index ca1bded6..8a4f0fae 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ extern crate clap; #[macro_use] extern crate error_chain; extern crate gutenberg; +extern crate chrono; extern crate staticfile; extern crate iron; @@ -12,10 +13,18 @@ extern crate ws; use std::time::Instant; +use chrono::Duration; mod cmd; +// Print the time elapsed rounded to 1 decimal +fn time_elapsed(instant: Instant) -> f64 { + let duration_ms = Duration::from_std(instant.elapsed()).unwrap().num_milliseconds() as f64 / 1000.0; + (duration_ms * 10.0).round() / 10.0 +} + + fn main() { let matches = clap_app!(Gutenberg => (version: crate_version!()) @@ -52,8 +61,7 @@ fn main() { let start = Instant::now(); match cmd::build() { Ok(()) => { - let duration = start.elapsed(); - println!("Site built in {}s.", duration.as_secs()); + println!("Site built in {:.1}s.", time_elapsed(start)); }, Err(e) => { println!("Failed to build the site"); diff --git a/src/site.rs b/src/site.rs index 499ae54d..085160d6 100644 --- a/src/site.rs +++ b/src/site.rs @@ -92,6 +92,16 @@ impl Site { html } + /// Reload the Tera templates + pub fn reload_templates(&mut self) -> Result<()> { + Ok(()) + } + + /// Copy the content of the `static` folder into the `public` folder + pub fn copy_static_files(&self) -> Result<()> { + Ok(()) + } + /// Re-parse and re-generate the site /// Very dumb for now, ideally it would only rebuild what changed pub fn rebuild(&mut self) -> Result<()> { From 57fd159025527d67bc445b50c0adbc07e87ce880 Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Thu, 9 Mar 2017 16:34:12 +0900 Subject: [PATCH 12/26] Copy static directory and live reload changes to it --- Cargo.lock | 6 +++--- Cargo.toml | 2 +- src/cmd/serve.rs | 40 +++++++++++++++++++++++++++------------- src/site.rs | 32 ++++++++++++++++++++++++++++++-- 4 files changed, 61 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 15104770..bc077371 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,7 +18,7 @@ dependencies = [ "slug 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "staticfile 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "syntect 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "tera 0.8.0 (git+https://github.com/Keats/tera)", + "tera 0.8.0 (git+https://github.com/Keats/tera?branch=reload)", "toml 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", "ws 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -774,7 +774,7 @@ dependencies = [ [[package]] name = "tera" version = "0.8.0" -source = "git+https://github.com/Keats/tera#2eb55de231e08da50ca32a443499fbce8d9003e0" +source = "git+https://github.com/Keats/tera?branch=reload#31f8bd8238e3e6bdef9ecf4468079c535609b5eb" dependencies = [ "chrono 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "error-chain 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1095,7 +1095,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum syn 0.11.9 (registry+https://github.com/rust-lang/crates.io-index)" = "480c834701caba3548aa991e54677281be3a5414a9d09ddbdf4ed74a569a9d19" "checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" "checksum syntect 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3f79be04af68d5fa09e71b3274159a955a25f25a5cbfba9a6ff64139b71d848a" -"checksum tera 0.8.0 (git+https://github.com/Keats/tera)" = "" +"checksum tera 0.8.0 (git+https://github.com/Keats/tera?branch=reload)" = "" "checksum term_size 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "07b6c1ac5b3fffd75073276bca1ceed01f67a28537097a2a9539e116e50fb21a" "checksum thread-id 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4437c97558c70d129e40629a5b385b3fb1ffac301e63941335e4d354081ec14a" "checksum thread_local 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c85048c6260d17cf486ceae3282d9fb6b90be220bf5b28c400f5485ffc29f0c7" diff --git a/Cargo.toml b/Cargo.toml index f381c61e..0ef92e45 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ glob = "0.2" serde = "0.9" serde_json = "0.9" serde_derive = "0.9" -tera = { git = "https://github.com/Keats/tera", branch = "master" } +tera = { git = "https://github.com/Keats/tera", branch = "reload" } # tera = "0.8" slug = "0.1" syntect = "1" diff --git a/src/cmd/serve.rs b/src/cmd/serve.rs index 7e4ce311..e36f1dd7 100644 --- a/src/cmd/serve.rs +++ b/src/cmd/serve.rs @@ -87,24 +87,29 @@ pub fn serve(interface: &str, port: &str) -> Result<()> { println!("Change detected, rebuilding site"); let what_changed = detect_change_kind(&pwd, &path); + let mut reload_path = String::new(); match what_changed { - ChangeKind::Content => println!("Content changed {}", path.display()), - ChangeKind::Templates => println!("Template changed {}", path.display()), - ChangeKind::StaticFiles => println!("Static file changes detected {}", path.display()), + (ChangeKind::Content, _) => println!("Content changed {}", path.display()), + (ChangeKind::Templates, _) => println!("Template changed {}", path.display()), + (ChangeKind::StaticFiles, p) => { + reload_path = p; + println!("Static file changes detected {}", path.display()); + }, }; + println!("Reloading {}", reload_path); let start = Instant::now(); match site.rebuild() { Ok(_) => { - println!("Done in {:.1}s.", time_elapsed(start)); - broadcaster.send(r#" - { + println!("Done in {:.1}s.\n", time_elapsed(start)); + broadcaster.send(format!(r#" + {{ "command": "reload", - "path": "", + "path": "{}", "originalPath": "", "liveCSS": true, "liveImg": true, "protocol": ["http://livereload.com/protocols/official-7"] - }"#).unwrap(); + }}"#, reload_path)).unwrap(); }, Err(e) => { println!("Failed to build the site"); @@ -160,7 +165,7 @@ fn is_temp_file(path: &Path) -> bool { /// Detect what changed from the given path so we have an idea what needs /// to be reloaded -fn detect_change_kind(pwd: &str, path: &Path) -> ChangeKind { +fn detect_change_kind(pwd: &str, path: &Path) -> (ChangeKind, String) { let path_str = format!("{}", path.display()) .replace(pwd, "") .replace("\\", "/"); @@ -174,7 +179,7 @@ fn detect_change_kind(pwd: &str, path: &Path) -> ChangeKind { panic!("Got a change in an unexpected path: {}", path_str); }; - change_kind + (change_kind, path_str) } #[cfg(test)] @@ -206,9 +211,18 @@ mod tests { #[test] fn test_can_detect_kind_of_changes() { let testcases = vec![ - (ChangeKind::Templates, "/home/vincent/site", Path::new("/home/vincent/site/templates/hello.html")), - (ChangeKind::StaticFiles, "/home/vincent/site", Path::new("/home/vincent/site/static/site.css")), - (ChangeKind::Content, "/home/vincent/site", Path::new("/home/vincent/site/content/posts/hello.md")), + ( + (ChangeKind::Templates, "/templates/hello.html".to_string()), + "/home/vincent/site", Path::new("/home/vincent/site/templates/hello.html") + ), + ( + (ChangeKind::StaticFiles, "/static/site.css".to_string()), + "/home/vincent/site", Path::new("/home/vincent/site/static/site.css") + ), + ( + (ChangeKind::Content, "/content/posts/hello.md".to_string()), + "/home/vincent/site", Path::new("/home/vincent/site/content/posts/hello.md") + ), ]; for (expected, pwd, path) in testcases { diff --git a/src/site.rs b/src/site.rs index 085160d6..59fa93d4 100644 --- a/src/site.rs +++ b/src/site.rs @@ -1,11 +1,12 @@ use std::collections::HashMap; use std::iter::FromIterator; -use std::fs::{create_dir, remove_dir_all}; +use std::fs::{create_dir, remove_dir_all, copy, remove_file}; use std::path::Path; use glob::glob; use tera::{Tera, Context}; use slug::slugify; +use walkdir::WalkDir; use errors::{Result, ResultExt}; use config::{Config, get_config}; @@ -98,7 +99,32 @@ impl Site { } /// Copy the content of the `static` folder into the `public` folder - pub fn copy_static_files(&self) -> Result<()> { + /// + /// TODO: only copy one file if possible because that would be a waster + /// to do re-copy the whole thing + pub fn copy_static_directory(&self) -> Result<()> { + let from = Path::new("static"); + let target = Path::new("public"); + + for entry in WalkDir::new(from).into_iter().filter_map(|e| e.ok()) { + let relative_path = entry.path().strip_prefix(&from).unwrap(); + let target_path = { + let mut target_path = target.to_path_buf(); + target_path.push(relative_path); + target_path + }; + + if entry.path().is_dir() { + if !target_path.exists() { + create_dir(&target_path)?; + } + } else { + if target_path.exists() { + remove_file(&target_path)?; + } + copy(entry.path(), &target_path)?; + } + } Ok(()) } @@ -106,6 +132,7 @@ impl Site { /// Very dumb for now, ideally it would only rebuild what changed pub fn rebuild(&mut self) -> Result<()> { self.parse_site()?; + self.templates.full_reload()?; self.build() } @@ -174,6 +201,7 @@ impl Site { let index = self.templates.render("index.html", &context)?; create_file(public.join("index.html"), &self.inject_livereload(index))?; + self.copy_static_directory()?; Ok(()) } From 09ade9573c669871e551391e3ad7a33980970d27 Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Thu, 9 Mar 2017 16:46:38 +0900 Subject: [PATCH 13/26] Use a lazy_static version of tera templates --- src/site.rs | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/site.rs b/src/site.rs index 59fa93d4..8a36809d 100644 --- a/src/site.rs +++ b/src/site.rs @@ -14,6 +14,18 @@ use page::Page; use utils::create_file; +lazy_static! { + static ref GUTENBERG_TERA: Tera = { + let mut tera = Tera::default(); + tera.add_raw_templates(vec![ + ("rss.xml", include_str!("templates/rss.xml")), + ("sitemap.xml", include_str!("templates/sitemap.xml")), + ]).unwrap(); + tera + }; +} + + #[derive(Debug, PartialEq)] enum RenderList { Tags, @@ -50,7 +62,10 @@ pub struct Site { impl Site { pub fn new(livereload: bool) -> Result { - let tera = Tera::new("templates/**/*").chain_err(|| "Error parsing templates")?; + let mut tera = Tera::new("templates/**/*") + .chain_err(|| "Error parsing templates")?; + tera.extend(&GUTENBERG_TERA)?; + let mut site = Site { config: get_config(), pages: HashMap::new(), @@ -258,10 +273,9 @@ impl Site { } fn render_sitemap(&self) -> Result<()> { - let tpl = String::from_utf8(include_bytes!("templates/sitemap.xml").to_vec()).unwrap(); let mut context = Context::new(); context.add("pages", &self.pages.values().collect::>()); - let sitemap = Tera::one_off(&tpl, &context, false)?; + let sitemap = self.templates.render("sitemap.xml", &context)?; let public = Path::new("public"); create_file(public.join("sitemap.xml"), &sitemap)?; @@ -278,7 +292,6 @@ impl Site { } fn render_rss_feed(&self) -> Result<()> { - let tpl = String::from_utf8(include_bytes!("templates/rss.xml").to_vec()).unwrap(); let mut context = Context::new(); let mut pages = self.pages.values() .filter(|p| p.meta.date.is_some()) @@ -295,7 +308,7 @@ impl Site { context.add("config", &self.config); context.add("feed_url", &self.get_rss_feed_url()); - let sitemap = Tera::one_off(&tpl, &context, false)?; + let sitemap = self.templates.render("rss.xml", &context)?; let public = Path::new("public"); create_file(public.join("rss.xml"), &sitemap)?; From 4406b160079575e54d625c6a13c2c7cc794d0e61 Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Fri, 10 Mar 2017 17:28:17 +0900 Subject: [PATCH 14/26] Detect whether the page needs highlighting --- Cargo.lock | 28 ++++++++++++++-------------- src/config.rs | 4 ++-- src/page.rs | 34 ++++++++++++++++++++++++++++++++-- 3 files changed, 48 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bc077371..6ae09884 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,7 +17,7 @@ dependencies = [ "serde_json 0.9.9 (registry+https://github.com/rust-lang/crates.io-index)", "slug 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "staticfile 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "syntect 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "syntect 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "tera 0.8.0 (git+https://github.com/Keats/tera?branch=reload)", "toml 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", @@ -263,7 +263,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "httparse 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "mime 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", @@ -302,7 +302,7 @@ dependencies = [ "error 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "modifier 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "plugin 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -346,7 +346,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "log" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -367,7 +367,7 @@ name = "mime" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -386,7 +386,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "miow 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "net2 0.2.27 (registry+https://github.com/rust-lang/crates.io-index)", "nix 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -403,8 +403,8 @@ dependencies = [ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "lazycell 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "miow 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "net2 0.2.27 (registry+https://github.com/rust-lang/crates.io-index)", "slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", @@ -423,7 +423,7 @@ dependencies = [ [[package]] name = "miow" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -755,7 +755,7 @@ dependencies = [ [[package]] name = "syntect" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bincode 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -971,7 +971,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "httparse 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "mio 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", "sha1 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1044,7 +1044,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum lazy_static 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7291b1dd97d331f752620b02dfdbc231df7fc01bf282a00769e1cdb963c460dc" "checksum lazycell 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ce12306c4739d86ee97c23139f3a34ddf0387bbf181bc7929d287025a8c3ef6b" "checksum libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)" = "88ee81885f9f04bff991e306fea7c1c60a5f0f9e409e99f6b40e3311a3363135" -"checksum log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ab83497bf8bf4ed2a74259c1c802351fcd67a65baa86394b6ba73c36f4838054" +"checksum log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "5141eca02775a762cc6cd564d8d2c50f67c0ea3a372cbf1c51592b3e029e10ad" "checksum matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "efd7622e3022e1a6eaa602c4cea8912254e5582c9c692e9167714182244801b1" "checksum memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1dbccc0e46f1ea47b9f17e6d67c5a96bd27030519c519c9c91327e31275a47b4" "checksum mime 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b5c93a4bd787ddc6e7833c519b73a50883deb5863d76d9b71eb8216fb7f94e66" @@ -1052,7 +1052,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum mio 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a637d1ca14eacae06296a008fa7ad955347e34efcb5891cfd8ba05491a37907e" "checksum mio 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "eecdbdd49a849336e77b453f021c89972a2cfb5b51931a0026ae0ac4602de681" "checksum miow 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3e690c5df6b2f60acd45d56378981e827ff8295562fc8d34f573deb267a59cd1" -"checksum miow 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3a78d2605eb97302c10cf944b8d96b0a2a890c52957caf92fcd1f24f69049579" +"checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" "checksum modifier 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "41f5c9112cb662acd3b204077e0de5bc66305fa8df65c8019d5adb10e9ab6e58" "checksum mount 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "32245731923cd096899502fc4c4317cfd09f121e80e73f7f576cf3777a824256" "checksum net2 0.2.27 (registry+https://github.com/rust-lang/crates.io-index)" = "18b9642ad6222faf5ce46f6966f59b71b9775ad5758c9e09fcf0a6c8061972b4" @@ -1094,7 +1094,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b4d15c810519a91cf877e7e36e63fe068815c678181439f2f29e2562147c3694" "checksum syn 0.11.9 (registry+https://github.com/rust-lang/crates.io-index)" = "480c834701caba3548aa991e54677281be3a5414a9d09ddbdf4ed74a569a9d19" "checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" -"checksum syntect 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3f79be04af68d5fa09e71b3274159a955a25f25a5cbfba9a6ff64139b71d848a" +"checksum syntect 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6728e7e9bbd971751d17d39b0e38e3558c10b9fb32125441bb17c434a2754e7c" "checksum tera 0.8.0 (git+https://github.com/Keats/tera?branch=reload)" = "" "checksum term_size 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "07b6c1ac5b3fffd75073276bca1ceed01f67a28537097a2a9539e116e50fb21a" "checksum thread-id 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4437c97558c70d129e40629a5b385b3fb1ffac301e63941335e4d354081ec14a" diff --git a/src/config.rs b/src/config.rs index d7e0c257..bd9b20c4 100644 --- a/src/config.rs +++ b/src/config.rs @@ -14,7 +14,7 @@ pub struct Config { pub title: String, /// Base URL of the site pub base_url: String, - /// Whether to highlight all code found in markdown files. Defaults to true + /// Whether to highlight all code blocks found in markdown files. Defaults to false pub highlight_code: Option, /// Description of the site pub description: Option, @@ -40,7 +40,7 @@ impl Config { } if config.highlight_code.is_none() { - config.highlight_code = Some(true); + config.highlight_code = Some(false); } if config.disable_rss.is_none() { diff --git a/src/page.rs b/src/page.rs index 0722c82b..4ff9c1ec 100644 --- a/src/page.rs +++ b/src/page.rs @@ -20,6 +20,7 @@ use markdown::markdown_to_html; lazy_static! { static ref PAGE_RE: Regex = Regex::new(r"^\n?\+\+\+\n((?s).*(?-s))\+\+\+\n((?s).*(?-s))$").unwrap(); static ref SUMMARY_RE: Regex = Regex::new(r"").unwrap(); + static ref CODE_BLOCK_RE: Regex = Regex::new(r"```").unwrap(); } @@ -113,13 +114,22 @@ impl Page { let mut page = Page::new(meta); page.filepath = filepath.to_string(); page.raw_content = content.to_string(); - page.content = markdown_to_html(&page.raw_content, config.highlight_code.unwrap()); + // We try to be smart about highlighting code as it can be time-consuming + // If the global config disables it, then we do nothing. However, + // if we see a code block in the content, we assume that this page needs + // to be highlighted. It could potentially have false positive if the content + // has ``` in it but that seems kind of unlikely + let mut should_highlight = config.highlight_code.unwrap(); + if should_highlight { + should_highlight = CODE_BLOCK_RE.is_match(&page.raw_content); + } + page.content = markdown_to_html(&page.raw_content, should_highlight); if page.raw_content.contains("") { page.summary = { let summary = SUMMARY_RE.split(&page.raw_content).collect::>()[0]; - markdown_to_html(summary, config.highlight_code.unwrap()) + markdown_to_html(summary, should_highlight) } } @@ -453,4 +463,24 @@ Hello world let page = res.unwrap(); assert_eq!(page.summary, "

Hello world

\n"); } + + #[test] + fn test_can_auto_detect_when_highlighting_needed() { + let content = r#" ++++ +title = "Hello" +description = "hey there" ++++ +``` +Hey there +``` +"#.to_string(); + let mut config = Config::default(); + config.highlight_code = Some(true); + let res = Page::parse("hello.md", &content, &config); + assert!(res.is_ok()); + let page = res.unwrap(); + assert!(page.content.starts_with(" Date: Fri, 10 Mar 2017 20:39:58 +0900 Subject: [PATCH 15/26] Refactor serve/rebuilding a bit --- src/cmd/serve.rs | 81 ++++++++++++++++++++++++++-------------------- src/config.rs | 1 + src/main.rs | 15 ++++++--- src/site.rs | 83 +++++++++++++++++++++++++++--------------------- src/utils.rs | 15 +++++++-- 5 files changed, 117 insertions(+), 78 deletions(-) diff --git a/src/cmd/serve.rs b/src/cmd/serve.rs index e36f1dd7..6211ce42 100644 --- a/src/cmd/serve.rs +++ b/src/cmd/serve.rs @@ -4,16 +4,17 @@ use std::sync::mpsc::channel; use std::time::{Instant, Duration}; use std::thread; +use chrono::prelude::*; use iron::{Iron, Request, IronResult, Response, status}; use mount::Mount; use staticfile::Static; use notify::{Watcher, RecursiveMode, watcher}; -use ws::{WebSocket}; +use ws::{WebSocket, Sender}; use gutenberg::Site; use gutenberg::errors::{Result}; -use ::time_elapsed; +use ::report_elapsed_time; #[derive(Debug, PartialEq)] @@ -31,10 +32,38 @@ fn livereload_handler(_: &mut Request) -> IronResult { } +fn rebuild_done_handling(broadcaster: &Sender, res: Result<()>, reload_path: &str) { + match res { + Ok(_) => { + broadcaster.send(format!(r#" + {{ + "command": "reload", + "path": "{}", + "originalPath": "", + "liveCSS": true, + "liveImg": true, + "protocol": ["http://livereload.com/protocols/official-7"] + }}"#, reload_path) + ).unwrap(); + }, + Err(e) => { + println!("Failed to build the site"); + println!("Error: {}", e); + for e in e.iter().skip(1) { + println!("Reason: {}", e) + } + } + } +} + + // Most of it taken from mdbook pub fn serve(interface: &str, port: &str) -> Result<()> { + println!("Building site..."); + let start = Instant::now(); let mut site = Site::new(true)?; site.build()?; + report_elapsed_time(start); let address = format!("{}:{}", interface, port); let ws_address = format!("{}:{}", interface, "1112"); @@ -67,7 +96,7 @@ pub fn serve(interface: &str, port: &str) -> Result<()> { watcher.watch("templates/", RecursiveMode::Recursive).unwrap(); let pwd = format!("{}", env::current_dir().unwrap().display()); println!("Listening for changes in {}/{{content, static, templates}}", pwd); - println!("Press CTRL+C to stop"); + println!("Press CTRL+C to stop\n"); use notify::DebouncedEvent::*; @@ -85,41 +114,23 @@ pub fn serve(interface: &str, port: &str) -> Result<()> { continue; } - println!("Change detected, rebuilding site"); - let what_changed = detect_change_kind(&pwd, &path); - let mut reload_path = String::new(); - match what_changed { - (ChangeKind::Content, _) => println!("Content changed {}", path.display()), - (ChangeKind::Templates, _) => println!("Template changed {}", path.display()), + println!("Change detected @ {}", Local::now().format("%Y-%m-%d %H:%M:%S").to_string()); + let start = Instant::now(); + match detect_change_kind(&pwd, &path) { + (ChangeKind::Content, _) => { + println!("-> Content changed {}", path.display()); + rebuild_done_handling(&broadcaster, site.rebuild(), ""); + }, + (ChangeKind::Templates, _) => { + println!("-> Template changed {}", path.display()); + rebuild_done_handling(&broadcaster, site.rebuild_after_template_change(), ""); + }, (ChangeKind::StaticFiles, p) => { - reload_path = p; - println!("Static file changes detected {}", path.display()); + println!("-> Static file changes detected {}", path.display()); + rebuild_done_handling(&broadcaster, site.copy_static_directory(), &p); }, }; - println!("Reloading {}", reload_path); - let start = Instant::now(); - match site.rebuild() { - Ok(_) => { - println!("Done in {:.1}s.\n", time_elapsed(start)); - broadcaster.send(format!(r#" - {{ - "command": "reload", - "path": "{}", - "originalPath": "", - "liveCSS": true, - "liveImg": true, - "protocol": ["http://livereload.com/protocols/official-7"] - }}"#, reload_path)).unwrap(); - }, - Err(e) => { - println!("Failed to build the site"); - println!("Error: {}", e); - for e in e.iter().skip(1) { - println!("Reason: {}", e) - } - } - } - + report_elapsed_time(start); } _ => {} } diff --git a/src/config.rs b/src/config.rs index bd9b20c4..ec26c617 100644 --- a/src/config.rs +++ b/src/config.rs @@ -14,6 +14,7 @@ pub struct Config { pub title: String, /// Base URL of the site pub base_url: String, + /// Whether to highlight all code blocks found in markdown files. Defaults to false pub highlight_code: Option, /// Description of the site diff --git a/src/main.rs b/src/main.rs index 8a4f0fae..fe017a04 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,9 +19,15 @@ mod cmd; // Print the time elapsed rounded to 1 decimal -fn time_elapsed(instant: Instant) -> f64 { - let duration_ms = Duration::from_std(instant.elapsed()).unwrap().num_milliseconds() as f64 / 1000.0; - (duration_ms * 10.0).round() / 10.0 +fn report_elapsed_time(instant: Instant) { + let duration_ms = Duration::from_std(instant.elapsed()).unwrap().num_milliseconds() as f64; + + if duration_ms < 1000.0 { + println!("Done in {}ms.\n", duration_ms); + } else { + let duration_sec = duration_ms / 1000.0; + println!("Done in {:.1}s.\n", ((duration_sec * 10.0).round() / 10.0)); + } } @@ -58,10 +64,11 @@ fn main() { }; }, ("build", Some(_)) => { + println!("Building site"); let start = Instant::now(); match cmd::build() { Ok(()) => { - println!("Site built in {:.1}s.", time_elapsed(start)); + report_elapsed_time(start); }, Err(e) => { println!("Failed to build the site"); diff --git a/src/site.rs b/src/site.rs index 8a36809d..c3b8d48c 100644 --- a/src/site.rs +++ b/src/site.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; use std::iter::FromIterator; -use std::fs::{create_dir, remove_dir_all, copy, remove_file}; +use std::fs::{remove_dir_all, copy, remove_file}; use std::path::Path; use glob::glob; @@ -11,7 +11,7 @@ use walkdir::WalkDir; use errors::{Result, ResultExt}; use config::{Config, get_config}; use page::Page; -use utils::create_file; +use utils::{create_file, create_directory}; lazy_static! { @@ -108,10 +108,6 @@ impl Site { html } - /// Reload the Tera templates - pub fn reload_templates(&mut self) -> Result<()> { - Ok(()) - } /// Copy the content of the `static` folder into the `public` folder /// @@ -131,7 +127,7 @@ impl Site { if entry.path().is_dir() { if !target_path.exists() { - create_dir(&target_path)?; + create_directory(&target_path)?; } } else { if target_path.exists() { @@ -143,24 +139,34 @@ impl Site { Ok(()) } - /// Re-parse and re-generate the site - /// Very dumb for now, ideally it would only rebuild what changed - pub fn rebuild(&mut self) -> Result<()> { - self.parse_site()?; - self.templates.full_reload()?; - self.build() - } - - /// Builds the site to the `public` directory after deleting it - pub fn build(&self) -> Result<()> { + /// Deletes the `public` directory if it exists + pub fn clean(&self) -> Result<()> { if Path::new("public").exists() { // Delete current `public` directory so we can start fresh remove_dir_all("public").chain_err(|| "Couldn't delete `public` directory")?; } - // Start from scratch - create_dir("public")?; + Ok(()) + } + + /// Re-parse and re-generate the site + /// Very dumb for now, ideally it would only rebuild what changed + pub fn rebuild(&mut self) -> Result<()> { + self.parse_site()?; + self.build() + } + + pub fn rebuild_after_template_change(&mut self) -> Result<()> { + self.templates.full_reload()?; + println!("Reloaded templates"); + self.build_pages() + } + + pub fn build_pages(&self) -> Result<()> { let public = Path::new("public"); + if !public.exists() { + create_directory(&public)?; + } let mut pages = vec![]; let mut category_pages: HashMap> = HashMap::new(); @@ -175,7 +181,7 @@ impl Site { current_path.push(section); if !current_path.exists() { - create_dir(¤t_path)?; + create_directory(¤t_path)?; } } @@ -185,7 +191,7 @@ impl Site { } // Make sure the folder exists - create_dir(¤t_path)?; + create_directory(¤t_path)?; // Finally, create a index.html file there with the page rendered let output = page.render_html(&self.templates, &self.config)?; create_file(current_path.join("index.html"), &self.inject_livereload(output))?; @@ -205,9 +211,6 @@ impl Site { self.render_categories_and_tags(RenderList::Categories, &category_pages)?; self.render_categories_and_tags(RenderList::Tags, &tag_pages)?; - self.render_sitemap()?; - self.render_rss_feed()?; - // And finally the index page let mut context = Context::new(); pages.sort_by(|a, b| a.partial_cmp(b).unwrap()); @@ -216,10 +219,18 @@ impl Site { let index = self.templates.render("index.html", &context)?; create_file(public.join("index.html"), &self.inject_livereload(index))?; - self.copy_static_directory()?; - Ok(()) } + + /// Builds the site to the `public` directory after deleting it + pub fn build(&self) -> Result<()> { + self.clean()?; + self.build_pages()?; + self.render_sitemap()?; + self.render_rss_feed()?; + self.copy_static_directory() + } + /// Render the /{categories, list} pages and each individual category/tag page fn render_categories_and_tags(&self, kind: RenderList, container: &HashMap>) -> Result<()> { if container.is_empty() { @@ -235,7 +246,7 @@ impl Site { let public = Path::new("public"); let mut output_path = public.to_path_buf(); output_path.push(name); - create_dir(&output_path)?; + create_directory(&output_path)?; // First we render the list of categories/tags page let mut sorted_container = vec![]; @@ -262,7 +273,7 @@ impl Site { context.add("config", &self.config); let single_output = self.templates.render(single_tpl_name, &context)?; - create_dir(&output_path.join(&slug))?; + create_directory(&output_path.join(&slug))?; create_file( output_path.join(&slug).join("index.html"), &self.inject_livereload(single_output) @@ -283,14 +294,6 @@ impl Site { Ok(()) } - fn get_rss_feed_url(&self) -> String { - if self.config.base_url.ends_with("/") { - format!("{}{}", self.config.base_url, "feed.xml") - } else { - format!("{}/{}", self.config.base_url, "feed.xml") - } - } - fn render_rss_feed(&self) -> Result<()> { let mut context = Context::new(); let mut pages = self.pages.values() @@ -306,7 +309,13 @@ impl Site { context.add("pages", &pages); context.add("last_build_date", &pages[0].meta.date); context.add("config", &self.config); - context.add("feed_url", &self.get_rss_feed_url()); + + let rss_feed_url = if self.config.base_url.ends_with("/") { + format!("{}{}", self.config.base_url, "feed.xml") + } else { + format!("{}/{}", self.config.base_url, "feed.xml") + }; + context.add("feed_url", &rss_feed_url); let sitemap = self.templates.render("rss.xml", &context)?; diff --git a/src/utils.rs b/src/utils.rs index 3e0a09f5..23f509a1 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,8 +1,8 @@ use std::io::prelude::*; -use std::fs::{File}; +use std::fs::{File, create_dir}; use std::path::Path; -use errors::Result; +use errors::{Result, ResultExt}; pub fn create_file>(path: P, content: &str) -> Result<()> { @@ -10,3 +10,14 @@ pub fn create_file>(path: P, content: &str) -> Result<()> { file.write_all(content.as_bytes())?; Ok(()) } + +/// Very similar to create_dir from the std except it checks if the folder +/// exists before creating it +pub fn create_directory>(path: P) -> Result<()> { + let path = path.as_ref(); + if !path.exists() { + create_dir(path) + .chain_err(|| format!("Was not able to create folder {}", path.display()))?; + } + Ok(()) +} From 2d26bf038c35d894035d6733181cf3c0129c5aed Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Fri, 10 Mar 2017 21:36:43 +0900 Subject: [PATCH 16/26] Force refresh on content/template change and various fixes --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- src/cmd/serve.rs | 6 ++++-- src/front_matter.rs | 5 +++-- src/main.rs | 3 +++ src/page.rs | 23 +++++++++-------------- src/site.rs | 5 +---- 7 files changed, 23 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6ae09884..44a8b3e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ [root] name = "gutenberg" -version = "0.1.0" +version = "0.0.1" dependencies = [ "chrono 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.20.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -774,7 +774,7 @@ dependencies = [ [[package]] name = "tera" version = "0.8.0" -source = "git+https://github.com/Keats/tera?branch=reload#31f8bd8238e3e6bdef9ecf4468079c535609b5eb" +source = "git+https://github.com/Keats/tera?branch=reload#ee038a6f3519ac35e1878ca9b29ec739a8f84a15" dependencies = [ "chrono 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "error-chain 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index 0ef92e45..10d6f95a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "gutenberg" -version = "0.1.0" +version = "0.0.1" authors = ["Vincent Prouillet "] license = "MIT" readme = "README.md" diff --git a/src/cmd/serve.rs b/src/cmd/serve.rs index 6211ce42..87da4c3f 100644 --- a/src/cmd/serve.rs +++ b/src/cmd/serve.rs @@ -119,11 +119,13 @@ pub fn serve(interface: &str, port: &str) -> Result<()> { match detect_change_kind(&pwd, &path) { (ChangeKind::Content, _) => { println!("-> Content changed {}", path.display()); - rebuild_done_handling(&broadcaster, site.rebuild(), ""); + // Force refresh + rebuild_done_handling(&broadcaster, site.rebuild_after_content_change(), "/x.js"); }, (ChangeKind::Templates, _) => { println!("-> Template changed {}", path.display()); - rebuild_done_handling(&broadcaster, site.rebuild_after_template_change(), ""); + // Force refresh + rebuild_done_handling(&broadcaster, site.rebuild_after_template_change(), "/x.js"); }, (ChangeKind::StaticFiles, p) => { println!("-> Static file changes detected {}", path.display()); diff --git a/src/front_matter.rs b/src/front_matter.rs index 1bf9411c..ca5349b5 100644 --- a/src/front_matter.rs +++ b/src/front_matter.rs @@ -36,9 +36,9 @@ pub struct FrontMatter { pub draft: Option, /// Only one category allowed pub category: Option, - /// Optional layout, if we want to specify which tpl to render for that page + /// Optional template, if we want to specify which template to render for that page #[serde(skip_serializing)] - pub layout: Option, + pub template: Option, /// Any extra parameter present in the front matter pub extra: Option>, } @@ -69,6 +69,7 @@ impl FrontMatter { Ok(f) } + /// Converts the date in the front matter, which can be in 2 formats, into a NaiveDateTime pub fn parse_date(&self) -> Option { match self.date { Some(ref d) => { diff --git a/src/main.rs b/src/main.rs index fe017a04..b0237a04 100644 --- a/src/main.rs +++ b/src/main.rs @@ -87,6 +87,9 @@ fn main() { Ok(()) => (), Err(e) => { println!("Error: {}", e); + for e in e.iter().skip(1) { + println!("Reason: {}", e) + } ::std::process::exit(1); }, }; diff --git a/src/page.rs b/src/page.rs index 4ff9c1ec..4f004bf1 100644 --- a/src/page.rs +++ b/src/page.rs @@ -182,22 +182,20 @@ impl Page { Page::parse(&path.strip_prefix("content").unwrap().to_string_lossy(), &content, config) } - fn get_layout_name(&self) -> String { - match self.meta.layout { - Some(ref l) => l.to_string(), - None => "page.html".to_string() - } - } - /// Renders the page using the default layout, unless specified in front-matter pub fn render_html(&self, tera: &Tera, config: &Config) -> Result { - let tpl = self.get_layout_name(); + let tpl_name = match self.meta.template { + Some(ref l) => l.to_string(), + None => "page.html".to_string() + }; + // TODO: create a helper to create context to ensure all contexts + // have the same names let mut context = Context::new(); - context.add("site", config); + context.add("config", config); context.add("page", self); - tera.render(&tpl, &context) - .chain_err(|| "Error while rendering template") + tera.render(&tpl_name, &context) + .chain_err(|| format!("Failed to render page '{}'", self.filename)) } } @@ -225,12 +223,10 @@ impl ser::Serialize for Page { impl PartialOrd for Page { fn partial_cmp(&self, other: &Page) -> Option { if self.meta.date.is_none() { - println!("No self data"); return Some(Ordering::Less); } if other.meta.date.is_none() { - println!("No other date"); return Some(Ordering::Greater); } @@ -337,7 +333,6 @@ Hello world"#; assert!(res.is_ok()); let page = res.unwrap(); assert_eq!(page.url, "posts/intro/hello-world"); - println!("{}", page.permalink); assert_eq!(page.permalink, format!("{}{}", conf.base_url, "/posts/intro/hello-world")); } diff --git a/src/site.rs b/src/site.rs index c3b8d48c..0094982a 100644 --- a/src/site.rs +++ b/src/site.rs @@ -149,16 +149,13 @@ impl Site { Ok(()) } - /// Re-parse and re-generate the site - /// Very dumb for now, ideally it would only rebuild what changed - pub fn rebuild(&mut self) -> Result<()> { + pub fn rebuild_after_content_change(&mut self) -> Result<()> { self.parse_site()?; self.build() } pub fn rebuild_after_template_change(&mut self) -> Result<()> { self.templates.full_reload()?; - println!("Reloaded templates"); self.build_pages() } From eaa09999fea208f54f497abb30bad0577623d51d Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Fri, 10 Mar 2017 22:19:36 +0900 Subject: [PATCH 17/26] Clippy run --- src/cmd/serve.rs | 10 +++------- src/front_matter.rs | 2 +- src/markdown.rs | 2 +- src/page.rs | 20 ++++++++++---------- src/site.rs | 8 ++++---- src/utils.rs | 2 +- 6 files changed, 20 insertions(+), 24 deletions(-) diff --git a/src/cmd/serve.rs b/src/cmd/serve.rs index 87da4c3f..e84c5eb6 100644 --- a/src/cmd/serve.rs +++ b/src/cmd/serve.rs @@ -155,22 +155,18 @@ fn is_temp_file(path: &Path) -> bool { x if x.ends_with("jb_tmp___") => true, x if x.ends_with("jb_bak___") => true, // vim - x if x.ends_with("~") => true, + x if x.ends_with('~') => true, _ => { if let Some(filename) = path.file_stem() { // emacs - filename.to_str().unwrap().starts_with("#") + filename.to_str().unwrap().starts_with('#') } else { false } } }, None => { - if path.ends_with(".DS_STORE") { - true - } else { - false - } + path.ends_with(".DS_STORE") }, } } diff --git a/src/front_matter.rs b/src/front_matter.rs index ca5349b5..b94827ea 100644 --- a/src/front_matter.rs +++ b/src/front_matter.rs @@ -73,7 +73,7 @@ impl FrontMatter { pub fn parse_date(&self) -> Option { match self.date { Some(ref d) => { - if d.contains("T") { + if d.contains('T') { DateTime::parse_from_rfc3339(d).ok().and_then(|s| Some(s.naive_local())) } else { NaiveDate::parse_from_str(d, "%Y-%m-%d").ok().and_then(|s| Some(s.and_hms(0,0,0))) diff --git a/src/markdown.rs b/src/markdown.rs index f1fe3bbf..8bc6d339 100644 --- a/src/markdown.rs +++ b/src/markdown.rs @@ -73,7 +73,7 @@ impl<'a> Iterator for CodeHighlightingParser<'a> { .and_then(|lang| SETUP.syntax_set.find_syntax_by_token(lang)) .unwrap_or_else(|| SETUP.syntax_set.find_syntax_plain_text()); self.highlighter = Some( - HighlightLines::new(&syntax, &SETUP.theme_set.themes["base16-ocean.dark"]) + HighlightLines::new(syntax, &SETUP.theme_set.themes["base16-ocean.dark"]) ); let snippet = start_coloured_html_snippet(&SETUP.theme_set.themes["base16-ocean.dark"]); Some(Event::Html(Owned(snippet))) diff --git a/src/page.rs b/src/page.rs index 4f004bf1..5863ddeb 100644 --- a/src/page.rs +++ b/src/page.rs @@ -19,8 +19,6 @@ use markdown::markdown_to_html; lazy_static! { static ref PAGE_RE: Regex = Regex::new(r"^\n?\+\+\+\n((?s).*(?-s))\+\+\+\n((?s).*(?-s))$").unwrap(); - static ref SUMMARY_RE: Regex = Regex::new(r"").unwrap(); - static ref CODE_BLOCK_RE: Regex = Regex::new(r"```").unwrap(); } @@ -108,7 +106,7 @@ impl Page { let content = &caps[2]; // 3. create our page, parse front matter and assign all of that - let meta = FrontMatter::parse(&front_matter) + let meta = FrontMatter::parse(front_matter) .chain_err(|| format!("Error when parsing front matter of file `{}`", filepath))?; let mut page = Page::new(meta); @@ -120,15 +118,17 @@ impl Page { // if we see a code block in the content, we assume that this page needs // to be highlighted. It could potentially have false positive if the content // has ``` in it but that seems kind of unlikely - let mut should_highlight = config.highlight_code.unwrap(); - if should_highlight { - should_highlight = CODE_BLOCK_RE.is_match(&page.raw_content); - } + let should_highlight = if config.highlight_code.unwrap() { + page.raw_content.contains("```") + } else { + false + }; + page.content = markdown_to_html(&page.raw_content, should_highlight); if page.raw_content.contains("") { page.summary = { - let summary = SUMMARY_RE.split(&page.raw_content).collect::>()[0]; + let summary = page.raw_content.splitn(2, "").collect::>()[0]; markdown_to_html(summary, should_highlight) } } @@ -157,10 +157,10 @@ impl Page { if !page.sections.is_empty() { page.url = format!("{}/{}", page.sections.join("/"), page.slug); } else { - page.url = format!("{}", page.slug); + page.url = page.slug.clone(); } } - page.permalink = if config.base_url.ends_with("/") { + page.permalink = if config.base_url.ends_with('/') { format!("{}{}", config.base_url, page.url) } else { format!("{}/{}", config.base_url, page.url) diff --git a/src/site.rs b/src/site.rs index 0094982a..dd433d9d 100644 --- a/src/site.rs +++ b/src/site.rs @@ -87,7 +87,7 @@ impl Site { let page = Page::from_file(&entry.as_path(), &self.config)?; for section in &page.sections { - self.sections.entry(section.clone()).or_insert(vec![]).push(page.slug.clone()); + self.sections.entry(section.clone()).or_insert_with(|| vec![]).push(page.slug.clone()); } self.pages.insert(page.slug.clone(), page); @@ -195,11 +195,11 @@ impl Site { pages.push(page); if let Some(ref category) = page.meta.category { - category_pages.entry(category.to_string()).or_insert(vec![]).push(page); + category_pages.entry(category.to_string()).or_insert_with(|| vec![]).push(page); } if let Some(ref tags) = page.meta.tags { for tag in tags { - tag_pages.entry(tag.to_string()).or_insert(vec![]).push(page); + tag_pages.entry(tag.to_string()).or_insert_with(|| vec![]).push(page); } } } @@ -307,7 +307,7 @@ impl Site { context.add("last_build_date", &pages[0].meta.date); context.add("config", &self.config); - let rss_feed_url = if self.config.base_url.ends_with("/") { + let rss_feed_url = if self.config.base_url.ends_with('/') { format!("{}{}", self.config.base_url, "feed.xml") } else { format!("{}/{}", self.config.base_url, "feed.xml") diff --git a/src/utils.rs b/src/utils.rs index 23f509a1..d0280723 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -11,7 +11,7 @@ pub fn create_file>(path: P, content: &str) -> Result<()> { Ok(()) } -/// Very similar to create_dir from the std except it checks if the folder +/// Very similar to `create_dir` from the std except it checks if the folder /// exists before creating it pub fn create_directory>(path: P) -> Result<()> { let path = path.as_ref(); From 60e30edce0d118b283e955e2da702e37f0056d3e Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Fri, 10 Mar 2017 22:24:56 +0900 Subject: [PATCH 18/26] Make it work on stable --- src/cmd/serve.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cmd/serve.rs b/src/cmd/serve.rs index e84c5eb6..30a621e5 100644 --- a/src/cmd/serve.rs +++ b/src/cmd/serve.rs @@ -74,7 +74,7 @@ pub fn serve(interface: &str, port: &str) -> Result<()> { mount.mount("/livereload.js", livereload_handler); // Starts with a _ to not trigger the unused lint // we need to assign to a variable otherwise it will block - let _iron = Iron::new(mount).http(address.clone()).unwrap(); + let _iron = Iron::new(mount).http(address.as_str()).unwrap(); println!("Web server is available at http://{}", address); // The websocket for livereload From dde9af3efd4fbbe9a882b5b1515cf09d4989df69 Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Sun, 12 Mar 2017 12:54:57 +0900 Subject: [PATCH 19/26] Copy assets found in content folders --- README.md | 1 + src/page.rs | 44 ++++++++++++++++++++++++++++++++++++-------- src/site.rs | 5 +++++ 3 files changed, 42 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index be241c9d..67f04810 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ Gallery at https://tmtheme-editor.herokuapp.com/#!/editor/theme/Agola%20Dark # TODO: +- find a way to add tests - syntax highlighting - pass a --config arg to the CLI to change from `config.toml` - have verbosity levels with a `verbosity` config variable with a default diff --git a/src/page.rs b/src/page.rs index 5863ddeb..44d1fb49 100644 --- a/src/page.rs +++ b/src/page.rs @@ -1,8 +1,8 @@ /// A page, can be a blog post or a basic page use std::cmp::Ordering; -use std::fs::File; +use std::fs::{File, read_dir}; use std::io::prelude::*; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::result::Result as StdResult; @@ -21,6 +21,27 @@ lazy_static! { static ref PAGE_RE: Regex = Regex::new(r"^\n?\+\+\+\n((?s).*(?-s))\+\+\+\n((?s).*(?-s))$").unwrap(); } +/// Looks into the current folder for the path and see if there's anything that is not a .md +/// file. Those will be copied next to the rendered .html file +fn find_related_assets(path: &Path) -> Vec { + let mut assets = vec![]; + + for entry in read_dir(path.parent().unwrap()).unwrap().filter_map(|e| e.ok()) { + let entry_path = entry.path(); + if entry_path.is_file() { + match entry_path.extension() { + Some(e) => match e.to_str() { + Some("md") => continue, + _ => assets.push(entry_path.to_path_buf()), + }, + None => continue, + } + } + } + + assets +} + #[derive(Clone, Debug, PartialEq, Deserialize)] pub struct Page { @@ -38,6 +59,9 @@ pub struct Page { /// The actual content of the page, in markdown #[serde(skip_serializing)] pub raw_content: String, + /// All the non-md files we found next to the .md file + #[serde(skip_serializing)] + pub assets: Vec, /// The HTML rendered of the page pub content: String, /// The front matter meta-data @@ -69,6 +93,7 @@ impl Page { filename: "".to_string(), sections: vec![], raw_content: "".to_string(), + assets: vec![], content: "".to_string(), slug: "".to_string(), url: "".to_string(), @@ -80,7 +105,7 @@ impl Page { } } - // Get word count and estimated reading time + /// Get word count and estimated reading time pub fn get_reading_analytics(&self) -> (usize, usize) { // Only works for latin language but good enough for a start let word_count: usize = self.raw_content.split_whitespace().count(); @@ -90,9 +115,9 @@ impl Page { (word_count, (word_count / 200)) } - // Parse a page given the content of the .md file - // Files without front matter or with invalid front matter are considered - // erroneous + /// Parse a page given the content of the .md file + /// Files without front matter or with invalid front matter are considered + /// erroneous pub fn parse(filepath: &str, content: &str, config: &Config) -> Result { // 1. separate front matter from content if !PAGE_RE.is_match(content) { @@ -169,6 +194,7 @@ impl Page { Ok(page) } + /// Read and parse a .md file into a Page struct pub fn from_file>(path: P, config: &Config) -> Result { let path = path.as_ref(); @@ -178,8 +204,10 @@ impl Page { .read_to_string(&mut content)?; // Remove the content string from name - // Maybe get a path as an arg instead and use strip_prefix? - Page::parse(&path.strip_prefix("content").unwrap().to_string_lossy(), &content, config) + let mut page = Page::parse(&path.strip_prefix("content").unwrap().to_string_lossy(), &content, config)?; + page.assets = find_related_assets(&path); + Ok(page) + } /// Renders the page using the default layout, unless specified in front-matter diff --git a/src/site.rs b/src/site.rs index dd433d9d..5931e4a7 100644 --- a/src/site.rs +++ b/src/site.rs @@ -192,6 +192,11 @@ impl Site { // Finally, create a index.html file there with the page rendered let output = page.render_html(&self.templates, &self.config)?; create_file(current_path.join("index.html"), &self.inject_livereload(output))?; + // Copy any asset we found previously into the same directory as the index.html + for asset in &page.assets { + let asset_path = asset.as_path(); + copy(&asset_path, ¤t_path.join(asset_path.file_name().unwrap()))?; + } pages.push(page); if let Some(ref category) = page.meta.category { From 4e99389566c6efd7115e7917e11914ad99aa7456 Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Sun, 12 Mar 2017 12:59:28 +0900 Subject: [PATCH 20/26] disable_rss -> generate_rss in config --- src/config.rs | 11 ++++++----- src/site.rs | 4 +++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/config.rs b/src/config.rs index ec26c617..c2c108d0 100644 --- a/src/config.rs +++ b/src/config.rs @@ -21,8 +21,9 @@ pub struct Config { pub description: Option, /// The language used in the site. Defaults to "en" pub language_code: Option, - /// Whether to disable RSS generation, defaults to false (== generate RSS) - pub disable_rss: Option, + /// Whether to generate RSS, defaults to false + pub generate_rss: Option, + /// All user params set in [extra] in the config pub extra: Option>, } @@ -44,8 +45,8 @@ impl Config { config.highlight_code = Some(false); } - if config.disable_rss.is_none() { - config.disable_rss = Some(false); + if config.generate_rss.is_none() { + config.generate_rss = Some(false); } Ok(config) @@ -71,7 +72,7 @@ impl Default for Config { highlight_code: Some(true), description: None, language_code: Some("en".to_string()), - disable_rss: Some(false), + generate_rss: Some(false), extra: None, } } diff --git a/src/site.rs b/src/site.rs index 5931e4a7..3bd3e622 100644 --- a/src/site.rs +++ b/src/site.rs @@ -229,7 +229,9 @@ impl Site { self.clean()?; self.build_pages()?; self.render_sitemap()?; - self.render_rss_feed()?; + if self.config.generate_rss.unwrap() { + self.render_rss_feed()?; + } self.copy_static_directory() } From 7e496878e5c40ccfb64939a0915eaa0104182897 Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Tue, 14 Mar 2017 21:25:45 +0900 Subject: [PATCH 21/26] Sections Parse _index.md files as sections and render them --- .gitignore | 3 +- Cargo.lock | 118 +++--- Cargo.toml | 17 +- src/cmd/build.rs | 4 +- src/cmd/serve.rs | 3 +- src/config.rs | 4 +- src/errors.rs | 3 +- src/front_matter.rs | 174 ++------- src/lib.rs | 8 +- src/page.rs | 345 ++++-------------- src/section.rs | 101 +++++ src/site.rs | 144 ++++++-- src/utils.rs | 49 +++ test_site/config.toml | 7 + test_site/content/posts/_index.md | 4 + test_site/content/posts/fixed-slug.md | 7 + test_site/content/posts/fixed-url.md | 7 + test_site/content/posts/no-section/simple.md | 6 + test_site/content/posts/python.md | 6 + test_site/content/posts/simple.md | 6 + test_site/content/posts/tutorials/_index.md | 4 + .../content/posts/tutorials/devops/_index.md | 4 + .../content/posts/tutorials/devops/docker.md | 6 + .../content/posts/tutorials/devops/nix.md | 6 + .../posts/tutorials/programming/_index.md | 4 + .../posts/tutorials/programming/python.md | 6 + .../posts/tutorials/programming/rust.md | 6 + test_site/content/posts/with-assets/index.md | 7 + test_site/content/posts/with-assets/with.js | 0 test_site/static/scripts/hello.js | 0 test_site/static/site.css | 3 + test_site/templates/categories.html | 3 + test_site/templates/category.html | 8 + test_site/templates/index.html | 27 ++ test_site/templates/page.html | 5 + test_site/templates/section.html | 10 + test_site/templates/tag.html | 7 + test_site/templates/tags.html | 3 + tests/front_matter.rs | 197 ++++++++++ tests/page.rs | 249 +++++++++++++ tests/site.rs | 176 +++++++++ 41 files changed, 1208 insertions(+), 539 deletions(-) create mode 100644 src/section.rs create mode 100644 test_site/config.toml create mode 100644 test_site/content/posts/_index.md create mode 100644 test_site/content/posts/fixed-slug.md create mode 100644 test_site/content/posts/fixed-url.md create mode 100644 test_site/content/posts/no-section/simple.md create mode 100644 test_site/content/posts/python.md create mode 100644 test_site/content/posts/simple.md create mode 100644 test_site/content/posts/tutorials/_index.md create mode 100644 test_site/content/posts/tutorials/devops/_index.md create mode 100644 test_site/content/posts/tutorials/devops/docker.md create mode 100644 test_site/content/posts/tutorials/devops/nix.md create mode 100644 test_site/content/posts/tutorials/programming/_index.md create mode 100644 test_site/content/posts/tutorials/programming/python.md create mode 100644 test_site/content/posts/tutorials/programming/rust.md create mode 100644 test_site/content/posts/with-assets/index.md create mode 100644 test_site/content/posts/with-assets/with.js create mode 100644 test_site/static/scripts/hello.js create mode 100644 test_site/static/site.css create mode 100644 test_site/templates/categories.html create mode 100644 test_site/templates/category.html create mode 100644 test_site/templates/index.html create mode 100644 test_site/templates/page.html create mode 100644 test_site/templates/section.html create mode 100644 test_site/templates/tag.html create mode 100644 test_site/templates/tags.html create mode 100644 tests/front_matter.rs create mode 100644 tests/page.rs create mode 100644 tests/site.rs diff --git a/.gitignore b/.gitignore index 99fac5fe..b4f2bd6b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ target .idea/ -site -theme +test_site/public diff --git a/Cargo.lock b/Cargo.lock index 44a8b3e7..b76548a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,14 +3,14 @@ name = "gutenberg" version = "0.0.1" dependencies = [ "chrono 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "clap 2.20.5 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.21.1 (registry+https://github.com/rust-lang/crates.io-index)", "error-chain 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "iron 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", "mount 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "notify 4.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "pulldown-cmark 0.0.8 (registry+https://github.com/rust-lang/crates.io-index)", + "pulldown-cmark 0.0.10 (registry+https://github.com/rust-lang/crates.io-index)", "regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)", @@ -18,7 +18,8 @@ dependencies = [ "slug 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "staticfile 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "syntect 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "tera 0.8.0 (git+https://github.com/Keats/tera?branch=reload)", + "tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "tera 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", "ws 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -26,7 +27,7 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -37,6 +38,16 @@ name = "ansi_term" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "atty" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "backtrace" version = "0.3.0" @@ -56,7 +67,7 @@ name = "backtrace-sys" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "gcc 0.3.43 (registry+https://github.com/rust-lang/crates.io-index)", + "gcc 0.3.45 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -67,7 +78,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-serialize 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)", "serde 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -76,11 +87,6 @@ name = "bitflags" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "bitflags" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "bitflags" version = "0.7.0" @@ -131,17 +137,17 @@ dependencies = [ [[package]] name = "clap" -version = "2.20.5" +version = "2.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "atty 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "term_size 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-segmentation 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "vec_map 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "vec_map 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -149,7 +155,7 @@ name = "cmake" version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "gcc 0.3.43 (registry+https://github.com/rust-lang/crates.io-index)", + "gcc 0.3.45 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -157,7 +163,7 @@ name = "conduit-mime-types" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-serialize 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -233,7 +239,7 @@ dependencies = [ [[package]] name = "gcc" -version = "0.3.43" +version = "0.3.45" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -253,7 +259,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "humansize" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -264,9 +270,9 @@ dependencies = [ "httparse 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", - "mime 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "mime 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-serialize 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)", "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)", "traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -293,6 +299,15 @@ dependencies = [ "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "iovec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "iron" version = "0.5.1" @@ -364,7 +379,7 @@ dependencies = [ [[package]] name = "mime" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", @@ -375,7 +390,7 @@ name = "miniz-sys" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "gcc 0.3.43 (registry+https://github.com/rust-lang/crates.io-index)", + "gcc 0.3.45 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -397,9 +412,10 @@ dependencies = [ [[package]] name = "mio" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ + "iovec 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "lazycell 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", @@ -563,7 +579,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", "chrono 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-serialize 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)", "serde 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)", "xml-rs 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -578,10 +594,10 @@ dependencies = [ [[package]] name = "pulldown-cmark" -version = "0.0.8" +version = "0.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bitflags 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -608,7 +624,7 @@ name = "regex" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "aho-corasick 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "aho-corasick 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", "memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "regex-syntax 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "thread_local 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -627,7 +643,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "rustc-serialize" -version = "0.3.22" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -766,20 +782,28 @@ dependencies = [ "onig 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "plist 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "regex-syntax 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-serialize 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)", "walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", "yaml-rust 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "tempdir" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "tera" -version = "0.8.0" -source = "git+https://github.com/Keats/tera?branch=reload#ee038a6f3519ac35e1878ca9b29ec739a8f84a15" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "chrono 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "error-chain 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "humansize 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "humansize 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", "pest 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -927,7 +951,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "vec_map" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -972,7 +996,7 @@ dependencies = [ "bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "httparse 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", "sha1 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1002,13 +1026,13 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] -"checksum aho-corasick 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0638fd549427caa90c499814196d1b9e3725eb4d15d7339d6de073a680ed0ca2" +"checksum aho-corasick 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "500909c4f87a9e52355b26626d890833e9e1d53ac566db76c36faa984b889699" "checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6" +"checksum atty 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d912da0db7fa85514874458ca3651fe2cddace8d0b0505571dbdcd41ab490159" "checksum backtrace 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f551bc2ddd53aea015d453ef0b635af89444afa5ed2405dd0b2062ad5d600d80" "checksum backtrace-sys 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d192fd129132fbc97497c1f2ec2c2c5174e376b95f535199ef4fe0a293d33842" "checksum bincode 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "55eb0b7fd108527b0c77860f75eca70214e11a8b4c6ef05148c54c05a25d48ad" "checksum bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8dead7461c1127cf637931a1e50934eb6eee8bff2f74433ac7909e9afcee04a3" -"checksum bitflags 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4f67931368edf3a9a51d29886d245f1c3db2f1ef0dcc9e35ff70341b78c10d23" "checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" "checksum bitflags 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e1ab483fc81a8143faa7203c4a3c02888ebd1a782e37e41fa34753ba9a162" "checksum byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0fc10e8cc6b2580fda3f36eb6dc5316657f812a3df879a44a66fc9f0fdbc4855" @@ -1017,7 +1041,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de1e760d7b6535af4241fca8bd8adf68e2e7edacc6b29f5d399050c5e48cf88c" "checksum chrono 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)" = "9213f7cd7c27e95c2b57c49f0e69b1ea65b27138da84a170133fd21b07659c00" "checksum chrono 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "158b0bd7d75cbb6bf9c25967a48a2e9f77da95876b858eadfabaa99cd069de6e" -"checksum clap 2.20.5 (registry+https://github.com/rust-lang/crates.io-index)" = "7db281b0520e97fbd15cd615dcd8f8bcad0c26f5f7d5effe705f090f39e9a758" +"checksum clap 2.21.1 (registry+https://github.com/rust-lang/crates.io-index)" = "74a80f603221c9cd9aa27a28f52af452850051598537bb6b359c38a7d61e5cda" "checksum cmake 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)" = "e1acc68a3f714627af38f9f5d09706a28584ba60dfe2cca68f40bf779f941b25" "checksum conduit-mime-types 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "95ca30253581af809925ef68c2641cc140d6183f43e12e0af4992d53768bd7b8" "checksum dbghelp-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "97590ba53bcb8ac28279161ca943a924d1fd4a8fb3fa63302591647c4fc5b850" @@ -1029,14 +1053,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6cc484842f1e2884faf56f529f960cc12ad8c71ce96cc7abba0a067c98fee344" "checksum fsevent 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)" = "dfe593ebcfc76884138b25426999890b10da8e6a46d01b499d7c54c604672c38" "checksum fsevent-sys 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "1a772d36c338d07a032d5375a36f15f9a7043bf0cb8ce7cee658e037c6032874" -"checksum gcc 0.3.43 (registry+https://github.com/rust-lang/crates.io-index)" = "c07c758b972368e703a562686adb39125707cc1ef3399da8c019fc6c2498a75d" +"checksum gcc 0.3.45 (registry+https://github.com/rust-lang/crates.io-index)" = "40899336fb50db0c78710f53e87afc54d8c7266fb76262fecc78ca1a7f09deae" "checksum getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9047cfbd08a437050b363d35ef160452c5fe8ea5187ae0a624708c91581d685" "checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" "checksum httparse 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a6e7a63e511f9edffbab707141fbb8707d1a3098615fb2adbd5769cdfcc9b17d" -"checksum humansize 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9b963e0c0a5149e12a9cab4d889404e4935e3484db7c4d9681e8bbdbcb9dfd80" +"checksum humansize 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "92d211e6e70b05749dce515b47684f29a3c8c38bbbb21c50b30aff9eca1b0bd3" "checksum hyper 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)" = "43a15e3273b2133aaac0150478ab443fb89f15c3de41d8d93d8f3bb14bf560f6" "checksum idna 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1053236e00ce4f668aeca4a769a09b3bf5a682d802abd6f3cb39374f6b162c11" "checksum inotify 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "887fcc180136e77a85e6a6128579a719027b1bab9b1c38ea4444244fe262c20c" +"checksum iovec 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "29d062ee61fccdf25be172e70f34c9f6efc597e1fb8f6526e8437b2046ab26be" "checksum iron 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2440ae846e7a8c7f9b401db8f6e31b4ea5e7d3688b91761337da7e054520c75b" "checksum itoa 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eb2f404fbc66fd9aac13e998248505e7ecb2ad8e44ab6388684c5fb11c6c251c" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" @@ -1047,10 +1072,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "5141eca02775a762cc6cd564d8d2c50f67c0ea3a372cbf1c51592b3e029e10ad" "checksum matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "efd7622e3022e1a6eaa602c4cea8912254e5582c9c692e9167714182244801b1" "checksum memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1dbccc0e46f1ea47b9f17e6d67c5a96bd27030519c519c9c91327e31275a47b4" -"checksum mime 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b5c93a4bd787ddc6e7833c519b73a50883deb5863d76d9b71eb8216fb7f94e66" +"checksum mime 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "5514f038123342d01ee5f95129e4ef1e0470c93bc29edf058a46f9ee3ba6737e" "checksum miniz-sys 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "28eaee17666671fa872e567547e8428e83308ebe5808cdf6a0e28397dbe2c726" "checksum mio 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a637d1ca14eacae06296a008fa7ad955347e34efcb5891cfd8ba05491a37907e" -"checksum mio 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "eecdbdd49a849336e77b453f021c89972a2cfb5b51931a0026ae0ac4602de681" +"checksum mio 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "aa30e3753079b08ce3d75cf3b44783e36fe0e1f64065f65c1d894d1688fb2580" "checksum miow 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3e690c5df6b2f60acd45d56378981e827ff8295562fc8d34f573deb267a59cd1" "checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" "checksum modifier 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "41f5c9112cb662acd3b204077e0de5bc66305fa8df65c8019d5adb10e9ab6e58" @@ -1069,14 +1094,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "3a8b4c6b8165cd1a1cd4b9b120978131389f64bdaf456435caa41e630edba903" "checksum plist 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6ab9bef2781bcdac1baf3e29eb297344cd24263e22fd9436d3a21215b7d8aa" "checksum plugin 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "1a6a0dc3910bc8db877ffed8e457763b317cf880df4ae19109b9f77d277cf6e0" -"checksum pulldown-cmark 0.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "1058d7bb927ca067656537eec4e02c2b4b70eaaa129664c5b90c111e20326f41" +"checksum pulldown-cmark 0.0.10 (registry+https://github.com/rust-lang/crates.io-index)" = "b0b0f7b64fd9ff618da552df85b0d356a1487e5ef41df8b5727b0f73bd1215a1" "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" "checksum rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "022e0636ec2519ddae48154b028864bdce4eaf7d35226ab8e65c611be97b189d" "checksum redox_syscall 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "8dd35cc9a8bdec562c757e3d43c1526b5c6d2653e23e2315065bc25556550753" "checksum regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4278c17d0f6d62dfef0ab00028feb45bd7d2102843f80763474eeb1be8a10c01" "checksum regex-syntax 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9191b1f57603095f105d317e375d19b1c9c5c3185ea9633a99a6dcbed04457" "checksum rustc-demangle 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3058a43ada2c2d0b92b3ae38007a2d0fa5e9db971be260e0171408a4ff471c95" -"checksum rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)" = "237546c689f20bb44980270c73c3b9edd0891c1be49cc1274406134a66d3957b" +"checksum rustc-serialize 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)" = "684ce48436d6465300c9ea783b6b14c4361d6b8dcbb1375b486a69cc19e2dfb0" "checksum rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c5f5376ea5e30ce23c03eb77cbe4962b988deead10910c372b226388b594c084" "checksum same-file 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d931a44fdaa43b8637009e7632a02adc4f2b2e0733c08caa4cf00e8da4a117a7" "checksum semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)" = "d4f410fedcf71af0345d7607d246e7ad15faaadd49d240ee3b24e5dc21a820ac" @@ -1095,7 +1120,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum syn 0.11.9 (registry+https://github.com/rust-lang/crates.io-index)" = "480c834701caba3548aa991e54677281be3a5414a9d09ddbdf4ed74a569a9d19" "checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" "checksum syntect 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6728e7e9bbd971751d17d39b0e38e3558c10b9fb32125441bb17c434a2754e7c" -"checksum tera 0.8.0 (git+https://github.com/Keats/tera?branch=reload)" = "" +"checksum tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "87974a6f5c1dfb344d733055601650059a3363de2a6104819293baff662132d6" +"checksum tera 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6f2ff83a1773a0482ddc961d0030b514f1848f592ae9612afb241e5eb455df75" "checksum term_size 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "07b6c1ac5b3fffd75073276bca1ceed01f67a28537097a2a9539e116e50fb21a" "checksum thread-id 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4437c97558c70d129e40629a5b385b3fb1ffac301e63941335e4d354081ec14a" "checksum thread_local 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c85048c6260d17cf486ceae3282d9fb6b90be220bf5b28c400f5485ffc29f0c7" @@ -1115,7 +1141,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum unsafe-any 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b351086021ebc264aea3ab4f94d61d889d98e5e9ec2d985d993f50133537fd3a" "checksum url 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f5ba8a749fb4479b043733416c244fa9d1d3af3d7c23804944651c8a448cb87e" "checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122" -"checksum vec_map 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cac5efe5cb0fa14ec2f84f83c701c562ee63f6dcc680861b21d65c682adfb05f" +"checksum vec_map 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8cdc8b93bd0198ed872357fb2e667f7125646b1762f16d60b2c96350d361897" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" "checksum walkdir 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "c66c0b9792f0a765345452775f3adbd28dde9d33f30d13e5dcc5ae17cf6f3780" "checksum walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "bb08f9e670fab86099470b97cd2b252d6527f0b3cc1401acdb595ffc9dd288ff" diff --git a/Cargo.toml b/Cargo.toml index 10d6f95a..5d74f60a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,9 @@ homepage = "https://github.com/Keats/gutenberg" repository = "https://github.com/Keats/gutenberg" keywords = ["static", "site", "generator", "blog"] +[[bin]] +name = "gutenberg" + [dependencies] error-chain = "0.10" clap = "2.19" @@ -20,11 +23,13 @@ glob = "0.2" serde = "0.9" serde_json = "0.9" serde_derive = "0.9" -tera = { git = "https://github.com/Keats/tera", branch = "reload" } -# tera = "0.8" +# tera = { path = "../tera" } +# tera = { git = "https://github.com/Keats/tera", branch = "reload" } +tera = "0.8" slug = "0.1" syntect = "1" chrono = "0.3" +toml = { version = "0.3", default-features = false, features = ["serde"]} # Below is for the serve cmd staticfile = "0.4" @@ -33,9 +38,5 @@ mount = "0.3" notify = "4" ws = "0.6" -[dependencies.toml] -version = "0.3" -default-features = false -features = ["serde"] - - +[dev-dependencies] +tempdir = "0.3" diff --git a/src/cmd/build.rs b/src/cmd/build.rs index a20459ca..ec581194 100644 --- a/src/cmd/build.rs +++ b/src/cmd/build.rs @@ -1,7 +1,9 @@ +use std::env; + use gutenberg::errors::Result; use gutenberg::Site; pub fn build() -> Result<()> { - Site::new(false)?.build() + Site::new(env::current_dir().unwrap())?.build() } diff --git a/src/cmd/serve.rs b/src/cmd/serve.rs index 30a621e5..0c83639e 100644 --- a/src/cmd/serve.rs +++ b/src/cmd/serve.rs @@ -61,7 +61,8 @@ fn rebuild_done_handling(broadcaster: &Sender, res: Result<()>, reload_path: &st pub fn serve(interface: &str, port: &str) -> Result<()> { println!("Building site..."); let start = Instant::now(); - let mut site = Site::new(true)?; + let mut site = Site::new(env::current_dir().unwrap())?; + site.enable_live_reload(); site.build()?; report_elapsed_time(start); diff --git a/src/config.rs b/src/config.rs index c2c108d0..76000f3b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -81,8 +81,8 @@ impl Default for Config { /// Get and parse the config. /// If it doesn't succeed, exit -pub fn get_config() -> Config { - match Config::from_file("config.toml") { +pub fn get_config(path: &Path) -> Config { + match Config::from_file(path.join("config.toml")) { Ok(c) => c, Err(e) => { println!("Failed to load config.toml"); diff --git a/src/errors.rs b/src/errors.rs index 5ff689c0..589c5fcb 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -2,8 +2,7 @@ use tera; use toml; error_chain! { - errors { - } + errors {} links { Tera(tera::Error, tera::ErrorKind); diff --git a/src/front_matter.rs b/src/front_matter.rs index b94827ea..c9d77321 100644 --- a/src/front_matter.rs +++ b/src/front_matter.rs @@ -1,12 +1,18 @@ use std::collections::HashMap; - +use std::path::Path; use toml; use tera::Value; use chrono::prelude::*; +use regex::Regex; -use errors::{Result}; +use errors::{Result, ResultExt}; + + +lazy_static! { + static ref PAGE_RE: Regex = Regex::new(r"^\n?\+\+\+\n((?s).*(?-s))\+\+\+\n?((?s).*(?-s))$").unwrap(); +} /// The front matter of every page @@ -85,157 +91,23 @@ impl FrontMatter { } -#[cfg(test)] -mod tests { - use super::{FrontMatter}; - use tera::to_value; - - - #[test] - fn test_can_parse_a_valid_front_matter() { - let content = r#" -title = "Hello" -description = "hey there""#; - let res = FrontMatter::parse(content); - println!("{:?}", res); - assert!(res.is_ok()); - let res = res.unwrap(); - assert_eq!(res.title, "Hello".to_string()); - assert_eq!(res.description, "hey there".to_string()); +/// Split a file between the front matter and its content +/// It will parse the front matter as well and returns any error encountered +/// TODO: add tests +pub fn split_content(file_path: &Path, content: &str) -> Result<(FrontMatter, String)> { + if !PAGE_RE.is_match(content) { + bail!("Couldn't find front matter in `{}`. Did you forget to add `+++`?", file_path.to_string_lossy()); } - #[test] - fn test_can_parse_tags() { - let content = r#" -title = "Hello" -description = "hey there" -slug = "hello-world" -tags = ["rust", "html"]"#; - let res = FrontMatter::parse(content); - assert!(res.is_ok()); - let res = res.unwrap(); + // 2. extract the front matter and the content + let caps = PAGE_RE.captures(content).unwrap(); + // caps[0] is the full match + let front_matter = &caps[1]; + let content = &caps[2]; - assert_eq!(res.title, "Hello".to_string()); - assert_eq!(res.slug.unwrap(), "hello-world".to_string()); - assert_eq!(res.tags.unwrap(), ["rust".to_string(), "html".to_string()]); - } + // 3. create our page, parse front matter and assign all of that + let meta = FrontMatter::parse(front_matter) + .chain_err(|| format!("Error when parsing front matter of file `{}`", file_path.to_string_lossy()))?; - #[test] - fn test_can_parse_extra_attributes_in_frontmatter() { - let content = r#" -title = "Hello" -description = "hey there" -slug = "hello-world" - -[extra] -language = "en" -authors = ["Bob", "Alice"]"#; - let res = FrontMatter::parse(content); - assert!(res.is_ok()); - let res = res.unwrap(); - - assert_eq!(res.title, "Hello".to_string()); - assert_eq!(res.slug.unwrap(), "hello-world".to_string()); - let extra = res.extra.unwrap(); - assert_eq!(extra.get("language").unwrap(), &to_value("en").unwrap()); - assert_eq!( - extra.get("authors").unwrap(), - &to_value(["Bob".to_string(), "Alice".to_string()]).unwrap() - ); - } - - #[test] - fn test_is_ok_with_url_instead_of_slug() { - let content = r#" -title = "Hello" -description = "hey there" -url = "hello-world""#; - let res = FrontMatter::parse(content); - assert!(res.is_ok()); - let res = res.unwrap(); - assert!(res.slug.is_none()); - assert_eq!(res.url.unwrap(), "hello-world".to_string()); - } - - #[test] - fn test_errors_with_empty_front_matter() { - let content = r#" "#; - let res = FrontMatter::parse(content); - assert!(res.is_err()); - } - - #[test] - fn test_errors_with_invalid_front_matter() { - let content = r#"title = 1\n"#; - let res = FrontMatter::parse(content); - assert!(res.is_err()); - } - - #[test] - fn test_errors_with_missing_required_value_front_matter() { - let content = r#"title = """#; - let res = FrontMatter::parse(content); - assert!(res.is_err()); - } - - #[test] - fn test_errors_on_non_string_tag() { - let content = r#" -title = "Hello" -description = "hey there" -slug = "hello-world" -tags = ["rust", 1]"#; - let res = FrontMatter::parse(content); - assert!(res.is_err()); - } - - #[test] - fn test_errors_on_present_but_empty_slug() { - let content = r#" -title = "Hello" -description = "hey there" -slug = """#; - let res = FrontMatter::parse(content); - assert!(res.is_err()); - } - - #[test] - fn test_errors_on_present_but_empty_url() { - let content = r#" -title = "Hello" -description = "hey there" -url = """#; - let res = FrontMatter::parse(content); - assert!(res.is_err()); - } - - #[test] - fn test_parse_date_yyyy_mm_dd() { - let content = r#" -title = "Hello" -description = "hey there" -date = "2016-10-10""#; - let res = FrontMatter::parse(content).unwrap(); - assert!(res.parse_date().is_some()); - } - - #[test] - fn test_parse_date_rfc3339() { - let content = r#" -title = "Hello" -description = "hey there" -date = "2002-10-02T15:00:00Z""#; - let res = FrontMatter::parse(content).unwrap(); - assert!(res.parse_date().is_some()); - } - - #[test] - fn test_cant_parse_random_date_format() { - let content = r#" -title = "Hello" -description = "hey there" -date = "2002/10/12""#; - let res = FrontMatter::parse(content).unwrap(); - assert!(res.parse_date().is_none()); - } + Ok((meta, content.to_string())) } diff --git a/src/lib.rs b/src/lib.rs index 81d1df9b..3c5fe81b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,8 @@ extern crate glob; extern crate syntect; extern crate slug; extern crate chrono; +#[cfg(test)] +extern crate tempdir; mod utils; mod config; @@ -22,9 +24,11 @@ mod page; mod front_matter; mod site; mod markdown; +mod section; pub use site::Site; pub use config::Config; -pub use front_matter::FrontMatter; -pub use page::Page; +pub use front_matter::{FrontMatter, split_content}; +pub use page::{Page}; +pub use section::{Section}; pub use utils::create_file; diff --git a/src/page.rs b/src/page.rs index 44d1fb49..6b2e69c4 100644 --- a/src/page.rs +++ b/src/page.rs @@ -1,32 +1,28 @@ /// A page, can be a blog post or a basic page use std::cmp::Ordering; -use std::fs::{File, read_dir}; -use std::io::prelude::*; +use std::fs::{read_dir}; use std::path::{Path, PathBuf}; use std::result::Result as StdResult; -use regex::Regex; use tera::{Tera, Context}; use serde::ser::{SerializeStruct, self}; use slug::slugify; use errors::{Result, ResultExt}; use config::Config; -use front_matter::{FrontMatter}; +use front_matter::{FrontMatter, split_content}; use markdown::markdown_to_html; +use utils::{read_file, find_content_components}; -lazy_static! { - static ref PAGE_RE: Regex = Regex::new(r"^\n?\+\+\+\n((?s).*(?-s))\+\+\+\n((?s).*(?-s))$").unwrap(); -} /// Looks into the current folder for the path and see if there's anything that is not a .md /// file. Those will be copied next to the rendered .html file fn find_related_assets(path: &Path) -> Vec { let mut assets = vec![]; - for entry in read_dir(path.parent().unwrap()).unwrap().filter_map(|e| e.ok()) { + for entry in read_dir(path).unwrap().filter_map(|e| e.ok()) { let entry_path = entry.path(); if entry_path.is_file() { match entry_path.extension() { @@ -43,24 +39,22 @@ fn find_related_assets(path: &Path) -> Vec { } -#[derive(Clone, Debug, PartialEq, Deserialize)] +#[derive(Clone, Debug, PartialEq)] pub struct Page { - /// .md filepath, excluding the content/ bit - #[serde(skip_serializing)] - pub filepath: String, + /// The .md path + pub file_path: PathBuf, + /// The parent directory of the file. Is actually the grand parent directory + /// if it's an asset folder + pub parent_path: PathBuf, /// The name of the .md file - #[serde(skip_serializing)] - pub filename: String, - /// The directories above our .md file are called sections - /// for example a file at content/kb/solutions/blabla.md will have 2 sections: + pub file_name: String, + /// The directories above our .md file + /// for example a file at content/kb/solutions/blabla.md will have 2 components: /// `kb` and `solutions` - #[serde(skip_serializing)] - pub sections: Vec, + pub components: Vec, /// The actual content of the page, in markdown - #[serde(skip_serializing)] pub raw_content: String, /// All the non-md files we found next to the .md file - #[serde(skip_serializing)] pub assets: Vec, /// The HTML rendered of the page pub content: String, @@ -89,9 +83,10 @@ pub struct Page { impl Page { pub fn new(meta: FrontMatter) -> Page { Page { - filepath: "".to_string(), - filename: "".to_string(), - sections: vec![], + file_path: PathBuf::new(), + parent_path: PathBuf::new(), + file_name: "".to_string(), + components: vec![], raw_content: "".to_string(), assets: vec![], content: "".to_string(), @@ -118,25 +113,13 @@ impl Page { /// Parse a page given the content of the .md file /// Files without front matter or with invalid front matter are considered /// erroneous - pub fn parse(filepath: &str, content: &str, config: &Config) -> Result { + pub fn parse(file_path: &Path, content: &str, config: &Config) -> Result { // 1. separate front matter from content - if !PAGE_RE.is_match(content) { - bail!("Couldn't find front matter in `{}`. Did you forget to add `+++`?", filepath); - } - - // 2. extract the front matter and the content - let caps = PAGE_RE.captures(content).unwrap(); - // caps[0] is the full match - let front_matter = &caps[1]; - let content = &caps[2]; - - // 3. create our page, parse front matter and assign all of that - let meta = FrontMatter::parse(front_matter) - .chain_err(|| format!("Error when parsing front matter of file `{}`", filepath))?; - + let (meta, content) = split_content(file_path, content)?; let mut page = Page::new(meta); - page.filepath = filepath.to_string(); - page.raw_content = content.to_string(); + page.file_path = file_path.to_path_buf(); + page.parent_path = page.file_path.parent().unwrap().to_path_buf(); + page.raw_content = content; // We try to be smart about highlighting code as it can be time-consuming // If the global config disables it, then we do nothing. However, @@ -158,33 +141,38 @@ impl Page { } } - let path = Path::new(filepath); - page.filename = path.file_stem().expect("Couldn't get filename").to_string_lossy().to_string(); + let path = Path::new(file_path); + page.file_name = path.file_stem().unwrap().to_string_lossy().to_string(); + page.slug = { if let Some(ref slug) = page.meta.slug { slug.trim().to_string() } else { - slugify(page.filename.clone()) + slugify(page.file_name.clone()) } }; - // 4. Find sections // Pages with custom urls exists outside of sections if let Some(ref u) = page.meta.url { page.url = u.trim().to_string(); } else { - // find out if we have sections - for section in path.parent().unwrap().components() { - page.sections.push(section.as_ref().to_string_lossy().to_string()); - } + page.components = find_content_components(&page.file_path); + if !page.components.is_empty() { + // If we have a folder with an asset, don't consider it as a component + if page.file_name == "index" { + page.components.pop(); + // also set parent_path to grandparent instead + page.parent_path = page.parent_path.parent().unwrap().to_path_buf(); + } - if !page.sections.is_empty() { - page.url = format!("{}/{}", page.sections.join("/"), page.slug); + // Don't add a trailing slash to sections + page.url = format!("{}/{}", page.components.join("/"), page.slug); } else { page.url = page.slug.clone(); } } + page.permalink = if config.base_url.ends_with('/') { format!("{}{}", config.base_url, page.url) } else { @@ -197,15 +185,14 @@ impl Page { /// Read and parse a .md file into a Page struct pub fn from_file>(path: P, config: &Config) -> Result { let path = path.as_ref(); + let content = read_file(path)?; + let mut page = Page::parse(path, &content, config)?; + page.assets = find_related_assets(&path.parent().unwrap()); - let mut content = String::new(); - File::open(path) - .chain_err(|| format!("Failed to open '{:?}'", path.display()))? - .read_to_string(&mut content)?; + if !page.assets.is_empty() && page.file_name != "index" { + bail!("Page `{}` has assets but is not named index.md", path.display()); + } - // Remove the content string from name - let mut page = Page::parse(&path.strip_prefix("content").unwrap().to_string_lossy(), &content, config)?; - page.assets = find_related_assets(&path); Ok(page) } @@ -223,7 +210,7 @@ impl Page { context.add("page", self); tera.render(&tpl_name, &context) - .chain_err(|| format!("Failed to render page '{}'", self.filename)) + .chain_err(|| format!("Failed to render page '{}'", self.file_name)) } } @@ -275,235 +262,25 @@ impl PartialOrd for Page { #[cfg(test)] mod tests { - use super::{Page}; - use config::Config; + use tempdir::TempDir; + use std::fs::File; + + use super::{find_related_assets}; #[test] - fn test_can_parse_a_valid_page() { - let content = r#" -+++ -title = "Hello" -description = "hey there" -slug = "hello-world" -+++ -Hello world"#; - let res = Page::parse("post.md", content, &Config::default()); - assert!(res.is_ok()); - let page = res.unwrap(); - - assert_eq!(page.meta.title, "Hello".to_string()); - assert_eq!(page.meta.slug.unwrap(), "hello-world".to_string()); - assert_eq!(page.raw_content, "Hello world".to_string()); - assert_eq!(page.content, "

Hello world

\n".to_string()); - } - - #[test] - fn test_can_find_one_parent_directory() { - let content = r#" -+++ -title = "Hello" -description = "hey there" -slug = "hello-world" -+++ -Hello world"#; - let res = Page::parse("posts/intro.md", content, &Config::default()); - assert!(res.is_ok()); - let page = res.unwrap(); - assert_eq!(page.sections, vec!["posts".to_string()]); - } - - #[test] - fn test_can_find_multiple_parent_directories() { - let content = r#" -+++ -title = "Hello" -description = "hey there" -slug = "hello-world" -+++ -Hello world"#; - let res = Page::parse("posts/intro/start.md", content, &Config::default()); - assert!(res.is_ok()); - let page = res.unwrap(); - assert_eq!(page.sections, vec!["posts".to_string(), "intro".to_string()]); - } - - #[test] - fn test_can_make_url_from_sections_and_slug() { - let content = r#" -+++ -title = "Hello" -description = "hey there" -slug = "hello-world" -+++ -Hello world"#; - let mut conf = Config::default(); - conf.base_url = "http://hello.com/".to_string(); - let res = Page::parse("posts/intro/start.md", content, &conf); - assert!(res.is_ok()); - let page = res.unwrap(); - assert_eq!(page.url, "posts/intro/hello-world"); - assert_eq!(page.permalink, "http://hello.com/posts/intro/hello-world"); - } - - #[test] - fn test_can_make_permalink_with_non_trailing_slash_base_url() { - let content = r#" -+++ -title = "Hello" -description = "hey there" -slug = "hello-world" -+++ -Hello world"#; - let mut conf = Config::default(); - conf.base_url = "http://hello.com".to_string(); - let res = Page::parse("posts/intro/start.md", content, &conf); - assert!(res.is_ok()); - let page = res.unwrap(); - assert_eq!(page.url, "posts/intro/hello-world"); - assert_eq!(page.permalink, format!("{}{}", conf.base_url, "/posts/intro/hello-world")); - } - - #[test] - fn test_can_make_url_from_slug_only() { - let content = r#" -+++ -title = "Hello" -description = "hey there" -slug = "hello-world" -+++ -Hello world"#; - let res = Page::parse("start.md", content, &Config::default()); - assert!(res.is_ok()); - let page = res.unwrap(); - assert_eq!(page.url, "hello-world"); - assert_eq!(page.permalink, format!("{}{}", Config::default().base_url, "hello-world")); - } - - #[test] - fn test_errors_on_invalid_front_matter_format() { - let content = r#" -title = "Hello" -description = "hey there" -slug = "hello-world" -+++ -Hello world"#; - let res = Page::parse("start.md", content, &Config::default()); - assert!(res.is_err()); - } - - #[test] - fn test_can_make_slug_from_non_slug_filename() { - let content = r#" -+++ -title = "Hello" -description = "hey there" -+++ -Hello world"#; - let res = Page::parse("file with space.md", content, &Config::default()); - assert!(res.is_ok()); - let page = res.unwrap(); - assert_eq!(page.slug, "file-with-space"); - assert_eq!(page.permalink, format!("{}{}", Config::default().base_url, "file-with-space")); - } - - #[test] - fn test_trim_slug_if_needed() { - let content = r#" -+++ -title = "Hello" -description = "hey there" -+++ -Hello world"#; - let res = Page::parse(" file with space.md", content, &Config::default()); - assert!(res.is_ok()); - let page = res.unwrap(); - assert_eq!(page.slug, "file-with-space"); - assert_eq!(page.permalink, format!("{}{}", Config::default().base_url, "file-with-space")); - } - - #[test] - fn test_reading_analytics_short() { - let content = r#" -+++ -title = "Hello" -description = "hey there" -+++ -Hello world"#; - let res = Page::parse("file with space.md", content, &Config::default()); - assert!(res.is_ok()); - let page = res.unwrap(); - let (word_count, reading_time) = page.get_reading_analytics(); - assert_eq!(word_count, 2); - assert_eq!(reading_time, 0); - } - - #[test] - fn test_reading_analytics_long() { - let mut content = r#" -+++ -title = "Hello" -description = "hey there" -+++ -Hello world"#.to_string(); - for _ in 0..1000 { - content.push_str(" Hello world"); - } - let res = Page::parse("hello.md", &content, &Config::default()); - assert!(res.is_ok()); - let page = res.unwrap(); - let (word_count, reading_time) = page.get_reading_analytics(); - assert_eq!(word_count, 2002); - assert_eq!(reading_time, 10); - } - - #[test] - fn test_automatic_summary_is_empty_string() { - let content = r#" -+++ -title = "Hello" -description = "hey there" -+++ -Hello world"#.to_string(); - let res = Page::parse("hello.md", &content, &Config::default()); - assert!(res.is_ok()); - let page = res.unwrap(); - assert_eq!(page.summary, ""); - } - - #[test] - fn test_can_specify_summary() { - let content = r#" -+++ -title = "Hello" -description = "hey there" -+++ -Hello world - -"#.to_string(); - let res = Page::parse("hello.md", &content, &Config::default()); - assert!(res.is_ok()); - let page = res.unwrap(); - assert_eq!(page.summary, "

Hello world

\n"); - } - - #[test] - fn test_can_auto_detect_when_highlighting_needed() { - let content = r#" -+++ -title = "Hello" -description = "hey there" -+++ -``` -Hey there -``` -"#.to_string(); - let mut config = Config::default(); - config.highlight_code = Some(true); - let res = Page::parse("hello.md", &content, &config); - assert!(res.is_ok()); - let page = res.unwrap(); - assert!(page.content.starts_with(", + /// The relative URL of the page + pub url: String, + /// The full URL for that page + pub permalink: String, + /// The front matter meta-data + pub meta: FrontMatter, + /// All direct pages of that section + pub pages: Vec, + /// All direct subsections + pub subsections: Vec
, +} + +impl Section { + pub fn new(file_path: &Path, meta: FrontMatter) -> Section { + Section { + file_path: file_path.to_path_buf(), + parent_path: file_path.parent().unwrap().to_path_buf(), + components: vec![], + url: "".to_string(), + permalink: "".to_string(), + meta: meta, + pages: vec![], + subsections: vec![], + } + } + + pub fn parse(file_path: &Path, content: &str, config: &Config) -> Result
{ + let (meta, _) = split_content(file_path, content)?; + let mut section = Section::new(file_path, meta); + section.components = find_content_components(§ion.file_path); + section.url = section.components.join("/"); + section.permalink = section.components.join("/"); + + section.permalink = if config.base_url.ends_with('/') { + format!("{}{}", config.base_url, section.url) + } else { + format!("{}/{}", config.base_url, section.url) + }; + + Ok(section) + } + + /// Read and parse a .md file into a Page struct + pub fn from_file>(path: P, config: &Config) -> Result
{ + let path = path.as_ref(); + let content = read_file(path)?; + + Section::parse(path, &content, config) + } + + /// Renders the page using the default layout, unless specified in front-matter + pub fn render_html(&self, tera: &Tera, config: &Config) -> Result { + let tpl_name = match self.meta.template { + Some(ref l) => l.to_string(), + None => "section.html".to_string() + }; + + // TODO: create a helper to create context to ensure all contexts + // have the same names + let mut context = Context::new(); + context.add("config", config); + context.add("section", self); + + tera.render(&tpl_name, &context) + .chain_err(|| format!("Failed to render section '{}'", self.file_path.display())) + } +} + +impl ser::Serialize for Section { + fn serialize(&self, serializer: S) -> StdResult where S: ser::Serializer { + let mut state = serializer.serialize_struct("section", 6)?; + state.serialize_field("title", &self.meta.title)?; + state.serialize_field("description", &self.meta.description)?; + state.serialize_field("url", &format!("/{}", self.url))?; + state.serialize_field("permalink", &self.permalink)?; + state.serialize_field("pages", &self.pages)?; + state.serialize_field("subsections", &self.subsections)?; + state.end() + } +} diff --git a/src/site.rs b/src/site.rs index 3bd3e622..2e9bd178 100644 --- a/src/site.rs +++ b/src/site.rs @@ -1,7 +1,7 @@ -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use std::iter::FromIterator; use std::fs::{remove_dir_all, copy, remove_file}; -use std::path::Path; +use std::path::{Path, PathBuf}; use glob::glob; use tera::{Tera, Context}; @@ -10,8 +10,9 @@ use walkdir::WalkDir; use errors::{Result, ResultExt}; use config::{Config, get_config}; -use page::Page; +use page::{Page}; use utils::{create_file, create_directory}; +use section::{Section}; lazy_static! { @@ -50,53 +51,98 @@ impl ListItem { } } - #[derive(Debug)] pub struct Site { - config: Config, - pages: HashMap, - sections: HashMap>, - templates: Tera, + pub base_path: PathBuf, + pub config: Config, + pub pages: HashMap, + pub sections: BTreeMap, + pub templates: Tera, live_reload: bool, + output_path: PathBuf, } impl Site { - pub fn new(livereload: bool) -> Result { - let mut tera = Tera::new("templates/**/*") - .chain_err(|| "Error parsing templates")?; + /// Parse a site at the given path. Defaults to the current dir + /// Passing in a path is only used in tests + pub fn new>(path: P) -> Result { + let path = path.as_ref(); + + let tpl_glob = format!("{}/{}", path.to_string_lossy().replace("\\", "/"), "templates/**/*"); + let mut tera = Tera::new(&tpl_glob).chain_err(|| "Error parsing templates")?; tera.extend(&GUTENBERG_TERA)?; let mut site = Site { - config: get_config(), + base_path: path.to_path_buf(), + config: get_config(&path), pages: HashMap::new(), - sections: HashMap::new(), + sections: BTreeMap::new(), templates: tera, - live_reload: livereload, + live_reload: false, + output_path: PathBuf::from("public"), }; site.parse_site()?; Ok(site) } + /// What the function name says + pub fn enable_live_reload(&mut self) { + self.live_reload = true; + } + + /// Used by tests to change the output path to a tmp dir + #[doc(hidden)] + pub fn set_output_path>(&mut self, path: P) { + self.output_path = path.as_ref().to_path_buf(); + } + /// Reads all .md files in the `content` directory and create pages /// out of them fn parse_site(&mut self) -> Result<()> { - // First step: do all the articles and group article by sections - // hardcoded pattern so can't error - for entry in glob("content/**/*.md").unwrap().filter_map(|e| e.ok()) { - let page = Page::from_file(&entry.as_path(), &self.config)?; + let path = self.base_path.to_string_lossy().replace("\\", "/"); + let content_glob = format!("{}/{}", path, "content/**/*.md"); - for section in &page.sections { - self.sections.entry(section.clone()).or_insert_with(|| vec![]).push(page.slug.clone()); + // parent_dir -> Section + let mut sections = BTreeMap::new(); + + // Glob is giving us the result order so _index will show up first + // for each directory + for entry in glob(&content_glob).unwrap().filter_map(|e| e.ok()) { + let path = entry.as_path(); + + if path.file_name().unwrap() == "_index.md" { + let section = Section::from_file(&path, &self.config)?; + sections.insert(section.parent_path.clone(), section); + } else { + let page = Page::from_file(&path, &self.config)?; + if sections.contains_key(&page.parent_path) { + sections.get_mut(&page.parent_path).unwrap().pages.push(page.clone()); + } + self.pages.insert(page.file_path.clone(), page); } - - self.pages.insert(page.slug.clone(), page); } + // Find out the direct subsections of each subsection if there are some + let mut grandparent_paths = HashMap::new(); + for section in sections.values() { + let grand_parent = section.parent_path.parent().unwrap().to_path_buf(); + grandparent_paths.entry(grand_parent).or_insert_with(|| vec![]).push(section.clone()); + } + + for (parent_path, section) in sections.iter_mut() { + match grandparent_paths.get(parent_path) { + Some(paths) => section.subsections.extend(paths.clone()), + None => continue, + }; + } + + self.sections = sections; + Ok(()) } - // Inject live reload script tag if in live reload mode + /// Inject live reload script tag if in live reload mode fn inject_livereload(&self, html: String) -> String { if self.live_reload { return html.replace( @@ -108,11 +154,10 @@ impl Site { html } - /// Copy the content of the `static` folder into the `public` folder /// - /// TODO: only copy one file if possible because that would be a waster - /// to do re-copy the whole thing + /// TODO: only copy one file if possible because that would be a waste + /// to do re-copy the whole thing. Benchmark first to see if it's a big difference pub fn copy_static_directory(&self) -> Result<()> { let from = Path::new("static"); let target = Path::new("public"); @@ -160,7 +205,7 @@ impl Site { } pub fn build_pages(&self) -> Result<()> { - let public = Path::new("public"); + let public = self.output_path.clone(); if !public.exists() { create_directory(&public)?; } @@ -168,40 +213,39 @@ impl Site { let mut pages = vec![]; let mut category_pages: HashMap> = HashMap::new(); let mut tag_pages: HashMap> = HashMap::new(); + // First we render the pages themselves for page in self.pages.values() { // Copy the nesting of the content directory if we have sections for that page let mut current_path = public.to_path_buf(); - // This loop happens when the page doesn't have a set URL - for section in &page.sections { - current_path.push(section); + for component in page.url.split("/") { + current_path.push(component); if !current_path.exists() { create_directory(¤t_path)?; } } - // if we have a url already set, use that as base - if let Some(ref url) = page.meta.url { - current_path.push(url); - } - // Make sure the folder exists create_directory(¤t_path)?; + // Finally, create a index.html file there with the page rendered let output = page.render_html(&self.templates, &self.config)?; create_file(current_path.join("index.html"), &self.inject_livereload(output))?; + // Copy any asset we found previously into the same directory as the index.html for asset in &page.assets { let asset_path = asset.as_path(); copy(&asset_path, ¤t_path.join(asset_path.file_name().unwrap()))?; } + pages.push(page); if let Some(ref category) = page.meta.category { category_pages.entry(category.to_string()).or_insert_with(|| vec![]).push(page); } + if let Some(ref tags) = page.meta.tags { for tag in tags { tag_pages.entry(tag.to_string()).or_insert_with(|| vec![]).push(page); @@ -232,6 +276,7 @@ impl Site { if self.config.generate_rss.unwrap() { self.render_rss_feed()?; } + self.render_sections()?; self.copy_static_directory() } @@ -247,7 +292,7 @@ impl Site { ("tags", "tags.html", "tag.html", "tag") }; - let public = Path::new("public"); + let public = self.output_path.clone(); let mut output_path = public.to_path_buf(); output_path.push(name); create_directory(&output_path)?; @@ -292,8 +337,7 @@ impl Site { context.add("pages", &self.pages.values().collect::>()); let sitemap = self.templates.render("sitemap.xml", &context)?; - let public = Path::new("public"); - create_file(public.join("sitemap.xml"), &sitemap)?; + create_file(self.output_path.join("sitemap.xml"), &sitemap)?; Ok(()) } @@ -309,6 +353,7 @@ impl Site { if pages.is_empty() { return Ok(()); } + pages.sort_by(|a, b| a.partial_cmp(b).unwrap()); context.add("pages", &pages); context.add("last_build_date", &pages[0].meta.date); @@ -323,8 +368,27 @@ impl Site { let sitemap = self.templates.render("rss.xml", &context)?; - let public = Path::new("public"); - create_file(public.join("rss.xml"), &sitemap)?; + create_file(self.output_path.join("rss.xml"), &sitemap)?; + + Ok(()) + } + + fn render_sections(&self) -> Result<()> { + let public = self.output_path.clone(); + + for section in self.sections.values() { + let mut output_path = public.to_path_buf(); + for component in §ion.components { + output_path.push(component); + + if !output_path.exists() { + create_directory(&output_path)?; + } + } + + let output = section.render_html(&self.templates, &self.config)?; + create_file(output_path.join("index.html"), &self.inject_livereload(output))?; + } Ok(()) } diff --git a/src/utils.rs b/src/utils.rs index d0280723..3068112d 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -21,3 +21,52 @@ pub fn create_directory>(path: P) -> Result<()> { } Ok(()) } + + +/// Return the content of a file, with error handling added +pub fn read_file>(path: P) -> Result { + let path = path.as_ref(); + + let mut content = String::new(); + File::open(path) + .chain_err(|| format!("Failed to open '{:?}'", path.display()))? + .read_to_string(&mut content)?; + + Ok(content) +} + + +/// Takes a full path to a .md and returns only the components after the `content` directory +/// Will not return the filename as last component +pub fn find_content_components>(path: P) -> Vec { + let path = path.as_ref(); + let mut is_in_content = false; + let mut components = vec![]; + + for section in path.parent().unwrap().components() { + let component = section.as_ref().to_string_lossy(); + + if is_in_content { + components.push(component.to_string()); + continue; + } + + if component == "content" { + is_in_content = true; + } + } + + components +} + + +#[cfg(test)] +mod tests { + use super::{find_content_components}; + + #[test] + fn test_find_content_components() { + let res = find_content_components("/home/vincent/code/site/content/posts/tutorials/python.md"); + assert_eq!(res, ["posts".to_string(), "tutorials".to_string()]); + } +} diff --git a/test_site/config.toml b/test_site/config.toml new file mode 100644 index 00000000..ba0629c8 --- /dev/null +++ b/test_site/config.toml @@ -0,0 +1,7 @@ +title = "My site" +base_url = "https://replace-this-with-your-url.com" +highlight_code = true + + +[extra.author] +name = "Vincent Prouillet" diff --git a/test_site/content/posts/_index.md b/test_site/content/posts/_index.md new file mode 100644 index 00000000..93ed2520 --- /dev/null +++ b/test_site/content/posts/_index.md @@ -0,0 +1,4 @@ ++++ +title = "Posts" +description = "" ++++ diff --git a/test_site/content/posts/fixed-slug.md b/test_site/content/posts/fixed-slug.md new file mode 100644 index 00000000..3eff97db --- /dev/null +++ b/test_site/content/posts/fixed-slug.md @@ -0,0 +1,7 @@ ++++ +title = "Fixed slug" +description = "" +slug = "something-else" ++++ + +A simple page with a slug defined diff --git a/test_site/content/posts/fixed-url.md b/test_site/content/posts/fixed-url.md new file mode 100644 index 00000000..e51171ea --- /dev/null +++ b/test_site/content/posts/fixed-url.md @@ -0,0 +1,7 @@ ++++ +title = "Fixed URL" +description = "" +url = "a-fixed-url" ++++ + +A simple page with fixed url diff --git a/test_site/content/posts/no-section/simple.md b/test_site/content/posts/no-section/simple.md new file mode 100644 index 00000000..43bfcc60 --- /dev/null +++ b/test_site/content/posts/no-section/simple.md @@ -0,0 +1,6 @@ ++++ +title = "Simple" +description = "" ++++ + +A simple page diff --git a/test_site/content/posts/python.md b/test_site/content/posts/python.md new file mode 100644 index 00000000..ae5f25f2 --- /dev/null +++ b/test_site/content/posts/python.md @@ -0,0 +1,6 @@ ++++ +title = "Python in posts" +description = "" ++++ + +Same filename but different path diff --git a/test_site/content/posts/simple.md b/test_site/content/posts/simple.md new file mode 100644 index 00000000..43bfcc60 --- /dev/null +++ b/test_site/content/posts/simple.md @@ -0,0 +1,6 @@ ++++ +title = "Simple" +description = "" ++++ + +A simple page diff --git a/test_site/content/posts/tutorials/_index.md b/test_site/content/posts/tutorials/_index.md new file mode 100644 index 00000000..049aa01f --- /dev/null +++ b/test_site/content/posts/tutorials/_index.md @@ -0,0 +1,4 @@ ++++ +title = "Tutorials" +description = "" ++++ diff --git a/test_site/content/posts/tutorials/devops/_index.md b/test_site/content/posts/tutorials/devops/_index.md new file mode 100644 index 00000000..b4f4ab5d --- /dev/null +++ b/test_site/content/posts/tutorials/devops/_index.md @@ -0,0 +1,4 @@ ++++ +title = "DevOps" +description = "" ++++ diff --git a/test_site/content/posts/tutorials/devops/docker.md b/test_site/content/posts/tutorials/devops/docker.md new file mode 100644 index 00000000..eb0073a9 --- /dev/null +++ b/test_site/content/posts/tutorials/devops/docker.md @@ -0,0 +1,6 @@ ++++ +title = "Docker" +description = "" ++++ + +A simple page diff --git a/test_site/content/posts/tutorials/devops/nix.md b/test_site/content/posts/tutorials/devops/nix.md new file mode 100644 index 00000000..f12ccb62 --- /dev/null +++ b/test_site/content/posts/tutorials/devops/nix.md @@ -0,0 +1,6 @@ ++++ +title = "Nix" +description = "" ++++ + +A simple page diff --git a/test_site/content/posts/tutorials/programming/_index.md b/test_site/content/posts/tutorials/programming/_index.md new file mode 100644 index 00000000..d46aac2f --- /dev/null +++ b/test_site/content/posts/tutorials/programming/_index.md @@ -0,0 +1,4 @@ ++++ +title = "Programming" +description = "" ++++ diff --git a/test_site/content/posts/tutorials/programming/python.md b/test_site/content/posts/tutorials/programming/python.md new file mode 100644 index 00000000..8a0a68d1 --- /dev/null +++ b/test_site/content/posts/tutorials/programming/python.md @@ -0,0 +1,6 @@ ++++ +title = "Python tutorial" +description = "" ++++ + +A simple page diff --git a/test_site/content/posts/tutorials/programming/rust.md b/test_site/content/posts/tutorials/programming/rust.md new file mode 100644 index 00000000..b2173237 --- /dev/null +++ b/test_site/content/posts/tutorials/programming/rust.md @@ -0,0 +1,6 @@ ++++ +title = "Rust" +description = "" ++++ + +A simple page diff --git a/test_site/content/posts/with-assets/index.md b/test_site/content/posts/with-assets/index.md new file mode 100644 index 00000000..2000dfd0 --- /dev/null +++ b/test_site/content/posts/with-assets/index.md @@ -0,0 +1,7 @@ ++++ +title = "With assets" +description = "hey there" +slug = "with-assets" ++++ + +Hello world diff --git a/test_site/content/posts/with-assets/with.js b/test_site/content/posts/with-assets/with.js new file mode 100644 index 00000000..e69de29b diff --git a/test_site/static/scripts/hello.js b/test_site/static/scripts/hello.js new file mode 100644 index 00000000..e69de29b diff --git a/test_site/static/site.css b/test_site/static/site.css new file mode 100644 index 00000000..b05faf8e --- /dev/null +++ b/test_site/static/site.css @@ -0,0 +1,3 @@ +body { + color: red; +} diff --git a/test_site/templates/categories.html b/test_site/templates/categories.html new file mode 100644 index 00000000..9ac110eb --- /dev/null +++ b/test_site/templates/categories.html @@ -0,0 +1,3 @@ +{% for category in categories %} + {{ category.name }} {{ category.slug }} {{ category.count }} +{% endfor %} diff --git a/test_site/templates/category.html b/test_site/templates/category.html new file mode 100644 index 00000000..70079cbc --- /dev/null +++ b/test_site/templates/category.html @@ -0,0 +1,8 @@ +Category: {{ category }} + + +{% for page in pages %} + +{% endfor %} diff --git a/test_site/templates/index.html b/test_site/templates/index.html new file mode 100644 index 00000000..417d614f --- /dev/null +++ b/test_site/templates/index.html @@ -0,0 +1,27 @@ + + + + + + + + + + + {{ config.title }} + + + +
+ {% block content %} +
+ {% for page in pages %} + + {% endfor %} +
+ {% endblock content %} +
+ + diff --git a/test_site/templates/page.html b/test_site/templates/page.html new file mode 100644 index 00000000..ff68f1f8 --- /dev/null +++ b/test_site/templates/page.html @@ -0,0 +1,5 @@ +{% extends "index.html" %} + +{% block content %} + {{ page.content | safe }} +{% endblock content %} diff --git a/test_site/templates/section.html b/test_site/templates/section.html new file mode 100644 index 00000000..cf4b1d17 --- /dev/null +++ b/test_site/templates/section.html @@ -0,0 +1,10 @@ +{% extends "index.html" %} + +{% block content %} + {% for page in section.pages %} + {{page.title}} + {% endfor %} + {% for subsection in section.subsections %} + {{subsection.title}} + {% endfor %} +{% endblock content %} diff --git a/test_site/templates/tag.html b/test_site/templates/tag.html new file mode 100644 index 00000000..62f6c1a7 --- /dev/null +++ b/test_site/templates/tag.html @@ -0,0 +1,7 @@ +Tag: {{ tag }} + +{% for page in pages %} + +{% endfor %} diff --git a/test_site/templates/tags.html b/test_site/templates/tags.html new file mode 100644 index 00000000..8a0b2543 --- /dev/null +++ b/test_site/templates/tags.html @@ -0,0 +1,3 @@ +{% for tag in tags %} + {{ tag.name }} {{ tag.slug }} {{ tag.count }} +{% endfor %} diff --git a/tests/front_matter.rs b/tests/front_matter.rs new file mode 100644 index 00000000..14291c64 --- /dev/null +++ b/tests/front_matter.rs @@ -0,0 +1,197 @@ +extern crate gutenberg; +extern crate tera; + +use std::path::Path; + +use gutenberg::{FrontMatter, split_content}; +use tera::to_value; + + +#[test] +fn test_can_parse_a_valid_front_matter() { + let content = r#" +title = "Hello" +description = "hey there""#; + let res = FrontMatter::parse(content); + println!("{:?}", res); + assert!(res.is_ok()); + let res = res.unwrap(); + assert_eq!(res.title, "Hello".to_string()); + assert_eq!(res.description, "hey there".to_string()); +} + +#[test] +fn test_can_parse_tags() { + let content = r#" +title = "Hello" +description = "hey there" +slug = "hello-world" +tags = ["rust", "html"]"#; + let res = FrontMatter::parse(content); + assert!(res.is_ok()); + let res = res.unwrap(); + + assert_eq!(res.title, "Hello".to_string()); + assert_eq!(res.slug.unwrap(), "hello-world".to_string()); + assert_eq!(res.tags.unwrap(), ["rust".to_string(), "html".to_string()]); +} + +#[test] +fn test_can_parse_extra_attributes_in_frontmatter() { + let content = r#" +title = "Hello" +description = "hey there" +slug = "hello-world" + +[extra] +language = "en" +authors = ["Bob", "Alice"]"#; + let res = FrontMatter::parse(content); + assert!(res.is_ok()); + let res = res.unwrap(); + + assert_eq!(res.title, "Hello".to_string()); + assert_eq!(res.slug.unwrap(), "hello-world".to_string()); + let extra = res.extra.unwrap(); + assert_eq!(extra.get("language").unwrap(), &to_value("en").unwrap()); + assert_eq!( + extra.get("authors").unwrap(), + &to_value(["Bob".to_string(), "Alice".to_string()]).unwrap() + ); +} + +#[test] +fn test_is_ok_with_url_instead_of_slug() { + let content = r#" +title = "Hello" +description = "hey there" +url = "hello-world""#; + let res = FrontMatter::parse(content); + assert!(res.is_ok()); + let res = res.unwrap(); + assert!(res.slug.is_none()); + assert_eq!(res.url.unwrap(), "hello-world".to_string()); +} + +#[test] +fn test_errors_with_empty_front_matter() { + let content = r#" "#; + let res = FrontMatter::parse(content); + assert!(res.is_err()); +} + +#[test] +fn test_errors_with_invalid_front_matter() { + let content = r#"title = 1\n"#; + let res = FrontMatter::parse(content); + assert!(res.is_err()); +} + +#[test] +fn test_errors_with_missing_required_value_front_matter() { + let content = r#"title = """#; + let res = FrontMatter::parse(content); + assert!(res.is_err()); +} + +#[test] +fn test_errors_on_non_string_tag() { + let content = r#" +title = "Hello" +description = "hey there" +slug = "hello-world" +tags = ["rust", 1]"#; + let res = FrontMatter::parse(content); + assert!(res.is_err()); +} + +#[test] +fn test_errors_on_present_but_empty_slug() { + let content = r#" +title = "Hello" +description = "hey there" +slug = """#; + let res = FrontMatter::parse(content); + assert!(res.is_err()); +} + +#[test] +fn test_errors_on_present_but_empty_url() { + let content = r#" +title = "Hello" +description = "hey there" +url = """#; + let res = FrontMatter::parse(content); + assert!(res.is_err()); +} + +#[test] +fn test_parse_date_yyyy_mm_dd() { + let content = r#" +title = "Hello" +description = "hey there" +date = "2016-10-10""#; + let res = FrontMatter::parse(content).unwrap(); + assert!(res.parse_date().is_some()); +} + +#[test] +fn test_parse_date_rfc3339() { + let content = r#" +title = "Hello" +description = "hey there" +date = "2002-10-02T15:00:00Z""#; + let res = FrontMatter::parse(content).unwrap(); + assert!(res.parse_date().is_some()); +} + +#[test] +fn test_cant_parse_random_date_format() { + let content = r#" +title = "Hello" +description = "hey there" +date = "2002/10/12""#; + let res = FrontMatter::parse(content).unwrap(); + assert!(res.parse_date().is_none()); +} + + +#[test] +fn test_can_split_content_valid() { + let content = r#" ++++ +title = "Title" +description = "hey there" +date = "2002/10/12" ++++ +Hello +"#; + let (front_matter, content) = split_content(Path::new(""), content).unwrap(); + assert_eq!(content, "Hello\n"); + assert_eq!(front_matter.title, "Title"); +} + +#[test] +fn test_can_split_content_with_only_frontmatter_valid() { + let content = r#" ++++ +title = "Title" +description = "hey there" +date = "2002/10/12" ++++"#; + let (front_matter, content) = split_content(Path::new(""), content).unwrap(); + assert_eq!(content, ""); + assert_eq!(front_matter.title, "Title"); +} + +#[test] +fn test_error_if_cannot_locate_frontmatter() { + let content = r#" ++++ +title = "Title" +description = "hey there" +date = "2002/10/12" +"#; + let res = split_content(Path::new(""), content); + assert!(res.is_err()); +} diff --git a/tests/page.rs b/tests/page.rs new file mode 100644 index 00000000..fc8dacc3 --- /dev/null +++ b/tests/page.rs @@ -0,0 +1,249 @@ +extern crate gutenberg; +extern crate tempdir; + +use tempdir::TempDir; + +use std::fs::File; +use std::path::Path; + +use gutenberg::{Page, Config}; + + +#[test] +fn test_can_parse_a_valid_page() { + let content = r#" ++++ +title = "Hello" +description = "hey there" +slug = "hello-world" ++++ +Hello world"#; + let res = Page::parse(Path::new("post.md"), content, &Config::default()); + assert!(res.is_ok()); + let page = res.unwrap(); + + assert_eq!(page.meta.title, "Hello".to_string()); + assert_eq!(page.meta.slug.unwrap(), "hello-world".to_string()); + assert_eq!(page.raw_content, "Hello world".to_string()); + assert_eq!(page.content, "

Hello world

\n".to_string()); +} + +#[test] +fn test_can_find_one_parent_directory() { + let content = r#" ++++ +title = "Hello" +description = "hey there" +slug = "hello-world" ++++ +Hello world"#; + let res = Page::parse(Path::new("content/posts/intro.md"), content, &Config::default()); + assert!(res.is_ok()); + let page = res.unwrap(); + assert_eq!(page.components, vec!["posts".to_string()]); +} + +#[test] +fn test_can_find_multiple_parent_directories() { + let content = r#" ++++ +title = "Hello" +description = "hey there" +slug = "hello-world" ++++ +Hello world"#; + let res = Page::parse(Path::new("content/posts/intro/start.md"), content, &Config::default()); + assert!(res.is_ok()); + let page = res.unwrap(); + assert_eq!(page.components, vec!["posts".to_string(), "intro".to_string()]); +} + +#[test] +fn test_can_make_url_from_sections_and_slug() { + let content = r#" ++++ +title = "Hello" +description = "hey there" +slug = "hello-world" ++++ +Hello world"#; + let mut conf = Config::default(); + conf.base_url = "http://hello.com/".to_string(); + let res = Page::parse(Path::new("content/posts/intro/start.md"), content, &conf); + assert!(res.is_ok()); + let page = res.unwrap(); + assert_eq!(page.url, "posts/intro/hello-world"); + assert_eq!(page.permalink, "http://hello.com/posts/intro/hello-world"); +} + +#[test] +fn test_can_make_permalink_with_non_trailing_slash_base_url() { + let content = r#" ++++ +title = "Hello" +description = "hey there" +slug = "hello-world" ++++ +Hello world"#; + let mut conf = Config::default(); + conf.base_url = "http://hello.com".to_string(); + let res = Page::parse(Path::new("content/posts/intro/hello-world.md"), content, &conf); + assert!(res.is_ok()); + let page = res.unwrap(); + assert_eq!(page.url, "posts/intro/hello-world"); + assert_eq!(page.permalink, format!("{}{}", conf.base_url, "/posts/intro/hello-world")); +} + +#[test] +fn test_can_make_url_from_slug_only() { + let content = r#" ++++ +title = "Hello" +description = "hey there" +slug = "hello-world" ++++ +Hello world"#; + let res = Page::parse(Path::new("start.md"), content, &Config::default()); + assert!(res.is_ok()); + let page = res.unwrap(); + assert_eq!(page.url, "hello-world"); + assert_eq!(page.permalink, format!("{}{}", Config::default().base_url, "hello-world")); +} + +#[test] +fn test_errors_on_invalid_front_matter_format() { + let content = r#" +title = "Hello" +description = "hey there" +slug = "hello-world" ++++ +Hello world"#; + let res = Page::parse(Path::new("start.md"), content, &Config::default()); + assert!(res.is_err()); +} + +#[test] +fn test_can_make_slug_from_non_slug_filename() { + let content = r#" ++++ +title = "Hello" +description = "hey there" ++++ +Hello world"#; + let res = Page::parse(Path::new("file with space.md"), content, &Config::default()); + assert!(res.is_ok()); + let page = res.unwrap(); + assert_eq!(page.slug, "file-with-space"); + assert_eq!(page.permalink, format!("{}{}", Config::default().base_url, "file-with-space")); +} + +#[test] +fn test_trim_slug_if_needed() { + let content = r#" ++++ +title = "Hello" +description = "hey there" ++++ +Hello world"#; + let res = Page::parse(Path::new(" file with space.md"), content, &Config::default()); + assert!(res.is_ok()); + let page = res.unwrap(); + assert_eq!(page.slug, "file-with-space"); + assert_eq!(page.permalink, format!("{}{}", Config::default().base_url, "file-with-space")); +} + +#[test] +fn test_reading_analytics_short() { + let content = r#" ++++ +title = "Hello" +description = "hey there" ++++ +Hello world"#; + let res = Page::parse(Path::new("hello.md"), content, &Config::default()); + assert!(res.is_ok()); + let page = res.unwrap(); + let (word_count, reading_time) = page.get_reading_analytics(); + assert_eq!(word_count, 2); + assert_eq!(reading_time, 0); +} + +#[test] +fn test_reading_analytics_long() { + let mut content = r#" ++++ +title = "Hello" +description = "hey there" ++++ +Hello world"#.to_string(); + for _ in 0..1000 { + content.push_str(" Hello world"); + } + let res = Page::parse(Path::new("hello.md"), &content, &Config::default()); + assert!(res.is_ok()); + let page = res.unwrap(); + let (word_count, reading_time) = page.get_reading_analytics(); + assert_eq!(word_count, 2002); + assert_eq!(reading_time, 10); +} + +#[test] +fn test_automatic_summary_is_empty_string() { + let content = r#" ++++ +title = "Hello" +description = "hey there" ++++ +Hello world"#.to_string(); + let res = Page::parse(Path::new("hello.md"), &content, &Config::default()); + assert!(res.is_ok()); + let page = res.unwrap(); + assert_eq!(page.summary, ""); +} + +#[test] +fn test_can_specify_summary() { + let content = r#" ++++ +title = "Hello" +description = "hey there" ++++ +Hello world + +"#.to_string(); + let res = Page::parse(Path::new("hello.md"), &content, &Config::default()); + assert!(res.is_ok()); + let page = res.unwrap(); + assert_eq!(page.summary, "

Hello world

\n"); +} + +#[test] +fn test_can_auto_detect_when_highlighting_needed() { + let content = r#" ++++ +title = "Hello" +description = "hey there" ++++ +``` +Hey there +``` +"#.to_string(); + let mut config = Config::default(); + config.highlight_code = Some(true); + let res = Page::parse(Path::new("hello.md"), &content, &config); + assert!(res.is_ok()); + let page = res.unwrap(); + assert!(page.content.starts_with(" { + { + let mut path = $root.clone(); + for component in $path.split("/") { + path = path.join(component); + } + Path::new(&path).exists() + } + } +} + +macro_rules! file_contains { + ($root: expr, $path: expr, $text: expr) => { + { + let mut path = $root.clone(); + for component in $path.split("/") { + path = path.join(component); + } + let mut file = File::open(&path).unwrap(); + let mut s = String::new(); + file.read_to_string(&mut s).unwrap(); + + s.contains($text) + } + } +} + +#[test] +fn test_can_build_site_without_live_reload() { + let mut path = env::current_dir().unwrap().to_path_buf(); + path.push("test_site"); + let mut site = Site::new(&path).unwrap(); + let tmp_dir = TempDir::new("example").expect("create temp dir"); + let public = &tmp_dir.path().join("public"); + site.set_output_path(&public); + site.build().unwrap(); + + assert!(Path::new(&public).exists()); + + assert!(file_exists!(public, "index.html")); + assert!(file_exists!(public, "sitemap.xml")); + assert!(file_exists!(public, "a-fixed-url/index.html")); + + assert!(file_exists!(public, "posts/python/index.html")); + assert!(file_exists!(public, "posts/tutorials/devops/nix/index.html")); + assert!(file_exists!(public, "posts/with-assets/index.html")); + + // Sections + assert!(file_exists!(public, "posts/index.html")); + assert!(file_exists!(public, "posts/tutorials/index.html")); + assert!(file_exists!(public, "posts/tutorials/devops/index.html")); + assert!(file_exists!(public, "posts/tutorials/programming/index.html")); + // TODO: add assertion for syntax highlighting + + // No tags or categories + assert_eq!(file_exists!(public, "categories/index.html"), false); + assert_eq!(file_exists!(public, "tags/index.html"), false); + + // no live reload code + assert_eq!(file_contains!(public, "index.html", "/livereload.js?port=1112&mindelay=10"), false); +} + +#[test] +fn test_can_build_site_with_live_reload() { + let mut path = env::current_dir().unwrap().to_path_buf(); + path.push("test_site"); + let mut site = Site::new(&path).unwrap(); + let tmp_dir = TempDir::new("example").expect("create temp dir"); + let public = &tmp_dir.path().join("public"); + site.set_output_path(&public); + site.enable_live_reload(); + site.build().unwrap(); + + assert!(Path::new(&public).exists()); + + assert!(file_exists!(public, "index.html")); + assert!(file_exists!(public, "sitemap.xml")); + assert!(file_exists!(public, "a-fixed-url/index.html")); + + assert!(file_exists!(public, "posts/python/index.html")); + assert!(file_exists!(public, "posts/tutorials/devops/nix/index.html")); + assert!(file_exists!(public, "posts/with-assets/index.html")); + + // Sections + assert!(file_exists!(public, "posts/index.html")); + assert!(file_exists!(public, "posts/tutorials/index.html")); + assert!(file_exists!(public, "posts/tutorials/devops/index.html")); + assert!(file_exists!(public, "posts/tutorials/programming/index.html")); + // TODO: add assertion for syntax highlighting + + // No tags or categories + assert_eq!(file_exists!(public, "categories/index.html"), false); + assert_eq!(file_exists!(public, "tags/index.html"), false); + + // no live reload code + assert!(file_contains!(public, "index.html", "/livereload.js?port=1112&mindelay=10")); +} + +#[test] +fn test_can_build_site_with_categories() { + let mut path = env::current_dir().unwrap().to_path_buf(); + path.push("test_site"); + let mut site = Site::new(&path).unwrap(); + let tmp_dir = TempDir::new("example").expect("create temp dir"); + site.set_output_path(&tmp_dir); + site.build().unwrap(); +} + +#[test] +fn test_can_build_site_with_tags() { + let mut path = env::current_dir().unwrap().to_path_buf(); + path.push("test_site"); + let mut site = Site::new(&path).unwrap(); + let tmp_dir = TempDir::new("example").expect("create temp dir"); + site.set_output_path(&tmp_dir); + site.build().unwrap(); +} From cd70aac0651cf49f7c1a6cc64067537f789fdeba Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Sun, 19 Mar 2017 19:29:43 +0900 Subject: [PATCH 22/26] Clippy run --- src/page.rs | 2 +- src/site.rs | 6 +++--- tests/site.rs | 14 +++++++------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/page.rs b/src/page.rs index 6b2e69c4..6ff93766 100644 --- a/src/page.rs +++ b/src/page.rs @@ -187,7 +187,7 @@ impl Page { let path = path.as_ref(); let content = read_file(path)?; let mut page = Page::parse(path, &content, config)?; - page.assets = find_related_assets(&path.parent().unwrap()); + page.assets = find_related_assets(path.parent().unwrap()); if !page.assets.is_empty() && page.file_name != "index" { bail!("Page `{}` has assets but is not named index.md", path.display()); diff --git a/src/site.rs b/src/site.rs index 2e9bd178..48793672 100644 --- a/src/site.rs +++ b/src/site.rs @@ -74,7 +74,7 @@ impl Site { let mut site = Site { base_path: path.to_path_buf(), - config: get_config(&path), + config: get_config(path), pages: HashMap::new(), sections: BTreeMap::new(), templates: tera, @@ -130,7 +130,7 @@ impl Site { grandparent_paths.entry(grand_parent).or_insert_with(|| vec![]).push(section.clone()); } - for (parent_path, section) in sections.iter_mut() { + for (parent_path, section) in &mut sections { match grandparent_paths.get(parent_path) { Some(paths) => section.subsections.extend(paths.clone()), None => continue, @@ -219,7 +219,7 @@ impl Site { // Copy the nesting of the content directory if we have sections for that page let mut current_path = public.to_path_buf(); - for component in page.url.split("/") { + for component in page.url.split('/') { current_path.push(component); if !current_path.exists() { diff --git a/tests/site.rs b/tests/site.rs index 7172c9fd..1c651621 100644 --- a/tests/site.rs +++ b/tests/site.rs @@ -23,34 +23,34 @@ fn test_can_parse_site() { let posts_path = path.join("content").join("posts"); // Make sure we remove all the pwd + content from the sections - let basic = site.pages.get(&posts_path.join("simple.md")).unwrap(); + let basic = &site.pages[&posts_path.join("simple.md")]; assert_eq!(basic.components, vec!["posts".to_string()]); // Make sure the page with a url doesn't have any sections - let url_post = site.pages.get(&posts_path.join("fixed-url.md")).unwrap(); + let url_post = &site.pages[&posts_path.join("fixed-url.md")]; assert!(url_post.components.is_empty()); // Make sure the article in a folder with only asset doesn't get counted as a section - let asset_folder_post = site.pages.get(&posts_path.join("with-assets").join("index.md")).unwrap(); + let asset_folder_post = &site.pages[&posts_path.join("with-assets").join("index.md")]; assert_eq!(asset_folder_post.components, vec!["posts".to_string()]); // That we have the right number of sections assert_eq!(site.sections.len(), 4); // And that the sections are correct - let posts_section = site.sections.get(&posts_path).unwrap(); + let posts_section = &site.sections[&posts_path]; assert_eq!(posts_section.subsections.len(), 1); assert_eq!(posts_section.pages.len(), 5); - let tutorials_section = site.sections.get(&posts_path.join("tutorials")).unwrap(); + let tutorials_section = &site.sections[&posts_path.join("tutorials")]; assert_eq!(tutorials_section.subsections.len(), 2); assert_eq!(tutorials_section.pages.len(), 0); - let devops_section = site.sections.get(&posts_path.join("tutorials").join("devops")).unwrap(); + let devops_section = &site.sections[&posts_path.join("tutorials").join("devops")]; assert_eq!(devops_section.subsections.len(), 0); assert_eq!(devops_section.pages.len(), 2); - let prog_section = site.sections.get(&posts_path.join("tutorials").join("programming")).unwrap(); + let prog_section = &site.sections[&posts_path.join("tutorials").join("programming")]; assert_eq!(prog_section.subsections.len(), 0); assert_eq!(prog_section.pages.len(), 2); } From d8995c156c2affac7800ac285a5e9702c027d2a0 Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Sun, 19 Mar 2017 19:40:31 +0900 Subject: [PATCH 23/26] Add sections to sitemap --- src/site.rs | 2 ++ src/templates/sitemap.xml | 7 ++++++- tests/site.rs | 5 ++++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/site.rs b/src/site.rs index 48793672..7679b513 100644 --- a/src/site.rs +++ b/src/site.rs @@ -335,6 +335,8 @@ impl Site { fn render_sitemap(&self) -> Result<()> { let mut context = Context::new(); context.add("pages", &self.pages.values().collect::>()); + context.add("sections", &self.sections.values().collect::>()); + // TODO: add categories and tags pages let sitemap = self.templates.render("sitemap.xml", &context)?; create_file(self.output_path.join("sitemap.xml"), &sitemap)?; diff --git a/src/templates/sitemap.xml b/src/templates/sitemap.xml index 0dadd5c4..24b7ccf9 100644 --- a/src/templates/sitemap.xml +++ b/src/templates/sitemap.xml @@ -1,10 +1,15 @@ {% for page in pages %} - {{ page.permalink }} + {{ page.permalink | safe }} {% if page.date %} {{ page.date }} {% endif %} {% endfor %} + {% for section in sections %} + + {{ section.permalink | safe }} + + {% endfor %} diff --git a/tests/site.rs b/tests/site.rs index 1c651621..f2d06fc2 100644 --- a/tests/site.rs +++ b/tests/site.rs @@ -78,7 +78,6 @@ macro_rules! file_contains { let mut file = File::open(&path).unwrap(); let mut s = String::new(); file.read_to_string(&mut s).unwrap(); - s.contains($text) } } @@ -117,6 +116,10 @@ fn test_can_build_site_without_live_reload() { // no live reload code assert_eq!(file_contains!(public, "index.html", "/livereload.js?port=1112&mindelay=10"), false); + + // Both pages and sections are in the sitemap + assert!(file_contains!(public, "sitemap.xml", "https://replace-this-with-your-url.com/posts/simple")); + assert!(file_contains!(public, "sitemap.xml", "https://replace-this-with-your-url.com/posts")); } #[test] From 6df68da84b31a5bf6bf8220ab5ab98479bd22ab3 Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Sun, 19 Mar 2017 20:20:24 +0900 Subject: [PATCH 24/26] Add tests for categories and tags --- src/config.rs | 5 ++++ tests/site.rs | 77 +++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 80 insertions(+), 2 deletions(-) diff --git a/src/config.rs b/src/config.rs index 76000f3b..f43b9081 100644 --- a/src/config.rs +++ b/src/config.rs @@ -8,6 +8,11 @@ use toml::{Value as Toml, self}; use errors::{Result, ResultExt}; +// TO ADD: +// highlight code theme +// generate_tags_pages +// generate_categories_pages + #[derive(Debug, PartialEq, Serialize, Deserialize)] pub struct Config { /// Title of the site diff --git a/tests/site.rs b/tests/site.rs index f2d06fc2..04f26753 100644 --- a/tests/site.rs +++ b/tests/site.rs @@ -163,9 +163,45 @@ fn test_can_build_site_with_categories() { let mut path = env::current_dir().unwrap().to_path_buf(); path.push("test_site"); let mut site = Site::new(&path).unwrap(); + + let mut i = 0; + for (_, page) in &mut site.pages { + page.meta.category = if i % 2 == 0 { + Some("A".to_string()) + } else { + Some("B".to_string()) + }; + i += 1; + } + let tmp_dir = TempDir::new("example").expect("create temp dir"); - site.set_output_path(&tmp_dir); + let public = &tmp_dir.path().join("public"); + site.set_output_path(&public); site.build().unwrap(); + + assert!(Path::new(&public).exists()); + + assert!(file_exists!(public, "index.html")); + assert!(file_exists!(public, "sitemap.xml")); + assert!(file_exists!(public, "a-fixed-url/index.html")); + + assert!(file_exists!(public, "posts/python/index.html")); + assert!(file_exists!(public, "posts/tutorials/devops/nix/index.html")); + assert!(file_exists!(public, "posts/with-assets/index.html")); + + // Sections + assert!(file_exists!(public, "posts/index.html")); + assert!(file_exists!(public, "posts/tutorials/index.html")); + assert!(file_exists!(public, "posts/tutorials/devops/index.html")); + assert!(file_exists!(public, "posts/tutorials/programming/index.html")); + // TODO: add assertion for syntax highlighting + + // Categories are there + assert!(file_exists!(public, "categories/index.html")); + assert!(file_exists!(public, "categories/a/index.html")); + assert!(file_exists!(public, "categories/b/index.html")); + // Tags aren't + assert_eq!(file_exists!(public, "tags/index.html"), false); } #[test] @@ -173,7 +209,44 @@ fn test_can_build_site_with_tags() { let mut path = env::current_dir().unwrap().to_path_buf(); path.push("test_site"); let mut site = Site::new(&path).unwrap(); + + let mut i = 0; + for (_, page) in &mut site.pages { + page.meta.tags = if i % 2 == 0 { + Some(vec!["tag1".to_string(), "tag2".to_string()]) + } else { + Some(vec!["tag with space".to_string()]) + }; + i += 1; + } + let tmp_dir = TempDir::new("example").expect("create temp dir"); - site.set_output_path(&tmp_dir); + let public = &tmp_dir.path().join("public"); + site.set_output_path(&public); site.build().unwrap(); + + assert!(Path::new(&public).exists()); + + assert!(file_exists!(public, "index.html")); + assert!(file_exists!(public, "sitemap.xml")); + assert!(file_exists!(public, "a-fixed-url/index.html")); + + assert!(file_exists!(public, "posts/python/index.html")); + assert!(file_exists!(public, "posts/tutorials/devops/nix/index.html")); + assert!(file_exists!(public, "posts/with-assets/index.html")); + + // Sections + assert!(file_exists!(public, "posts/index.html")); + assert!(file_exists!(public, "posts/tutorials/index.html")); + assert!(file_exists!(public, "posts/tutorials/devops/index.html")); + assert!(file_exists!(public, "posts/tutorials/programming/index.html")); + // TODO: add assertion for syntax highlighting + + // Tags are there + assert!(file_exists!(public, "tags/index.html")); + assert!(file_exists!(public, "tags/tag1/index.html")); + assert!(file_exists!(public, "tags/tag2/index.html")); + assert!(file_exists!(public, "tags/tag-with-space/index.html")); + // Categories aren't + assert_eq!(file_exists!(public, "categories/index.html"), false); } From fd10e77e3941214d84846b4bee86c74060ffbc43 Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Sun, 19 Mar 2017 20:34:02 +0900 Subject: [PATCH 25/26] Sort section pages after parsing --- src/site.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/site.rs b/src/site.rs index 7679b513..67bf5400 100644 --- a/src/site.rs +++ b/src/site.rs @@ -131,6 +131,8 @@ impl Site { } for (parent_path, section) in &mut sections { + section.pages.sort_by(|a, b| a.partial_cmp(b).unwrap()); + match grandparent_paths.get(parent_path) { Some(paths) => section.subsections.extend(paths.clone()), None => continue, From f63ec475ebbfef9bb3aa5af551bae03ce17d12d0 Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Mon, 20 Mar 2017 12:42:43 +0900 Subject: [PATCH 26/26] Add categories and tags to sitemap --- src/config.rs | 9 +++ src/page.rs | 6 +- src/section.rs | 6 +- src/site.rs | 120 +++++++++++++++++++++++++++----------- src/templates/sitemap.xml | 10 ++++ tests/site.rs | 20 ++++--- 6 files changed, 119 insertions(+), 52 deletions(-) diff --git a/src/config.rs b/src/config.rs index f43b9081..045f4fd0 100644 --- a/src/config.rs +++ b/src/config.rs @@ -66,6 +66,15 @@ impl Config { Config::parse(&content) } + + /// Makes a url, taking into account that the base url might have a trailing slash + pub fn make_permalink(&self, path: &str) -> String { + if self.base_url.ends_with('/') { + format!("{}{}", self.base_url, path) + } else { + format!("{}/{}", self.base_url, path) + } + } } impl Default for Config { diff --git a/src/page.rs b/src/page.rs index 6ff93766..be7d6edf 100644 --- a/src/page.rs +++ b/src/page.rs @@ -173,11 +173,7 @@ impl Page { } } - page.permalink = if config.base_url.ends_with('/') { - format!("{}{}", config.base_url, page.url) - } else { - format!("{}/{}", config.base_url, page.url) - }; + page.permalink = config.make_permalink(&page.url); Ok(page) } diff --git a/src/section.rs b/src/section.rs index 2c6cc68f..3013a447 100644 --- a/src/section.rs +++ b/src/section.rs @@ -52,11 +52,7 @@ impl Section { section.url = section.components.join("/"); section.permalink = section.components.join("/"); - section.permalink = if config.base_url.ends_with('/') { - format!("{}{}", config.base_url, section.url) - } else { - format!("{}/{}", config.base_url, section.url) - }; + section.permalink = config.make_permalink(§ion.url); Ok(section) } diff --git a/src/site.rs b/src/site.rs index 67bf5400..12709abf 100644 --- a/src/site.rs +++ b/src/site.rs @@ -60,6 +60,8 @@ pub struct Site { pub templates: Tera, live_reload: bool, output_path: PathBuf, + pub tags: HashMap>, + pub categories: HashMap>, } impl Site { @@ -80,8 +82,10 @@ impl Site { templates: tera, live_reload: false, output_path: PathBuf::from("public"), + tags: HashMap::new(), + categories: HashMap::new(), }; - site.parse_site()?; + site.parse()?; Ok(site) } @@ -99,7 +103,7 @@ impl Site { /// Reads all .md files in the `content` directory and create pages /// out of them - fn parse_site(&mut self) -> Result<()> { + pub fn parse(&mut self) -> Result<()> { let path = self.base_path.to_string_lossy().replace("\\", "/"); let content_glob = format!("{}/{}", path, "content/**/*.md"); @@ -122,7 +126,6 @@ impl Site { self.pages.insert(page.file_path.clone(), page); } } - // Find out the direct subsections of each subsection if there are some let mut grandparent_paths = HashMap::new(); for section in sections.values() { @@ -140,10 +143,32 @@ impl Site { } self.sections = sections; + self.parse_tags_and_categories(); Ok(()) } + /// Separated from `parse` for easier testing + pub fn parse_tags_and_categories(&mut self) { + for page in self.pages.values() { + if let Some(ref category) = page.meta.category { + self.categories + .entry(category.to_string()) + .or_insert_with(|| vec![]) + .push(page.file_path.clone()); + } + + if let Some(ref tags) = page.meta.tags { + for tag in tags { + self.tags + .entry(tag.to_string()) + .or_insert_with(|| vec![]) + .push(page.file_path.clone()); + } + } + } + } + /// Inject live reload script tag if in live reload mode fn inject_livereload(&self, html: String) -> String { if self.live_reload { @@ -197,7 +222,7 @@ impl Site { } pub fn rebuild_after_content_change(&mut self) -> Result<()> { - self.parse_site()?; + self.parse()?; self.build() } @@ -213,8 +238,6 @@ impl Site { } let mut pages = vec![]; - let mut category_pages: HashMap> = HashMap::new(); - let mut tag_pages: HashMap> = HashMap::new(); // First we render the pages themselves for page in self.pages.values() { @@ -243,21 +266,11 @@ impl Site { } pages.push(page); - - if let Some(ref category) = page.meta.category { - category_pages.entry(category.to_string()).or_insert_with(|| vec![]).push(page); - } - - if let Some(ref tags) = page.meta.tags { - for tag in tags { - tag_pages.entry(tag.to_string()).or_insert_with(|| vec![]).push(page); - } - } } // Outputting categories and pages - self.render_categories_and_tags(RenderList::Categories, &category_pages)?; - self.render_categories_and_tags(RenderList::Tags, &tag_pages)?; + self.render_categories_and_tags(RenderList::Categories)?; + self.render_categories_and_tags(RenderList::Tags)?; // And finally the index page let mut context = Context::new(); @@ -275,48 +288,63 @@ impl Site { self.clean()?; self.build_pages()?; self.render_sitemap()?; + if self.config.generate_rss.unwrap() { self.render_rss_feed()?; } + self.render_sections()?; self.copy_static_directory() } /// Render the /{categories, list} pages and each individual category/tag page - fn render_categories_and_tags(&self, kind: RenderList, container: &HashMap>) -> Result<()> { - if container.is_empty() { + /// They are the same thing fundamentally, a list of pages with something in common + fn render_categories_and_tags(&self, kind: RenderList) -> Result<()> { + let items = match kind { + RenderList::Categories => &self.categories, + RenderList::Tags => &self.tags, + }; + + if items.is_empty() { return Ok(()); } - let (name, list_tpl_name, single_tpl_name, var_name) = if kind == RenderList::Categories { - ("categories", "categories.html", "category.html", "category") + let (list_tpl_name, single_tpl_name, name, var_name) = if kind == RenderList::Categories { + ("categories.html", "category.html", "categories", "category") } else { - ("tags", "tags.html", "tag.html", "tag") + ("tags.html", "tag.html", "tags", "tag") }; + // Create the categories/tags directory first let public = self.output_path.clone(); let mut output_path = public.to_path_buf(); output_path.push(name); create_directory(&output_path)?; - // First we render the list of categories/tags page - let mut sorted_container = vec![]; - for (item, count) in Vec::from_iter(container).into_iter().map(|(a, b)| (a, b.len())) { - sorted_container.push(ListItem::new(item, count)); + // Then render the index page for that kind. + // We sort by number of page in that category/tag + let mut sorted_items = vec![]; + for (item, count) in Vec::from_iter(items).into_iter().map(|(a, b)| (a, b.len())) { + sorted_items.push(ListItem::new(&item, count)); } - sorted_container.sort_by(|a, b| b.count.cmp(&a.count)); - + sorted_items.sort_by(|a, b| b.count.cmp(&a.count)); let mut context = Context::new(); - context.add(name, &sorted_container); + context.add(name, &sorted_items); context.add("config", &self.config); - + // And render it immediately let list_output = self.templates.render(list_tpl_name, &context)?; create_file(output_path.join("index.html"), &self.inject_livereload(list_output))?; - // and then each individual item - for (item_name, mut pages) in container.clone() { - let mut context = Context::new(); + // Now, each individual item + for (item_name, pages_paths) in items.iter() { + let mut pages: Vec<&Page> = self.pages + .iter() + .filter(|&(path, _)| pages_paths.contains(&path)) + .map(|(_, page)| page) + .collect(); pages.sort_by(|a, b| a.partial_cmp(b).unwrap()); + + let mut context = Context::new(); let slug = slugify(&item_name); context.add(var_name, &item_name); context.add(&format!("{}_slug", var_name), &slug); @@ -338,7 +366,29 @@ impl Site { let mut context = Context::new(); context.add("pages", &self.pages.values().collect::>()); context.add("sections", &self.sections.values().collect::>()); - // TODO: add categories and tags pages + + let mut categories = vec![]; + if !self.categories.is_empty() { + categories.push(self.config.make_permalink("categories")); + for category in self.categories.keys() { + categories.push( + self.config.make_permalink(&format!("categories/{}", slugify(category))) + ); + } + } + context.add("categories", &categories); + + let mut tags = vec![]; + if !self.tags.is_empty() { + tags.push(self.config.make_permalink("tags")); + for tag in self.tags.keys() { + tags.push( + self.config.make_permalink(&format!("tags/{}", slugify(tag))) + ); + } + } + context.add("tags", &tags); + let sitemap = self.templates.render("sitemap.xml", &context)?; create_file(self.output_path.join("sitemap.xml"), &sitemap)?; diff --git a/src/templates/sitemap.xml b/src/templates/sitemap.xml index 24b7ccf9..d867752c 100644 --- a/src/templates/sitemap.xml +++ b/src/templates/sitemap.xml @@ -12,4 +12,14 @@ {{ section.permalink | safe }} {% endfor %} + {% for category in categories %} + + {{ category | safe }} + + {% endfor %} + {% for tag in tags %} + + {{ tag | safe }} + + {% endfor %} diff --git a/tests/site.rs b/tests/site.rs index 04f26753..3df6926e 100644 --- a/tests/site.rs +++ b/tests/site.rs @@ -164,22 +164,21 @@ fn test_can_build_site_with_categories() { path.push("test_site"); let mut site = Site::new(&path).unwrap(); - let mut i = 0; - for (_, page) in &mut site.pages { + for (i, page) in site.pages.values_mut().enumerate() { page.meta.category = if i % 2 == 0 { Some("A".to_string()) } else { Some("B".to_string()) }; - i += 1; } - + site.parse_tags_and_categories(); let tmp_dir = TempDir::new("example").expect("create temp dir"); let public = &tmp_dir.path().join("public"); site.set_output_path(&public); site.build().unwrap(); assert!(Path::new(&public).exists()); + assert_eq!(site.categories.len(), 2); assert!(file_exists!(public, "index.html")); assert!(file_exists!(public, "sitemap.xml")); @@ -202,6 +201,10 @@ fn test_can_build_site_with_categories() { assert!(file_exists!(public, "categories/b/index.html")); // Tags aren't assert_eq!(file_exists!(public, "tags/index.html"), false); + + // Categories are in the sitemap + assert!(file_contains!(public, "sitemap.xml", "https://replace-this-with-your-url.com/categories")); + assert!(file_contains!(public, "sitemap.xml", "https://replace-this-with-your-url.com/categories/a")); } #[test] @@ -210,15 +213,14 @@ fn test_can_build_site_with_tags() { path.push("test_site"); let mut site = Site::new(&path).unwrap(); - let mut i = 0; - for (_, page) in &mut site.pages { + for (i, page) in site.pages.values_mut().enumerate() { page.meta.tags = if i % 2 == 0 { Some(vec!["tag1".to_string(), "tag2".to_string()]) } else { Some(vec!["tag with space".to_string()]) }; - i += 1; } + site.parse_tags_and_categories(); let tmp_dir = TempDir::new("example").expect("create temp dir"); let public = &tmp_dir.path().join("public"); @@ -226,6 +228,7 @@ fn test_can_build_site_with_tags() { site.build().unwrap(); assert!(Path::new(&public).exists()); + assert_eq!(site.tags.len(), 3); assert!(file_exists!(public, "index.html")); assert!(file_exists!(public, "sitemap.xml")); @@ -249,4 +252,7 @@ fn test_can_build_site_with_tags() { assert!(file_exists!(public, "tags/tag-with-space/index.html")); // Categories aren't assert_eq!(file_exists!(public, "categories/index.html"), false); + // Tags are in the sitemap + assert!(file_contains!(public, "sitemap.xml", "https://replace-this-with-your-url.com/tags")); + assert!(file_contains!(public, "sitemap.xml", "https://replace-this-with-your-url.com/tags/tag-with-space")); }