mirror of
https://github.com/getzola/zola
synced 2024-12-13 22:02:29 +00:00
Add image resizing support #225
This commit is contained in:
parent
3eb571fdbf
commit
6662014e55
25 changed files with 738 additions and 54 deletions
178
Cargo.lock
generated
178
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -53,4 +53,5 @@ members = [
|
|||
"components/templates",
|
||||
"components/utils",
|
||||
"components/search",
|
||||
"components/imageproc",
|
||||
]
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
components/imageproc/Cargo.toml
Normal file
15
components/imageproc/Cargo.toml
Normal 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
components/imageproc/src/lib.rs
Normal file
327
components/imageproc/src/lib.rs
Normal 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)
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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!");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ pagination = { path = "../pagination" }
|
|||
taxonomies = { path = "../taxonomies" }
|
||||
content = { path = "../content" }
|
||||
search = { path = "../search" }
|
||||
imageproc = { path = "../imageproc" }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3"
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
|
|
@ -14,3 +14,4 @@ utils = { path = "../utils" }
|
|||
content = { path = "../content" }
|
||||
config = { path = "../config" }
|
||||
taxonomies = { path = "../taxonomies" }
|
||||
imageproc = { path = "../imageproc" }
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
+++
|
||||
title = "Image Resizing"
|
||||
weight = 120
|
||||
+++
|
||||
|
||||
TODO: talk about resize_image
|
||||
|
||||
{{ gallery() }}
|
|
@ -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
docs/templates/shortcodes/gallery.html
vendored
Normal file
6
docs/templates/shortcodes/gallery.html
vendored
Normal 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>
|
||||
 
|
||||
{% endfor %}
|
1
docs/templates/shortcodes/resize_image.html
vendored
Normal file
1
docs/templates/shortcodes/resize_image.html
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
<img src="{{ resize_image(path=path, width=width, height=height, op=op) }}" />
|
|
@ -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();
|
||||
|
|
|
@ -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…
Reference in a new issue