@@ -16,7 +16,7 @@ matrix: | |||
# The earliest stable Rust version that works | |||
- env: TARGET=x86_64-unknown-linux-gnu | |||
rust: 1.29.0 | |||
rust: 1.30.0 | |||
before_install: set -e | |||
@@ -1,5 +1,14 @@ | |||
# Changelog | |||
## 0.5.1 (2018-12-14) | |||
- Fix deleting markdown file in `zola serve` | |||
- Fix pagination for taxonomies being broken and add missing documentation for it | |||
- Add missing pager pages from the sitemap | |||
- Allow and parse full RFC339 datetimes in filenames | |||
- Live reload is now enabled for the 404 page on serve | |||
## 0.5.0 (2018-11-17) | |||
### Breaking | |||
@@ -1,6 +1,6 @@ | |||
[package] | |||
name = "zola" | |||
version = "0.5.0" | |||
version = "0.5.1" | |||
authors = ["Vincent Prouillet <prouillet.vincent@gmail.com>"] | |||
license = "MIT" | |||
readme = "README.md" | |||
@@ -37,6 +37,14 @@ impl Taxonomy { | |||
false | |||
} | |||
} | |||
pub fn paginate_path(&self) -> &str { | |||
if let Some(ref path) = self.paginate_path { | |||
path | |||
} else { | |||
"page" | |||
} | |||
} | |||
} | |||
impl Default for Taxonomy { | |||
@@ -20,8 +20,11 @@ use content::file_info::FileInfo; | |||
use content::ser::SerializingPage; | |||
lazy_static! { | |||
// Check whether a string starts with yyyy-mm-dd{-,_} | |||
static ref DATE_IN_FILENAME: Regex = Regex::new(r"^^([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))(_|-)").unwrap(); | |||
// Based on https://regex101.com/r/H2n38Z/1/tests | |||
// A regex parsing RFC3339 date followed by {_,-}, some characters and ended by .md | |||
static ref RFC3339_DATE: Regex = Regex::new( | |||
r"^(?P<datetime>(\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])(T([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.[0-9]+)?(Z|(\+|-)([01][0-9]|2[0-3]):([0-5][0-9])))?)(_|-)(?P<slug>.+$)" | |||
).unwrap(); | |||
} | |||
#[derive(Clone, Debug, PartialEq)] | |||
@@ -113,11 +116,11 @@ impl Page { | |||
page.word_count = Some(word_count); | |||
page.reading_time = Some(reading_time); | |||
let mut has_date_in_name = false; | |||
if DATE_IN_FILENAME.is_match(&page.file.name) { | |||
has_date_in_name = true; | |||
let mut slug_from_dated_filename = None; | |||
if let Some(ref caps) = RFC3339_DATE.captures(&page.file.name.replace(".md", "")) { | |||
slug_from_dated_filename = Some(caps.name("slug").unwrap().as_str().to_string()); | |||
if page.meta.date.is_none() { | |||
page.meta.date = Some(page.file.name[..10].to_string()); | |||
page.meta.date = Some(caps.name("datetime").unwrap().as_str().to_string()); | |||
page.meta.date_to_datetime(); | |||
} | |||
} | |||
@@ -132,9 +135,8 @@ impl Page { | |||
slugify(&page.file.name) | |||
} | |||
} else { | |||
if has_date_in_name { | |||
// skip the date + the {_,-} | |||
slugify(&page.file.name[11..]) | |||
if let Some(slug) = slug_from_dated_filename { | |||
slugify(&slug) | |||
} else { | |||
slugify(&page.file.name) | |||
} | |||
@@ -507,7 +509,7 @@ Hello world | |||
} | |||
#[test] | |||
fn can_get_date_from_filename() { | |||
fn can_get_date_from_short_date_in_filename() { | |||
let config = Config::default(); | |||
let content = r#" | |||
+++ | |||
@@ -523,6 +525,23 @@ Hello world | |||
assert_eq!(page.slug, "hello"); | |||
} | |||
#[test] | |||
fn can_get_date_from_full_rfc3339_date_in_filename() { | |||
let config = Config::default(); | |||
let content = r#" | |||
+++ | |||
+++ | |||
Hello world | |||
<!-- more -->"# | |||
.to_string(); | |||
let res = Page::parse(Path::new("2018-10-02T15:00:00Z-hello.md"), &content, &config); | |||
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, "hello"); | |||
} | |||
#[test] | |||
fn frontmatter_date_override_filename_date() { | |||
let config = Config::default(); | |||
@@ -80,7 +80,7 @@ impl Section { | |||
pub fn parse(file_path: &Path, content: &str, config: &Config) -> Result<Section> { | |||
let (meta, content) = split_section_content(file_path, content)?; | |||
let mut section = Section::new(file_path, meta); | |||
section.raw_content = content.clone(); | |||
section.raw_content = content; | |||
let (word_count, reading_time) = get_reading_analytics(§ion.raw_content); | |||
section.word_count = Some(word_count); | |||
section.reading_time = Some(reading_time); | |||
@@ -14,7 +14,7 @@ use taxonomies::{Taxonomy, TaxonomyItem}; | |||
#[derive(Clone, Debug, PartialEq)] | |||
enum PaginationRoot<'a> { | |||
Section(&'a Section), | |||
Taxonomy(&'a Taxonomy), | |||
Taxonomy(&'a Taxonomy, &'a TaxonomyItem), | |||
} | |||
/// A list of all the pages in the paginator with their index and links | |||
@@ -93,14 +93,14 @@ impl<'a> Paginator<'a> { | |||
all_pages: &item.pages, | |||
pagers: Vec::with_capacity(item.pages.len() / paginate_by), | |||
paginate_by, | |||
root: PaginationRoot::Taxonomy(taxonomy), | |||
root: PaginationRoot::Taxonomy(taxonomy, item), | |||
permalink: item.permalink.clone(), | |||
path: format!("{}/{}", taxonomy.kind.name, item.slug), | |||
paginate_path: taxonomy | |||
.kind | |||
.paginate_path | |||
.clone() | |||
.unwrap_or_else(|| "pages".to_string()), | |||
.unwrap_or_else(|| "page".to_string()), | |||
is_index: false, | |||
template: format!("{}/single.html", taxonomy.kind.name), | |||
}; | |||
@@ -212,8 +212,9 @@ impl<'a> Paginator<'a> { | |||
context | |||
.insert("section", &SerializingSection::from_section_basic(s, Some(library))); | |||
} | |||
PaginationRoot::Taxonomy(t) => { | |||
PaginationRoot::Taxonomy(t, item) => { | |||
context.insert("taxonomy", &t.kind); | |||
context.insert("term", &item.serialize(library)); | |||
} | |||
}; | |||
context.insert("current_url", &pager.permalink); | |||
@@ -349,7 +350,7 @@ mod tests { | |||
assert_eq!(paginator.pagers[1].index, 2); | |||
assert_eq!(paginator.pagers[1].pages.len(), 1); | |||
assert_eq!(paginator.pagers[1].permalink, "https://vincent.is/tags/something/pages/2/"); | |||
assert_eq!(paginator.pagers[1].path, "tags/something/pages/2/"); | |||
assert_eq!(paginator.pagers[1].permalink, "https://vincent.is/tags/something/page/2/"); | |||
assert_eq!(paginator.pagers[1].path, "tags/something/page/2/"); | |||
} | |||
} |
@@ -13,7 +13,7 @@ use library::Library; | |||
use sorting::sort_pages_by_date; | |||
#[derive(Debug, Clone, PartialEq, Serialize)] | |||
struct SerializedTaxonomyItem<'a> { | |||
pub struct SerializedTaxonomyItem<'a> { | |||
name: &'a str, | |||
slug: &'a str, | |||
permalink: &'a str, | |||
@@ -71,6 +71,10 @@ impl TaxonomyItem { | |||
TaxonomyItem { name: name.to_string(), permalink, slug, pages } | |||
} | |||
pub fn serialize<'a>(&'a self, library: &'a Library) -> SerializedTaxonomyItem<'a> { | |||
SerializedTaxonomyItem::from_item(self, library) | |||
} | |||
} | |||
#[derive(Debug, Clone, PartialEq, Serialize)] | |||
@@ -311,7 +311,7 @@ pub fn after_content_change(site: &mut Site, path: &Path) -> Result<()> { | |||
if is_md { | |||
// only delete if it was able to be added in the first place | |||
if !index.exists() && !path.exists() { | |||
delete_element(site, path, is_section)?; | |||
return delete_element(site, path, is_section); | |||
} | |||
// Added another .md in a assets directory | |||
@@ -352,6 +352,7 @@ pub fn after_template_change(site: &mut Site, path: &Path) -> Result<()> { | |||
site.render_orphan_pages() | |||
} | |||
"section.html" => site.render_sections(), | |||
"404.html" => site.render_404(), | |||
// Either the index or some unknown template changed | |||
// We can't really know what this change affects so rebuild all | |||
// the things | |||
@@ -228,9 +228,22 @@ fn can_rebuild_after_renaming_section_folder() { | |||
fn can_rebuild_after_renaming_non_md_asset_in_colocated_folder() { | |||
let tmp_dir = tempdir().expect("create temp dir"); | |||
let (site_path, mut site) = load_and_build_site!(tmp_dir); | |||
let (old_path, new_path) = rename!(site_path, "content/posts/with-assets/zola.png", "gutenberg.png"); | |||
let (old_path, new_path) = | |||
rename!(site_path, "content/posts/with-assets/zola.png", "gutenberg.png"); | |||
// Testing that we don't try to load some images as markdown or something | |||
let res = after_content_rename(&mut site, &old_path, &new_path); | |||
assert!(res.is_ok()); | |||
} | |||
#[test] | |||
fn can_rebuild_after_deleting_file() { | |||
let tmp_dir = tempdir().expect("create temp dir"); | |||
let (site_path, mut site) = load_and_build_site!(tmp_dir); | |||
let path = site_path.join("content").join("posts").join("fixed-slug.md"); | |||
fs::remove_file(&path).unwrap(); | |||
let res = after_content_change(&mut site, &path); | |||
println!("{:?}", res); | |||
assert!(res.is_ok()); | |||
} |
@@ -31,7 +31,7 @@ pub struct Rendered { | |||
// means we will have example, example-1, example-2 etc | |||
fn find_anchor(anchors: &[String], name: String, level: u8) -> String { | |||
if level == 0 && !anchors.contains(&name) { | |||
return name.to_string(); | |||
return name; | |||
} | |||
let new_anchor = format!("{}-{}", name, level + 1); | |||
@@ -1,7 +1,7 @@ | |||
use pest::iterators::Pair; | |||
use pest::Parser; | |||
use tera::{to_value, Context, Map, Value}; | |||
use regex::Regex; | |||
use tera::{to_value, Context, Map, Value}; | |||
use context::RenderContext; | |||
use errors::{Result, ResultExt}; | |||
@@ -20,9 +20,9 @@ lazy_static! { | |||
fn replace_string_markers(input: &str) -> String { | |||
match input.chars().next().unwrap() { | |||
'"' => input.replace('"', "").to_string(), | |||
'\'' => input.replace('\'', "").to_string(), | |||
'`' => input.replace('`', "").to_string(), | |||
'"' => input.replace('"', ""), | |||
'\'' => input.replace('\'', ""), | |||
'`' => input.replace('`', ""), | |||
_ => unreachable!("How did you even get there"), | |||
} | |||
} | |||
@@ -627,10 +627,8 @@ impl Site { | |||
ensure_directory_exists(&self.output_path)?; | |||
let mut context = Context::new(); | |||
context.insert("config", &self.config); | |||
create_file( | |||
&self.output_path.join("404.html"), | |||
&render_template("404.html", &self.tera, &context, &self.config.theme)?, | |||
) | |||
let output = render_template("404.html", &self.tera, &context, &self.config.theme)?; | |||
create_file(&self.output_path.join("404.html"), &self.inject_livereload(output)) | |||
} | |||
/// Renders robots.txt | |||
@@ -646,7 +644,6 @@ impl Site { | |||
/// Renders all taxonomies with at least one non-draft post | |||
pub fn render_taxonomies(&self) -> Result<()> { | |||
// TODO: make parallel? | |||
for taxonomy in &self.taxonomies { | |||
self.render_taxonomy(taxonomy)?; | |||
} | |||
@@ -669,24 +666,26 @@ impl Site { | |||
.items | |||
.par_iter() | |||
.map(|item| { | |||
if taxonomy.kind.rss { | |||
self.render_rss_feed( | |||
item.pages.iter().map(|p| self.library.get_page_by_key(*p)).collect(), | |||
Some(&PathBuf::from(format!("{}/{}", taxonomy.kind.name, item.slug))), | |||
)?; | |||
} | |||
let path = output_path.join(&item.slug); | |||
if taxonomy.kind.is_paginated() { | |||
self.render_paginated( | |||
&output_path, | |||
&path, | |||
&Paginator::from_taxonomy(&taxonomy, item, &self.library), | |||
) | |||
)?; | |||
} else { | |||
let single_output = | |||
taxonomy.render_term(item, &self.tera, &self.config, &self.library)?; | |||
let path = output_path.join(&item.slug); | |||
create_directory(&path)?; | |||
create_file(&path.join("index.html"), &self.inject_livereload(single_output)) | |||
create_file(&path.join("index.html"), &self.inject_livereload(single_output))?; | |||
} | |||
if taxonomy.kind.rss { | |||
self.render_rss_feed( | |||
item.pages.iter().map(|p| self.library.get_page_by_key(*p)).collect(), | |||
Some(&PathBuf::from(format!("{}/{}", taxonomy.kind.name, item.slug))), | |||
) | |||
} else { | |||
Ok(()) | |||
} | |||
}) | |||
.collect::<Result<()>>() | |||
@@ -720,6 +719,18 @@ impl Site { | |||
.iter() | |||
.map(|s| SitemapEntry::new(s.permalink.clone(), None)) | |||
.collect::<Vec<_>>(); | |||
for section in | |||
self.library.sections_values().iter().filter(|s| s.meta.paginate_by.is_some()) | |||
{ | |||
let number_pagers = (section.pages.len() as f64 | |||
/ section.meta.paginate_by.unwrap() as f64) | |||
.ceil() as isize; | |||
for i in 1..number_pagers + 1 { | |||
let permalink = | |||
format!("{}{}/{}/", section.permalink, section.meta.paginate_path, i); | |||
sections.push(SitemapEntry::new(permalink, None)) | |||
} | |||
} | |||
sections.sort_by(|a, b| a.permalink.cmp(&b.permalink)); | |||
context.insert("sections", §ions); | |||
@@ -733,12 +744,29 @@ impl Site { | |||
self.config.make_permalink(&format!("{}/{}", &name, item.slug)), | |||
None, | |||
)); | |||
if taxonomy.kind.is_paginated() { | |||
let number_pagers = (item.pages.len() as f64 | |||
/ taxonomy.kind.paginate_by.unwrap() as f64) | |||
.ceil() as isize; | |||
for i in 1..number_pagers + 1 { | |||
let permalink = self.config.make_permalink(&format!( | |||
"{}/{}/{}/{}", | |||
name, | |||
item.slug, | |||
taxonomy.kind.paginate_path(), | |||
i | |||
)); | |||
terms.push(SitemapEntry::new(permalink, None)) | |||
} | |||
} | |||
} | |||
terms.sort_by(|a, b| a.permalink.cmp(&b.permalink)); | |||
taxonomies.push(terms); | |||
} | |||
context.insert("taxonomies", &taxonomies); | |||
context.insert("taxonomies", &taxonomies); | |||
context.insert("config", &self.config); | |||
let sitemap = &render_template("sitemap.xml", &self.tera, &context, &self.config.theme)?; | |||
@@ -771,7 +799,7 @@ impl Site { | |||
pages.par_sort_unstable_by(sort_actual_pages_by_date); | |||
context.insert("last_build_date", &pages[0].meta.date.clone().map(|d| d.to_string())); | |||
context.insert("last_build_date", &pages[0].meta.date.clone()); | |||
// limit to the last n elements if the limit is set; otherwise use all. | |||
let num_entries = self.config.rss_limit.unwrap_or(pages.len()); | |||
let p = pages | |||
@@ -794,7 +822,7 @@ impl Site { | |||
let feed = &render_template("rss.xml", &self.tera, &context, &self.config.theme)?; | |||
if let Some(ref base) = base_path { | |||
let mut output_path = self.output_path.clone().to_path_buf(); | |||
let mut output_path = self.output_path.clone(); | |||
for component in base.components() { | |||
output_path.push(component); | |||
if !output_path.exists() { | |||
@@ -805,16 +833,13 @@ impl Site { | |||
} else { | |||
create_file(&self.output_path.join("rss.xml"), feed)?; | |||
} | |||
Ok(()) | |||
} | |||
/// Renders a single section | |||
pub fn render_section(&self, section: &Section, render_pages: bool) -> Result<()> { | |||
ensure_directory_exists(&self.output_path)?; | |||
let public = self.output_path.clone(); | |||
let mut output_path = public.to_path_buf(); | |||
let mut output_path = self.output_path.clone(); | |||
for component in §ion.file.components { | |||
output_path.push(component); | |||
@@ -1,3 +1,4 @@ | |||
extern crate config; | |||
extern crate site; | |||
extern crate tempfile; | |||
@@ -7,6 +8,7 @@ use std::fs::File; | |||
use std::io::prelude::*; | |||
use std::path::Path; | |||
use config::Taxonomy; | |||
use site::Site; | |||
use tempfile::tempdir; | |||
@@ -465,6 +467,13 @@ fn can_build_site_with_pagination_for_section() { | |||
"posts/page/4/index.html", | |||
"Last: https://replace-this-with-your-url.com/posts/page/5/" | |||
)); | |||
// sitemap contains the pager pages | |||
assert!(file_contains!( | |||
public, | |||
"sitemap.xml", | |||
"<loc>https://replace-this-with-your-url.com/posts/page/4/</loc>" | |||
)); | |||
} | |||
#[test] | |||
@@ -510,6 +519,93 @@ fn can_build_site_with_pagination_for_index() { | |||
assert!(file_contains!(public, "index.html", "Last: https://replace-this-with-your-url.com/")); | |||
assert_eq!(file_contains!(public, "index.html", "has_prev"), false); | |||
assert_eq!(file_contains!(public, "index.html", "has_next"), false); | |||
// sitemap contains the pager pages | |||
assert!(file_contains!( | |||
public, | |||
"sitemap.xml", | |||
"<loc>https://replace-this-with-your-url.com/page/1/</loc>" | |||
)) | |||
} | |||
#[test] | |||
fn can_build_site_with_pagination_for_taxonomy() { | |||
let mut path = env::current_dir().unwrap().parent().unwrap().parent().unwrap().to_path_buf(); | |||
path.push("test_site"); | |||
let mut site = Site::new(&path, "config.toml").unwrap(); | |||
site.config.taxonomies.push(Taxonomy { | |||
name: "tags".to_string(), | |||
paginate_by: Some(2), | |||
paginate_path: None, | |||
rss: true, | |||
}); | |||
site.load().unwrap(); | |||
for (i, (_, page)) in site.library.pages_mut().iter_mut().enumerate() { | |||
page.meta.taxonomies = { | |||
let mut taxonomies = HashMap::new(); | |||
taxonomies | |||
.insert("tags".to_string(), vec![if i % 2 == 0 { "A" } else { "B" }.to_string()]); | |||
taxonomies | |||
}; | |||
} | |||
site.populate_taxonomies().unwrap(); | |||
let tmp_dir = tempdir().expect("create temp dir"); | |||
let public = &tmp_dir.path().join("public"); | |||
site.set_output_path(&public); | |||
site.build().unwrap(); | |||
assert!(Path::new(&public).exists()); | |||
assert!(file_exists!(public, "index.html")); | |||
assert!(file_exists!(public, "sitemap.xml")); | |||
assert!(file_exists!(public, "robots.txt")); | |||
assert!(file_exists!(public, "a-fixed-url/index.html")); | |||
assert!(file_exists!(public, "posts/python/index.html")); | |||
assert!(file_exists!(public, "posts/tutorials/devops/nix/index.html")); | |||
assert!(file_exists!(public, "posts/with-assets/index.html")); | |||
// Tags | |||
assert!(file_exists!(public, "tags/index.html")); | |||
// With RSS | |||
assert!(file_exists!(public, "tags/a/rss.xml")); | |||
assert!(file_exists!(public, "tags/b/rss.xml")); | |||
// And pagination! | |||
assert!(file_exists!(public, "tags/a/page/1/index.html")); | |||
assert!(file_exists!(public, "tags/b/page/1/index.html")); | |||
assert!(file_exists!(public, "tags/a/page/2/index.html")); | |||
assert!(file_exists!(public, "tags/b/page/2/index.html")); | |||
// should redirect to posts/ | |||
assert!(file_contains!( | |||
public, | |||
"tags/a/page/1/index.html", | |||
"http-equiv=\"refresh\" content=\"0;url=https://replace-this-with-your-url.com/tags/a/\"" | |||
)); | |||
assert!(file_contains!(public, "tags/a/index.html", "Num pagers: 6")); | |||
assert!(file_contains!(public, "tags/a/index.html", "Page size: 2")); | |||
assert!(file_contains!(public, "tags/a/index.html", "Current index: 1")); | |||
assert!(!file_contains!(public, "tags/a/index.html", "has_prev")); | |||
assert!(file_contains!(public, "tags/a/index.html", "has_next")); | |||
assert!(file_contains!( | |||
public, | |||
"tags/a/index.html", | |||
"First: https://replace-this-with-your-url.com/tags/a/" | |||
)); | |||
assert!(file_contains!( | |||
public, | |||
"tags/a/index.html", | |||
"Last: https://replace-this-with-your-url.com/tags/a/page/6/" | |||
)); | |||
assert_eq!(file_contains!(public, "tags/a/index.html", "has_prev"), false); | |||
// sitemap contains the pager pages | |||
assert!(file_contains!( | |||
public, | |||
"sitemap.xml", | |||
"<loc>https://replace-this-with-your-url.com/tags/a/page/6/</loc>" | |||
)) | |||
} | |||
#[test] | |||
@@ -417,11 +417,11 @@ mod tests { | |||
assert_eq!( | |||
result, | |||
json!({ | |||
"category": { | |||
"date": "1979-05-27T07:32:00Z", | |||
"key": "value" | |||
}, | |||
}) | |||
"category": { | |||
"date": "1979-05-27T07:32:00Z", | |||
"key": "value" | |||
}, | |||
}) | |||
); | |||
} | |||
@@ -438,12 +438,12 @@ mod tests { | |||
assert_eq!( | |||
result, | |||
json!({ | |||
"headers": ["Number", "Title"], | |||
"records": [ | |||
["1", "Gutenberg"], | |||
["2", "Printing"] | |||
], | |||
}) | |||
"headers": ["Number", "Title"], | |||
"records": [ | |||
["1", "Gutenberg"], | |||
["2", "Printing"] | |||
], | |||
}) | |||
) | |||
} | |||
@@ -460,12 +460,12 @@ mod tests { | |||
assert_eq!( | |||
result, | |||
json!({ | |||
"key": "value", | |||
"array": [1, 2, 3], | |||
"subpackage": { | |||
"subkey": 5 | |||
} | |||
}) | |||
"key": "value", | |||
"array": [1, 2, 3], | |||
"subpackage": { | |||
"subkey": 5 | |||
} | |||
}) | |||
) | |||
} | |||
} |
@@ -144,7 +144,7 @@ pub fn make_get_taxonomy(all_taxonomies: &[Taxonomy], library: &Library) -> Glob | |||
None => { | |||
return Err( | |||
format!("`get_taxonomy` received an unknown taxonomy as kind: {}", kind).into() | |||
) | |||
); | |||
} | |||
}; | |||
@@ -180,12 +180,12 @@ pub fn make_get_taxonomy_url(all_taxonomies: &[Taxonomy]) -> GlobalFn { | |||
"`get_taxonomy_url` received an unknown taxonomy as kind: {}", | |||
kind | |||
) | |||
.into()) | |||
.into()); | |||
} | |||
}; | |||
if let Some(ref permalink) = container.get(&name) { | |||
return Ok(to_value(permalink.clone()).unwrap()); | |||
if let Some(permalink) = container.get(&name) { | |||
return Ok(to_value(permalink).unwrap()); | |||
} | |||
Err(format!("`get_taxonomy_url`: couldn't find `{}` in `{}` taxonomy", name, kind).into()) | |||
@@ -226,7 +226,7 @@ pub fn make_resize_image(imageproc: Arc<Mutex<imageproc::Processor>>) -> GlobalF | |||
return Err(format!("`resize_image`: Cannot find path: {}", path).into()); | |||
} | |||
let imageop = imageproc::ImageOp::from_args(path.clone(), &op, width, height, quality) | |||
let imageop = imageproc::ImageOp::from_args(path, &op, width, height, quality) | |||
.map_err(|e| format!("`resize_image`: {}", e))?; | |||
let url = imageproc.insert(imageop); | |||
@@ -16,7 +16,7 @@ create a **page** at `[base_url]/about`). | |||
If the file is given any name *other* than `index.md` or `_index.md`, then it will | |||
create a page with that name (without the `.md`). So naming a file in the root of your | |||
content directory `about.md` would also create a page at `[base_url]/about`. | |||
Another exception to that rule is that a filename starting with a YYYY-mm-dd date followed by | |||
Another exception to that rule is that a filename starting with a datetime (YYYY-mm-dd or [a RFC3339 datetime](https://www.ietf.org/rfc/rfc3339.txt)) followed by | |||
an underscore (`_`) or a dash (`-`) will use that date as the page date, unless already set | |||
in the front-matter. The page name will be anything after `_`/`-` so a filename like `2018-10-10-hello-world.md` will | |||
be available at `[base_url]/hello-world` | |||
@@ -26,6 +26,7 @@ Here is a full list of the supported languages and the short names you can use: | |||
- Plain Text -> ["txt"] | |||
- Assembly x86 (NASM) -> ["asm", "inc", "nasm"] | |||
- Crystal -> ["cr"] | |||
- Dart -> ["dart"] | |||
- Elixir -> ["ex", "exs"] | |||
- fsharp -> ["fs"] | |||
- Handlebars -> ["handlebars", "handlebars.html", "hbr", "hbrs", "hbs", "hdbs", "hjs", "mu", "mustache", "rac", "stache", "template", "tmpl"] | |||
@@ -14,7 +14,9 @@ Zola is available on [Brew](https://brew.sh): | |||
$ brew install zola | |||
``` | |||
## Linux | |||
## From source | |||
To build it from source, you will need to have Git, [Rust (at least 1.30) and Cargo](https://www.rust-lang.org/) | |||
installed. You will also need additional dependencies to compile [libsass](https://github.com/sass/libsass): | |||
### Arch Linux | |||
@@ -7,10 +7,9 @@ Two things can get paginated: a section or a taxonomy term. | |||
A paginated section gets the same `section` variable as a normal | |||
[section page](./documentation/templates/pages-sections.md#section-variables) minus its pages | |||
while a paginated taxonomy gets the a `taxonomy` variable of type `TaxonomyConfig`, equivalent | |||
to the taxonomy definition in the `config.toml`. | |||
while | |||
In addition, a paginated page gets a `paginator` variable of the `Pager` type: | |||
Both get a paginated page gets a `paginator` variable of the `Pager` type: | |||
```ts | |||
// How many items per page | |||
@@ -33,3 +32,17 @@ pages: Array<Page>; | |||
// Which page are we on | |||
current_index: Number; | |||
``` | |||
## Section | |||
A paginated section gets the same `section` variable as a normal | |||
[section page](./documentation/templates/pages-sections.md#section-variables) minus its pages. | |||
## Taxonomy term | |||
A paginated taxonomy gets two variables: | |||
- a `taxonomy` variable of type `TaxonomyConfig` | |||
- a `term` variable of type `TaxonomyTerm`. | |||
See the [taxonomies page](./documentation/templates/taxonomies.md) for a detailed version of the types. |
@@ -17,10 +17,22 @@ permalink: String; | |||
pages: Array<Page>; | |||
``` | |||
## Non-paginated taxonomies | |||
If a taxonomy is not paginated, the templates get the following variables: | |||
and a `TaxonomyConfig`: | |||
```ts | |||
name: String, | |||
slug: String, | |||
paginate_by: Number?; | |||
paginate_path: String?; | |||
rss: Bool; | |||
``` | |||
``` | |||
### Taxonomy list (`list.html`) | |||
This template is never paginated and therefore get the following variables in all cases. | |||
### Single term (`single.html`) | |||
```ts | |||
// The site config | |||
config: Config; | |||
@@ -30,11 +42,12 @@ taxonomy: TaxonomyConfig; | |||
current_url: String; | |||
// The current path for that page | |||
current_path: String; | |||
// The current term being rendered | |||
term: TaxonomyTerm; | |||
// All terms for that taxonomy | |||
terms: Array<TaxonomyTerm>; | |||
``` | |||
### Taxonomy list (`list.html`) | |||
### Single term (`single.html`) | |||
```ts | |||
// The site config | |||
config: Config; | |||
@@ -44,8 +57,9 @@ taxonomy: TaxonomyConfig; | |||
current_url: String; | |||
// The current path for that page | |||
current_path: String; | |||
// All terms for that taxonomy | |||
terms: Array<TaxonomyTerm>; | |||
// The current term being rendered | |||
term: TaxonomyTerm; | |||
``` | |||
## Paginated taxonomies | |||
A paginated taxonomy term will also get a `paginator` variable, see the [pagination page](./documentation/templates/pagination.md) | |||
for more details on that. |
@@ -12,6 +12,7 @@ apps: | |||
zola: | |||
command: zola | |||
plugs: | |||
- home | |||
- network | |||
- network-bind | |||
@@ -0,0 +1,202 @@ | |||
%YAML 1.2 | |||
--- | |||
# http://www.sublimetext.com/docs/3/syntax.html | |||
name: Dart | |||
file_extensions: | |||
- dart | |||
scope: source.dart | |||
contexts: | |||
main: | |||
- match: ^(#!.*)$ | |||
scope: meta.preprocessor.script.dart | |||
- match: ^\w*\b(library|import|part of|part|export)\b | |||
captures: | |||
0: keyword.other.import.dart | |||
push: | |||
- meta_scope: meta.declaration.dart | |||
- match: ; | |||
captures: | |||
0: punctuation.terminator.dart | |||
pop: true | |||
- include: strings | |||
- include: comments | |||
- match: \b(as|show|hide)\b | |||
scope: keyword.other.import.dart | |||
- include: comments | |||
- include: punctuation | |||
- include: annotations | |||
- include: keywords | |||
- include: constants-and-special-vars | |||
- include: strings | |||
annotations: | |||
- match: "@[a-zA-Z]+" | |||
scope: storage.type.annotation.dart | |||
comments: | |||
- match: /\*\*/ | |||
scope: comment.block.empty.dart | |||
captures: | |||
0: punctuation.definition.comment.dart | |||
- include: comments-doc-oldschool | |||
- include: comments-doc | |||
- include: comments-inline | |||
comments-doc: | |||
- match: /// | |||
scope: comment.block.documentation.dart | |||
comments-doc-oldschool: | |||
- match: /\*\* | |||
push: | |||
- meta_scope: comment.block.documentation.dart | |||
- match: \*/ | |||
pop: true | |||
- include: dartdoc | |||
comments-inline: | |||
- match: /\* | |||
push: | |||
- meta_scope: comment.block.dart | |||
- match: \*/ | |||
pop: true | |||
- match: ((//).*)$ | |||
captures: | |||
1: comment.line.double-slash.dart | |||
constants-and-special-vars: | |||
- match: (?<!\$)\b(true|false|null)\b(?!\$) | |||
scope: constant.language.dart | |||
- match: (?<!\$)\b(this|super)\b(?!\$) | |||
scope: variable.language.dart | |||
- match: '(?<!\$)\b((0(x|X)[0-9a-fA-F]*)|(([0-9]+\.?[0-9]*)|(\.[0-9]+))((e|E)(\+|-)?[0-9]+)?)\b(?!\$)' | |||
scope: constant.numeric.dart | |||
- match: "(?<![a-zA-Z0-9_$])[_$]*[A-Z][a-zA-Z0-9_$]*" | |||
scope: support.class.dart | |||
- match: '([_$]*[a-z][a-zA-Z0-9_$]*)(\(|\s+=>)' | |||
captures: | |||
1: entity.name.function.dart | |||
dartdoc: | |||
- match: '(\[.*?\])' | |||
captures: | |||
0: variable.name.source.dart | |||
- match: " .*" | |||
captures: | |||
0: variable.name.source.dart | |||
- match: "```.*?$" | |||
push: | |||
- meta_content_scope: variable.other.source.dart | |||
- match: "```" | |||
pop: true | |||
- match: (`.*?`) | |||
captures: | |||
0: variable.other.source.dart | |||
- match: (`.*?`) | |||
captures: | |||
0: variable.other.source.dart | |||
- match: (\* (( ).*))$ | |||
captures: | |||
2: variable.other.source.dart | |||
- match: (\* .*)$ | |||
keywords: | |||
- match: (?<!\$)\bas\b(?!\$) | |||
scope: keyword.cast.dart | |||
- match: (?<!\$)\b(try|on|catch|finally|throw|rethrow)\b(?!\$) | |||
scope: keyword.control.catch-exception.dart | |||
- match: (?<!\$)\b(break|case|continue|default|do|else|for|if|in|return|switch|while)\b(?!\$) | |||
scope: keyword.control.dart | |||
- match: (?<!\$)\b(sync(\*)?|async(\*)?|await|yield(\*)?)\b(?!\$) | |||
scope: keyword.control.dart | |||
- match: (?<!\$)\bassert\b(?!\$) | |||
scope: keyword.control.dart | |||
- match: (?<!\$)\b(new)\b(?!\$) | |||
scope: keyword.control.new.dart | |||
- match: (?<!\$)\b(abstract|class|enum|extends|external|factory|implements|get|mixin|native|operator|set|typedef|with)\b(?!\$) | |||
scope: keyword.declaration.dart | |||
- match: (?<!\$)\b(is\!?)\b(?!\$) | |||
scope: keyword.operator.dart | |||
- match: '\?|:' | |||
scope: keyword.operator.ternary.dart | |||
- match: (<<|>>>?|~|\^|\||&) | |||
scope: keyword.operator.bitwise.dart | |||
- match: ((&|\^|\||<<|>>>?)=) | |||
scope: keyword.operator.assignment.bitwise.dart | |||
- match: (=>) | |||
scope: keyword.operator.closure.dart | |||
- match: (==|!=|<=?|>=?) | |||
scope: keyword.operator.comparison.dart | |||
- match: '(([+*/%-]|\~)=)' | |||
scope: keyword.operator.assignment.arithmetic.dart | |||
- match: (=) | |||
scope: keyword.operator.assignment.dart | |||
- match: (\-\-|\+\+) | |||
scope: keyword.operator.increment-decrement.dart | |||
- match: (\-|\+|\*|\/|\~\/|%) | |||
scope: keyword.operator.arithmetic.dart | |||
- match: (!|&&|\|\|) | |||
scope: keyword.operator.logical.dart | |||
- match: (?<!\$)\b(static|final|const)\b(?!\$) | |||
scope: storage.modifier.dart | |||
- match: (?<!\$)\b(?:void|bool|num|int|double|dynamic|var)\b(?!\$) | |||
scope: storage.type.primitive.dart | |||
punctuation: | |||
- match: "," | |||
scope: punctuation.comma.dart | |||
- match: ; | |||
scope: punctuation.terminator.dart | |||
- match: \. | |||
scope: punctuation.dot.dart | |||
string-interp: | |||
- match: '\$((\w+)|\{([^{}]+)\})' | |||
captures: | |||
2: variable.parameter.dart | |||
3: variable.parameter.dart | |||
- match: \\. | |||
scope: constant.character.escape.dart | |||
strings: | |||
- match: (?<!r)""" | |||
push: | |||
- meta_scope: string.interpolated.triple.double.dart | |||
- match: '"""(?!")' | |||
pop: true | |||
- include: string-interp | |||
- match: (?<!r)''' | |||
push: | |||
- meta_scope: string.interpolated.triple.single.dart | |||
- match: "'''(?!')" | |||
pop: true | |||
- include: string-interp | |||
- match: r""" | |||
push: | |||
- meta_scope: string.quoted.triple.double.dart | |||
- match: '"""(?!")' | |||
pop: true | |||
- match: r''' | |||
push: | |||
- meta_scope: string.quoted.triple.single.dart | |||
- match: "'''(?!')" | |||
pop: true | |||
- match: (?<!\|r)" | |||
push: | |||
- meta_scope: string.interpolated.double.dart | |||
- match: '"' | |||
pop: true | |||
- match: \n | |||
scope: invalid.string.newline | |||
- include: string-interp | |||
- match: r" | |||
push: | |||
- meta_scope: string.quoted.double.dart | |||
- match: '"' | |||
pop: true | |||
- match: \n | |||
scope: invalid.string.newline | |||
- match: (?<!\|r)' | |||
push: | |||
- meta_scope: string.interpolated.single.dart | |||
- match: "'" | |||
pop: true | |||
- match: \n | |||
scope: invalid.string.newline | |||
- include: string-interp | |||
- match: r' | |||
push: | |||
- meta_scope: string.quoted.single.dart | |||
- match: "'" | |||
pop: true | |||
- match: \n | |||
scope: invalid.string.newline |
@@ -1,7 +1,21 @@ | |||
Tag: {{ term.name }} | |||
{% if not paginator %} | |||
Tag: {{ term.name }} | |||
{% for page in term.pages %} | |||
<article> | |||
<h3 class="post__title"><a href="{{ page.permalink }}">{{ page.title }}</a></h3> | |||
</article> | |||
{% endfor %} | |||
{% for page in term.pages %} | |||
<article> | |||
<h3 class="post__title"><a href="{{ page.permalink | safe }}">{{ page.title | safe }}</a></h3> | |||
</article> | |||
{% endfor %} | |||
{% else %} | |||
Tag: {{ term.name }} | |||
{% for page in paginator.pages %} | |||
{{page.title|safe}} | |||
{% endfor %} | |||
Num pagers: {{ paginator.number_pagers }} | |||
Page size: {{ paginator.paginate_by }} | |||
Current index: {{ paginator.current_index }} | |||
First: {{ paginator.first | safe }} | |||
Last: {{ paginator.last | safe }} | |||
{% if paginator.previous %}has_prev{% endif%} | |||
{% if paginator.next %}has_next{% endif%} | |||
{% endif %} |