Browse Source

Add image resizing support #225

index-subcmd
Vojtech Kral Vincent Prouillet 6 years ago
parent
commit
6662014e55
25 changed files with 738 additions and 54 deletions
  1. +178
    -0
      Cargo.lock
  2. +1
    -0
      Cargo.toml
  3. +1
    -0
      components/content/Cargo.toml
  4. +1
    -0
      components/content/src/lib.rs
  5. +25
    -1
      components/content/src/page.rs
  6. +4
    -1
      components/content/src/section.rs
  7. +1
    -0
      components/errors/Cargo.toml
  8. +2
    -0
      components/errors/src/lib.rs
  9. +15
    -0
      components/imageproc/Cargo.toml
  10. +327
    -0
      components/imageproc/src/lib.rs
  11. +5
    -1
      components/rendering/src/context.rs
  12. +1
    -1
      components/rendering/src/lib.rs
  13. +31
    -24
      components/rendering/src/shortcode.rs
  14. +1
    -0
      components/site/Cargo.toml
  15. +43
    -9
      components/site/src/lib.rs
  16. +1
    -0
      components/templates/Cargo.toml
  17. +54
    -14
      components/templates/src/global_fns.rs
  18. +1
    -0
      components/templates/src/lib.rs
  19. +26
    -0
      components/utils/src/fs.rs
  20. +8
    -0
      docs/content/documentation/content/image-resizing/index.md
  21. +1
    -0
      docs/content/documentation/templates/pages-sections.md
  22. +6
    -0
      docs/templates/shortcodes/gallery.html
  23. +1
    -0
      docs/templates/shortcodes/resize_image.html
  24. +2
    -2
      src/cmd/serve.rs
  25. +2
    -1
      src/console.rs

+ 178
- 0
Cargo.lock View File

@@ -1,3 +1,8 @@
[[package]]
name = "adler32"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "aho-corasick"
version = "0.6.4"
@@ -183,6 +188,11 @@ dependencies = [
"cc 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "color_quant"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "config"
version = "0.1.0"
@@ -204,6 +214,7 @@ dependencies = [
"errors 0.1.0",
"front_matter 0.1.0",
"globset 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"imageproc 0.1.0",
"rayon 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rendering 0.1.0",
"serde 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -254,6 +265,15 @@ dependencies = [
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "deflate"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "dtoa"
version = "0.4.2"
@@ -290,6 +310,14 @@ dependencies = [
"strum_macros 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "enum_primitive"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "error-chain"
version = "0.11.0"
@@ -303,6 +331,7 @@ name = "errors"
version = "0.1.0"
dependencies = [
"error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"image 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)",
"tera 0.11.8 (registry+https://github.com/rust-lang/crates.io-index)",
"toml 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -401,6 +430,15 @@ name = "getopts"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "gif"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"color_quant 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"lzw 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "glob"
version = "0.2.11"
@@ -500,6 +538,44 @@ dependencies = [
"unicode-normalization 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "image"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"enum_primitive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"gif 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)",
"jpeg-decoder 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)",
"num-iter 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
"num-rational 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)",
"png 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"scoped_threadpool 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "imageproc"
version = "0.1.0"
dependencies = [
"errors 0.1.0",
"image 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rayon 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"tera 0.11.8 (registry+https://github.com/rust-lang/crates.io-index)",
"twox-hash 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"utils 0.1.0",
]

[[package]]
name = "inflate"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "inotify"
version = "0.3.0"
@@ -537,6 +613,15 @@ name = "itoa"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "jpeg-decoder"
version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"rayon 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "kernel32-sys"
version = "0.2.2"
@@ -587,6 +672,11 @@ dependencies = [
"cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "lzw"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "mac"
version = "0.1.1"
@@ -810,6 +900,32 @@ dependencies = [
"num-traits 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "num-iter"
version = "0.1.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "num-rational"
version = "0.1.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "num-traits"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"num-traits 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "num-traits"
version = "0.2.5"
@@ -948,6 +1064,17 @@ dependencies = [
"typemap 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "png"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"deflate 0.7.18 (registry+https://github.com/rust-lang/crates.io-index)",
"inflate 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"num-iter 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "precomputed-hash"
version = "0.1.1"
@@ -999,6 +1126,16 @@ dependencies = [
"proc-macro2 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "rand"
version = "0.3.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "rand"
version = "0.4.2"
@@ -1009,6 +1146,15 @@ dependencies = [
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "rayon"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"either 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rayon-core 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "rayon"
version = "1.0.1"
@@ -1175,6 +1321,11 @@ dependencies = [
"pkg-config 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "scoped_threadpool"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "scopeguard"
version = "0.3.3"
@@ -1250,6 +1401,7 @@ dependencies = [
"errors 0.1.0",
"front_matter 0.1.0",
"glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"imageproc 0.1.0",
"pagination 0.1.0",
"rayon 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"sass-rs 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1441,6 +1593,7 @@ dependencies = [
"config 0.1.0",
"content 0.1.0",
"errors 0.1.0",
"imageproc 0.1.0",
"lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"pulldown-cmark 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"taxonomies 0.1.0",
@@ -1544,6 +1697,14 @@ name = "traitobject"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "twox-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "typeable"
version = "0.1.2"
@@ -1750,6 +1911,7 @@ dependencies = [
]

[metadata]
"checksum adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7e522997b529f05601e05166c07ed17789691f562762c7f3b987263d2dedee5c"
"checksum aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d6531d44de723825aa81398a6415283229725a00fa30713812ab9323faa82fc4"
"checksum ammonia 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fd4c682378117e4186a492b2252b9537990e1617f44aed9788b9a1149de45477"
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
@@ -1773,14 +1935,17 @@ dependencies = [
"checksum chrono 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "6962c635d530328acc53ac6a955e83093fedc91c5809dfac1fa60fa470830a37"
"checksum clap 2.31.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f0f16b89cbb9ee36d87483dc939fe9f1e13c05898d56d7b230a0d4dff033a536"
"checksum cmake 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)" = "95470235c31c726d72bf2e1f421adc1e65b9d561bf5529612cbe1a72da1467b3"
"checksum color_quant 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0dbbb57365263e881e805dc77d94697c9118fd94d8da011240555aa7b23445bd"
"checksum crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f739f8c5363aca78cfb059edf753d8f0d36908c348f3d8d1503f03d8b75d9cf3"
"checksum crossbeam-epoch 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "927121f5407de9956180ff5e936fe3cf4324279280001cd56b669d28ee7e9150"
"checksum crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2760899e32a1d58d5abb31129f8fae5de75220bc2176e77ff7c627ae45c918d9"
"checksum ctrlc 3.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "630391922b1b893692c6334369ff528dcc3a9d8061ccf4c803aa8f83cb13db5e"
"checksum deflate 0.7.18 (registry+https://github.com/rust-lang/crates.io-index)" = "32c8120d981901a9970a3a1c97cf8b630e0fa8c3ca31e75b6fd6fd5f9f427b31"
"checksum dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "09c3753c3db574d215cba4ea76018483895d7bff25a31b49ba45db21c48e50ab"
"checksum duct 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "166298c17c5b4fe5997b962c2f22e887c7c5adc44308eb9103ce5b66af45a423"
"checksum either 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3be565ca5c557d7f59e7cfcf1844f9e3033650c929c6566f511e8005f205c1d0"
"checksum elasticlunr-rs 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4837d77a1e157489a3933b743fd774ae75074e0e390b2b7f071530048a0d87ee"
"checksum enum_primitive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "be4551092f4d519593039259a9ed8daedf0da12e5109c5280338073eaeb81180"
"checksum error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ff511d5dc435d703f4971bc399647c9bc38e20cb41452e3b9feb4765419ed3f3"
"checksum filetime 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "714653f3e34871534de23771ac7b26e999651a0a228f47beb324dfdf1dd4b10f"
"checksum flate2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9fac2277e84e5e858483756647a9d0aa8d9a2b7cba517fd84325a0aaa69a0909"
@@ -1793,6 +1958,7 @@ dependencies = [
"checksum futf 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7c9c1ce3fa9336301af935ab852c437817d14cd33690446569392e65170aac3b"
"checksum gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)" = "5e33ec290da0d127825013597dbdfc28bee4964690c7ce1166cbc2a7bd08b1bb"
"checksum getopts 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)" = "b900c08c1939860ce8b54dc6a89e26e00c04c380fd0e09796799bd7f12861e05"
"checksum gif 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e2e41945ba23db3bf51b24756d73d81acb4f28d85c3dccc32c6fae904438c25f"
"checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb"
"checksum globset 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "142754da2c9b3722affd909f9e27f2a6700a7a303f362971e0a74c652005a43d"
"checksum html5ever 0.22.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b04478cf718862650a0bf66acaf8f2f8c906fbc703f35c916c1f4211b069a364"
@@ -1800,10 +1966,13 @@ dependencies = [
"checksum humansize 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b6cab2627acfc432780848602f3f558f7e9dd427352224b0d9324025796d2a5e"
"checksum hyper 0.10.13 (registry+https://github.com/rust-lang/crates.io-index)" = "368cb56b2740ebf4230520e2b90ebb0461e69034d85d1945febd9b3971426db2"
"checksum idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "014b298351066f1512874135335d62a789ffe78a9974f94b43ed5621951eaf7d"
"checksum image 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)" = "545f000e8aa4e569e93f49c446987133452e0091c2494ac3efd3606aa3d309f2"
"checksum inflate 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f5f9f47468e9a76a6452271efadc88fe865a82be91fe75e6c0c57b87ccea59d4"
"checksum inotify 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "887fcc180136e77a85e6a6128579a719027b1bab9b1c38ea4444244fe262c20c"
"checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08"
"checksum iron 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d8e17268922834707e1c29e8badbf9c712c9c43378e1b6a3388946baff10be2"
"checksum itoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c069bbec61e1ca5a596166e55dfe4773ff745c3d16b700013bcaff9a6df2c682"
"checksum jpeg-decoder 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "c8b7d43206b34b3f94ea9445174bda196e772049b9bddbc620c9d29b2d20110d"
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
"checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a"
"checksum lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e6412c5e2ad9584b0b8e979393122026cdd6d2a80b933f890dcd694ddbe73739"
@@ -1812,6 +1981,7 @@ dependencies = [
"checksum linked-hash-map 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "70fb39025bc7cdd76305867c4eccf2f2dcf6e9a57f5b21a93e1c2d86cd03ec9e"
"checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b"
"checksum log 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6fddaa003a65722a7fb9e26b0ce95921fe4ba590542ced664d8ce2fa26f9f3ac"
"checksum lzw 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7d947cbb889ed21c2a84be6ffbaebf5b4e0f4340638cba0444907e38b56be084"
"checksum mac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
"checksum maplit 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "08cbb6b4fef96b6d77bfc40ec491b1690c779e77b05cd9f07f787ed376fd4c43"
"checksum markup5ever 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfedc97d5a503e96816d10fedcd5b42f760b2e525ce2f7ec71f6a41780548475"
@@ -1835,6 +2005,9 @@ dependencies = [
"checksum nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "9a2228dca57108069a5262f2ed8bd2e82496d2e074a06d1ccc7ce1687b6ae0a2"
"checksum notify 4.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "5c3812da3098f210a0bb440f9c008471a031aa4c1de07a264fdd75456c95a4eb"
"checksum num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "e83d528d2677f0518c570baf2b7abdcf0cd2d248860b68507bdcb3e91d4c0cea"
"checksum num-iter 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "af3fdbbc3291a5464dc57b03860ec37ca6bf915ed6ee385e7c6c052c422b2124"
"checksum num-rational 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "ee314c74bd753fc86b4780aa9475da469155f3848473a261d2d18e35245a784e"
"checksum num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31"
"checksum num-traits 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "630de1ef5cc79d0cdd78b7e33b81f083cbfe90de0f4b2b2f07f905867c70e9fe"
"checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30"
"checksum onig 3.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f5eeb268a4620c74ea5768c6d2ccd492d60a47a8754666b91a46bfc35cd4d1ba"
@@ -1850,6 +2023,7 @@ dependencies = [
"checksum pkg-config 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "110d5ee3593dbb73f56294327fe5668bcc997897097cbc76b51e7aed3f52452f"
"checksum plist 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c61ac2afed2856590ae79d6f358a24b85ece246d2aa134741a66d589519b7503"
"checksum plugin 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "1a6a0dc3910bc8db877ffed8e457763b317cf880df4ae19109b9f77d277cf6e0"
"checksum png 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f0b0cabbbd20c2d7f06dbf015e06aad59b6ca3d9ed14848783e98af9aaf19925"
"checksum precomputed-hash 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
"checksum proc-macro2 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "1b06e2f335f48d24442b35a19df506a835fb3547bc3c06ef27340da9acf5cae7"
"checksum proc-macro2 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "effdb53b25cdad54f8f48843d67398f7ef2e14f12c1b4cb4effc549a6462a4d6"
@@ -1857,7 +2031,9 @@ dependencies = [
"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a"
"checksum quote 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9949cfe66888ffe1d53e6ec9d9f3b70714083854be20fd5e271b232a017401e8"
"checksum quote 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e44651a0dc4cdd99f71c83b561e221f714912d11af1a4dff0631f923d53af035"
"checksum rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)" = "15a732abf9d20f0ad8eeb6f909bf6868722d9a06e1e50802b6a70351f40b4eb1"
"checksum rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eba5f8cb59cc50ed56be8880a5c7b496bfd9bd26394e176bc67884094145c2c5"
"checksum rayon 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ed02d09394c94ffbdfdc755ad62a132e94c3224a8354e78a1200ced34df12edf"
"checksum rayon 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "80e811e76f1dbf68abf87a759083d34600017fc4e10b6bd5ad84a700f9dba4b1"
"checksum rayon-core 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9d24ad214285a7729b174ed6d3bcfcb80177807f959d95fafd5bfc5c4f201ac8"
"checksum redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c214e91d3ecf43e9a4e41e578973adeb14b474f2bee858742d127af75a0112b1"
@@ -1874,6 +2050,7 @@ dependencies = [
"checksum same-file 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "cfb6eded0b06a0b512c8ddbcf04089138c9b4362c2f696f3c3d76039d68f3637"
"checksum sass-rs 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90f8cf6e645aa843ffffcbdc1e8752b1f221dfa314c81895aeb229a77aea7e05"
"checksum sass-sys 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "bae88baa915f59c39820e544cfd9296d815a6b8efc3f276a78a0505866d2b4e9"
"checksum scoped_threadpool 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8"
"checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27"
"checksum sequence_trie 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "32157204e5c9d3c04007bd7e56e96e987635ce0e8e23c085b1e403861b76c351"
"checksum serde 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)" = "e9a2d9a9ac5120e0f768801ca2b58ad6eec929dc9d1d616c162f208869c2ce95"
@@ -1909,6 +2086,7 @@ dependencies = [
"checksum time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "d825be0eb33fda1a7e68012d51e9c7f451dc1a69391e7fdc197060bb8c56667b"
"checksum toml 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "a0263c6c02c4db6c8f7681f9fd35e90de799ebd4cfdeab77a38f4ff6b3d8c0d9"
"checksum traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079"
"checksum twox-hash 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "475352206e7a290c5fccc27624a163e8d0d115f7bb60ca18a64fc9ce056d7435"
"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 ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fd2be2d6639d0f8fe6cdda291ad456e23629558d466e2789d2c3e9892bda285d"


+ 1
- 0
Cargo.toml View File

@@ -53,4 +53,5 @@ members = [
"components/templates",
"components/utils",
"components/search",
"components/imageproc",
]

+ 1
- 0
components/content/Cargo.toml View File

@@ -14,6 +14,7 @@ config = { path = "../config" }
utils = { path = "../utils" }
rendering = { path = "../rendering" }
front_matter = { path = "../front_matter" }
imageproc = { path = "../imageproc" }

[dev-dependencies]
tempfile = "3"


+ 1
- 0
components/content/src/lib.rs View File

@@ -7,6 +7,7 @@ extern crate errors;
extern crate config;
extern crate front_matter;
extern crate rendering;
extern crate imageproc;
extern crate utils;

#[cfg(test)]


+ 25
- 1
components/content/src/page.rs View File

@@ -15,6 +15,7 @@ use utils::site::get_reading_analytics;
use utils::templates::render_template;
use front_matter::{PageFrontMatter, InsertAnchor, split_page_content};
use rendering::{RenderContext, Header, render_content};
use imageproc;

use file_info::FileInfo;

@@ -162,13 +163,16 @@ impl Page {
/// We need access to all pages url to render links relative to content
/// so that can't happen at the same time as parsing
pub fn render_markdown(&mut self, permalinks: &HashMap<String, String>, tera: &Tera, config: &Config, anchor_insert: InsertAnchor) -> Result<()> {
let context = RenderContext::new(
let mut context = RenderContext::new(
tera,
config,
&self.permalink,
permalinks,
anchor_insert
);

context.teracontext.add("page", self);

let res = render_content(
&self.raw_content.replacen("<!-- more -->", "<a name=\"continue-reading\"></a>", 1),
&context
@@ -202,6 +206,23 @@ impl Page {
render_template(&tpl_name, tera, &context, &config.theme)
.chain_err(|| format!("Failed to render page '{}'", self.file.path.display()))
}

/// Creates two vectors of asset URLs. The first one contains all asset files,
/// the second one that which appear to be an image file.
fn serialize_assets(&self) -> (Vec<String>, Vec<String>) {
let mut assets = vec![];
let mut images = vec![];
for asset in self.assets.iter() {
if let Some(filename) = asset.file_name().and_then(|f| f.to_str()) {
let url = self.path.clone() + filename;
if imageproc::file_is_img(&asset) {
images.push(url.clone());
}
assets.push(url);
}
}
(assets, images)
}
}

impl Default for Page {
@@ -246,6 +267,9 @@ impl ser::Serialize for Page {
state.serialize_field("next", &self.next)?;
state.serialize_field("toc", &self.toc)?;
state.serialize_field("draft", &self.is_draft())?;
let (assets, assets_imgs) = self.serialize_assets();
state.serialize_field("assets", &assets)?;
state.serialize_field("assets_imgs", &assets_imgs)?;
state.end()
}
}


+ 4
- 1
components/content/src/section.rs View File

@@ -98,13 +98,16 @@ impl Section {
/// We need access to all pages url to render links relative to content
/// so that can't happen at the same time as parsing
pub fn render_markdown(&mut self, permalinks: &HashMap<String, String>, tera: &Tera, config: &Config) -> Result<()> {
let context = RenderContext::new(
let mut context = RenderContext::new(
tera,
config,
&self.permalink,
permalinks,
self.meta.insert_anchor_links,
);

context.teracontext.add("section", self);

let res = render_content(&self.raw_content, &context)
.chain_err(|| format!("Failed to render content of {}", self.file.path.display()))?;
self.content = res.0;


+ 1
- 0
components/errors/Cargo.toml View File

@@ -7,3 +7,4 @@ authors = ["Vincent Prouillet <prouillet.vincent@gmail.com>"]
error-chain = "0.11"
tera = "0.11"
toml = "0.4"
image = "0.18.0"

+ 2
- 0
components/errors/src/lib.rs View File

@@ -4,6 +4,7 @@
extern crate error_chain;
extern crate tera;
extern crate toml;
extern crate image;

error_chain! {
errors {}
@@ -15,6 +16,7 @@ error_chain! {
foreign_links {
Io(::std::io::Error);
Toml(toml::de::Error);
Image(image::ImageError);
}
}



+ 15
- 0
components/imageproc/Cargo.toml View File

@@ -0,0 +1,15 @@
[package]
name = "imageproc"
version = "0.1.0"
authors = ["Vojtěch Král <vojtech@kral.hk>"]

[dependencies]
lazy_static = "1"
regex = "0.2"
tera = "0.11.0"
image = "0.18.0"
rayon = "0.9"
twox-hash = "1.1"

errors = { path = "../errors" }
utils = { path = "../utils" }

+ 327
- 0
components/imageproc/src/lib.rs View File

@@ -0,0 +1,327 @@
#[macro_use]
extern crate lazy_static;
extern crate regex;
extern crate image;
extern crate rayon;
extern crate twox_hash;

extern crate utils;
extern crate errors;

use std::path::{Path, PathBuf};
use std::hash::{Hash, Hasher};
use std::collections::HashMap;
use std::collections::hash_map::Entry as HEntry;
use std::fs::{self, File};

use regex::Regex;
use image::{GenericImage, FilterType};
use image::jpeg::JPEGEncoder;
use rayon::prelude::*;
use twox_hash::XxHash;

use utils::fs as ufs;
use errors::{Result, ResultExt};


static RESIZED_SUBDIR: &'static str = "_resized_images";
lazy_static!{
pub static ref RESIZED_FILENAME: Regex = Regex::new(r#"([0-9a-f]{16})([0-9a-f]{2})[.]jpg"#).unwrap();
}

/// Describes the precise kind of a resize operation
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ResizeOp {
/// A simple scale operation that doesn't take aspect ratio into account
Scale(u32, u32),
/// Scales the image to a specified width with height computed such that aspect ratio is preserved
FitWidth(u32),
/// Scales the image to a specified height with width computed such that aspect ratio is preserved
FitHeight(u32),
/// Scales the image such that it fits within the specified width and height preserving aspect ratio.
/// Either dimension may end up being smaller, but never larger than specified.
Fit(u32, u32),
/// Scales the image such that it fills the specified width and height. Output will always have the exact dimensions specified.
/// The part of the image that doesn't fit in the thumbnail due to differing aspect ratio will be cropped away, if any.
Fill(u32, u32),
}

impl ResizeOp {
pub fn from_args(op: &str, width: Option<u32>, height: Option<u32>) -> Result<ResizeOp> {
use ResizeOp::*;

// Validate args:
match op {
"fitwidth" => if width.is_none() { return Err(format!("op=fitwidth requires a `width` argument").into()) },
"fitheight" => if height.is_none() { return Err(format!("op=fitwidth requires a `height` argument").into()) },
"scale" | "fit" | "fill" => if width.is_none() || height.is_none() {
return Err(format!("op={} requires a `width` and `height` argument", op).into())
},
_ => return Err(format!("Invalid image resize operation: {}", op).into())
};

Ok(match op {
"scale" => Scale(width.unwrap(), height.unwrap()),
"fitwidth" => FitWidth(width.unwrap()),
"fitheight" => FitHeight(height.unwrap()),
"fit" => Fit(width.unwrap(), height.unwrap()),
"fill" => Fill(width.unwrap(), height.unwrap()),
_ => unreachable!(),
})
}

pub fn width(self) -> Option<u32> {
use ResizeOp::*;

match self {
Scale(w, _) => Some(w),
FitWidth(w) => Some(w),
FitHeight(_) => None,
Fit(w, _) => Some(w),
Fill(w, _) => Some(w),
}
}

pub fn height(self) -> Option<u32> {
use ResizeOp::*;

match self {
Scale(_, h) => Some(h),
FitWidth(_) => None,
FitHeight(h) => Some(h),
Fit(_, h) => Some(h),
Fill(_, h) => Some(h),
}
}
}

impl From<ResizeOp> for u8 {
fn from(op: ResizeOp) -> u8 {
use ResizeOp::*;

match op {
Scale(_, _) => 1,
FitWidth(_) => 2,
FitHeight(_) => 3,
Fit(_, _) => 4,
Fill(_, _) => 5,
}
}
}

impl Hash for ResizeOp {
fn hash<H: Hasher>(&self, hasher: &mut H) {
hasher.write_u8(u8::from(*self));
if let Some(w) = self.width() { hasher.write_u32(w); }
if let Some(h) = self.height() { hasher.write_u32(h); }
}
}

/// Holds all data needed to perform a resize operation
#[derive(Debug, PartialEq, Eq)]
pub struct ImageOp {
source: String,
op: ResizeOp,
quality: u8,
hash: u64,
collision: Option<u32>,
}

impl ImageOp {
pub fn new(source: String, op: ResizeOp, quality: u8) -> ImageOp {
let mut hasher = XxHash::with_seed(0);
hasher.write(source.as_ref());
op.hash(&mut hasher);
hasher.write_u8(quality);
let hash = hasher.finish();

ImageOp { source, op, quality, hash, collision: None }
}

pub fn from_args(source: String, op: &str, width: Option<u32>, height: Option<u32>, quality: u8) -> Result<ImageOp> {
let op = ResizeOp::from_args(op, width, height)?;
Ok(Self::new(source, op, quality))
}

fn num_colli(&self) -> u32 { self.collision.unwrap_or(0) }

fn perform(&self, content_path: &Path, target_path: &Path) -> Result<()> {
use ResizeOp::*;

let src_path = content_path.join(&self.source);
if !ufs::file_stale(&src_path, target_path) {
return Ok(())
}

let mut img = image::open(&src_path)?;
let (img_w, img_h) = img.dimensions();

const RESIZE_FILTER: FilterType = FilterType::Gaussian;
const RATIO_EPSILLION: f32 = 0.1;

let img = match self.op {
Scale(w, h) => img.resize_exact(w, h, RESIZE_FILTER),
FitWidth(w) => img.resize(w, u32::max_value(), RESIZE_FILTER),
FitHeight(h) => img.resize(u32::max_value(), h, RESIZE_FILTER),
Fit(w, h) => img.resize(w, h, RESIZE_FILTER),
Fill(w, h) => {
let fw = img_w as f32 / w as f32;
let fh = img_h as f32 / h as f32;

if (fw - fh).abs() <= RATIO_EPSILLION {
// The aspect is similar enough that there's not much point in cropping
img.resize_exact(w, h, RESIZE_FILTER)
} else {
// We perform the fill such that a crop is performed first and then resize_exact can be used,
// which should be cheaper than resizing and then cropping (smaller number of pixels to resize).
let (crop_w, crop_h) = match fw < fh {
true => (img_w, (fw * h as f32).round() as u32),
false => ((fh * w as f32).round() as u32, img_h),
};
let (off_w, off_h) = match fw < fh {
true => (0, (img_h - crop_h) / 2),
false => ((img_w - crop_w) / 2, 0),
};
img.crop(off_w, off_h, crop_w, crop_h).resize_exact(w, h, RESIZE_FILTER)
}
},
};

let mut f = File::create(target_path)?;
let mut enc = JPEGEncoder::new_with_quality(&mut f, self.quality);
let (img_w, img_h) = img.dimensions();
enc.encode(&img.raw_pixels(), img_w, img_h, img.color())?;
Ok(())
}
}


/// A strcture into which image operations can be enqueued and then performed.
/// All output is written in a subdirectory in `static_path`,
/// taking care of file stale status based on timestamps and possible hash collisions.
#[derive(Debug)]
pub struct Processor {
content_path: PathBuf,
resized_path: PathBuf,
resized_url: String,
img_ops: HashMap<u64, ImageOp>,
// Hash collisions go here:
img_ops_colls: Vec<ImageOp>,
}

impl Processor {
pub fn new(content_path: PathBuf, static_path: &Path, base_url: &str) -> Processor {
Processor {
content_path,
resized_path: static_path.join(RESIZED_SUBDIR),
resized_url: Self::resized_url(base_url),
img_ops: HashMap::new(),
img_ops_colls: Vec::new(),
}
}

fn resized_url(base_url: &str) -> String {
match base_url.ends_with('/') {
true => format!("{}{}", base_url, RESIZED_SUBDIR),
false => format!("{}/{}", base_url, RESIZED_SUBDIR),
}
}

pub fn set_base_url(&mut self, base_url: &str) {
self.resized_url = Self::resized_url(base_url);
}

pub fn source_exists(&self, source: &str) -> bool {
self.content_path.join(source).exists()
}

pub fn num_img_ops(&self) -> usize {
self.img_ops.len() + self.img_ops_colls.len()
}

fn insert_with_colls(&mut self, mut img_op: ImageOp) -> u32 {
match self.img_ops.entry(img_op.hash) {
HEntry::Occupied(entry) => if *entry.get() == img_op { return 0; },
HEntry::Vacant(entry) => {
entry.insert(img_op);
return 0;
},
}

// If we get here, that means a hash collision.
let mut num = 1;
for op in self.img_ops_colls.iter().filter(|op| op.hash == img_op.hash) {
if *op == img_op {
return num;
} else {
num += 1;
}
}

if num == 1 {
self.img_ops.get_mut(&img_op.hash).unwrap().collision = Some(0);
}
img_op.collision = Some(num);
self.img_ops_colls.push(img_op);
num
}

fn op_filename(hash: u64, colli_num: u32) -> String {
// Please keep this in sync with RESIZED_FILENAME
assert!(colli_num < 256, "Unexpectedly large number of collisions: {}", colli_num);
format!("{:016x}{:02x}.jpg", hash, colli_num)
}

fn op_url(&self, hash: u64, colli_num: u32) -> String {
format!("{}/{}", &self.resized_url, Self::op_filename(hash, colli_num))
}

pub fn insert(&mut self, img_op: ImageOp) -> String {
let hash = img_op.hash;
let num_colli = self.insert_with_colls(img_op);
self.op_url(hash, num_colli)
}

pub fn prune(&self) -> Result<()> {
ufs::ensure_directory_exists(&self.resized_path)?;
let entries = fs::read_dir(&self.resized_path)?;
for entry in entries {
let entry_path = entry?.path();
if entry_path.is_file() {
let filename = entry_path.file_name().unwrap().to_string_lossy();
if let Some(capts) = RESIZED_FILENAME.captures(filename.as_ref()) {
let hash = u64::from_str_radix(capts.get(1).unwrap().as_str(), 16).unwrap();
let num_colli = u32::from_str_radix(capts.get(2).unwrap().as_str(), 16).unwrap();
if num_colli > 0 || !self.img_ops.contains_key(&hash) {
fs::remove_file(&entry_path)?;
}
}
}
}
Ok(())
}

pub fn do_process(&mut self) -> Result<()> {
self.img_ops.par_iter().map(|(hash, op)| {
let target = self.resized_path.join(Self::op_filename(*hash, op.num_colli()));
op.perform(&self.content_path, &target)
.chain_err(|| format!("Failed to process image: {}", op.source))
})
.fold(|| Ok(()), Result::and)
.reduce(|| Ok(()), Result::and)
}
}


/// Looks at file's extension and returns whether it's a supported image format
pub fn file_is_img<P: AsRef<Path>>(p: P) -> bool {
p.as_ref().extension().and_then(|s| s.to_str()).map(|ext| {
match ext.to_lowercase().as_str() {
"jpg" | "jpeg" => true,
"png" => true,
"gif" => true,
"bmp" => true,
_ => false,
}
}).unwrap_or(false)
}

+ 5
- 1
components/rendering/src/context.rs View File

@@ -1,6 +1,6 @@
use std::collections::HashMap;

use tera::Tera;
use tera::{Tera, Context};
use front_matter::InsertAnchor;
use config::Config;

@@ -10,6 +10,7 @@ use config::Config;
pub struct RenderContext<'a> {
pub tera: &'a Tera,
pub config: &'a Config,
pub teracontext: Context,
pub current_page_permalink: &'a str,
pub permalinks: &'a HashMap<String, String>,
pub insert_anchor: InsertAnchor,
@@ -23,8 +24,11 @@ impl<'a> RenderContext<'a> {
permalinks: &'a HashMap<String, String>,
insert_anchor: InsertAnchor,
) -> RenderContext<'a> {
let mut teracontext = Context::new();
teracontext.insert("config", config);
RenderContext {
tera,
teracontext,
current_page_permalink,
permalinks,
insert_anchor,


+ 1
- 1
components/rendering/src/lib.rs View File

@@ -34,7 +34,7 @@ pub use context::RenderContext;
pub fn render_content(content: &str, context: &RenderContext) -> Result<(String, Vec<Header>)> {
// Don't do anything if there is nothing like a shortcode in the content
if content.contains("{{") || content.contains("{%") {
let rendered = render_shortcodes(content, context.tera, context.config)?;
let rendered = render_shortcodes(content, context)?;
return markdown_to_html(&rendered, context);
}



+ 31
- 24
components/rendering/src/shortcode.rs View File

@@ -1,9 +1,9 @@
use pest::Parser;
use pest::iterators::Pair;
use tera::{Tera, Map, Context, Value, to_value};
use tera::{Map, Context, Value, to_value};

use errors::{Result, ResultExt};
use config::Config;
use ::context::RenderContext;

// This include forces recompiling this source file if the grammar file changes.
// Uncomment it when doing changes to the .pest file
@@ -84,20 +84,20 @@ fn parse_shortcode_call(pair: Pair<Rule>) -> (String, Map<String, Value>) {
}


fn render_shortcode(name: String, args: Map<String, Value>, tera: &Tera, config: &Config, body: Option<&str>) -> Result<String> {
let mut context = Context::new();
fn render_shortcode(name: String, args: Map<String, Value>, context: &RenderContext, body: Option<&str>) -> Result<String> {
let mut teracontext = Context::new();
for (key, value) in args.iter() {
context.insert(key, value);
teracontext.insert(key, value);
}
if let Some(ref b) = body {
// Trimming right to avoid most shortcodes with bodies ending up with a HTML new line
context.insert("body", b.trim_right());
teracontext.insert("body", b.trim_right());
}
context.insert("config", config);
teracontext.extend(context.teracontext.clone());
let tpl_name = format!("shortcodes/{}.html", name);

let res = tera
.render(&tpl_name, &context)
let res = context.tera
.render(&tpl_name, &teracontext)
.chain_err(|| format!("Failed to render {} shortcode", name))?;

// We trim left every single line of a shortcode to avoid the accidental
@@ -105,7 +105,7 @@ fn render_shortcode(name: String, args: Map<String, Value>, tera: &Tera, config:
Ok(res.lines().map(|s| s.trim_left()).collect())
}

pub fn render_shortcodes(content: &str, tera: &Tera, config: &Config) -> Result<String> {
pub fn render_shortcodes(content: &str, context: &RenderContext) -> Result<String> {
let mut res = String::with_capacity(content.len());

let mut pairs = match ContentParser::parse(Rule::page, content) {
@@ -138,7 +138,7 @@ pub fn render_shortcodes(content: &str, tera: &Tera, config: &Config) -> Result<
Rule::text | Rule::text_in_ignored_body_sc | Rule::text_in_body_sc => res.push_str(p.into_span().as_str()),
Rule::inline_shortcode => {
let (name, args) = parse_shortcode_call(p);
res.push_str(&render_shortcode(name, args, tera, config, None)?);
res.push_str(&render_shortcode(name, args, context, None)?);
},
Rule::shortcode_with_body => {
let mut inner = p.into_inner();
@@ -146,7 +146,7 @@ pub fn render_shortcodes(content: &str, tera: &Tera, config: &Config) -> Result<
// we don't care about the closing tag
let (name, args) = parse_shortcode_call(inner.next().unwrap());
let body = inner.next().unwrap().into_span().as_str();
res.push_str(&render_shortcode(name, args, tera, config, Some(body))?);
res.push_str(&render_shortcode(name, args, context, Some(body))?);
},
Rule::ignored_inline_shortcode => {
res.push_str(
@@ -179,6 +179,10 @@ pub fn render_shortcodes(content: &str, tera: &Tera, config: &Config) -> Result<

#[cfg(test)]
mod tests {
use std::collections::HashMap;
use tera::Tera;
use config::Config;
use front_matter::InsertAnchor;
use super::*;

macro_rules! assert_lex_rule {
@@ -195,6 +199,13 @@ mod tests {
};
}

fn render_shortcodes(code: &str, tera: &Tera) -> String {
let config = Config::default();
let permalinks = HashMap::new();
let context = RenderContext::new(&tera, &config, "", &permalinks, InsertAnchor::None);
super::render_shortcodes(code, &context).unwrap()
}

#[test]
fn lex_text() {
let inputs = vec!["Hello world", "HEllo \n world", "Hello 1 2 true false 'hey'"];
@@ -281,26 +292,22 @@ mod tests {

#[test]
fn does_nothing_with_no_shortcodes() {
let res = render_shortcodes("Hello World", &Tera::default(), &Config::default());
assert_eq!(res.unwrap(), "Hello World");
let res = render_shortcodes("Hello World", &Tera::default());
assert_eq!(res, "Hello World");
}

#[test]
fn can_unignore_inline_shortcode() {
let res = render_shortcodes(
"Hello World {{/* youtube() */}}",
&Tera::default(),
&Config::default(),
);
assert_eq!(res.unwrap(), "Hello World {{ youtube() }}");
let res = render_shortcodes("Hello World {{/* youtube() */}}", &Tera::default());
assert_eq!(res, "Hello World {{ youtube() }}");
}

#[test]
fn can_unignore_shortcode_with_body() {
let res = render_shortcodes(r#"
Hello World
{%/* youtube() */%}Some body {{ hello() }}{%/* end */%}"#, &Tera::default(), &Config::default());
assert_eq!(res.unwrap(), "\nHello World\n{% youtube() %}Some body {{ hello() }}{% end %}");
{%/* youtube() */%}Some body {{ hello() }}{%/* end */%}"#, &Tera::default());
assert_eq!(res, "\nHello World\n{% youtube() %}Some body {{ hello() }}{% end %}");
}

#[test]
@@ -343,7 +350,7 @@ Hello World
fn can_render_inline_shortcodes() {
let mut tera = Tera::default();
tera.add_raw_template("shortcodes/youtube.html", "Hello {{id}}").unwrap();
let res = render_shortcodes("Inline {{ youtube(id=1) }}.", &tera, &Config::default()).unwrap();
let res = render_shortcodes("Inline {{ youtube(id=1) }}.", &tera);
assert_eq!(res, "Inline Hello 1.");
}

@@ -351,7 +358,7 @@ Hello World
fn can_render_shortcodes_with_body() {
let mut tera = Tera::default();
tera.add_raw_template("shortcodes/youtube.html", "{{body}}").unwrap();
let res = render_shortcodes("Body\n {% youtube() %}Hey!{% end %}", &tera, &Config::default()).unwrap();
let res = render_shortcodes("Body\n {% youtube() %}Hey!{% end %}", &tera);
assert_eq!(res, "Body\n Hey!");
}
}

+ 1
- 0
components/site/Cargo.toml View File

@@ -20,6 +20,7 @@ pagination = { path = "../pagination" }
taxonomies = { path = "../taxonomies" }
content = { path = "../content" }
search = { path = "../search" }
imageproc = { path = "../imageproc" }

[dev-dependencies]
tempfile = "3"

+ 43
- 9
components/site/src/lib.rs View File

@@ -16,6 +16,7 @@ extern crate pagination;
extern crate taxonomies;
extern crate content;
extern crate search;
extern crate imageproc;

#[cfg(test)]
extern crate tempfile;
@@ -24,6 +25,7 @@ use std::collections::HashMap;
use std::fs::{create_dir_all, remove_dir_all, copy};
use std::mem;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};

use glob::glob;
use tera::{Tera, Context};
@@ -66,9 +68,11 @@ pub struct Site {
pub pages: HashMap<PathBuf, Page>,
pub sections: HashMap<PathBuf, Section>,
pub tera: Tera,
imageproc: Arc<Mutex<imageproc::Processor>>,
// the live reload port to be used if there is one
pub live_reload: Option<u16>,
pub output_path: PathBuf,
content_path: PathBuf,
pub static_path: PathBuf,
pub tags: Option<Taxonomy>,
pub categories: Option<Taxonomy>,
@@ -109,15 +113,21 @@ impl Site {
// the `extend` above already does it but hey
tera.build_inheritance_chains()?;

let content_path = path.join("content");
let static_path = path.join("static");
let imageproc = imageproc::Processor::new(content_path.clone(), &static_path, &config.base_url);

let site = Site {
base_path: path.to_path_buf(),
config,
tera,
pages: HashMap::new(),
sections: HashMap::new(),
imageproc: Arc::new(Mutex::new(imageproc)),
live_reload: None,
output_path: path.join("public"),
static_path: path.join("static"),
content_path,
static_path,
tags: None,
categories: None,
permalinks: HashMap::new(),
@@ -128,7 +138,7 @@ impl Site {

/// The index section is ALWAYS at that path
pub fn index_section_path(&self) -> PathBuf {
self.base_path.join("content").join("_index.md")
self.content_path.join("_index.md")
}

pub fn enable_live_reload(&mut self) {
@@ -153,6 +163,12 @@ impl Site {
orphans
}

pub fn set_base_url(&mut self, base_url: String) {
let mut imageproc = self.imageproc.lock().unwrap();
imageproc.set_base_url(&base_url);
self.config.base_url = base_url;
}

pub fn set_output_path<P: AsRef<Path>>(&mut self, path: P) {
self.output_path = path.as_ref().to_path_buf();
}
@@ -217,7 +233,7 @@ impl Site {
if !self.sections.contains_key(&index_path) {
let mut index_section = Section::default();
index_section.permalink = self.config.make_permalink("");
index_section.file.parent = self.base_path.join("content");
index_section.file.parent = self.content_path.clone();
index_section.file.relative = "_index.md".to_string();
self.sections.insert(index_path, index_section);
}
@@ -229,10 +245,10 @@ impl Site {
self.add_page(p, false)?;
}

self.register_early_global_fns();
self.render_markdown()?;
self.populate_sections();
self.populate_tags_and_categories();

self.register_tera_global_fns();

Ok(())
@@ -270,6 +286,16 @@ impl Site {
Ok(())
}

/// Adds global fns that are to be available to shortcodes while rendering markdown
pub fn register_early_global_fns(&mut self) {
self.tera.register_global_function(
"get_url", global_fns::make_get_url(self.permalinks.clone(), self.config.clone())
);
self.tera.register_global_function(
"resize_image", global_fns::make_resize_image(self.imageproc.clone())
);
}

pub fn register_tera_global_fns(&mut self) {
self.tera.register_global_function("trans", global_fns::make_trans(self.config.clone()));
self.tera.register_global_function("get_page", global_fns::make_get_page(&self.pages));
@@ -278,10 +304,6 @@ impl Site {
"get_taxonomy_url",
global_fns::make_get_taxonomy_url(self.tags.clone(), self.categories.clone())
);
self.tera.register_global_function(
"get_url",
global_fns::make_get_url(self.permalinks.clone(), self.config.clone())
);
}

/// Add a page to the site
@@ -441,6 +463,17 @@ impl Site {
Ok(())
}

pub fn num_img_ops(&self) -> usize {
let imageproc = self.imageproc.lock().unwrap();
imageproc.num_img_ops()
}

pub fn process_images(&self) -> Result<()> {
let mut imageproc = self.imageproc.lock().unwrap();
imageproc.prune()?;
imageproc.do_process()
}

/// Deletes the `public` directory if it exists
pub fn clean(&self) -> Result<()> {
if self.output_path.exists() {
@@ -510,6 +543,7 @@ impl Site {
self.compile_sass(&self.base_path)?;
}

self.process_images()?;
self.copy_static_directories()?;

if self.config.build_search_index {
@@ -820,7 +854,7 @@ impl Site {
/// Used only on reload
pub fn render_index(&self) -> Result<()> {
self.render_section(
&self.sections[&self.base_path.join("content").join("_index.md")],
&self.sections[&self.content_path.join("_index.md")],
false
)
}


+ 1
- 0
components/templates/Cargo.toml View File

@@ -14,3 +14,4 @@ utils = { path = "../utils" }
content = { path = "../content" }
config = { path = "../config" }
taxonomies = { path = "../taxonomies" }
imageproc = { path = "../imageproc" }

+ 54
- 14
components/templates/src/global_fns.rs View File

@@ -1,5 +1,6 @@
use std::collections::HashMap;
use std::path::{PathBuf};
use std::path::PathBuf;
use std::sync::{Arc, Mutex};

use tera::{GlobalFn, Value, from_value, to_value, Result};

@@ -7,29 +8,41 @@ use content::{Page, Section};
use config::Config;
use utils::site::resolve_internal_link;
use taxonomies::Taxonomy;
use imageproc;


macro_rules! required_string_arg {
($e: expr, $err: expr) => {
macro_rules! required_arg {
($ty: ty, $e: expr, $err: expr) => {
match $e {
Some(v) => match from_value::<String>(v.clone()) {
Some(v) => match from_value::<$ty>(v.clone()) {
Ok(u) => u,
Err(_) => return Err($err.into())
},
None => return Err($err.into())
};
}
};
}

macro_rules! optional_arg {
($ty: ty, $e: expr, $err: expr) => {
match $e {
Some(v) => match from_value::<$ty>(v.clone()) {
Ok(u) => Some(u),
Err(_) => return Err($err.into())
},
None => None
}
};
}


pub fn make_trans(config: Config) -> GlobalFn {
let translations_config = config.translations;
let default_lang = to_value(config.default_language).unwrap();
let default_lang = config.default_language.clone();

Box::new(move |args| -> Result<Value> {
let key = required_string_arg!(args.get("key"), "`trans` requires a `key` argument.");
let lang_arg = args.get("lang").unwrap_or(&default_lang).clone();
let lang = from_value::<String>(lang_arg).unwrap();
let key = required_arg!(String, args.get("key"), "`trans` requires a `key` argument.");
let lang = optional_arg!(String, args.get("lang"), "`trans`: `lang` must be a string.").unwrap_or(default_lang.clone());
let translations = &translations_config[lang.as_str()];
Ok(to_value(&translations[key.as_str()]).unwrap())
})
@@ -43,7 +56,7 @@ pub fn make_get_page(all_pages: &HashMap<PathBuf, Page>) -> GlobalFn {
}

Box::new(move |args| -> Result<Value> {
let path = required_string_arg!(args.get("path"), "`get_page` requires a `path` argument with a string value");
let path = required_arg!(String, args.get("path"), "`get_page` requires a `path` argument with a string value");
match pages.get(&path) {
Some(p) => Ok(to_value(p).unwrap()),
None => Err(format!("Page `{}` not found.", path).into())
@@ -61,7 +74,7 @@ pub fn make_get_section(all_sections: &HashMap<PathBuf, Section>) -> GlobalFn {
}

Box::new(move |args| -> Result<Value> {
let path = required_string_arg!(args.get("path"), "`get_section` requires a `path` argument with a string value");
let path = required_arg!(String, args.get("path"), "`get_section` requires a `path` argument with a string value");
//println!("Found {:#?}", sections.get(&path).unwrap().pages[0]);
match sections.get(&path) {
Some(p) => Ok(to_value(p).unwrap()),
@@ -84,7 +97,7 @@ pub fn make_get_url(permalinks: HashMap<String, String>, config: Config) -> Glob
from_value::<bool>(c.clone()).unwrap_or(true)
});

let path = required_string_arg!(args.get("path"), "`get_url` requires a `path` argument with a string value");
let path = required_arg!(String, args.get("path"), "`get_url` requires a `path` argument with a string value");
if path.starts_with("./") {
match resolve_internal_link(&path, &permalinks) {
Ok(url) => Ok(to_value(url).unwrap()),
@@ -107,8 +120,8 @@ pub fn make_get_url(permalinks: HashMap<String, String>, config: Config) -> Glob

pub fn make_get_taxonomy_url(tags: Option<Taxonomy>, categories: Option<Taxonomy>) -> GlobalFn {
Box::new(move |args| -> Result<Value> {
let kind = required_string_arg!(args.get("kind"), "`get_taxonomy_url` requires a `kind` argument with a string value");
let name = required_string_arg!(args.get("name"), "`get_taxonomy_url` requires a `name` argument with a string value");
let kind = required_arg!(String, args.get("kind"), "`get_taxonomy_url` requires a `kind` argument with a string value");
let name = required_arg!(String, args.get("name"), "`get_taxonomy_url` requires a `name` argument with a string value");
let container = match kind.as_ref() {
"tag" => &tags,
"category" => &categories,
@@ -128,6 +141,33 @@ pub fn make_get_taxonomy_url(tags: Option<Taxonomy>, categories: Option<Taxonomy
})
}

pub fn make_resize_image(imageproc: Arc<Mutex<imageproc::Processor>>) -> GlobalFn {
static DEFAULT_OP: &'static str = "fill";
const DEFAULT_Q: u8 = 75;

Box::new(move |args| -> Result<Value> {
let path = required_arg!(String, args.get("path"), "`resize_image` requires a `path` argument with a string value");
let width = optional_arg!(u32, args.get("width"), "`resize_image`: `width` must be a non-negative integer");
let height = optional_arg!(u32, args.get("height"), "`resize_image`: `height` must be a non-negative integer");
let op = optional_arg!(String, args.get("op"), "`resize_image`: `op` must be a string").unwrap_or(DEFAULT_OP.to_owned());
let quality = optional_arg!(u8, args.get("quality"), "`resize_image`: `quality` must be a number").unwrap_or(DEFAULT_Q);
if quality == 0 || quality > 100 {
return Err("`resize_image`: `quality` must be in range 1-100".to_string().into());
}

let mut imageproc = imageproc.lock().unwrap();
if !imageproc.source_exists(&path) {
return Err(format!("`resize_image`: Cannot find path: {}", path).into());
}

let imageop = imageproc::ImageOp::from_args(path.clone(), &op, width, height, quality).map_err(|e| format!("`resize_image`: {}", e))?;
let url = imageproc.insert(imageop);

to_value(url).map_err(|err| err.into())
})
}


#[cfg(test)]
mod tests {
use super::{make_get_url, make_get_taxonomy_url, make_trans};


+ 1
- 0
components/templates/src/lib.rs View File

@@ -11,6 +11,7 @@ extern crate utils;
extern crate content;
extern crate config;
extern crate taxonomies;
extern crate imageproc;

pub mod filters;
pub mod global_fns;


+ 26
- 0
components/utils/src/fs.rs View File

@@ -93,6 +93,32 @@ pub fn copy_directory(src: &PathBuf, dest: &PathBuf) -> Result<()> {
Ok(())
}

/// Compares source and target files' timestamps and returns true if the source file
/// has been created _or_ updated after the target file has
pub fn file_stale<PS, PT>(p_source: PS, p_target: PT) -> bool where PS: AsRef<Path>, PT: AsRef<Path> {
let p_source = p_source.as_ref();
let p_target = p_target.as_ref();

if ! p_target.exists() {
return true;
}

let get_time = |path: &Path| path.metadata().ok().and_then(|meta| {
Some(match (meta.created().ok(), meta.modified().ok()) {
(Some(tc), Some(tm)) => tc.max(tm),
(Some(tc), None) => tc,
(None, Some(tm)) => tm,
(None, None) => return None,
})
});

let time_source = get_time(p_source);
let time_target = get_time(p_target);

time_source.and_then(|ts| time_target.map(|tt| ts > tt)).unwrap_or(true)
}


#[cfg(test)]
mod tests {
use std::fs::File;


+ 8
- 0
docs/content/documentation/content/image-resizing/index.md View File

@@ -0,0 +1,8 @@
+++
title = "Image Resizing"
weight = 120
+++

TODO: talk about resize_image

{{ gallery() }}

+ 1
- 0
docs/content/documentation/templates/pages-sections.md View File

@@ -37,6 +37,7 @@ previous: Page?;
next: Page?;
// See the Table of contents section below for more details
toc: Array<Header>;
// TODO: add assets & assets_imgs (also draft is missing?)
```

## Section variables


+ 6
- 0
docs/templates/shortcodes/gallery.html View File

@@ -0,0 +1,6 @@
{% for img in page.assets_imgs %}
<a href="{{ config.base_url }}/{{ img }}">
<img src="{{ resize_image(path=img, width=240, height=180, op="fill") }}" />
</a>
&ensp;
{% endfor %}

+ 1
- 0
docs/templates/shortcodes/resize_image.html View File

@@ -0,0 +1 @@
<img src="{{ resize_image(path=path, width=width, height=height, op=op) }}" />

+ 2
- 2
src/cmd/serve.rs View File

@@ -86,13 +86,13 @@ fn create_new_site(interface: &str, port: &str, output_dir: &str, base_url: &str

let base_address = format!("{}:{}", base_url, port);
let address = format!("{}:{}", interface, port);

site.config.base_url = if site.config.base_url.ends_with('/') {
let base_url = if site.config.base_url.ends_with('/') {
format!("http://{}/", base_address)
} else {
format!("http://{}", base_address)
};

site.set_base_url(base_url);
site.set_output_path(output_dir);
site.load()?;
site.enable_live_reload();


+ 2
- 1
src/console.rs View File

@@ -27,10 +27,11 @@ pub fn error(message: &str) {
/// Display in the console the number of pages/sections in the site
pub fn notify_site_size(site: &Site) {
println!(
"-> Creating {} pages ({} orphan) and {} sections",
"-> Creating {} pages ({} orphan), {} sections, and processing {} images",
site.pages.len(),
site.get_all_orphan_pages().len(),
site.sections.len() - 1, // -1 since we do not the index as a section
site.num_img_ops(),
);
}



Loading…
Cancel
Save