Merge pull request #529 from getzola/next

0.5.1
This commit is contained in:
Vincent Prouillet 2018-12-14 20:54:15 +01:00 committed by GitHub
commit 3685ed1672
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 954 additions and 436 deletions

View file

@ -16,7 +16,7 @@ matrix:
# The earliest stable Rust version that works # The earliest stable Rust version that works
- env: TARGET=x86_64-unknown-linux-gnu - env: TARGET=x86_64-unknown-linux-gnu
rust: 1.29.0 rust: 1.30.0
before_install: set -e before_install: set -e

View file

@ -1,5 +1,14 @@
# Changelog # 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) ## 0.5.0 (2018-11-17)
### Breaking ### Breaking

765
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
[package] [package]
name = "zola" name = "zola"
version = "0.5.0" version = "0.5.1"
authors = ["Vincent Prouillet <prouillet.vincent@gmail.com>"] authors = ["Vincent Prouillet <prouillet.vincent@gmail.com>"]
license = "MIT" license = "MIT"
readme = "README.md" readme = "README.md"

View file

@ -37,6 +37,14 @@ impl Taxonomy {
false false
} }
} }
pub fn paginate_path(&self) -> &str {
if let Some(ref path) = self.paginate_path {
path
} else {
"page"
}
}
} }
impl Default for Taxonomy { impl Default for Taxonomy {

View file

@ -20,8 +20,11 @@ use content::file_info::FileInfo;
use content::ser::SerializingPage; use content::ser::SerializingPage;
lazy_static! { lazy_static! {
// Check whether a string starts with yyyy-mm-dd{-,_} // Based on https://regex101.com/r/H2n38Z/1/tests
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(); // 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)] #[derive(Clone, Debug, PartialEq)]
@ -113,11 +116,11 @@ impl Page {
page.word_count = Some(word_count); page.word_count = Some(word_count);
page.reading_time = Some(reading_time); page.reading_time = Some(reading_time);
let mut has_date_in_name = false; let mut slug_from_dated_filename = None;
if DATE_IN_FILENAME.is_match(&page.file.name) { if let Some(ref caps) = RFC3339_DATE.captures(&page.file.name.replace(".md", "")) {
has_date_in_name = true; slug_from_dated_filename = Some(caps.name("slug").unwrap().as_str().to_string());
if page.meta.date.is_none() { 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(); page.meta.date_to_datetime();
} }
} }
@ -132,9 +135,8 @@ impl Page {
slugify(&page.file.name) slugify(&page.file.name)
} }
} else { } else {
if has_date_in_name { if let Some(slug) = slug_from_dated_filename {
// skip the date + the {_,-} slugify(&slug)
slugify(&page.file.name[11..])
} else { } else {
slugify(&page.file.name) slugify(&page.file.name)
} }
@ -507,7 +509,7 @@ Hello world
} }
#[test] #[test]
fn can_get_date_from_filename() { fn can_get_date_from_short_date_in_filename() {
let config = Config::default(); let config = Config::default();
let content = r#" let content = r#"
+++ +++
@ -523,6 +525,23 @@ Hello world
assert_eq!(page.slug, "hello"); 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] #[test]
fn frontmatter_date_override_filename_date() { fn frontmatter_date_override_filename_date() {
let config = Config::default(); let config = Config::default();

View file

@ -80,7 +80,7 @@ impl Section {
pub fn parse(file_path: &Path, content: &str, config: &Config) -> Result<Section> { pub fn parse(file_path: &Path, content: &str, config: &Config) -> Result<Section> {
let (meta, content) = split_section_content(file_path, content)?; let (meta, content) = split_section_content(file_path, content)?;
let mut section = Section::new(file_path, meta); 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(&section.raw_content); let (word_count, reading_time) = get_reading_analytics(&section.raw_content);
section.word_count = Some(word_count); section.word_count = Some(word_count);
section.reading_time = Some(reading_time); section.reading_time = Some(reading_time);

View file

@ -14,7 +14,7 @@ use taxonomies::{Taxonomy, TaxonomyItem};
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
enum PaginationRoot<'a> { enum PaginationRoot<'a> {
Section(&'a Section), 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 /// 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, all_pages: &item.pages,
pagers: Vec::with_capacity(item.pages.len() / paginate_by), pagers: Vec::with_capacity(item.pages.len() / paginate_by),
paginate_by, paginate_by,
root: PaginationRoot::Taxonomy(taxonomy), root: PaginationRoot::Taxonomy(taxonomy, item),
permalink: item.permalink.clone(), permalink: item.permalink.clone(),
path: format!("{}/{}", taxonomy.kind.name, item.slug), path: format!("{}/{}", taxonomy.kind.name, item.slug),
paginate_path: taxonomy paginate_path: taxonomy
.kind .kind
.paginate_path .paginate_path
.clone() .clone()
.unwrap_or_else(|| "pages".to_string()), .unwrap_or_else(|| "page".to_string()),
is_index: false, is_index: false,
template: format!("{}/single.html", taxonomy.kind.name), template: format!("{}/single.html", taxonomy.kind.name),
}; };
@ -212,8 +212,9 @@ impl<'a> Paginator<'a> {
context context
.insert("section", &SerializingSection::from_section_basic(s, Some(library))); .insert("section", &SerializingSection::from_section_basic(s, Some(library)));
} }
PaginationRoot::Taxonomy(t) => { PaginationRoot::Taxonomy(t, item) => {
context.insert("taxonomy", &t.kind); context.insert("taxonomy", &t.kind);
context.insert("term", &item.serialize(library));
} }
}; };
context.insert("current_url", &pager.permalink); context.insert("current_url", &pager.permalink);
@ -349,7 +350,7 @@ mod tests {
assert_eq!(paginator.pagers[1].index, 2); assert_eq!(paginator.pagers[1].index, 2);
assert_eq!(paginator.pagers[1].pages.len(), 1); 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].permalink, "https://vincent.is/tags/something/page/2/");
assert_eq!(paginator.pagers[1].path, "tags/something/pages/2/"); assert_eq!(paginator.pagers[1].path, "tags/something/page/2/");
} }
} }

View file

@ -13,7 +13,7 @@ use library::Library;
use sorting::sort_pages_by_date; use sorting::sort_pages_by_date;
#[derive(Debug, Clone, PartialEq, Serialize)] #[derive(Debug, Clone, PartialEq, Serialize)]
struct SerializedTaxonomyItem<'a> { pub struct SerializedTaxonomyItem<'a> {
name: &'a str, name: &'a str,
slug: &'a str, slug: &'a str,
permalink: &'a str, permalink: &'a str,
@ -71,6 +71,10 @@ impl TaxonomyItem {
TaxonomyItem { name: name.to_string(), permalink, slug, pages } 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)] #[derive(Debug, Clone, PartialEq, Serialize)]

View file

@ -311,7 +311,7 @@ pub fn after_content_change(site: &mut Site, path: &Path) -> Result<()> {
if is_md { if is_md {
// only delete if it was able to be added in the first place // only delete if it was able to be added in the first place
if !index.exists() && !path.exists() { 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 // 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() site.render_orphan_pages()
} }
"section.html" => site.render_sections(), "section.html" => site.render_sections(),
"404.html" => site.render_404(),
// Either the index or some unknown template changed // Either the index or some unknown template changed
// We can't really know what this change affects so rebuild all // We can't really know what this change affects so rebuild all
// the things // the things

View file

@ -228,9 +228,22 @@ fn can_rebuild_after_renaming_section_folder() {
fn can_rebuild_after_renaming_non_md_asset_in_colocated_folder() { fn can_rebuild_after_renaming_non_md_asset_in_colocated_folder() {
let tmp_dir = tempdir().expect("create temp dir"); let tmp_dir = tempdir().expect("create temp dir");
let (site_path, mut site) = load_and_build_site!(tmp_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 // 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); let res = after_content_rename(&mut site, &old_path, &new_path);
assert!(res.is_ok()); 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());
}

View file

@ -31,7 +31,7 @@ pub struct Rendered {
// means we will have example, example-1, example-2 etc // means we will have example, example-1, example-2 etc
fn find_anchor(anchors: &[String], name: String, level: u8) -> String { fn find_anchor(anchors: &[String], name: String, level: u8) -> String {
if level == 0 && !anchors.contains(&name) { if level == 0 && !anchors.contains(&name) {
return name.to_string(); return name;
} }
let new_anchor = format!("{}-{}", name, level + 1); let new_anchor = format!("{}-{}", name, level + 1);

View file

@ -1,7 +1,7 @@
use pest::iterators::Pair; use pest::iterators::Pair;
use pest::Parser; use pest::Parser;
use tera::{to_value, Context, Map, Value};
use regex::Regex; use regex::Regex;
use tera::{to_value, Context, Map, Value};
use context::RenderContext; use context::RenderContext;
use errors::{Result, ResultExt}; use errors::{Result, ResultExt};
@ -20,9 +20,9 @@ lazy_static! {
fn replace_string_markers(input: &str) -> String { fn replace_string_markers(input: &str) -> String {
match input.chars().next().unwrap() { match input.chars().next().unwrap() {
'"' => input.replace('"', "").to_string(), '"' => input.replace('"', ""),
'\'' => input.replace('\'', "").to_string(), '\'' => input.replace('\'', ""),
'`' => input.replace('`', "").to_string(), '`' => input.replace('`', ""),
_ => unreachable!("How did you even get there"), _ => unreachable!("How did you even get there"),
} }
} }

View file

@ -627,10 +627,8 @@ impl Site {
ensure_directory_exists(&self.output_path)?; ensure_directory_exists(&self.output_path)?;
let mut context = Context::new(); let mut context = Context::new();
context.insert("config", &self.config); context.insert("config", &self.config);
create_file( let output = render_template("404.html", &self.tera, &context, &self.config.theme)?;
&self.output_path.join("404.html"), create_file(&self.output_path.join("404.html"), &self.inject_livereload(output))
&render_template("404.html", &self.tera, &context, &self.config.theme)?,
)
} }
/// Renders robots.txt /// Renders robots.txt
@ -646,7 +644,6 @@ impl Site {
/// Renders all taxonomies with at least one non-draft post /// Renders all taxonomies with at least one non-draft post
pub fn render_taxonomies(&self) -> Result<()> { pub fn render_taxonomies(&self) -> Result<()> {
// TODO: make parallel?
for taxonomy in &self.taxonomies { for taxonomy in &self.taxonomies {
self.render_taxonomy(taxonomy)?; self.render_taxonomy(taxonomy)?;
} }
@ -669,24 +666,26 @@ impl Site {
.items .items
.par_iter() .par_iter()
.map(|item| { .map(|item| {
let path = output_path.join(&item.slug);
if taxonomy.kind.is_paginated() {
self.render_paginated(
&path,
&Paginator::from_taxonomy(&taxonomy, item, &self.library),
)?;
} else {
let single_output =
taxonomy.render_term(item, &self.tera, &self.config, &self.library)?;
create_directory(&path)?;
create_file(&path.join("index.html"), &self.inject_livereload(single_output))?;
}
if taxonomy.kind.rss { if taxonomy.kind.rss {
self.render_rss_feed( self.render_rss_feed(
item.pages.iter().map(|p| self.library.get_page_by_key(*p)).collect(), item.pages.iter().map(|p| self.library.get_page_by_key(*p)).collect(),
Some(&PathBuf::from(format!("{}/{}", taxonomy.kind.name, item.slug))), Some(&PathBuf::from(format!("{}/{}", taxonomy.kind.name, item.slug))),
)?;
}
if taxonomy.kind.is_paginated() {
self.render_paginated(
&output_path,
&Paginator::from_taxonomy(&taxonomy, item, &self.library),
) )
} else { } else {
let single_output = Ok(())
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))
} }
}) })
.collect::<Result<()>>() .collect::<Result<()>>()
@ -720,6 +719,18 @@ impl Site {
.iter() .iter()
.map(|s| SitemapEntry::new(s.permalink.clone(), None)) .map(|s| SitemapEntry::new(s.permalink.clone(), None))
.collect::<Vec<_>>(); .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)); sections.sort_by(|a, b| a.permalink.cmp(&b.permalink));
context.insert("sections", &sections); context.insert("sections", &sections);
@ -733,12 +744,29 @@ impl Site {
self.config.make_permalink(&format!("{}/{}", &name, item.slug)), self.config.make_permalink(&format!("{}/{}", &name, item.slug)),
None, 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)); terms.sort_by(|a, b| a.permalink.cmp(&b.permalink));
taxonomies.push(terms); taxonomies.push(terms);
} }
context.insert("taxonomies", &taxonomies);
context.insert("taxonomies", &taxonomies);
context.insert("config", &self.config); context.insert("config", &self.config);
let sitemap = &render_template("sitemap.xml", &self.tera, &context, &self.config.theme)?; 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); 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. // 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 num_entries = self.config.rss_limit.unwrap_or(pages.len());
let p = pages let p = pages
@ -794,7 +822,7 @@ impl Site {
let feed = &render_template("rss.xml", &self.tera, &context, &self.config.theme)?; let feed = &render_template("rss.xml", &self.tera, &context, &self.config.theme)?;
if let Some(ref base) = base_path { 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() { for component in base.components() {
output_path.push(component); output_path.push(component);
if !output_path.exists() { if !output_path.exists() {
@ -805,16 +833,13 @@ impl Site {
} else { } else {
create_file(&self.output_path.join("rss.xml"), feed)?; create_file(&self.output_path.join("rss.xml"), feed)?;
} }
Ok(()) Ok(())
} }
/// Renders a single section /// Renders a single section
pub fn render_section(&self, section: &Section, render_pages: bool) -> Result<()> { pub fn render_section(&self, section: &Section, render_pages: bool) -> Result<()> {
ensure_directory_exists(&self.output_path)?; ensure_directory_exists(&self.output_path)?;
let public = self.output_path.clone(); let mut output_path = self.output_path.clone();
let mut output_path = public.to_path_buf();
for component in &section.file.components { for component in &section.file.components {
output_path.push(component); output_path.push(component);

View file

@ -1,3 +1,4 @@
extern crate config;
extern crate site; extern crate site;
extern crate tempfile; extern crate tempfile;
@ -7,6 +8,7 @@ use std::fs::File;
use std::io::prelude::*; use std::io::prelude::*;
use std::path::Path; use std::path::Path;
use config::Taxonomy;
use site::Site; use site::Site;
use tempfile::tempdir; use tempfile::tempdir;
@ -465,6 +467,13 @@ fn can_build_site_with_pagination_for_section() {
"posts/page/4/index.html", "posts/page/4/index.html",
"Last: https://replace-this-with-your-url.com/posts/page/5/" "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] #[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!(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_prev"), false);
assert_eq!(file_contains!(public, "index.html", "has_next"), 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] #[test]

View file

@ -417,11 +417,11 @@ mod tests {
assert_eq!( assert_eq!(
result, result,
json!({ json!({
"category": { "category": {
"date": "1979-05-27T07:32:00Z", "date": "1979-05-27T07:32:00Z",
"key": "value" "key": "value"
}, },
}) })
); );
} }
@ -438,12 +438,12 @@ mod tests {
assert_eq!( assert_eq!(
result, result,
json!({ json!({
"headers": ["Number", "Title"], "headers": ["Number", "Title"],
"records": [ "records": [
["1", "Gutenberg"], ["1", "Gutenberg"],
["2", "Printing"] ["2", "Printing"]
], ],
}) })
) )
} }
@ -460,12 +460,12 @@ mod tests {
assert_eq!( assert_eq!(
result, result,
json!({ json!({
"key": "value", "key": "value",
"array": [1, 2, 3], "array": [1, 2, 3],
"subpackage": { "subpackage": {
"subkey": 5 "subkey": 5
} }
}) })
) )
} }
} }

View file

@ -144,7 +144,7 @@ pub fn make_get_taxonomy(all_taxonomies: &[Taxonomy], library: &Library) -> Glob
None => { None => {
return Err( return Err(
format!("`get_taxonomy` received an unknown taxonomy as kind: {}", kind).into() 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: {}", "`get_taxonomy_url` received an unknown taxonomy as kind: {}",
kind kind
) )
.into()) .into());
} }
}; };
if let Some(ref permalink) = container.get(&name) { if let Some(permalink) = container.get(&name) {
return Ok(to_value(permalink.clone()).unwrap()); return Ok(to_value(permalink).unwrap());
} }
Err(format!("`get_taxonomy_url`: couldn't find `{}` in `{}` taxonomy", name, kind).into()) 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()); 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))?; .map_err(|e| format!("`resize_image`: {}", e))?;
let url = imageproc.insert(imageop); let url = imageproc.insert(imageop);

View file

@ -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 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 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`. 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 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 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` be available at `[base_url]/hello-world`

View file

@ -26,6 +26,7 @@ Here is a full list of the supported languages and the short names you can use:
- Plain Text -> ["txt"] - Plain Text -> ["txt"]
- Assembly x86 (NASM) -> ["asm", "inc", "nasm"] - Assembly x86 (NASM) -> ["asm", "inc", "nasm"]
- Crystal -> ["cr"] - Crystal -> ["cr"]
- Dart -> ["dart"]
- Elixir -> ["ex", "exs"] - Elixir -> ["ex", "exs"]
- fsharp -> ["fs"] - fsharp -> ["fs"]
- Handlebars -> ["handlebars", "handlebars.html", "hbr", "hbrs", "hbs", "hdbs", "hjs", "mu", "mustache", "rac", "stache", "template", "tmpl"] - Handlebars -> ["handlebars", "handlebars.html", "hbr", "hbrs", "hbs", "hdbs", "hjs", "mu", "mustache", "rac", "stache", "template", "tmpl"]

View file

@ -14,7 +14,9 @@ Zola is available on [Brew](https://brew.sh):
$ brew install zola $ 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 ### Arch Linux

View file

@ -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 A paginated section gets the same `section` variable as a normal
[section page](./documentation/templates/pages-sections.md#section-variables) minus its pages [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 while
to the taxonomy definition in the `config.toml`.
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 ```ts
// How many items per page // How many items per page
@ -33,3 +32,17 @@ pages: Array<Page>;
// Which page are we on // Which page are we on
current_index: Number; 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.

View file

@ -17,8 +17,35 @@ permalink: String;
pages: Array<Page>; pages: Array<Page>;
``` ```
## Non-paginated taxonomies and a `TaxonomyConfig`:
If a taxonomy is not paginated, the templates get the following variables:
```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.
```ts
// The site config
config: Config;
// The data of the taxonomy, from the config
taxonomy: TaxonomyConfig;
// The current full permalink for that page
current_url: String;
// The current path for that page
current_path: String;
// All terms for that taxonomy
terms: Array<TaxonomyTerm>;
```
### Single term (`single.html`) ### Single term (`single.html`)
```ts ```ts
@ -34,18 +61,5 @@ current_path: String;
term: TaxonomyTerm; term: TaxonomyTerm;
``` ```
### Taxonomy list (`list.html`) A paginated taxonomy term will also get a `paginator` variable, see the [pagination page](./documentation/templates/pagination.md)
```ts for more details on that.
// The site config
config: Config;
// The data of the taxonomy, from the config
taxonomy: TaxonomyConfig;
// The current full permalink for that page
current_url: String;
// The current path for that page
current_path: String;
// All terms for that taxonomy
terms: Array<TaxonomyTerm>;
```
## Paginated taxonomies

View file

@ -12,6 +12,7 @@ apps:
zola: zola:
command: zola command: zola
plugs: plugs:
- home
- network - network
- network-bind - network-bind

View file

@ -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

Binary file not shown.

View file

@ -1,7 +1,21 @@
Tag: {{ term.name }} {% if not paginator %}
Tag: {{ term.name }}
{% for page in term.pages %} {% for page in term.pages %}
<article> <article>
<h3 class="post__title"><a href="{{ page.permalink }}">{{ page.title }}</a></h3> <h3 class="post__title"><a href="{{ page.permalink | safe }}">{{ page.title | safe }}</a></h3>
</article> </article>
{% endfor %} {% 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 %}