:smile: ...";
- let result =
- MarkdownFilter::new(config.clone(), HashMap::new()).filter(&to_value(&md).unwrap(), &HashMap::new());
+ let result = MarkdownFilter::new(config.clone(), HashMap::new())
+ .filter(&to_value(&md).unwrap(), &HashMap::new());
assert!(result.is_ok());
assert_eq!(result.unwrap(), to_value(&"Hello https://google.com 😄 …
\n").unwrap());
let md = "```py\ni=0\n```";
- let result = MarkdownFilter::new(config, HashMap::new()).filter(&to_value(&md).unwrap(), &HashMap::new());
+ let result = MarkdownFilter::new(config, HashMap::new())
+ .filter(&to_value(&md).unwrap(), &HashMap::new());
assert!(result.is_ok());
assert!(result.unwrap().as_str().unwrap().contains("Hello. Check out my blog!\n").unwrap());
+ assert_eq!(
+ result.unwrap(),
+ to_value(&"Hello. Check out my blog!
\n").unwrap()
+ );
}
#[test]
diff --git a/components/templates/src/global_fns/load_data.rs b/components/templates/src/global_fns/load_data.rs
index 0f74c648..a5b929fa 100644
--- a/components/templates/src/global_fns/load_data.rs
+++ b/components/templates/src/global_fns/load_data.rs
@@ -190,7 +190,15 @@ impl LoadData {
impl TeraFn for LoadData {
fn call(&self, args: &HashMap) -> Result {
- let required = if let Some(req) = optional_arg!(bool, args.get("required"), "`load_data`: `required` must be a boolean (true or false)") { req } else { true };
+ let required = if let Some(req) = optional_arg!(
+ bool,
+ args.get("required"),
+ "`load_data`: `required` must be a boolean (true or false)"
+ ) {
+ req
+ } else {
+ true
+ };
let path_arg = optional_arg!(String, args.get("path"), GET_DATA_ARGUMENT_ERROR_MESSAGE);
let url_arg = optional_arg!(String, args.get("url"), GET_DATA_ARGUMENT_ERROR_MESSAGE);
let data_source = DataSource::from_args(path_arg.clone(), url_arg, &self.base_path)?;
@@ -198,13 +206,19 @@ impl TeraFn for LoadData {
// If the file doesn't exist, source is None
match (&data_source, required) {
// If the file was not required, return a Null value to the template
- (None, false) => { return Ok(Value::Null); },
+ (None, false) => {
+ return Ok(Value::Null);
+ }
// If the file was required, error
(None, true) => {
// source is None only with path_arg (not URL), so path_arg is safely unwrap
- return Err(format!("{} doesn't exist", &self.base_path.join(path_arg.unwrap()).display()).into());
- },
- _ => {},
+ return Err(format!(
+ "{} doesn't exist",
+ &self.base_path.join(path_arg.unwrap()).display()
+ )
+ .into());
+ }
+ _ => {}
}
let data_source = data_source.unwrap();
let file_format = get_output_format_from_args(&args, &data_source)?;
@@ -223,25 +237,26 @@ impl TeraFn for LoadData {
.get(url.as_str())
.header(header::ACCEPT, file_format.as_accept_header())
.send()
- .and_then(|res| res.error_for_status()) {
- Ok(r) => {
- r.text()
- .map_err(|e| format!("Failed to parse response from {}: {:?}", url, e).into())
- },
- Err(e) => {
- if !required {
- // HTTP error is discarded (because required=false) and
- // Null value is returned to the template
- return Ok(Value::Null);
- }
- Err(match e.status() {
- Some(status) => format!("Failed to request {}: {}", url, status),
- None => format!("Could not get response status for url: {}", url),
- }.into())
+ .and_then(|res| res.error_for_status())
+ {
+ Ok(r) => r.text().map_err(|e| {
+ format!("Failed to parse response from {}: {:?}", url, e).into()
+ }),
+ Err(e) => {
+ if !required {
+ // HTTP error is discarded (because required=false) and
+ // Null value is returned to the template
+ return Ok(Value::Null);
}
+ Err(match e.status() {
+ Some(status) => format!("Failed to request {}: {}", url, status),
+ None => format!("Could not get response status for url: {}", url),
+ }
+ .into())
+ }
}
- },
- // Now that we have discarded recoverable errors, we can unwrap the result
+ }
+ // Now that we have discarded recoverable errors, we can unwrap the result
}?;
let result_value: Result = match file_format {
@@ -542,7 +557,6 @@ mod tests {
assert_eq!(result.unwrap(), tera::Value::Null);
}
-
#[test]
fn set_default_user_agent() {
let user_agent = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
@@ -693,7 +707,6 @@ mod tests {
}
}
-
#[test]
fn can_load_json() {
let static_fn = LoadData::new(PathBuf::from("../utils/test-files"));
From 3b210d648c3af8e4f256c5c06403012b7871757a Mon Sep 17 00:00:00 2001
From: Vincent Prouillet
Date: Tue, 2 Feb 2021 20:57:37 +0100
Subject: [PATCH 012/113] Use zola 0.13 for docs
---
docs/config.toml | 7 +++++--
docs/content/documentation/content/taxonomies.md | 2 +-
netlify.toml | 2 +-
3 files changed, 7 insertions(+), 4 deletions(-)
diff --git a/docs/config.toml b/docs/config.toml
index 62cc309c..1b367f53 100644
--- a/docs/config.toml
+++ b/docs/config.toml
@@ -3,10 +3,13 @@ title = "Zola"
description = "Everything you need to make a static site engine in one binary."
compile_sass = true
-highlight_code = true
insert_anchor_links = true
-highlight_theme = "kronuz"
build_search_index = true
+[markdown]
+highlight_code = true
+highlight_theme = "kronuz"
+
+
[extra]
author = "Vincent Prouillet"
diff --git a/docs/content/documentation/content/taxonomies.md b/docs/content/documentation/content/taxonomies.md
index 39e78a04..bfef476e 100644
--- a/docs/content/documentation/content/taxonomies.md
+++ b/docs/content/documentation/content/taxonomies.md
@@ -131,7 +131,7 @@ In a similar manner to how section and pages calculate their output path:
The taxonomy pages are then available at the following paths:
-```plain
+```
$BASE_URL/$NAME/ (taxonomy)
$BASE_URL/$NAME/$SLUG (taxonomy entry)
```
diff --git a/netlify.toml b/netlify.toml
index d9fb82b4..8074da34 100644
--- a/netlify.toml
+++ b/netlify.toml
@@ -4,7 +4,7 @@
command = "zola build"
[build.environment]
- ZOLA_VERSION = "0.11.0"
+ ZOLA_VERSION = "0.13.0"
[context.deploy-preview]
command = "zola build --base-url $DEPLOY_PRIME_URL"
From cf86e9398602cae63f580d136adc106daa7cfffb Mon Sep 17 00:00:00 2001
From: Vincent Prouillet
Date: Tue, 2 Feb 2021 21:31:17 +0100
Subject: [PATCH 013/113] Fix dir renaming not picked up by zola serve
Fix #1324
---
src/cmd/serve.rs | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/cmd/serve.rs b/src/cmd/serve.rs
index 122f83d4..ad4e9bb8 100644
--- a/src/cmd/serve.rs
+++ b/src/cmd/serve.rs
@@ -476,9 +476,11 @@ pub fn serve(
if is_ignored_file(&site.config.ignored_content_globset, &path) {
continue;
}
- if is_temp_file(&path) || path.is_dir() {
+
+ if path.is_file() && is_temp_file(&path) {
continue;
}
+
// We only care about changes in non-empty folders
if path.is_dir() && is_folder_empty(&path) {
continue;
From aa57541c21bc060c7e49b152b30842932b284fc3 Mon Sep 17 00:00:00 2001
From: Vincent Prouillet
Date: Tue, 2 Feb 2021 21:53:36 +0100
Subject: [PATCH 014/113] Force newline after front-matter
Closes #1287
---
CHANGELOG.md | 6 +++++-
components/front_matter/src/lib.rs | 10 ++++++----
components/library/src/content/page.rs | 4 ++--
components/templates/src/global_fns/load_data.rs | 3 +--
4 files changed, 14 insertions(+), 9 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8c486611..c147c306 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,7 +2,11 @@
## unreleased
-### Features
+### Breaking
+
+- Newlines are now required after the closing `+++` of front-matter
+
+### Other
- internal links are now resolved in the `markdown` filter in the templates (#1296 #1316)
- Add a `required` argument to `load_data` so it can be allowed to fail
diff --git a/components/front_matter/src/lib.rs b/components/front_matter/src/lib.rs
index 5b54fd51..9397af89 100644
--- a/components/front_matter/src/lib.rs
+++ b/components/front_matter/src/lib.rs
@@ -15,9 +15,9 @@ pub use section::SectionFrontMatter;
lazy_static! {
static ref TOML_RE: Regex =
- Regex::new(r"^[[:space:]]*\+\+\+(\r?\n(?s).*?(?-s))\+\+\+\r?\n?((?s).*(?-s))$").unwrap();
+ Regex::new(r"^[[:space:]]*\+\+\+(\r?\n(?s).*?(?-s))\+\+\+\r?\n((?s).*(?-s))$").unwrap();
static ref YAML_RE: Regex =
- Regex::new(r"^[[:space:]]*---(\r?\n(?s).*?(?-s))---\r?\n?((?s).*(?-s))$").unwrap();
+ Regex::new(r"^[[:space:]]*---(\r?\n(?s).*?(?-s))---\r?\n((?s).*(?-s))$").unwrap();
}
pub enum RawFrontMatter<'a> {
@@ -174,13 +174,15 @@ Hello
title = "Title"
description = "hey there"
date = 2002-10-12
-+++"#; "toml")]
++++
+"#; "toml")]
#[test_case(r#"
---
title: Title
description: hey there
date: 2002-10-12
----"#; "yaml")]
+---
+"#; "yaml")]
fn can_split_content_with_only_frontmatter_valid(content: &str) {
let (front_matter, content) = split_page_content(Path::new(""), content).unwrap();
assert_eq!(content, "");
diff --git a/components/library/src/content/page.rs b/components/library/src/content/page.rs
index c453b87f..13034158 100644
--- a/components/library/src/content/page.rs
+++ b/components/library/src/content/page.rs
@@ -490,7 +490,7 @@ Hello world"#;
let mut config = Config::default();
config.slugify.paths = SlugifyStrategy::On;
let res =
- Page::parse(Path::new(" file with space.md"), "+++\n+++", &config, &PathBuf::new());
+ Page::parse(Path::new(" file with space.md"), "+++\n+++\n", &config, &PathBuf::new());
assert!(res.is_ok());
let page = res.unwrap();
assert_eq!(page.slug, "file-with-space");
@@ -501,7 +501,7 @@ Hello world"#;
fn can_make_path_from_utf8_filename() {
let mut config = Config::default();
config.slugify.paths = SlugifyStrategy::Safe;
- let res = Page::parse(Path::new("日本.md"), "+++\n++++", &config, &PathBuf::new());
+ let res = Page::parse(Path::new("日本.md"), "+++\n+++\n", &config, &PathBuf::new());
assert!(res.is_ok());
let page = res.unwrap();
assert_eq!(page.slug, "日本");
diff --git a/components/templates/src/global_fns/load_data.rs b/components/templates/src/global_fns/load_data.rs
index a5b929fa..286b63c5 100644
--- a/components/templates/src/global_fns/load_data.rs
+++ b/components/templates/src/global_fns/load_data.rs
@@ -255,8 +255,7 @@ impl TeraFn for LoadData {
.into())
}
}
- }
- // Now that we have discarded recoverable errors, we can unwrap the result
+ } // Now that we have discarded recoverable errors, we can unwrap the result
}?;
let result_value: Result = match file_format {
From 53456bd052f0a55cdc8ed80a773f567c8023959a Mon Sep 17 00:00:00 2001
From: southerntofu <52931252+southerntofu@users.noreply.github.com>
Date: Thu, 4 Feb 2021 08:54:12 +0000
Subject: [PATCH 015/113] Don't panic for missing highlight theme (close #1299)
(#1307)
* No panic when markdown.highlight_theme is missing
* Better error message for missing theme
* Better error messages
Co-authored-by: southerntofu
---
components/config/src/config/mod.rs | 18 ++++++++----------
components/config/src/lib.rs | 12 +++---------
components/config/src/theme.rs | 12 ++++--------
components/site/src/lib.rs | 4 ++--
components/utils/src/fs.rs | 15 +--------------
5 files changed, 18 insertions(+), 43 deletions(-)
diff --git a/components/config/src/config/mod.rs b/components/config/src/config/mod.rs
index 415f215b..a8118c78 100644
--- a/components/config/src/config/mod.rs
+++ b/components/config/src/config/mod.rs
@@ -16,7 +16,7 @@ use toml::Value as Toml;
use crate::highlighting::THEME_SET;
use crate::theme::Theme;
use errors::{bail, Error, Result};
-use utils::fs::read_file_with_error;
+use utils::fs::read_file;
// We want a default base url for tests
static DEFAULT_BASE_URL: &str = "http://a-website.com";
@@ -124,8 +124,9 @@ impl Config {
bail!("A base URL is required in config.toml with key `base_url`");
}
- if !THEME_SET.themes.contains_key(&config.highlight_theme) {
- bail!("Highlight theme {} not available", config.highlight_theme)
+ let highlight_theme = config.highlight_theme();
+ if !THEME_SET.themes.contains_key(highlight_theme) {
+ bail!("Highlight theme {} defined in config does not exist.", highlight_theme);
}
if config.languages.iter().any(|l| l.code == config.default_language) {
@@ -169,11 +170,8 @@ impl Config {
/// Parses a config file from the given path
pub fn from_file>(path: P) -> Result {
let path = path.as_ref();
- let file_name = path.file_name().unwrap();
- let content = read_file_with_error(
- path,
- &format!("No `{:?}` file found. Are you in the right directory?", file_name),
- )?;
+ let content = read_file(path)
+ .map_err(|e| errors::Error::chain("Failed to load config", e))?;
Config::parse(&content)
}
@@ -270,8 +268,8 @@ impl Config {
/// Parse the theme.toml file and merges the extra data from the theme
/// with the config extra data
- pub fn merge_with_theme(&mut self, path: &PathBuf) -> Result<()> {
- let theme = Theme::from_file(path)?;
+ pub fn merge_with_theme(&mut self, path: &PathBuf, theme_name: &str) -> Result<()> {
+ let theme = Theme::from_file(path, theme_name)?;
self.add_theme_extra(&theme)
}
diff --git a/components/config/src/lib.rs b/components/config/src/lib.rs
index 529a8b4a..f431f232 100644
--- a/components/config/src/lib.rs
+++ b/components/config/src/lib.rs
@@ -4,18 +4,12 @@ mod theme;
pub use crate::config::{
languages::Language, link_checker::LinkChecker, slugify::Slugify, taxonomies::Taxonomy, Config,
};
+use errors::Result;
use std::path::Path;
/// Get and parse the config.
/// If it doesn't succeed, exit
-pub fn get_config(filename: &Path) -> Config {
- match Config::from_file(filename) {
- Ok(c) => c,
- Err(e) => {
- println!("Failed to load {}", filename.display());
- println!("Error: {}", e);
- ::std::process::exit(1);
- }
- }
+pub fn get_config(filename: &Path) -> Result {
+ Config::from_file(filename)
}
diff --git a/components/config/src/theme.rs b/components/config/src/theme.rs
index 6c68401a..5022319b 100644
--- a/components/config/src/theme.rs
+++ b/components/config/src/theme.rs
@@ -5,7 +5,7 @@ use serde_derive::{Deserialize, Serialize};
use toml::Value as Toml;
use errors::{bail, Result};
-use utils::fs::read_file_with_error;
+use utils::fs::read_file;
/// Holds the data from a `theme.toml` file.
/// There are other fields than `extra` in it but Zola
@@ -39,13 +39,9 @@ impl Theme {
}
/// Parses a theme file from the given path
- pub fn from_file(path: &PathBuf) -> Result {
- let content = read_file_with_error(
- path,
- "No `theme.toml` file found. \
- Is the `theme` defined in your `config.toml` present in the `themes` directory \
- and does it have a `theme.toml` inside?",
- )?;
+ pub fn from_file(path: &PathBuf, theme_name: &str) -> Result {
+ let content = read_file(path)
+ .map_err(|e| errors::Error::chain(format!("Failed to load theme {}", theme_name), e))?;
Theme::parse(&content)
}
}
diff --git a/components/site/src/lib.rs b/components/site/src/lib.rs
index edeaedbe..48932742 100644
--- a/components/site/src/lib.rs
+++ b/components/site/src/lib.rs
@@ -72,12 +72,12 @@ impl Site {
pub fn new, P2: AsRef>(path: P, config_file: P2) -> Result {
let path = path.as_ref();
let config_file = config_file.as_ref();
- let mut config = get_config(config_file);
+ let mut config = get_config(config_file)?;
config.load_extra_syntaxes(path)?;
if let Some(theme) = config.theme.clone() {
// Grab data from the extra section of the theme
- config.merge_with_theme(&path.join("themes").join(&theme).join("theme.toml"))?;
+ config.merge_with_theme(&path.join("themes").join(&theme).join("theme.toml"), &theme)?;
}
let tera = tpls::load_tera(path, &config)?;
diff --git a/components/utils/src/fs.rs b/components/utils/src/fs.rs
index a46f6d94..afb7a04e 100644
--- a/components/utils/src/fs.rs
+++ b/components/utils/src/fs.rs
@@ -49,7 +49,7 @@ pub fn create_directory(path: &Path) -> Result<()> {
pub fn read_file(path: &Path) -> Result {
let mut content = String::new();
File::open(path)
- .map_err(|e| Error::chain(format!("Failed to open '{:?}'", path.display()), e))?
+ .map_err(|e| Error::chain(format!("Failed to open '{}'", path.display()), e))?
.read_to_string(&mut content)?;
// Remove utf-8 BOM if any.
@@ -60,19 +60,6 @@ pub fn read_file(path: &Path) -> Result {
Ok(content)
}
-/// Return the content of a file, with error handling added.
-/// The default error message is overwritten by the message given.
-/// That means it is allocating 2 strings, oh well
-pub fn read_file_with_error(path: &Path, message: &str) -> Result {
- let res = read_file(&path);
- if res.is_ok() {
- return res;
- }
- let mut err = Error::msg(message);
- err.source = res.unwrap_err().source;
- Err(err)
-}
-
/// Looks into the current folder for the path and see if there's anything that is not a .md
/// file. Those will be copied next to the rendered .html file
pub fn find_related_assets(path: &Path) -> Vec {
From a65a2d52c70def075d8b4ed4c57dfd81b1f96ba3 Mon Sep 17 00:00:00 2001
From: Matt Riggott
Date: Sat, 6 Feb 2021 15:41:10 +0000
Subject: [PATCH 016/113] Update minify-html dependency to version 0.4.2
(#1340)
* Update minify-html dependency to version 0.4.2
Fixes https://github.com/getzola/zola/issues/1300. See also https://github.com/wilsonzlin/minify-html/issues/21
* Update minify-html dependency in Cargo.lock
* Add test to check pre whitespace isn't collapsed
---
Cargo.lock | 4 ++--
components/utils/Cargo.toml | 2 +-
components/utils/src/minify.rs | 31 +++++++++++++++++++++++++++++++
3 files changed, 34 insertions(+), 3 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 0099cb07..3e05b5ae 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1388,9 +1388,9 @@ dependencies = [
[[package]]
name = "minify-html"
-version = "0.4.1"
+version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "345dcbf4663db7d3a78a6e6b6c279c8bb6c32d6365cd98da977d7f7543b6c167"
+checksum = "d7af79ea8ac719a1cc299787058518a12018700af7ec7be4b789930526e1a1ab"
dependencies = [
"aho-corasick",
"lazy_static",
diff --git a/components/utils/Cargo.toml b/components/utils/Cargo.toml
index 2c2f664e..66bbca8a 100644
--- a/components/utils/Cargo.toml
+++ b/components/utils/Cargo.toml
@@ -15,7 +15,7 @@ serde_derive = "1"
slug = "0.1"
percent-encoding = "2"
filetime = "0.2.12"
-minify-html = "0.4"
+minify-html = "0.4.2"
errors = { path = "../errors" }
diff --git a/components/utils/src/minify.rs b/components/utils/src/minify.rs
index a0513262..faab7812 100644
--- a/components/utils/src/minify.rs
+++ b/components/utils/src/minify.rs
@@ -59,4 +59,35 @@ mod tests {
let res = html(input.to_owned()).unwrap();
assert_eq!(res, expected);
}
+
+ // https://github.com/getzola/zola/issues/1300
+ #[test]
+ fn can_minify_and_preserve_whitespace_in_pre_elements() {
+ let input = r#"
+
+
+
+
+
+
+ fn main() {
+ println!("Hello, world!");
+ loop {
+ println!("Hello, world!");
+ }
+ }
+
+
+
+"#;
+ let expected = r#"fn main() {
+ println!("Hello, world!");
+ loop {
+ println!("Hello, world!");
+ }
+ }
+
"#;
+ let res = html(input.to_owned()).unwrap();
+ assert_eq!(res, expected);
+ }
}
From 3cb61c3bcdb6fad6c43b75d9696c26cbaad3c911 Mon Sep 17 00:00:00 2001
From: Chiu-Hsiang Hsu <2716047+wdv4758h@users.noreply.github.com>
Date: Sun, 7 Feb 2021 16:13:03 +0000
Subject: [PATCH 017/113] Support embed YouTube playlist (#1342)
---
components/templates/src/builtins/shortcodes/youtube.html | 2 +-
docs/content/documentation/content/shortcodes.md | 3 +++
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/components/templates/src/builtins/shortcodes/youtube.html b/components/templates/src/builtins/shortcodes/youtube.html
index d1dbf38d..9d910aca 100644
--- a/components/templates/src/builtins/shortcodes/youtube.html
+++ b/components/templates/src/builtins/shortcodes/youtube.html
@@ -1,3 +1,3 @@
-
+
diff --git a/docs/content/documentation/content/shortcodes.md b/docs/content/documentation/content/shortcodes.md
index 8deb23c3..72c2834e 100644
--- a/docs/content/documentation/content/shortcodes.md
+++ b/docs/content/documentation/content/shortcodes.md
@@ -164,6 +164,7 @@ Embed a responsive player for a YouTube video.
The arguments are:
- `id`: the video id (mandatory)
+- `playlist: the playlist id (optional)
- `class`: a class to add to the `` surrounding the iframe
- `autoplay`: when set to "true", the video autoplays on load
@@ -172,6 +173,8 @@ Usage example:
```md
{{/* youtube(id="dQw4w9WgXcQ") */}}
+{{/* youtube(id="dQw4w9WgXcQ", playlist="RDdQw4w9WgXcQ") */}}
+
{{/* youtube(id="dQw4w9WgXcQ", autoplay=true) */}}
{{/* youtube(id="dQw4w9WgXcQ", autoplay=true, class="youtube") */}}
From d734b1723c4097fe2bf5da1fe6b0f5c0060c6c2d Mon Sep 17 00:00:00 2001
From: Chiu-Hsiang Hsu <2716047+wdv4758h@users.noreply.github.com>
Date: Sat, 13 Feb 2021 12:07:01 +0000
Subject: [PATCH 018/113] Fix "serve" subcommand for URL encoded static files
(#1351)
---
src/cmd/serve.rs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/cmd/serve.rs b/src/cmd/serve.rs
index ad4e9bb8..25051995 100644
--- a/src/cmd/serve.rs
+++ b/src/cmd/serve.rs
@@ -109,7 +109,7 @@ async fn handle_request(req: Request, mut root: PathBuf) -> Result
Date: Thu, 18 Feb 2021 16:53:40 +0000
Subject: [PATCH 019/113] add force arg long switch to CLI (#1362)
---
src/cli.rs | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/cli.rs b/src/cli.rs
index ab1321f9..ceef7be3 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -30,6 +30,7 @@ pub fn build_cli() -> App<'static, 'static> {
.help("Name of the project. Will create a new directory with that name in the current directory"),
Arg::with_name("force")
.short("f")
+ .long("force")
.takes_value(false)
.help("Force creation of project even if directory is non-empty")
]),
From 8eac5a5994c53e4d00bbad6ee9ccc7c706529f6b Mon Sep 17 00:00:00 2001
From: Philip Kristoffersen
Date: Thu, 18 Feb 2021 22:30:10 +0100
Subject: [PATCH 020/113] WebP support in resize_image (#1360)
* Removing unused webpl
* Adding clarification comment
* Updating documentation
* Adding webp
---
Cargo.lock | 36 +++++++++++++-
components/imageproc/Cargo.toml | 1 +
components/imageproc/src/lib.rs | 47 ++++++++++++++-----
components/templates/src/global_fns/mod.rs | 10 ++--
.../content/image-processing/index.md | 3 +-
5 files changed, 77 insertions(+), 20 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 3e05b5ae..e1dd981e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -212,6 +212,9 @@ name = "cc"
version = "1.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48"
+dependencies = [
+ "jobserver",
+]
[[package]]
name = "cedarwood"
@@ -1017,9 +1020,9 @@ dependencies = [
[[package]]
name = "image"
-version = "0.23.12"
+version = "0.23.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7ce04077ead78e39ae8610ad26216aed811996b043d47beed5090db674f9e9b5"
+checksum = "293f07a1875fa7e9c5897b51aa68b2d8ed8271b87e1a44cb64b9c3d98aabbc0d"
dependencies = [
"bytemuck",
"byteorder",
@@ -1045,6 +1048,7 @@ dependencies = [
"regex",
"tera",
"utils",
+ "webp",
]
[[package]]
@@ -1112,6 +1116,15 @@ dependencies = [
"regex",
]
+[[package]]
+name = "jobserver"
+version = "0.1.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c71313ebb9439f74b00d9d2dcec36440beaf57a6aa0623068441dd7cd81a7f2"
+dependencies = [
+ "libc",
+]
+
[[package]]
name = "jpeg-decoder"
version = "0.1.22"
@@ -1209,6 +1222,15 @@ dependencies = [
"utils",
]
+[[package]]
+name = "libwebp-sys"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e70c064738b35a28fd6f991d27c0d9680353641d167ae3702a8228dd8272ef6"
+dependencies = [
+ "cc",
+]
+
[[package]]
name = "lindera"
version = "0.3.5"
@@ -3114,6 +3136,16 @@ dependencies = [
"wasm-bindgen",
]
+[[package]]
+name = "webp"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea1f2bd35e46165ef40a7fd74f33f64f2912ad92593fbfc5ec75eb2604cfd7b5"
+dependencies = [
+ "image",
+ "libwebp-sys",
+]
+
[[package]]
name = "webpki"
version = "0.21.4"
diff --git a/components/imageproc/Cargo.toml b/components/imageproc/Cargo.toml
index b4547978..a24e8a34 100644
--- a/components/imageproc/Cargo.toml
+++ b/components/imageproc/Cargo.toml
@@ -10,6 +10,7 @@ regex = "1.0"
tera = "1"
image = "0.23"
rayon = "1"
+webp="0.1.1"
errors = { path = "../errors" }
utils = { path = "../utils" }
diff --git a/components/imageproc/src/lib.rs b/components/imageproc/src/lib.rs
index 0b0a8b48..e0daaf64 100644
--- a/components/imageproc/src/lib.rs
+++ b/components/imageproc/src/lib.rs
@@ -1,11 +1,11 @@
-use std::collections::hash_map::DefaultHasher;
+use std::{collections::hash_map::DefaultHasher, io::Write};
use std::collections::hash_map::Entry as HEntry;
use std::collections::HashMap;
use std::fs::{self, File};
use std::hash::{Hash, Hasher};
use std::path::{Path, PathBuf};
-use image::imageops::FilterType;
+use image::{EncodableLayout, imageops::FilterType};
use image::{GenericImageView, ImageOutputFormat};
use lazy_static::lazy_static;
use rayon::prelude::*;
@@ -18,7 +18,7 @@ static RESIZED_SUBDIR: &str = "processed_images";
lazy_static! {
pub static ref RESIZED_FILENAME: Regex =
- Regex::new(r#"([0-9a-f]{16})([0-9a-f]{2})[.](jpg|png)"#).unwrap();
+ Regex::new(r#"([0-9a-f]{16})([0-9a-f]{2})[.](jpg|png|webp)"#).unwrap();
}
/// Describes the precise kind of a resize operation
@@ -132,6 +132,7 @@ impl Hash for ResizeOp {
}
}
}
+const DEFAULT_Q_JPG: u8 = 75;
/// Thumbnail image format
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -140,22 +141,26 @@ pub enum Format {
Jpeg(u8),
/// PNG
Png,
+ /// WebP, The `u8` argument is WebP quality (in percent), None meaning lossless.
+ WebP(Option),
}
impl Format {
- pub fn from_args(source: &str, format: &str, quality: u8) -> Result {
+ pub fn from_args(source: &str, format: &str, quality: Option) -> Result {
use Format::*;
-
- assert!(quality > 0 && quality <= 100, "Jpeg quality must be within the range [1; 100]");
-
+ if let Some(quality) = quality {
+ assert!(quality > 0 && quality <= 100, "Quality must be within the range [1; 100]");
+ }
+ let jpg_quality = quality.unwrap_or(DEFAULT_Q_JPG);
match format {
"auto" => match Self::is_lossy(source) {
- Some(true) => Ok(Jpeg(quality)),
+ Some(true) => Ok(Jpeg(jpg_quality)),
Some(false) => Ok(Png),
None => Err(format!("Unsupported image file: {}", source).into()),
},
- "jpeg" | "jpg" => Ok(Jpeg(quality)),
- "png" => Ok(Png),
+ "jpeg" | "jpg" => Ok(Jpeg(jpg_quality)),
+ "png" => Ok(Png),
+ "webp" => Ok(WebP(quality)),
_ => Err(format!("Invalid image format: {}", format).into()),
}
}
@@ -170,6 +175,8 @@ impl Format {
"png" => Some(false),
"gif" => Some(false),
"bmp" => Some(false),
+ // It is assumed that webp is lossless, but it can be both
+ "webp" => Some(false),
_ => None,
})
.unwrap_or(None)
@@ -182,6 +189,7 @@ impl Format {
match *self {
Png => "png",
Jpeg(_) => "jpg",
+ WebP(_) => "webp"
}
}
}
@@ -193,7 +201,9 @@ impl Hash for Format {
let q = match *self {
Png => 0,
- Jpeg(q) => q,
+ Jpeg(q) => q,
+ WebP(None) => 0,
+ WebP(Some(q)) => q
};
hasher.write_u8(q);
@@ -232,7 +242,7 @@ impl ImageOp {
width: Option,
height: Option,
format: &str,
- quality: u8,
+ quality: Option,
) -> Result {
let op = ResizeOp::from_args(op, width, height)?;
let format = Format::from_args(&source, format, quality)?;
@@ -303,6 +313,19 @@ impl ImageOp {
Format::Jpeg(q) => {
img.write_to(&mut f, ImageOutputFormat::Jpeg(q))?;
}
+ Format::WebP(q) => {
+ let encoder = webp::Encoder::from_image(&img);
+ let memory = match q {
+ Some(q) => {
+ encoder.encode(q as f32 / 100.)
+ }
+ None => {
+ encoder.encode_lossless()
+ }
+ };
+ let mut bytes = memory.as_bytes();
+ f.write_all(&mut bytes)?;
+ }
}
Ok(())
diff --git a/components/templates/src/global_fns/mod.rs b/components/templates/src/global_fns/mod.rs
index fa7bb283..7f4d48d4 100644
--- a/components/templates/src/global_fns/mod.rs
+++ b/components/templates/src/global_fns/mod.rs
@@ -221,7 +221,6 @@ impl ResizeImage {
static DEFAULT_OP: &str = "fill";
static DEFAULT_FMT: &str = "auto";
-const DEFAULT_Q: u8 = 75;
impl TeraFn for ResizeImage {
fn call(&self, args: &HashMap) -> Result {
@@ -248,10 +247,11 @@ impl TeraFn for ResizeImage {
.unwrap_or_else(|| DEFAULT_FMT.to_string());
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());
+ optional_arg!(u8, args.get("quality"), "`resize_image`: `quality` must be a number");
+ if let Some(quality) = quality {
+ if quality == 0 || quality > 100 {
+ return Err("`resize_image`: `quality` must be in range 1-100".to_string().into());
+ }
}
let mut imageproc = self.imageproc.lock().unwrap();
diff --git a/docs/content/documentation/content/image-processing/index.md b/docs/content/documentation/content/image-processing/index.md
index bd860c37..30a4a310 100644
--- a/docs/content/documentation/content/image-processing/index.md
+++ b/docs/content/documentation/content/image-processing/index.md
@@ -28,10 +28,11 @@ resize_image(path, width, height, op, format, quality)
- `"auto"`
- `"jpg"`
- `"png"`
+ - `"webp"`
The default is `"auto"`, this means that the format is chosen based on input image format.
JPEG is chosen for JPEGs and other lossy formats, and PNG is chosen for PNGs and other lossless formats.
-- `quality` (_optional_): JPEG quality of the resized image, in percent. Only used when encoding JPEGs; default value is `75`.
+- `quality` (_optional_): JPEG or WebP quality of the resized image, in percent. Only used when encoding JPEGs or WebPs; for JPEG default value is `75`, for WebP default is lossless.
### Image processing and return value
From 3ba2d3356471590ccf57a51462faab0774933857 Mon Sep 17 00:00:00 2001
From: apiraino
Date: Fri, 19 Feb 2021 18:46:36 +0100
Subject: [PATCH 021/113] Copyright date always ending on current year (#1366)
---
docs/templates/index.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/templates/index.html b/docs/templates/index.html
index 307ccfe1..5fbdb9bc 100644
--- a/docs/templates/index.html
+++ b/docs/templates/index.html
@@ -99,7 +99,7 @@
{% endblock content %}
From 6d6df45f2377e2e319a85e40ea37b93b553d3998 Mon Sep 17 00:00:00 2001
From: Hanno Braun
Date: Fri, 19 Feb 2021 20:51:08 +0100
Subject: [PATCH 022/113] Make shortcodes work in `markdown` filter (#1358)
* Move `load_tera` to `templates`
I don't know if this is a good place for it, conceptually. I'm moving it
there because I need to use it from `templates`, and `templates` can't
depend on `site`, because there's already a dependency in the opposite
direction.
* Load templates in `markdown` filter
This enables the `markdown` filter to handle shortcodes, as long as
those shortcodes don't access any context variables.
Addresses #1350
* Update documentation of `markdown` filter
* Only load templates for `markdown` filter once
* Clarify `markdown` filter documentation
This is a lightly edited version of what @southerntofu suggested.
---
components/site/src/lib.rs | 6 +-
components/site/src/tpls.rs | 60 ++++---------------
components/templates/src/filters.rs | 52 ++++++++++------
components/templates/src/lib.rs | 46 +++++++++++++-
.../documentation/templates/overview.md | 3 +-
5 files changed, 93 insertions(+), 74 deletions(-)
diff --git a/components/site/src/lib.rs b/components/site/src/lib.rs
index 48932742..bc374134 100644
--- a/components/site/src/lib.rs
+++ b/components/site/src/lib.rs
@@ -20,7 +20,7 @@ use front_matter::InsertAnchor;
use library::{find_taxonomies, Library, Page, Paginator, Section, Taxonomy};
use relative_path::RelativePathBuf;
use std::time::Instant;
-use templates::render_redirect_template;
+use templates::{load_tera, render_redirect_template};
use utils::fs::{
copy_directory, copy_file_if_needed, create_directory, create_file, ensure_directory_exists,
};
@@ -80,7 +80,7 @@ impl Site {
config.merge_with_theme(&path.join("themes").join(&theme).join("theme.toml"), &theme)?;
}
- let tera = tpls::load_tera(path, &config)?;
+ let tera = load_tera(path, &config)?;
let content_path = path.join("content");
let static_path = path.join("static");
@@ -295,7 +295,7 @@ impl Site {
// taxonomy Tera fns are loaded in `register_early_global_fns`
// so we do need to populate it first.
self.populate_taxonomies()?;
- tpls::register_early_global_fns(self);
+ tpls::register_early_global_fns(self)?;
self.populate_sections();
self.render_markdown()?;
tpls::register_tera_global_fns(self);
diff --git a/components/site/src/tpls.rs b/components/site/src/tpls.rs
index 0377695e..19c6f7ff 100644
--- a/components/site/src/tpls.rs
+++ b/components/site/src/tpls.rs
@@ -1,58 +1,16 @@
-use std::path::Path;
-
-use tera::Tera;
-
use crate::Site;
-use config::Config;
-use errors::{bail, Error, Result};
-use templates::{filters, global_fns, ZOLA_TERA};
-use utils::templates::rewrite_theme_paths;
-
-pub fn load_tera(path: &Path, config: &Config) -> Result {
- let tpl_glob =
- format!("{}/{}", path.to_string_lossy().replace("\\", "/"), "templates/**/*.{*ml,md}");
-
- // Only parsing as we might be extending templates from themes and that would error
- // as we haven't loaded them yet
- let mut tera =
- Tera::parse(&tpl_glob).map_err(|e| Error::chain("Error parsing templates", e))?;
-
- if let Some(ref theme) = config.theme {
- // Test that the templates folder exist for that theme
- let theme_path = path.join("themes").join(&theme);
- if !theme_path.join("templates").exists() {
- bail!("Theme `{}` is missing a templates folder", theme);
- }
-
- let theme_tpl_glob = format!(
- "{}/{}",
- path.to_string_lossy().replace("\\", "/"),
- format!("themes/{}/templates/**/*.{{*ml,md}}", theme)
- );
- let mut tera_theme = Tera::parse(&theme_tpl_glob)
- .map_err(|e| Error::chain("Error parsing templates from themes", e))?;
- rewrite_theme_paths(&mut tera_theme, &theme);
-
- if theme_path.join("templates").join("robots.txt").exists() {
- tera_theme.add_template_file(theme_path.join("templates").join("robots.txt"), None)?;
- }
- tera.extend(&tera_theme)?;
- }
- tera.extend(&ZOLA_TERA)?;
- tera.build_inheritance_chains()?;
-
- if path.join("templates").join("robots.txt").exists() {
- tera.add_template_file(path.join("templates").join("robots.txt"), Some("robots.txt"))?;
- }
-
- Ok(tera)
-}
+use templates::{filters, global_fns};
+use tera::Result as TeraResult;
/// Adds global fns that are to be available to shortcodes while rendering markdown
-pub fn register_early_global_fns(site: &mut Site) {
+pub fn register_early_global_fns(site: &mut Site) -> TeraResult<()> {
site.tera.register_filter(
"markdown",
- filters::MarkdownFilter::new(site.config.clone(), site.permalinks.clone()),
+ filters::MarkdownFilter::new(
+ site.base_path.clone(),
+ site.config.clone(),
+ site.permalinks.clone(),
+ )?,
);
site.tera.register_function(
@@ -87,6 +45,8 @@ pub fn register_early_global_fns(site: &mut Site) {
site.content_path.clone(),
]),
);
+
+ Ok(())
}
/// Functions filled once we have parsed all the pages/sections only, so not available in shortcodes
diff --git a/components/templates/src/filters.rs b/components/templates/src/filters.rs
index 51ecca47..5e8575bd 100644
--- a/components/templates/src/filters.rs
+++ b/components/templates/src/filters.rs
@@ -1,21 +1,27 @@
use std::borrow::Cow;
use std::collections::HashMap;
use std::hash::BuildHasher;
+use std::path::PathBuf;
use base64::{decode, encode};
use config::Config;
use rendering::{render_content, RenderContext};
-use tera::{to_value, try_get_value, Filter as TeraFilter, Result as TeraResult, Value};
+use tera::{Filter as TeraFilter, Result as TeraResult, Tera, Value, to_value, try_get_value};
+
+use crate::load_tera;
#[derive(Debug)]
pub struct MarkdownFilter {
config: Config,
permalinks: HashMap,
+ tera: Tera,
}
impl MarkdownFilter {
- pub fn new(config: Config, permalinks: HashMap) -> Self {
- Self { config, permalinks }
+ pub fn new(path: PathBuf, config: Config, permalinks: HashMap) -> TeraResult {
+ let tera = load_tera(&path, &config)
+ .map_err(|err| tera::Error::msg(err))?;
+ Ok(Self { config, permalinks, tera })
}
}
@@ -23,6 +29,8 @@ impl TeraFilter for MarkdownFilter {
fn filter(&self, value: &Value, args: &HashMap) -> TeraResult {
let mut context = RenderContext::from_config(&self.config);
context.permalinks = Cow::Borrowed(&self.permalinks);
+ context.tera = Cow::Borrowed(&self.tera);
+
let s = try_get_value!("markdown", "value", String, value);
let inline = match args.get("inline") {
Some(val) => try_get_value!("markdown", "inline", bool, val),
@@ -63,7 +71,7 @@ pub fn base64_decode(
#[cfg(test)]
mod tests {
- use std::collections::HashMap;
+ use std::{collections::HashMap, path::PathBuf};
use tera::{to_value, Filter};
@@ -72,7 +80,8 @@ mod tests {
#[test]
fn markdown_filter() {
- let result = MarkdownFilter::new(Config::default(), HashMap::new())
+ let result = MarkdownFilter::new(PathBuf::new(), Config::default(), HashMap::new())
+ .unwrap()
.filter(&to_value(&"# Hey").unwrap(), &HashMap::new());
assert!(result.is_ok());
assert_eq!(result.unwrap(), to_value(&"Hey
\n").unwrap());
@@ -82,10 +91,11 @@ mod tests {
fn markdown_filter_inline() {
let mut args = HashMap::new();
args.insert("inline".to_string(), to_value(true).unwrap());
- let result = MarkdownFilter::new(Config::default(), HashMap::new()).filter(
- &to_value(&"Using `map`, `filter`, and `fold` instead of `for`").unwrap(),
- &args,
- );
+ let result =
+ MarkdownFilter::new(PathBuf::new(), Config::default(), HashMap::new()).unwrap().filter(
+ &to_value(&"Using `map`, `filter`, and `fold` instead of `for`").unwrap(),
+ &args,
+ );
assert!(result.is_ok());
assert_eq!(result.unwrap(), to_value(&"Using map
, filter
, and fold
instead of for
").unwrap());
}
@@ -95,18 +105,19 @@ mod tests {
fn markdown_filter_inline_tables() {
let mut args = HashMap::new();
args.insert("inline".to_string(), to_value(true).unwrap());
- let result = MarkdownFilter::new(Config::default(), HashMap::new()).filter(
- &to_value(
- &r#"
+ let result =
+ MarkdownFilter::new(PathBuf::new(), Config::default(), HashMap::new()).unwrap().filter(
+ &to_value(
+ &r#"
|id|author_id| timestamp_created|title |content |
|-:|--------:|-----------------------:|:---------------------|:-----------------|
| 1| 1|2018-09-05 08:03:43.141Z|How to train your ORM |Badly written blog|
| 2| 1|2018-08-22 13:11:50.050Z|How to bake a nice pie|Badly written blog|
"#,
- )
- .unwrap(),
- &args,
- );
+ )
+ .unwrap(),
+ &args,
+ );
assert!(result.is_ok());
assert!(result.unwrap().as_str().unwrap().contains(""));
}
@@ -120,13 +131,15 @@ mod tests {
config.markdown.external_links_target_blank = true;
let md = "Hello :smile: ...";
- let result = MarkdownFilter::new(config.clone(), HashMap::new())
+ let result = MarkdownFilter::new(PathBuf::new(), config.clone(), HashMap::new())
+ .unwrap()
.filter(&to_value(&md).unwrap(), &HashMap::new());
assert!(result.is_ok());
assert_eq!(result.unwrap(), to_value(&"Hello https://google.com 😄 …
\n").unwrap());
let md = "```py\ni=0\n```";
- let result = MarkdownFilter::new(config, HashMap::new())
+ let result = MarkdownFilter::new(PathBuf::new(), config, HashMap::new())
+ .unwrap()
.filter(&to_value(&md).unwrap(), &HashMap::new());
assert!(result.is_ok());
assert!(result.unwrap().as_str().unwrap().contains(" Result {
tera.render("internal/alias.html", &context)
.map_err(|e| Error::chain(format!("Failed to render alias for '{}'", url), e))
}
+
+pub fn load_tera(path: &Path, config: &Config) -> Result {
+ let tpl_glob =
+ format!("{}/{}", path.to_string_lossy().replace("\\", "/"), "templates/**/*.{*ml,md}");
+
+ // Only parsing as we might be extending templates from themes and that would error
+ // as we haven't loaded them yet
+ let mut tera =
+ Tera::parse(&tpl_glob).map_err(|e| Error::chain("Error parsing templates", e))?;
+
+ if let Some(ref theme) = config.theme {
+ // Test that the templates folder exist for that theme
+ let theme_path = path.join("themes").join(&theme);
+ if !theme_path.join("templates").exists() {
+ bail!("Theme `{}` is missing a templates folder", theme);
+ }
+
+ let theme_tpl_glob = format!(
+ "{}/{}",
+ path.to_string_lossy().replace("\\", "/"),
+ format!("themes/{}/templates/**/*.{{*ml,md}}", theme)
+ );
+ let mut tera_theme = Tera::parse(&theme_tpl_glob)
+ .map_err(|e| Error::chain("Error parsing templates from themes", e))?;
+ rewrite_theme_paths(&mut tera_theme, &theme);
+
+ if theme_path.join("templates").join("robots.txt").exists() {
+ tera_theme.add_template_file(theme_path.join("templates").join("robots.txt"), None)?;
+ }
+ tera.extend(&tera_theme)?;
+ }
+ tera.extend(&ZOLA_TERA)?;
+ tera.build_inheritance_chains()?;
+
+ if path.join("templates").join("robots.txt").exists() {
+ tera.add_template_file(path.join("templates").join("robots.txt"), Some("robots.txt"))?;
+ }
+
+ Ok(tera)
+}
diff --git a/docs/content/documentation/templates/overview.md b/docs/content/documentation/templates/overview.md
index 086294d5..118eeb4d 100644
--- a/docs/content/documentation/templates/overview.md
+++ b/docs/content/documentation/templates/overview.md
@@ -64,7 +64,8 @@ Zola adds a few filters in addition to [those](https://tera.netlify.com/docs/#fi
in Tera.
### markdown
-Converts the given variable to HTML using Markdown. Shortcodes won't work within this filter.
+Converts the given variable to HTML using Markdown. Please note that shortcodes evaluated by this filter cannot access the current rendering context. `config` will be available, but accessing `section` or `page` (among others) from a shortcode called within the `markdown` filter will prevent your site from building. See [this discussion](https://github.com/getzola/zola/pull/1358).
+
By default, the filter will wrap all text in a paragraph. To disable this behaviour, you can
pass `true` to the inline argument:
From d3ab3936de8657ee405693b7ed79790af90a313f Mon Sep 17 00:00:00 2001
From: Skyper
Date: Sat, 20 Feb 2021 12:31:37 +0000
Subject: [PATCH 023/113] [WIP] Add support for base64-encoded hash values to
'get_file_hash' (#1339)
* Add support for base64-encoded hash values
The global template function 'get_file_hash' can now return a
base64-encoded hash value when its 'base64' parameter is set to true.
See discussion in #519.
* Fix integrity attribute's value in test site
SRI hash values must be base64-encoded.
* Update documentation about 'get_file_hash'
* Fix 'can_get_hash_for_static_files' unit test
---
components/site/tests/site.rs | 7 +-
components/templates/src/global_fns/mod.rs | 84 +++++++++++++++++--
.../documentation/templates/overview.md | 18 ++--
test_site/templates/index.html | 2 +-
4 files changed, 96 insertions(+), 15 deletions(-)
diff --git a/components/site/tests/site.rs b/components/site/tests/site.rs
index 1958b25d..178cda04 100644
--- a/components/site/tests/site.rs
+++ b/components/site/tests/site.rs
@@ -761,8 +761,11 @@ fn can_get_hash_for_static_files() {
"index.html",
"src=\"https://replace-this-with-your-url.com/scripts/hello.js\""
));
- assert!(file_contains!(public, "index.html",
- "integrity=\"sha384-01422f31eaa721a6c4ac8c6fa09a27dd9259e0dfcf3c7593d7810d912a9de5ca2f582df978537bcd10f76896db61fbb9\""));
+ assert!(file_contains!(
+ public,
+ "index.html",
+ "integrity=\"sha384-AUIvMeqnIabErIxvoJon3ZJZ4N/PPHWT14ENkSqd5covWC35eFN7zRD3aJbbYfu5\""
+ ));
}
#[test]
diff --git a/components/templates/src/global_fns/mod.rs b/components/templates/src/global_fns/mod.rs
index 7f4d48d4..8b8fc910 100644
--- a/components/templates/src/global_fns/mod.rs
+++ b/components/templates/src/global_fns/mod.rs
@@ -4,6 +4,7 @@ use std::path::PathBuf;
use std::sync::{Arc, Mutex, RwLock};
use std::{fs, io, result};
+use base64::encode as encode_b64;
use sha2::{Digest, Sha256, Sha384, Sha512};
use svg_metadata as svg;
use tera::{from_value, to_value, Error, Function as TeraFn, Result, Value};
@@ -95,18 +96,36 @@ fn compute_file_sha256(mut file: fs::File) -> result::Result
Ok(format!("{:x}", hasher.finalize()))
}
+fn compute_file_sha256_base64(mut file: fs::File) -> result::Result {
+ let mut hasher = Sha256::new();
+ io::copy(&mut file, &mut hasher)?;
+ Ok(format!("{}", encode_b64(hasher.finalize())))
+}
+
fn compute_file_sha384(mut file: fs::File) -> result::Result {
let mut hasher = Sha384::new();
io::copy(&mut file, &mut hasher)?;
Ok(format!("{:x}", hasher.finalize()))
}
+fn compute_file_sha384_base64(mut file: fs::File) -> result::Result {
+ let mut hasher = Sha384::new();
+ io::copy(&mut file, &mut hasher)?;
+ Ok(format!("{}", encode_b64(hasher.finalize())))
+}
+
fn compute_file_sha512(mut file: fs::File) -> result::Result {
let mut hasher = Sha512::new();
io::copy(&mut file, &mut hasher)?;
Ok(format!("{:x}", hasher.finalize()))
}
+fn compute_file_sha512_base64(mut file: fs::File) -> result::Result {
+ let mut hasher = Sha512::new();
+ io::copy(&mut file, &mut hasher)?;
+ Ok(format!("{}", encode_b64(hasher.finalize())))
+}
+
fn file_not_found_err(search_paths: &[PathBuf], url: &str) -> Result {
Err(format!(
"file `{}` not found; searched in{}",
@@ -178,6 +197,7 @@ impl GetFileHash {
}
const DEFAULT_SHA_TYPE: u16 = 384;
+const DEFAULT_BASE64: bool = false;
impl TeraFn for GetFileHash {
fn call(&self, args: &HashMap) -> Result {
@@ -192,12 +212,21 @@ impl TeraFn for GetFileHash {
"`get_file_hash`: `sha_type` must be 256, 384 or 512"
)
.unwrap_or(DEFAULT_SHA_TYPE);
+ let base64 = optional_arg!(
+ bool,
+ args.get("base64"),
+ "`get_file_hash`: `base64` must be true or false"
+ )
+ .unwrap_or(DEFAULT_BASE64);
- let compute_hash_fn = match sha_type {
- 256 => compute_file_sha256,
- 384 => compute_file_sha384,
- 512 => compute_file_sha512,
- _ => return Err("`get_file_hash`: `sha_type` must be 256, 384 or 512".into()),
+ let compute_hash_fn = match (sha_type, base64) {
+ (256, true) => compute_file_sha256_base64,
+ (256, false) => compute_file_sha256,
+ (384, true) => compute_file_sha384_base64,
+ (384, false) => compute_file_sha384,
+ (512, true) => compute_file_sha512_base64,
+ (512, false) => compute_file_sha512,
+ _ => return Err("`get_file_hash`: bad arguments".into()),
};
let hash = open_file(&self.search_paths, &path).and_then(compute_hash_fn);
@@ -819,12 +848,37 @@ title = "A title"
);
}
+ #[test]
+ fn can_get_file_hash_sha256_base64() {
+ let static_fn = GetFileHash::new(vec![TEST_CONTEXT.static_path.clone()]);
+ let mut args = HashMap::new();
+ args.insert("path".to_string(), to_value("app.css").unwrap());
+ args.insert("sha_type".to_string(), to_value(256).unwrap());
+ args.insert("base64".to_string(), to_value(true).unwrap());
+ assert_eq!(static_fn.call(&args).unwrap(), "Vy5pHcaMP81lOuRjJhvbOPNdxvAXFdnOaHmTGd0ViEA=");
+ }
+
#[test]
fn can_get_file_hash_sha384() {
let static_fn = GetFileHash::new(vec![TEST_CONTEXT.static_path.clone()]);
let mut args = HashMap::new();
args.insert("path".to_string(), to_value("app.css").unwrap());
- assert_eq!(static_fn.call(&args).unwrap(), "141c09bd28899773b772bbe064d8b718fa1d6f2852b7eafd5ed6689d26b74883b79e2e814cd69d5b52ab476aa284c414");
+ assert_eq!(
+ static_fn.call(&args).unwrap(),
+ "141c09bd28899773b772bbe064d8b718fa1d6f2852b7eafd5ed6689d26b74883b79e2e814cd69d5b52ab476aa284c414"
+ );
+ }
+
+ #[test]
+ fn can_get_file_hash_sha384_base64() {
+ let static_fn = GetFileHash::new(vec![TEST_CONTEXT.static_path.clone()]);
+ let mut args = HashMap::new();
+ args.insert("path".to_string(), to_value("app.css").unwrap());
+ args.insert("base64".to_string(), to_value(true).unwrap());
+ assert_eq!(
+ static_fn.call(&args).unwrap(),
+ "FBwJvSiJl3O3crvgZNi3GPodbyhSt+r9XtZonSa3SIO3ni6BTNadW1KrR2qihMQU"
+ );
}
#[test]
@@ -833,7 +887,23 @@ title = "A title"
let mut args = HashMap::new();
args.insert("path".to_string(), to_value("app.css").unwrap());
args.insert("sha_type".to_string(), to_value(512).unwrap());
- assert_eq!(static_fn.call(&args).unwrap(), "379dfab35123b9159d9e4e92dc90e2be44cf3c2f7f09b2e2df80a1b219b461de3556c93e1a9ceb3008e999e2d6a54b4f1d65ee9be9be63fa45ec88931623372f");
+ assert_eq!(
+ static_fn.call(&args).unwrap(),
+ "379dfab35123b9159d9e4e92dc90e2be44cf3c2f7f09b2e2df80a1b219b461de3556c93e1a9ceb3008e999e2d6a54b4f1d65ee9be9be63fa45ec88931623372f"
+ );
+ }
+
+ #[test]
+ fn can_get_file_hash_sha512_base64() {
+ let static_fn = GetFileHash::new(vec![TEST_CONTEXT.static_path.clone()]);
+ let mut args = HashMap::new();
+ args.insert("path".to_string(), to_value("app.css").unwrap());
+ args.insert("sha_type".to_string(), to_value(512).unwrap());
+ args.insert("base64".to_string(), to_value(true).unwrap());
+ assert_eq!(
+ static_fn.call(&args).unwrap(),
+ "N536s1EjuRWdnk6S3JDivkTPPC9/CbLi34Chshm0Yd41Vsk+GpzrMAjpmeLWpUtPHWXum+m+Y/pF7IiTFiM3Lw=="
+ );
}
#[test]
diff --git a/docs/content/documentation/templates/overview.md b/docs/content/documentation/templates/overview.md
index 118eeb4d..f324eb36 100644
--- a/docs/content/documentation/templates/overview.md
+++ b/docs/content/documentation/templates/overview.md
@@ -144,26 +144,34 @@ An example is:
In the case of non-internal links, you can also add a cachebust of the format `?h=` at the end of a URL
by passing `cachebust=true` to the `get_url` function.
-
### `get_file_hash`
-Gets the hash digest for a static file. Supported hashes are SHA-256, SHA-384 (default) and SHA-512. Requires `path`. The `sha_type` key is optional and must be one of 256, 384 or 512.
+Returns the hash digest of a static file. Supported hashing algorithms are
+SHA-256, SHA-384 (default) and SHA-512. Requires `path`. The `sha_type`
+parameter is optional and must be one of 256, 384 or 512.
```jinja2
{{/* get_file_hash(path="js/app.js", sha_type=256) */}}
```
-This can be used to implement subresource integrity. Do note that subresource integrity is typically used when using external scripts, which `get_file_hash` does not support.
+The function can also output a base64-encoded hash value when its `base64`
+parameter is set to `true`. This can be used to implement [subresource
+integrity](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity).
```jinja2
+ integrity="sha384-{{ get_file_hash(path="js/app.js", sha_type=384, base64=true) | safe }}">
```
-Whenever hashing files, whether using `get_file_hash` or `get_url(..., cachebust=true)`, the file is searched for in three places: `static/`, `content/` and the output path (so e.g. compiled SASS can be hashed, too.)
+Do note that subresource integrity is typically used when using external
+scripts, which `get_file_hash` does not support.
+Whenever hashing files, whether using `get_file_hash` or `get_url(...,
+cachebust=true)`, the file is searched for in three places: `static/`,
+`content/` and the output path (so e.g. compiled SASS can be hashed, too.)
### `get_image_metadata`
+
Gets metadata for an image. This supports common formats like JPEG, PNG, as well as SVG.
Currently, the only supported keys are `width` and `height`.
diff --git a/test_site/templates/index.html b/test_site/templates/index.html
index 9ed72df3..35a24be4 100644
--- a/test_site/templates/index.html
+++ b/test_site/templates/index.html
@@ -14,5 +14,5 @@
{% block script %}
+ integrity="sha384-{{ get_file_hash(path="scripts/hello.js", base64=true) | safe }}">
{% endblock script %}
From 3262f69bb06b619d333aacae7cb0e5abe8d497e5 Mon Sep 17 00:00:00 2001
From: Vincent Prouillet
Date: Sat, 20 Feb 2021 14:07:36 +0100
Subject: [PATCH 024/113] Single compute_hash fn + cargo fmt
---
components/config/src/config/mod.rs | 4 +-
components/imageproc/src/lib.rs | 20 +++---
components/site/src/lib.rs | 3 +-
components/templates/src/filters.rs | 11 ++--
components/templates/src/global_fns/mod.rs | 74 +++++++++-------------
components/templates/src/lib.rs | 2 +-
6 files changed, 50 insertions(+), 64 deletions(-)
diff --git a/components/config/src/config/mod.rs b/components/config/src/config/mod.rs
index a8118c78..57682b3b 100644
--- a/components/config/src/config/mod.rs
+++ b/components/config/src/config/mod.rs
@@ -170,8 +170,8 @@ impl Config {
/// Parses a config file from the given path
pub fn from_file>(path: P) -> Result {
let path = path.as_ref();
- let content = read_file(path)
- .map_err(|e| errors::Error::chain("Failed to load config", e))?;
+ let content =
+ read_file(path).map_err(|e| errors::Error::chain("Failed to load config", e))?;
Config::parse(&content)
}
diff --git a/components/imageproc/src/lib.rs b/components/imageproc/src/lib.rs
index e0daaf64..97cad10e 100644
--- a/components/imageproc/src/lib.rs
+++ b/components/imageproc/src/lib.rs
@@ -1,11 +1,11 @@
-use std::{collections::hash_map::DefaultHasher, io::Write};
use std::collections::hash_map::Entry as HEntry;
use std::collections::HashMap;
use std::fs::{self, File};
use std::hash::{Hash, Hasher};
use std::path::{Path, PathBuf};
+use std::{collections::hash_map::DefaultHasher, io::Write};
-use image::{EncodableLayout, imageops::FilterType};
+use image::{imageops::FilterType, EncodableLayout};
use image::{GenericImageView, ImageOutputFormat};
use lazy_static::lazy_static;
use rayon::prelude::*;
@@ -159,7 +159,7 @@ impl Format {
None => Err(format!("Unsupported image file: {}", source).into()),
},
"jpeg" | "jpg" => Ok(Jpeg(jpg_quality)),
- "png" => Ok(Png),
+ "png" => Ok(Png),
"webp" => Ok(WebP(quality)),
_ => Err(format!("Invalid image format: {}", format).into()),
}
@@ -189,7 +189,7 @@ impl Format {
match *self {
Png => "png",
Jpeg(_) => "jpg",
- WebP(_) => "webp"
+ WebP(_) => "webp",
}
}
}
@@ -201,9 +201,9 @@ impl Hash for Format {
let q = match *self {
Png => 0,
- Jpeg(q) => q,
+ Jpeg(q) => q,
WebP(None) => 0,
- WebP(Some(q)) => q
+ WebP(Some(q)) => q,
};
hasher.write_u8(q);
@@ -316,12 +316,8 @@ impl ImageOp {
Format::WebP(q) => {
let encoder = webp::Encoder::from_image(&img);
let memory = match q {
- Some(q) => {
- encoder.encode(q as f32 / 100.)
- }
- None => {
- encoder.encode_lossless()
- }
+ Some(q) => encoder.encode(q as f32 / 100.),
+ None => encoder.encode_lossless(),
};
let mut bytes = memory.as_bytes();
f.write_all(&mut bytes)?;
diff --git a/components/site/src/lib.rs b/components/site/src/lib.rs
index bc374134..bf15f75f 100644
--- a/components/site/src/lib.rs
+++ b/components/site/src/lib.rs
@@ -77,7 +77,8 @@ impl Site {
if let Some(theme) = config.theme.clone() {
// Grab data from the extra section of the theme
- config.merge_with_theme(&path.join("themes").join(&theme).join("theme.toml"), &theme)?;
+ config
+ .merge_with_theme(&path.join("themes").join(&theme).join("theme.toml"), &theme)?;
}
let tera = load_tera(path, &config)?;
diff --git a/components/templates/src/filters.rs b/components/templates/src/filters.rs
index 5e8575bd..17ccc17c 100644
--- a/components/templates/src/filters.rs
+++ b/components/templates/src/filters.rs
@@ -6,7 +6,7 @@ use std::path::PathBuf;
use base64::{decode, encode};
use config::Config;
use rendering::{render_content, RenderContext};
-use tera::{Filter as TeraFilter, Result as TeraResult, Tera, Value, to_value, try_get_value};
+use tera::{to_value, try_get_value, Filter as TeraFilter, Result as TeraResult, Tera, Value};
use crate::load_tera;
@@ -18,9 +18,12 @@ pub struct MarkdownFilter {
}
impl MarkdownFilter {
- pub fn new(path: PathBuf, config: Config, permalinks: HashMap) -> TeraResult {
- let tera = load_tera(&path, &config)
- .map_err(|err| tera::Error::msg(err))?;
+ pub fn new(
+ path: PathBuf,
+ config: Config,
+ permalinks: HashMap,
+ ) -> TeraResult {
+ let tera = load_tera(&path, &config).map_err(|err| tera::Error::msg(err))?;
Ok(Self { config, permalinks, tera })
}
}
diff --git a/components/templates/src/global_fns/mod.rs b/components/templates/src/global_fns/mod.rs
index 8b8fc910..42b2cbf9 100644
--- a/components/templates/src/global_fns/mod.rs
+++ b/components/templates/src/global_fns/mod.rs
@@ -5,7 +5,7 @@ use std::sync::{Arc, Mutex, RwLock};
use std::{fs, io, result};
use base64::encode as encode_b64;
-use sha2::{Digest, Sha256, Sha384, Sha512};
+use sha2::{digest, Sha256, Sha384, Sha512};
use svg_metadata as svg;
use tera::{from_value, to_value, Error, Function as TeraFn, Result, Value};
@@ -90,40 +90,22 @@ fn open_file(search_paths: &[PathBuf], url: &str) -> result::Result result::Result {
- let mut hasher = Sha256::new();
+fn compute_file_hash(
+ mut file: fs::File,
+ base64: bool,
+) -> result::Result
+where
+ digest::Output: core::fmt::LowerHex,
+ D: std::io::Write,
+{
+ let mut hasher = D::new();
io::copy(&mut file, &mut hasher)?;
- Ok(format!("{:x}", hasher.finalize()))
-}
-
-fn compute_file_sha256_base64(mut file: fs::File) -> result::Result {
- let mut hasher = Sha256::new();
- io::copy(&mut file, &mut hasher)?;
- Ok(format!("{}", encode_b64(hasher.finalize())))
-}
-
-fn compute_file_sha384(mut file: fs::File) -> result::Result {
- let mut hasher = Sha384::new();
- io::copy(&mut file, &mut hasher)?;
- Ok(format!("{:x}", hasher.finalize()))
-}
-
-fn compute_file_sha384_base64(mut file: fs::File) -> result::Result {
- let mut hasher = Sha384::new();
- io::copy(&mut file, &mut hasher)?;
- Ok(format!("{}", encode_b64(hasher.finalize())))
-}
-
-fn compute_file_sha512(mut file: fs::File) -> result::Result {
- let mut hasher = Sha512::new();
- io::copy(&mut file, &mut hasher)?;
- Ok(format!("{:x}", hasher.finalize()))
-}
-
-fn compute_file_sha512_base64(mut file: fs::File) -> result::Result {
- let mut hasher = Sha512::new();
- io::copy(&mut file, &mut hasher)?;
- Ok(format!("{}", encode_b64(hasher.finalize())))
+ let val = format!("{:x}", hasher.finalize());
+ if base64 {
+ Ok(encode_b64(val))
+ } else {
+ Ok(val)
+ }
}
fn file_not_found_err(search_paths: &[PathBuf], url: &str) -> Result {
@@ -174,7 +156,9 @@ impl TeraFn for GetUrl {
}
if cachebust {
- match open_file(&self.search_paths, &path).and_then(compute_file_sha256) {
+ match open_file(&self.search_paths, &path)
+ .and_then(|f| compute_file_hash::(f, false))
+ {
Ok(hash) => {
permalink = format!("{}?h={}", permalink, hash);
}
@@ -219,17 +203,19 @@ impl TeraFn for GetFileHash {
)
.unwrap_or(DEFAULT_BASE64);
- let compute_hash_fn = match (sha_type, base64) {
- (256, true) => compute_file_sha256_base64,
- (256, false) => compute_file_sha256,
- (384, true) => compute_file_sha384_base64,
- (384, false) => compute_file_sha384,
- (512, true) => compute_file_sha512_base64,
- (512, false) => compute_file_sha512,
- _ => return Err("`get_file_hash`: bad arguments".into()),
+ let f = match open_file(&self.search_paths, &path) {
+ Ok(f) => f,
+ Err(e) => {
+ return Err(format!("File {} could not be open {}", path, e).into());
+ }
};
- let hash = open_file(&self.search_paths, &path).and_then(compute_hash_fn);
+ let hash = match sha_type {
+ 256 => compute_file_hash::(f, base64),
+ 384 => compute_file_hash::(f, base64),
+ 512 => compute_file_hash::(f, base64),
+ _ => return Err("`get_file_hash`: Invalid sha value".into()),
+ };
match hash {
Ok(digest) => Ok(to_value(digest).unwrap()),
diff --git a/components/templates/src/lib.rs b/components/templates/src/lib.rs
index 5751fe10..d7ff9b84 100644
--- a/components/templates/src/lib.rs
+++ b/components/templates/src/lib.rs
@@ -7,7 +7,7 @@ use config::Config;
use lazy_static::lazy_static;
use tera::{Context, Tera};
-use errors::{Error, Result, bail};
+use errors::{bail, Error, Result};
use utils::templates::rewrite_theme_paths;
lazy_static! {
From 5ce082dfada7caa68454fd5c60ae3e40e16a4078 Mon Sep 17 00:00:00 2001
From: apiraino
Date: Sat, 20 Feb 2021 17:26:46 +0100
Subject: [PATCH 025/113] Add Sourcehut Pages as deployment platform (#1365)
---
.../documentation/deployment/sourcehut.md | 53 +++++++++++++++++++
1 file changed, 53 insertions(+)
create mode 100644 docs/content/documentation/deployment/sourcehut.md
diff --git a/docs/content/documentation/deployment/sourcehut.md b/docs/content/documentation/deployment/sourcehut.md
new file mode 100644
index 00000000..7f5449f2
--- /dev/null
+++ b/docs/content/documentation/deployment/sourcehut.md
@@ -0,0 +1,53 @@
++++
+title = "Sourcehut Pages"
+weight = 15
++++
+
+Deploying your static Zola website on [Sourcehut Pages][srht] is very simple.
+
+You only need to create a manifest `.build.yml` file in your root folder of your Zola project and push your changes to the Sourcehut git/hg repository. To create your `.build.yml` you can start by [a template][srht-tpl].
+
+Example:
+
+``` yaml
+image: alpine/edge
+packages: [ zola ]
+oauth: pages.sr.ht/PAGES:RW
+environment:
+ site: www.example.org
+sources:
+ - https://git.sr.ht/~your_username/my-website
+tasks:
+ - build: |
+ cd my-website
+ zola build
+ - package: |
+ cd my-website
+ tar -C public -cvz . > ../site.tar.gz
+ - upload: |
+ acurl -f https://pages.sr.ht/publish/$site -Fcontent=@site.tar.gz
+```
+
+This manifest will checkout your code from `sources`, build and upload the generated static files to `site` using a wrapper script around `curl` (called `acurl`, already available in all Sourcehut builds).
+
+From this template you need to customize the variable `site` with the domain that will host your website and `sources` to point to your Sourcehut git/hg public URL (in this example `my-website` is the name of the repository).
+
+Then commit and push your changes:
+
+``` sh
+$ git push
+Enumerating objects: 5, done.
+...
+remote: Build started:
+remote: https://builds.sr.ht/~your_username/job/430625 [.build.yml]
+To git.sr.ht:~your_username/www
+ fbe9afa..59ae556 master -> master
+```
+
+The build job will be automatically triggered. Notice that Sourcehut returns a direct link to the build page.
+
+By default you can use a subdomain of Sourcehut Pages to host your static website (e.g. "your_username.srht.site"). If you want to use a custom domain (e.g. "blog.mydomain.org"), you will need to configure a DNS record to point to the Sourcehut server. Instructions to do this are detailed on [Sourcehut][srht-custom-domain].
+
+[srht]: https://srht.site
+[srht-tpl]: https://git.sr.ht/~sircmpwn/pages.sr.ht-examples
+[srht-custom-domain]: https://srht.site/custom-domains
From 0b94e0917d25bd0ab5db026dc9e22b7dfa4ed2bb Mon Sep 17 00:00:00 2001
From: Vincent Prouillet
Date: Sun, 21 Feb 2021 08:41:49 +0100
Subject: [PATCH 026/113] Update changelog
---
CHANGELOG.md | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c147c306..4fec052d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,11 @@
- internal links are now resolved in the `markdown` filter in the templates (#1296 #1316)
- Add a `required` argument to `load_data` so it can be allowed to fail
+- `get_file_hash` now supports returning the base64 encoded hash
+- the `markdown` filter not renders shortcodes
+- Image processing now supports WebP
+- Fix `zola serve` failing for some static files
+- Fix `zola serve` not picking up directory renaming
## 0.13.0 (2021-01-09)
From 2bbd3bd4240b23d581bc7f5b8b2703d5155af631 Mon Sep 17 00:00:00 2001
From: Vincent Prouillet
Date: Sun, 21 Feb 2021 09:09:02 +0100
Subject: [PATCH 027/113] Fix compute_hash
---
components/templates/src/global_fns/mod.rs | 18 +++++++-----------
1 file changed, 7 insertions(+), 11 deletions(-)
diff --git a/components/templates/src/global_fns/mod.rs b/components/templates/src/global_fns/mod.rs
index 42b2cbf9..b0c98b9f 100644
--- a/components/templates/src/global_fns/mod.rs
+++ b/components/templates/src/global_fns/mod.rs
@@ -100,11 +100,10 @@ where
{
let mut hasher = D::new();
io::copy(&mut file, &mut hasher)?;
- let val = format!("{:x}", hasher.finalize());
if base64 {
- Ok(encode_b64(val))
+ Ok(format!("{}", encode_b64(hasher.finalize())))
} else {
- Ok(val)
+ Ok(format!("{:x}", hasher.finalize()))
}
}
@@ -206,7 +205,7 @@ impl TeraFn for GetFileHash {
let f = match open_file(&self.search_paths, &path) {
Ok(f) => f,
Err(e) => {
- return Err(format!("File {} could not be open {}", path, e).into());
+ return Err(format!("File {} could not be open: {} (searched in {:?})", path, e, self.search_paths).into());
}
};
@@ -897,12 +896,9 @@ title = "A title"
let static_fn = GetFileHash::new(vec![TEST_CONTEXT.static_path.clone()]);
let mut args = HashMap::new();
args.insert("path".to_string(), to_value("doesnt-exist").unwrap());
- assert_eq!(
- format!(
- "file `doesnt-exist` not found; searched in {}",
- TEST_CONTEXT.static_path.to_str().unwrap()
- ),
- format!("{}", static_fn.call(&args).unwrap_err())
- );
+ let err = format!("{}", static_fn.call(&args).unwrap_err());
+ println!("{:?}", err);
+
+ assert!(err.contains("File doesnt-exist could not be open"));
}
}
From 7fc7ef4720cf85866a7734dca83a152a9d2b2be9 Mon Sep 17 00:00:00 2001
From: Vincent Prouillet
Date: Mon, 22 Feb 2021 20:39:31 +0100
Subject: [PATCH 028/113] Add tests for slug strategy in filenames with dates
---
components/library/src/content/page.rs | 44 ++++++++++++++++++++++
components/templates/src/global_fns/mod.rs | 6 ++-
2 files changed, 49 insertions(+), 1 deletion(-)
diff --git a/components/library/src/content/page.rs b/components/library/src/content/page.rs
index 13034158..cb69dbcf 100644
--- a/components/library/src/content/page.rs
+++ b/components/library/src/content/page.rs
@@ -683,6 +683,26 @@ Hello world
assert_eq!(page.slug, "hello");
}
+ // https://github.com/getzola/zola/pull/1323#issuecomment-779401063
+ #[test]
+ fn can_get_date_from_short_date_in_filename_respects_slugification_strategy() {
+ let mut config = Config::default();
+ config.slugify.paths = SlugifyStrategy::Off;
+ let content = r#"
++++
++++
+Hello world
+"#
+ .to_string();
+ let res =
+ Page::parse(Path::new("2018-10-08_ こんにちは.md"), &content, &config, &PathBuf::new());
+ assert!(res.is_ok());
+ let page = res.unwrap();
+
+ assert_eq!(page.meta.date, Some("2018-10-08".to_string()));
+ assert_eq!(page.slug, " こんにちは");
+ }
+
#[test]
fn can_get_date_from_full_rfc3339_date_in_filename() {
let config = Config::default();
@@ -705,6 +725,30 @@ Hello world
assert_eq!(page.slug, "hello");
}
+ // https://github.com/getzola/zola/pull/1323#issuecomment-779401063
+ #[test]
+ fn can_get_date_from_full_rfc3339_date_in_filename_respects_slugification_strategy() {
+ let mut config = Config::default();
+ config.slugify.paths = SlugifyStrategy::Off;
+ let content = r#"
++++
++++
+Hello world
+"#
+ .to_string();
+ let res = Page::parse(
+ Path::new("2018-10-02T15:00:00Z- こんにちは.md"),
+ &content,
+ &config,
+ &PathBuf::new(),
+ );
+ assert!(res.is_ok());
+ let page = res.unwrap();
+
+ assert_eq!(page.meta.date, Some("2018-10-02T15:00:00Z".to_string()));
+ assert_eq!(page.slug, " こんにちは");
+ }
+
#[test]
fn frontmatter_date_override_filename_date() {
let config = Config::default();
diff --git a/components/templates/src/global_fns/mod.rs b/components/templates/src/global_fns/mod.rs
index b0c98b9f..4fe0ccaa 100644
--- a/components/templates/src/global_fns/mod.rs
+++ b/components/templates/src/global_fns/mod.rs
@@ -205,7 +205,11 @@ impl TeraFn for GetFileHash {
let f = match open_file(&self.search_paths, &path) {
Ok(f) => f,
Err(e) => {
- return Err(format!("File {} could not be open: {} (searched in {:?})", path, e, self.search_paths).into());
+ return Err(format!(
+ "File {} could not be open: {} (searched in {:?})",
+ path, e, self.search_paths
+ )
+ .into());
}
};
From 2a46e07e74e54503f41ea424a38c8c15c7b54f00 Mon Sep 17 00:00:00 2001
From: Vincent Prouillet
Date: Mon, 22 Feb 2021 21:27:50 +0100
Subject: [PATCH 029/113] Add path to TaxonomyTerm
Closes #1336
---
CHANGELOG.md | 1 +
components/library/src/pagination/mod.rs | 2 ++
components/library/src/taxonomies/mod.rs | 14 ++++++++++----
docs/content/documentation/templates/taxonomies.md | 1 +
4 files changed, 14 insertions(+), 4 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4fec052d..a632eb32 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,7 @@
- Image processing now supports WebP
- Fix `zola serve` failing for some static files
- Fix `zola serve` not picking up directory renaming
+- Add `path` to the taxonomy terms to be on par with pages and sections
## 0.13.0 (2021-01-09)
diff --git a/components/library/src/pagination/mod.rs b/components/library/src/pagination/mod.rs
index 37037f44..a5e53a15 100644
--- a/components/library/src/pagination/mod.rs
+++ b/components/library/src/pagination/mod.rs
@@ -413,6 +413,7 @@ mod tests {
let taxonomy_item = TaxonomyItem {
name: "Something".to_string(),
slug: "something".to_string(),
+ path: "/tags/something".to_string(),
permalink: "https://vincent.is/tags/something/".to_string(),
pages: library.pages().keys().collect(),
};
@@ -446,6 +447,7 @@ mod tests {
let taxonomy_item = TaxonomyItem {
name: "Something".to_string(),
slug: "something".to_string(),
+ path: "/some-tags/something/".to_string(),
permalink: "https://vincent.is/some-tags/something/".to_string(),
pages: library.pages().keys().collect(),
};
diff --git a/components/library/src/taxonomies/mod.rs b/components/library/src/taxonomies/mod.rs
index f71d8d95..f4660d1b 100644
--- a/components/library/src/taxonomies/mod.rs
+++ b/components/library/src/taxonomies/mod.rs
@@ -18,6 +18,7 @@ use utils::slugs::slugify_paths;
pub struct SerializedTaxonomyItem<'a> {
name: &'a str,
slug: &'a str,
+ path: &'a str,
permalink: &'a str,
pages: Vec>,
}
@@ -34,6 +35,7 @@ impl<'a> SerializedTaxonomyItem<'a> {
SerializedTaxonomyItem {
name: &item.name,
slug: &item.slug,
+ path: &item.path,
permalink: &item.permalink,
pages,
}
@@ -45,6 +47,7 @@ impl<'a> SerializedTaxonomyItem<'a> {
pub struct TaxonomyItem {
pub name: String,
pub slug: String,
+ pub path: String,
pub permalink: String,
pub pages: Vec,
}
@@ -73,16 +76,17 @@ impl TaxonomyItem {
.collect();
let (mut pages, ignored_pages) = sort_pages_by_date(data);
let item_slug = slugify_paths(name, config.slugify.taxonomies);
- let permalink = if taxonomy.lang != config.default_language {
- config.make_permalink(&format!("/{}/{}/{}", taxonomy.lang, taxo_slug, item_slug))
+ let path = if taxonomy.lang != config.default_language {
+ format!("/{}/{}/{}/", taxonomy.lang, taxo_slug, item_slug)
} else {
- config.make_permalink(&format!("/{}/{}", taxo_slug, item_slug))
+ format!("/{}/{}/", taxo_slug, item_slug)
};
+ let permalink = config.make_permalink(&path);
// We still append pages without dates at the end
pages.extend(ignored_pages);
- TaxonomyItem { name: name.to_string(), permalink, slug: item_slug, pages }
+ TaxonomyItem { name: name.to_string(), permalink, path, slug: item_slug, pages }
}
pub fn serialize<'a>(&'a self, library: &'a Library) -> SerializedTaxonomyItem<'a> {
@@ -338,6 +342,7 @@ mod tests {
assert_eq!(tags.items[0].name, "db");
assert_eq!(tags.items[0].slug, "db");
assert_eq!(tags.items[0].permalink, "http://a-website.com/tags/db/");
+ assert_eq!(tags.items[0].path, "/tags/db/");
assert_eq!(tags.items[0].pages.len(), 1);
assert_eq!(tags.items[1].name, "js");
@@ -438,6 +443,7 @@ mod tests {
assert_eq!(tags.items[1].name, "js");
assert_eq!(tags.items[1].slug, "js");
assert_eq!(tags.items[1].permalink, "http://a-website.com/tags/js/");
+ assert_eq!(tags.items[1].path, "/tags/js/");
assert_eq!(tags.items[1].pages.len(), 2);
assert_eq!(tags.items[2].name, "rust");
diff --git a/docs/content/documentation/templates/taxonomies.md b/docs/content/documentation/templates/taxonomies.md
index 7999957c..e8e41536 100644
--- a/docs/content/documentation/templates/taxonomies.md
+++ b/docs/content/documentation/templates/taxonomies.md
@@ -13,6 +13,7 @@ First, `TaxonomyTerm` has the following fields:
```ts
name: String;
slug: String;
+path: String;
permalink: String;
pages: Array;
```
From 51644a79e31a9ffaf54c5447c79c13dbc4171c6e Mon Sep 17 00:00:00 2001
From: Vincent Prouillet
Date: Sun, 28 Feb 2021 22:30:56 +0100
Subject: [PATCH 030/113] Handle random jetbrains temp files
---
src/cmd/serve.rs | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/src/cmd/serve.rs b/src/cmd/serve.rs
index 25051995..a063eac5 100644
--- a/src/cmd/serve.rs
+++ b/src/cmd/serve.rs
@@ -468,7 +468,7 @@ pub fn serve(
match rx.recv() {
Ok(event) => {
let can_do_fast_reload = !matches!(event, Remove(_));
-
+
match event {
// Intellij does weird things on edit, chmod is there to count those changes
// https://github.com/passcod/notify/issues/150#issuecomment-494912080
@@ -477,7 +477,7 @@ pub fn serve(
continue;
}
- if path.is_file() && is_temp_file(&path) {
+ if is_temp_file(&path) {
continue;
}
@@ -485,7 +485,6 @@ pub fn serve(
if path.is_dir() && is_folder_empty(&path) {
continue;
}
-
println!(
"Change detected @ {}",
Local::now().format("%Y-%m-%d %H:%M:%S").to_string()
@@ -600,7 +599,7 @@ fn is_temp_file(path: &Path) -> bool {
x if x.ends_with("jb_old___") => true,
x if x.ends_with("jb_tmp___") => true,
x if x.ends_with("jb_bak___") => true,
- // vim
+ // vim & jetbrains
x if x.ends_with('~') => true,
_ => {
if let Some(filename) = path.file_stem() {
From 9487b6fab8fdf41f62b156a9fd546988c147bbca Mon Sep 17 00:00:00 2001
From: Kristofor Salmin <4795426+kristoforsalmin@users.noreply.github.com>
Date: Thu, 4 Mar 2021 21:51:33 +0300
Subject: [PATCH 031/113] Fix serve command when used with config option
(#1385)
* Fix serve command when used with config option
* Use current config path instead of extension to detect change kind
* Fix serve command tests
---
src/cmd/serve.rs | 39 +++++++++++++++++++++++++++++++--------
1 file changed, 31 insertions(+), 8 deletions(-)
diff --git a/src/cmd/serve.rs b/src/cmd/serve.rs
index a063eac5..99fae64b 100644
--- a/src/cmd/serve.rs
+++ b/src/cmd/serve.rs
@@ -270,10 +270,16 @@ pub fn serve(
return Err(format!("Cannot start server on address {}.", address).into());
}
+ let config_filename = config_file
+ .file_name()
+ .unwrap()
+ .to_str()
+ .unwrap_or("config.toml");
+
// An array of (path, bool, bool) where the path should be watched for changes, and the boolean value
// indicates whether this file/folder must exist for zola serve to operate
let watch_this = vec![
- ("config.toml", WatchMode::Required),
+ (config_filename, WatchMode::Required),
("content", WatchMode::Required),
("sass", WatchMode::Condition(site.config.compile_sass)),
("static", WatchMode::Optional),
@@ -491,7 +497,7 @@ pub fn serve(
);
let start = Instant::now();
- match detect_change_kind(&root_dir, &path) {
+ match detect_change_kind(&root_dir, &path, &config_filename) {
(ChangeKind::Content, _) => {
console::info(&format!("-> Content changed {}", path.display()));
@@ -617,10 +623,13 @@ fn is_temp_file(path: &Path) -> bool {
/// Detect what changed from the given path so we have an idea what needs
/// to be reloaded
-fn detect_change_kind(pwd: &Path, path: &Path) -> (ChangeKind, PathBuf) {
+fn detect_change_kind(pwd: &Path, path: &Path, config_filename: &str) -> (ChangeKind, PathBuf) {
let mut partial_path = PathBuf::from("/");
partial_path.push(path.strip_prefix(pwd).unwrap_or(path));
+ let mut partial_config_path = PathBuf::from("/");
+ partial_config_path.push(config_filename);
+
let change_kind = if partial_path.starts_with("/templates") {
ChangeKind::Templates
} else if partial_path.starts_with("/themes") {
@@ -631,7 +640,7 @@ fn detect_change_kind(pwd: &Path, path: &Path) -> (ChangeKind, PathBuf) {
ChangeKind::StaticFiles
} else if partial_path.starts_with("/sass") {
ChangeKind::Sass
- } else if partial_path == Path::new("/config.toml") {
+ } else if partial_path == partial_config_path {
ChangeKind::Config
} else {
unreachable!("Got a change in an unexpected path: {}", partial_path.display());
@@ -680,36 +689,48 @@ mod tests {
(ChangeKind::Templates, PathBuf::from("/templates/hello.html")),
Path::new("/home/vincent/site"),
Path::new("/home/vincent/site/templates/hello.html"),
+ "config.toml",
),
(
(ChangeKind::Themes, PathBuf::from("/themes/hello.html")),
Path::new("/home/vincent/site"),
Path::new("/home/vincent/site/themes/hello.html"),
+ "config.toml",
),
(
(ChangeKind::StaticFiles, PathBuf::from("/static/site.css")),
Path::new("/home/vincent/site"),
Path::new("/home/vincent/site/static/site.css"),
+ "config.toml",
),
(
(ChangeKind::Content, PathBuf::from("/content/posts/hello.md")),
Path::new("/home/vincent/site"),
Path::new("/home/vincent/site/content/posts/hello.md"),
+ "config.toml",
),
(
(ChangeKind::Sass, PathBuf::from("/sass/print.scss")),
Path::new("/home/vincent/site"),
Path::new("/home/vincent/site/sass/print.scss"),
+ "config.toml",
),
(
(ChangeKind::Config, PathBuf::from("/config.toml")),
Path::new("/home/vincent/site"),
Path::new("/home/vincent/site/config.toml"),
+ "config.toml",
+ ),
+ (
+ (ChangeKind::Config, PathBuf::from("/config.staging.toml")),
+ Path::new("/home/vincent/site"),
+ Path::new("/home/vincent/site/config.staging.toml"),
+ "config.staging.toml",
),
];
- for (expected, pwd, path) in test_cases {
- assert_eq!(expected, detect_change_kind(&pwd, &path));
+ for (expected, pwd, path, config_filename) in test_cases {
+ assert_eq!(expected, detect_change_kind(&pwd, &path, &config_filename));
}
}
@@ -719,7 +740,8 @@ mod tests {
let expected = (ChangeKind::Templates, PathBuf::from("/templates/hello.html"));
let pwd = Path::new(r#"C:\\Users\johan\site"#);
let path = Path::new(r#"C:\\Users\johan\site\templates\hello.html"#);
- assert_eq!(expected, detect_change_kind(pwd, path));
+ let config_filename = "config.toml";
+ assert_eq!(expected, detect_change_kind(pwd, path, config_filename));
}
#[test]
@@ -727,6 +749,7 @@ mod tests {
let expected = (ChangeKind::Templates, PathBuf::from("/templates/hello.html"));
let pwd = Path::new("/home/johan/site");
let path = Path::new("templates/hello.html");
- assert_eq!(expected, detect_change_kind(pwd, path));
+ let config_filename = "config.toml";
+ assert_eq!(expected, detect_change_kind(pwd, path, config_filename));
}
}
From 67f9b9499beeb47480a3e4c86272c7e6b7c14356 Mon Sep 17 00:00:00 2001
From: snake66
Date: Sat, 6 Mar 2021 22:49:04 +0100
Subject: [PATCH 032/113] Fix language specific non-internal urls for get_url.
(#1381)
* Fix language specific non-internal urls for get_url.
* get_url: PathBuf for URL's don't work on Windows.
---
components/templates/src/global_fns/mod.rs | 43 ++++++++++++++++++++--
1 file changed, 40 insertions(+), 3 deletions(-)
diff --git a/components/templates/src/global_fns/mod.rs b/components/templates/src/global_fns/mod.rs
index 4fe0ccaa..7038c157 100644
--- a/components/templates/src/global_fns/mod.rs
+++ b/components/templates/src/global_fns/mod.rs
@@ -149,21 +149,32 @@ impl TeraFn for GetUrl {
}
} else {
// anything else
- let mut permalink = self.config.make_permalink(&path);
+ let mut segments = vec![];
+
+ if lang != self.config.default_language {
+ segments.push(lang);
+ };
+
+ segments.push(path);
+
+ let path_with_lang = segments.join("/");
+
+ let mut permalink = self.config.make_permalink(&path_with_lang);
if !trailing_slash && permalink.ends_with('/') {
permalink.pop(); // Removes the slash
}
if cachebust {
- match open_file(&self.search_paths, &path)
+ match open_file(&self.search_paths, &path_with_lang)
.and_then(|f| compute_file_hash::(f, false))
{
Ok(hash) => {
permalink = format!("{}?h={}", permalink, hash);
}
- Err(_) => return file_not_found_err(&self.search_paths, &path),
+ Err(_) => return file_not_found_err(&self.search_paths, &path_with_lang),
};
}
+
Ok(to_value(permalink).unwrap())
}
}
@@ -825,6 +836,32 @@ title = "A title"
);
}
+ #[test]
+ fn can_get_feed_url_with_default_language() {
+ let config = Config::parse(TRANS_CONFIG).unwrap();
+ let static_fn = GetUrl::new(config.clone(), HashMap::new(), vec![TEST_CONTEXT.static_path.clone()]);
+ let mut args = HashMap::new();
+ args.insert("path".to_string(), to_value(config.feed_filename).unwrap());
+ args.insert("lang".to_string(), to_value("fr").unwrap());
+ assert_eq!(
+ static_fn.call(&args).unwrap(),
+ "https://remplace-par-ton-url.fr/atom.xml"
+ );
+ }
+
+ #[test]
+ fn can_get_feed_url_with_other_language() {
+ let config = Config::parse(TRANS_CONFIG).unwrap();
+ let static_fn = GetUrl::new(config.clone(), HashMap::new(), vec![TEST_CONTEXT.static_path.clone()]);
+ let mut args = HashMap::new();
+ args.insert("path".to_string(), to_value(config.feed_filename).unwrap());
+ args.insert("lang".to_string(), to_value("en").unwrap());
+ assert_eq!(
+ static_fn.call(&args).unwrap(),
+ "https://remplace-par-ton-url.fr/en/atom.xml"
+ );
+ }
+
#[test]
fn can_get_file_hash_sha256() {
let static_fn = GetFileHash::new(vec![TEST_CONTEXT.static_path.clone()]);
From 25ef603990f2d7456f247d2fe55f70211df0a833 Mon Sep 17 00:00:00 2001
From: Vincent Prouillet
Date: Sat, 6 Mar 2021 23:00:56 +0100
Subject: [PATCH 033/113] Error on invalid file/section
---
components/site/src/lib.rs | 18 +++---------------
1 file changed, 3 insertions(+), 15 deletions(-)
diff --git a/components/site/src/lib.rs b/components/site/src/lib.rs
index bf15f75f..97704778 100644
--- a/components/site/src/lib.rs
+++ b/components/site/src/lib.rs
@@ -243,17 +243,11 @@ impl Site {
.collect::>();
for index_file in index_files {
- let section = match Section::from_file(
+ let section = Section::from_file(
index_file.path(),
&self.config,
&self.base_path,
- ) {
- Err(e) => {
- println!("Failed to load section: {:?}", e);
- continue;
- }
- Ok(sec) => sec,
- };
+ )?;
// if the section is drafted we can skip the enitre dir
if section.meta.draft && !self.include_drafts {
@@ -264,13 +258,7 @@ impl Site {
self.add_section(section, false)?;
}
} else {
- let page = match Page::from_file(path, &self.config, &self.base_path) {
- Err(e) => {
- println!("Failed to load page: {:?}", e);
- continue;
- }
- Ok(p) => p,
- };
+ let page = Page::from_file(path, &self.config, &self.base_path)?;
// should we skip drafts?
if page.meta.draft && !self.include_drafts {
From 5964fc192c5e991859acd12e576a8d156773bd5e Mon Sep 17 00:00:00 2001
From: Marco Tolk
Date: Thu, 11 Mar 2021 19:47:42 +0100
Subject: [PATCH 034/113] fixes #1401 (#1404)
Co-authored-by: Marco Tolk
---
Cargo.lock | 1 +
Cargo.toml | 3 +++
src/cmd/init.rs | 19 +++++++++----------
3 files changed, 13 insertions(+), 10 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index e1dd981e..959c84fe 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3302,6 +3302,7 @@ dependencies = [
"open",
"percent-encoding",
"relative-path",
+ "same-file",
"serde_json",
"site",
"termcolor",
diff --git a/Cargo.toml b/Cargo.toml
index 33ec011b..30f897dc 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -45,6 +45,9 @@ errors = { path = "components/errors" }
front_matter = { path = "components/front_matter" }
utils = { path = "components/utils" }
+[dev-dependencies]
+same-file = "1"
+
[workspace]
members = [
"components/config",
diff --git a/src/cmd/init.rs b/src/cmd/init.rs
index 1a381737..5ce05f17 100644
--- a/src/cmd/init.rs
+++ b/src/cmd/init.rs
@@ -250,16 +250,15 @@ mod tests {
#[test]
fn strip_unc_test() {
let mut dir = temp_dir();
- dir.push("new_project");
+ dir.push("new_project1");
if dir.exists() {
remove_dir_all(&dir).expect("Could not free test directory");
}
create_dir(&dir).expect("Could not create test directory");
if cfg!(target_os = "windows") {
- assert_eq!(
- strip_unc(&canonicalize(Path::new(&dir)).unwrap()),
- "C:\\Users\\VssAdministrator\\AppData\\Local\\Temp\\new_project"
- )
+ let stripped_path = strip_unc(&canonicalize(Path::new(&dir)).unwrap());
+ assert!(same_file::is_same_file(Path::new(&stripped_path),&dir).unwrap());
+ assert!(!stripped_path.starts_with(LOCAL_UNC),"The path was not stripped.");
} else {
assert_eq!(
strip_unc(&canonicalize(Path::new(&dir)).unwrap()),
@@ -277,15 +276,15 @@ mod tests {
#[cfg(target_os = "windows")]
fn strip_unc_required_test() {
let mut dir = temp_dir();
- dir.push("new_project");
+ dir.push("new_project2");
if dir.exists() {
remove_dir_all(&dir).expect("Could not free test directory");
}
create_dir(&dir).expect("Could not create test directory");
- assert_eq!(
- canonicalize(Path::new(&dir)).unwrap().to_str().unwrap(),
- "\\\\?\\C:\\Users\\VssAdministrator\\AppData\\Local\\Temp\\new_project"
- );
+
+ let canonicalized_path = canonicalize(Path::new(&dir)).unwrap();
+ assert!(same_file::is_same_file(Path::new(&canonicalized_path),&dir).unwrap());
+ assert!(canonicalized_path.to_str().unwrap().starts_with(LOCAL_UNC));
remove_dir_all(&dir).unwrap();
}
From 534174ae78e36def4ac3cd98c2c2f0b683870a15 Mon Sep 17 00:00:00 2001
From: Tiago Seabra
Date: Sun, 14 Mar 2021 08:49:29 +0000
Subject: [PATCH 035/113] feat: add base16-atelierdune-light highlight theme
(#1406)
* feat: add base16-aterlierdune-light highlight theme
* docs: add base16-atelierdune-light theme to CHANGELOG
---
CHANGELOG.md | 1 +
.../getting-started/configuration.md | 1 +
sublime/themes/all.themedump | Bin 32385 -> 32813 bytes
.../themes/base16-atelierdune-light.tmTheme | 549 ++++++++++++++++++
4 files changed, 551 insertions(+)
create mode 100644 sublime/themes/base16-atelierdune-light.tmTheme
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a632eb32..2b2188e4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,7 @@
- Fix `zola serve` failing for some static files
- Fix `zola serve` not picking up directory renaming
- Add `path` to the taxonomy terms to be on par with pages and sections
+- Add the `base16-aterlierdune-light` syntax highlight theme
## 0.13.0 (2021-01-09)
diff --git a/docs/content/documentation/getting-started/configuration.md b/docs/content/documentation/getting-started/configuration.md
index 71c1de2a..8673dc54 100644
--- a/docs/content/documentation/getting-started/configuration.md
+++ b/docs/content/documentation/getting-started/configuration.md
@@ -193,6 +193,7 @@ Zola currently has the following highlight themes available:
- [ayu-dark](https://github.com/dempfi/ayu)
- [ayu-light](https://github.com/dempfi/ayu)
- [ayu-mirage](https://github.com/dempfi/ayu)
+- [base16-aterlierdune-light](https://atelierbram.github.io/syntax-highlighting/atelier-schemes/dune/)
- [base16-ocean-dark](https://tmtheme-editor.herokuapp.com/#!/editor/theme/Base16%20Ocean%20Dark)
- [base16-ocean-light](https://tmtheme-editor.herokuapp.com/#!/editor/theme/Base16%20Ocean%20Light)
- [bbedit](https://tmtheme-editor.herokuapp.com/#!/editor/theme/BBEdit)
diff --git a/sublime/themes/all.themedump b/sublime/themes/all.themedump
index 6c66c2c2c3d5d705d966ad2703828321ccd2b3df..40aeff7c032ca6464404508d7a101604c66c319a 100644
GIT binary patch
delta 30391
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