Browse Source

Merge pull request #529 from getzola/next

0.5.1
index-subcmd
Vincent Prouillet GitHub 6 years ago
parent
commit
3685ed1672
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 945 additions and 427 deletions
  1. +1
    -1
      .travis.yml
  2. +9
    -0
      CHANGELOG.md
  3. +430
    -335
      Cargo.lock
  4. +1
    -1
      Cargo.toml
  5. +8
    -0
      components/config/src/config.rs
  6. +29
    -10
      components/library/src/content/page.rs
  7. +1
    -1
      components/library/src/content/section.rs
  8. +7
    -6
      components/library/src/pagination/mod.rs
  9. +5
    -1
      components/library/src/taxonomies/mod.rs
  10. +2
    -1
      components/rebuild/src/lib.rs
  11. +14
    -1
      components/rebuild/tests/rebuild.rs
  12. +1
    -1
      components/rendering/src/markdown.rs
  13. +4
    -4
      components/rendering/src/shortcode.rs
  14. +48
    -23
      components/site/src/lib.rs
  15. +96
    -0
      components/site/tests/site.rs
  16. +17
    -17
      components/templates/src/global_fns/load_data.rs
  17. +5
    -5
      components/templates/src/global_fns/mod.rs
  18. +1
    -1
      docs/content/documentation/content/page.md
  19. +1
    -0
      docs/content/documentation/content/syntax-highlighting.md
  20. +3
    -1
      docs/content/documentation/getting-started/installation.md
  21. +16
    -3
      docs/content/documentation/templates/pagination.md
  22. +23
    -9
      docs/content/documentation/templates/taxonomies.md
  23. +1
    -0
      snapcraft.yaml
  24. +202
    -0
      sublime_syntaxes/Dart.sublime-syntax
  25. BIN
      sublime_syntaxes/newlines.packdump
  26. +20
    -6
      test_site/templates/tags/single.html

+ 1
- 1
.travis.yml 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


+ 9
- 0
CHANGELOG.md 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


+ 430
- 335
Cargo.lock
File diff suppressed because it is too large
View File


+ 1
- 1
Cargo.toml 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"


+ 8
- 0
components/config/src/config.rs 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 {


+ 29
- 10
components/library/src/content/page.rs 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{-,_}
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)] #[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;
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() { 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 {
// skip the date + the {_,-}
slugify(&page.file.name[11..])
if let Some(slug) = slug_from_dated_filename {
slugify(&slug)
} 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();


+ 1
- 1
components/library/src/content/section.rs 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);


+ 7
- 6
components/library/src/pagination/mod.rs 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].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/");
} }
} }

+ 5
- 1
components/library/src/taxonomies/mod.rs 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)]


+ 2
- 1
components/rebuild/src/lib.rs 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


+ 14
- 1
components/rebuild/tests/rebuild.rs 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());
}

+ 1
- 1
components/rendering/src/markdown.rs 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);


+ 4
- 4
components/rendering/src/shortcode.rs 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('\'', "").to_string(),
'`' => input.replace('`', "").to_string(),
'"' => input.replace('"', ""),
'\'' => input.replace('\'', ""),
'`' => input.replace('`', ""),
_ => unreachable!("How did you even get there"), _ => unreachable!("How did you even get there"),
} }
} }


+ 48
- 23
components/site/src/lib.rs 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(
&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 /// 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| {
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() { if taxonomy.kind.is_paginated() {
self.render_paginated( self.render_paginated(
&output_path,
&path,
&Paginator::from_taxonomy(&taxonomy, item, &self.library), &Paginator::from_taxonomy(&taxonomy, item, &self.library),
)
)?;
} else { } else {
let single_output = let single_output =
taxonomy.render_term(item, &self.tera, &self.config, &self.library)?; taxonomy.render_term(item, &self.tera, &self.config, &self.library)?;
let path = output_path.join(&item.slug);
create_directory(&path)?; 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<()>>() .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 = public.to_path_buf();
let mut output_path = self.output_path.clone();
for component in &section.file.components { for component in &section.file.components {
output_path.push(component); output_path.push(component);




+ 96
- 0
components/site/tests/site.rs 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]


+ 17
- 17
components/templates/src/global_fns/load_data.rs View File

@@ -417,11 +417,11 @@ mod tests {
assert_eq!( assert_eq!(
result, result,
json!({ 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!( assert_eq!(
result, result,
json!({ 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!( assert_eq!(
result, result,
json!({ json!({
"key": "value",
"array": [1, 2, 3],
"subpackage": {
"subkey": 5
}
})
"key": "value",
"array": [1, 2, 3],
"subpackage": {
"subkey": 5
}
})
) )
} }
} }

+ 5
- 5
components/templates/src/global_fns/mod.rs 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) {
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()) 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);




+ 1
- 1
docs/content/documentation/content/page.md 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`


+ 1
- 0
docs/content/documentation/content/syntax-highlighting.md 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"]


+ 3
- 1
docs/content/documentation/getting-started/installation.md 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




+ 16
- 3
docs/content/documentation/templates/pagination.md 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
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 ```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.

+ 23
- 9
docs/content/documentation/templates/taxonomies.md View File

@@ -17,10 +17,22 @@ permalink: String;
pages: Array<Page>; 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 ```ts
// The site config // The site config
config: Config; config: Config;
@@ -30,11 +42,12 @@ taxonomy: TaxonomyConfig;
current_url: String; current_url: String;
// The current path for that page // The current path for that page
current_path: String; 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 ```ts
// The site config // The site config
config: Config; config: Config;
@@ -44,8 +57,9 @@ taxonomy: TaxonomyConfig;
current_url: String; current_url: String;
// The current path for that page // The current path for that page
current_path: String; 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.

+ 1
- 0
snapcraft.yaml View File

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




+ 202
- 0
sublime_syntaxes/Dart.sublime-syntax 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

BIN
sublime_syntaxes/newlines.packdump View File


+ 20
- 6
test_site/templates/tags/single.html View File

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

Loading…
Cancel
Save