Browse Source

First draft of theme support

index-subcmd
Vincent Prouillet 6 years ago
parent
commit
c77cc9b170
20 changed files with 114 additions and 30 deletions
  1. +3
    -0
      Cargo.lock
  2. +3
    -0
      components/config/src/lib.rs
  3. +2
    -1
      components/content/src/page.rs
  4. +2
    -1
      components/content/src/section.rs
  5. +1
    -0
      components/pagination/Cargo.toml
  6. +3
    -1
      components/pagination/src/lib.rs
  7. +56
    -21
      components/site/src/lib.rs
  8. +1
    -0
      components/site/test_site/config.toml
  9. +3
    -0
      components/site/test_site/themes/sample/sass/sample.scss
  10. +0
    -0
      components/site/test_site/themes/sample/static/some.js
  11. +1
    -0
      components/site/test_site/themes/sample/templates/index.html
  12. +3
    -0
      components/site/test_site/themes/sample/theme.toml
  13. +4
    -1
      components/site/tests/site.rs
  14. +1
    -0
      components/taxonomies/Cargo.toml
  15. +4
    -2
      components/taxonomies/src/lib.rs
  16. +1
    -1
      components/templates/src/lib.rs
  17. +1
    -0
      components/utils/Cargo.toml
  18. +2
    -0
      components/utils/src/lib.rs
  19. +21
    -0
      components/utils/src/templates.rs
  20. +2
    -2
      src/cmd/serve.rs

+ 3
- 0
Cargo.lock View File

@@ -4,6 +4,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"errors 0.1.0", "errors 0.1.0",
"tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"tera 0.10.9 (registry+https://github.com/rust-lang/crates.io-index)",
] ]


[[package]] [[package]]
@@ -775,6 +776,7 @@ dependencies = [
"serde 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)",
"tera 0.10.9 (registry+https://github.com/rust-lang/crates.io-index)", "tera 0.10.9 (registry+https://github.com/rust-lang/crates.io-index)",
"utils 0.1.0",
] ]


[[package]] [[package]]
@@ -1163,6 +1165,7 @@ dependencies = [
"serde_derive 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)",
"slug 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "slug 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"tera 0.10.9 (registry+https://github.com/rust-lang/crates.io-index)", "tera 0.10.9 (registry+https://github.com/rust-lang/crates.io-index)",
"utils 0.1.0",
] ]


[[package]] [[package]]


+ 3
- 0
components/config/src/lib.rs View File

@@ -23,6 +23,8 @@ pub struct Config {
/// Base URL of the site, the only required config argument /// Base URL of the site, the only required config argument
pub base_url: String, pub base_url: String,


/// Theme to use
pub theme: Option<String>,
/// Title of the site. Defaults to None /// Title of the site. Defaults to None
pub title: Option<String>, pub title: Option<String>,
/// Whether to highlight all code blocks found in markdown files. Defaults to false /// Whether to highlight all code blocks found in markdown files. Defaults to false
@@ -130,6 +132,7 @@ impl Default for Config {
fn default() -> Config { fn default() -> Config {
Config { Config {
title: Some("".to_string()), title: Some("".to_string()),
theme: None,
base_url: "http://a-website.com/".to_string(), base_url: "http://a-website.com/".to_string(),
highlight_code: Some(true), highlight_code: Some(true),
highlight_theme: Some("base16-ocean-dark".to_string()), highlight_theme: Some("base16-ocean-dark".to_string()),


+ 2
- 1
components/content/src/page.rs View File

@@ -12,6 +12,7 @@ use errors::{Result, ResultExt};
use config::Config; use config::Config;
use utils::fs::{read_file, find_related_assets}; use utils::fs::{read_file, find_related_assets};
use utils::site::get_reading_analytics; use utils::site::get_reading_analytics;
use utils::templates::render_template;
use front_matter::{PageFrontMatter, InsertAnchor, split_page_content}; use front_matter::{PageFrontMatter, InsertAnchor, split_page_content};
use rendering::{Context, Header, markdown_to_html}; use rendering::{Context, Header, markdown_to_html};


@@ -154,7 +155,7 @@ impl Page {
context.add("current_url", &self.permalink); context.add("current_url", &self.permalink);
context.add("current_path", &self.path); context.add("current_path", &self.path);


tera.render(&tpl_name, &context)
render_template(&tpl_name, tera, &context, config.theme.clone())
.chain_err(|| format!("Failed to render page '{}'", self.file.path.display())) .chain_err(|| format!("Failed to render page '{}'", self.file.path.display()))
} }
} }


+ 2
- 1
components/content/src/section.rs View File

@@ -9,6 +9,7 @@ use config::Config;
use front_matter::{SectionFrontMatter, split_section_content}; use front_matter::{SectionFrontMatter, split_section_content};
use errors::{Result, ResultExt}; use errors::{Result, ResultExt};
use utils::fs::read_file; use utils::fs::read_file;
use utils::templates::render_template;
use rendering::{Context, Header, markdown_to_html}; use rendering::{Context, Header, markdown_to_html};


use page::Page; use page::Page;
@@ -113,7 +114,7 @@ impl Section {
context.add("current_url", &self.permalink); context.add("current_url", &self.permalink);
context.add("current_path", &self.path); context.add("current_path", &self.path);


tera.render(&tpl_name, &context)
render_template(&tpl_name, tera, &context, config.theme.clone())
.chain_err(|| format!("Failed to render section '{}'", self.file.path.display())) .chain_err(|| format!("Failed to render section '{}'", self.file.path.display()))
} }




+ 1
- 0
components/pagination/Cargo.toml View File

@@ -11,6 +11,7 @@ serde_derive = "1.0"
errors = { path = "../errors" } errors = { path = "../errors" }
config = { path = "../config" } config = { path = "../config" }
content = { path = "../content" } content = { path = "../content" }
utils = { path = "../utils" }


[dev-dependencies] [dev-dependencies]
front_matter = { path = "../front_matter" } front_matter = { path = "../front_matter" }

+ 3
- 1
components/pagination/src/lib.rs View File

@@ -5,6 +5,7 @@ extern crate tera;
extern crate errors; extern crate errors;
extern crate config; extern crate config;
extern crate content; extern crate content;
extern crate utils;


#[cfg(test)] #[cfg(test)]
extern crate front_matter; extern crate front_matter;
@@ -16,6 +17,7 @@ use tera::{Tera, Context, to_value, Value};
use errors::{Result, ResultExt}; use errors::{Result, ResultExt};
use config::Config; use config::Config;
use content::{Page, Section}; use content::{Page, Section};
use utils::templates::render_template;




/// 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
@@ -170,7 +172,7 @@ impl<'a> Paginator<'a> {
context.add("current_path", &pager.path); context.add("current_path", &pager.path);
context.add("paginator", &self.build_paginator_context(pager)); context.add("paginator", &self.build_paginator_context(pager));


tera.render(&self.section.get_template_name(), &context)
render_template(&self.section.get_template_name(), tera, &context, config.theme.clone())
.chain_err(|| format!("Failed to render pager {} of section '{}'", pager.index, self.section.file.path.display())) .chain_err(|| format!("Failed to render pager {} of section '{}'", pager.index, self.section.file.path.display()))
} }
} }


+ 56
- 21
components/site/src/lib.rs View File

@@ -33,6 +33,7 @@ use sass_rs::{Options, compile_file};
use errors::{Result, ResultExt}; use errors::{Result, ResultExt};
use config::{Config, get_config}; use config::{Config, get_config};
use utils::fs::{create_file, create_directory, ensure_directory_exists}; use utils::fs::{create_file, create_directory, ensure_directory_exists};
use utils::templates::render_template;
use content::{Page, Section, populate_previous_and_next_pages, sort_pages}; use content::{Page, Section, populate_previous_and_next_pages, sort_pages};
use templates::{GUTENBERG_TERA, global_fns, render_redirect_template}; use templates::{GUTENBERG_TERA, global_fns, render_redirect_template};
use front_matter::{SortBy, InsertAnchor}; use front_matter::{SortBy, InsertAnchor};
@@ -67,7 +68,7 @@ pub struct Site {
pub tera: Tera, pub tera: Tera,
live_reload: bool, live_reload: bool,
output_path: PathBuf, output_path: PathBuf,
static_path: PathBuf,
pub static_path: PathBuf,
pub tags: Option<Taxonomy>, pub tags: Option<Taxonomy>,
pub categories: Option<Taxonomy>, pub categories: Option<Taxonomy>,
/// A map of all .md files (section and pages) and their permalink /// A map of all .md files (section and pages) and their permalink
@@ -80,14 +81,29 @@ impl Site {
/// Passing in a path is only used in tests /// Passing in a path is only used in tests
pub fn new<P: AsRef<Path>>(path: P, config_file: &str) -> Result<Site> { pub fn new<P: AsRef<Path>>(path: P, config_file: &str) -> Result<Site> {
let path = path.as_ref(); let path = path.as_ref();
let config = get_config(path, config_file);


let tpl_glob = format!("{}/{}", path.to_string_lossy().replace("\\", "/"), "templates/**/*.*ml"); let tpl_glob = format!("{}/{}", path.to_string_lossy().replace("\\", "/"), "templates/**/*.*ml");
let mut tera = Tera::new(&tpl_glob).chain_err(|| "Error parsing templates")?; let mut tera = Tera::new(&tpl_glob).chain_err(|| "Error parsing templates")?;
tera.extend(&GUTENBERG_TERA)?; tera.extend(&GUTENBERG_TERA)?;


if let Some(ref theme) = config.theme {
// Test that the {templates,static} folder exist for that theme
let theme_path = path.join("themes").join(theme);
if !theme_path.join("templates").exists() {
bail!("Theme `{}` is missing a templates folder", theme);
}
if !theme_path.join("static").exists() {
bail!("Theme `{}` is missing a static folder", theme);
}
let theme_tpl_glob = format!("{}/{}", path.to_string_lossy().replace("\\", "/"), "themes/**/*.html");
let tera_themes = Tera::new(&theme_tpl_glob).chain_err(|| "Error parsing templates from themes")?;
tera.extend(&tera_themes)?;
}

let site = Site { let site = Site {
base_path: path.to_path_buf(), base_path: path.to_path_buf(),
config: get_config(path, config_file),
config: config,
pages: HashMap::new(), pages: HashMap::new(),
sections: HashMap::new(), sections: HashMap::new(),
tera: tera, tera: tera,
@@ -242,7 +258,7 @@ impl Site {


if render { if render {
let insert_anchor = self.find_parent_section_insert_anchor(&self.pages[&path].file.parent); let insert_anchor = self.find_parent_section_insert_anchor(&self.pages[&path].file.parent);
let mut page = self.pages.get_mut(&path).unwrap();
let page = self.pages.get_mut(&path).unwrap();
page.render_markdown(&self.permalinks, &self.tera, &self.config, insert_anchor)?; page.render_markdown(&self.permalinks, &self.tera, &self.config, insert_anchor)?;
} }


@@ -259,7 +275,7 @@ impl Site {
let prev = self.sections.insert(section.file.path.clone(), section); let prev = self.sections.insert(section.file.path.clone(), section);


if render { if render {
let mut section = self.sections.get_mut(&path).unwrap();
let section = self.sections.get_mut(&path).unwrap();
section.render_markdown(&self.permalinks, &self.tera, &self.config)?; section.render_markdown(&self.permalinks, &self.tera, &self.config)?;
} }


@@ -353,9 +369,9 @@ impl Site {
html html
} }


/// Copy static file to public directory.
pub fn copy_static_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
let relative_path = path.as_ref().strip_prefix(&self.static_path).unwrap();
/// Copy the file at the given path into the public folder
pub fn copy_static_file<P: AsRef<Path>>(&self, path: P, base_path: &PathBuf) -> Result<()> {
let relative_path = path.as_ref().strip_prefix(base_path).unwrap();
let target_path = self.output_path.join(relative_path); let target_path = self.output_path.join(relative_path);
if let Some(parent_directory) = target_path.parent() { if let Some(parent_directory) = target_path.parent() {
create_dir_all(parent_directory)?; create_dir_all(parent_directory)?;
@@ -364,24 +380,36 @@ impl Site {
Ok(()) Ok(())
} }


/// Copy the content of the `static` folder into the `public` folder
pub fn copy_static_directory(&self) -> Result<()> {
for entry in WalkDir::new(&self.static_path).into_iter().filter_map(|e| e.ok()) {
let relative_path = entry.path().strip_prefix(&self.static_path).unwrap();
/// Copy the content of the given folder into the `public` folder
fn copy_static_directory(&self, path: &PathBuf) -> Result<()> {
for entry in WalkDir::new(path).into_iter().filter_map(|e| e.ok()) {
let relative_path = entry.path().strip_prefix(path).unwrap();
let target_path = self.output_path.join(relative_path); let target_path = self.output_path.join(relative_path);

if entry.path().is_dir() { if entry.path().is_dir() {
if !target_path.exists() { if !target_path.exists() {
create_directory(&target_path)?; create_directory(&target_path)?;
} }
} else { } else {
let entry_fullpath = self.base_path.join(entry.path()); let entry_fullpath = self.base_path.join(entry.path());
self.copy_static_file(entry_fullpath)?;
self.copy_static_file(entry_fullpath, path)?;
} }
} }
Ok(()) Ok(())
} }


/// Copy the main `static` folder and the theme `static` folder if a theme is used
pub fn copy_static_directories(&self) -> Result<()> {
// The user files will overwrite the theme files
if let Some(ref theme) = self.config.theme {
self.copy_static_directory(
&self.base_path.join("themes").join(theme).join("static")
)?;
}
self.copy_static_directory(&self.static_path)?;

Ok(())
}

/// Deletes the `public` directory if it exists /// Deletes the `public` directory if it exists
pub fn clean(&self) -> Result<()> { pub fn clean(&self) -> Result<()> {
if self.output_path.exists() { if self.output_path.exists() {
@@ -440,17 +468,24 @@ impl Site {
self.render_categories()?; self.render_categories()?;
self.render_tags()?; self.render_tags()?;


if let Some(ref theme) = self.config.theme {
let theme_path = self.base_path.join("themes").join(theme);
if theme_path.join("sass").exists() {
self.compile_sass(&theme_path)?;
}
}

if self.config.compile_sass.unwrap() { if self.config.compile_sass.unwrap() {
self.compile_sass()?;
self.compile_sass(&self.base_path)?;
} }


self.copy_static_directory()
self.copy_static_directories()
} }


pub fn compile_sass(&self) -> Result<()> {
pub fn compile_sass(&self, base_path: &PathBuf) -> Result<()> {
ensure_directory_exists(&self.output_path)?; ensure_directory_exists(&self.output_path)?;


let base_path = self.base_path.to_string_lossy().replace("\\", "/");
let base_path = base_path.to_string_lossy().replace("\\", "/");
let sass_glob = format!("{}/{}", base_path, "sass/**/*.scss"); let sass_glob = format!("{}/{}", base_path, "sass/**/*.scss");
let files = glob(&sass_glob) let files = glob(&sass_glob)
.unwrap() .unwrap()
@@ -495,7 +530,7 @@ impl Site {
ensure_directory_exists(&self.output_path)?; ensure_directory_exists(&self.output_path)?;
create_file( create_file(
&self.output_path.join("robots.txt"), &self.output_path.join("robots.txt"),
&self.tera.render("robots.txt", &Context::new())?
&render_template("robots.txt", &self.tera, &Context::new(), self.config.theme.clone())?
) )
} }


@@ -582,7 +617,7 @@ impl Site {
} }
context.add("tags", &tags); context.add("tags", &tags);


let sitemap = self.tera.render("sitemap.xml", &context)?;
let sitemap = &render_template("sitemap.xml", &self.tera, &context, self.config.theme.clone())?;


create_file(&self.output_path.join("sitemap.xml"), &sitemap)?; create_file(&self.output_path.join("sitemap.xml"), &sitemap)?;


@@ -616,9 +651,9 @@ impl Site {
}; };
context.add("feed_url", &rss_feed_url); context.add("feed_url", &rss_feed_url);


let sitemap = self.tera.render("rss.xml", &context)?;
let feed = &render_template("rss.xml", &self.tera, &context, self.config.theme.clone())?;


create_file(&self.output_path.join("rss.xml"), &sitemap)?;
create_file(&self.output_path.join("rss.xml"), &feed)?;


Ok(()) Ok(())
} }


+ 1
- 0
components/site/test_site/config.toml View File

@@ -4,6 +4,7 @@ highlight_code = true
compile_sass = true compile_sass = true
generate_rss = true generate_rss = true
rss_limit = 2 rss_limit = 2
theme = "sample"


[extra.author] [extra.author]
name = "Vincent Prouillet" name = "Vincent Prouillet"

+ 3
- 0
components/site/test_site/themes/sample/sass/sample.scss View File

@@ -0,0 +1,3 @@
body {
font-weight: bold;
}

+ 0
- 0
components/site/test_site/themes/sample/static/some.js View File


+ 1
- 0
components/site/test_site/themes/sample/templates/index.html View File

@@ -0,0 +1 @@
Hello

+ 3
- 0
components/site/test_site/themes/sample/theme.toml View File

@@ -0,0 +1,3 @@
name = "sample"

[extra]

+ 4
- 1
components/site/tests/site.rs View File

@@ -100,7 +100,6 @@ fn can_build_site_without_live_reload() {
site.build().unwrap(); site.build().unwrap();


assert!(Path::new(&public).exists()); assert!(Path::new(&public).exists());

assert!(file_exists!(public, "index.html")); assert!(file_exists!(public, "index.html"));
assert!(file_exists!(public, "sitemap.xml")); assert!(file_exists!(public, "sitemap.xml"));
assert!(file_exists!(public, "robots.txt")); assert!(file_exists!(public, "robots.txt"));
@@ -130,6 +129,10 @@ fn can_build_site_without_live_reload() {
assert_eq!(file_exists!(public, "categories/index.html"), false); assert_eq!(file_exists!(public, "categories/index.html"), false);
assert_eq!(file_exists!(public, "tags/index.html"), false); assert_eq!(file_exists!(public, "tags/index.html"), false);


// Theme files are there
assert!(file_exists!(public, "sample.css"));
assert!(file_exists!(public, "some.js"));

// no live reload code // no live reload code
assert_eq!(file_contains!(public, "index.html", "/livereload.js?port=1112&mindelay=10"), false); assert_eq!(file_contains!(public, "index.html", "/livereload.js?port=1112&mindelay=10"), false);




+ 1
- 0
components/taxonomies/Cargo.toml View File

@@ -13,3 +13,4 @@ errors = { path = "../errors" }
config = { path = "../config" } config = { path = "../config" }
content = { path = "../content" } content = { path = "../content" }
front_matter = { path = "../front_matter" } front_matter = { path = "../front_matter" }
utils = { path = "../utils" }

+ 4
- 2
components/taxonomies/src/lib.rs View File

@@ -7,6 +7,7 @@ extern crate errors;
extern crate config; extern crate config;
extern crate content; extern crate content;
extern crate front_matter; extern crate front_matter;
extern crate utils;


use std::collections::HashMap; use std::collections::HashMap;


@@ -17,6 +18,7 @@ use config::Config;
use errors::{Result, ResultExt}; use errors::{Result, ResultExt};
use content::{Page, sort_pages}; use content::{Page, sort_pages};
use front_matter::SortBy; use front_matter::SortBy;
use utils::templates::render_template;




#[derive(Debug, Copy, Clone, PartialEq)] #[derive(Debug, Copy, Clone, PartialEq)]
@@ -139,7 +141,7 @@ impl Taxonomy {
context.add("current_url", &config.make_permalink(&format!("{}/{}", name, item.slug))); context.add("current_url", &config.make_permalink(&format!("{}/{}", name, item.slug)));
context.add("current_path", &format!("/{}/{}", name, item.slug)); context.add("current_path", &format!("/{}/{}", name, item.slug));


tera.render(&format!("{}.html", name), &context)
render_template(&format!("{}.html", name), tera, &context, config.theme.clone())
.chain_err(|| format!("Failed to render {} page.", name)) .chain_err(|| format!("Failed to render {} page.", name))
} }


@@ -151,7 +153,7 @@ impl Taxonomy {
context.add("current_url", &config.make_permalink(&name)); context.add("current_url", &config.make_permalink(&name));
context.add("current_path", &name); context.add("current_path", &name);


tera.render(&format!("{}.html", name), &context)
render_template(&format!("{}.html", name), tera, &context, config.theme.clone())
.chain_err(|| format!("Failed to render {} page.", name)) .chain_err(|| format!("Failed to render {} page.", name))
} }
} }

+ 1
- 1
components/templates/src/lib.rs View File

@@ -14,8 +14,8 @@ pub mod filters;
pub mod global_fns; pub mod global_fns;


use tera::{Tera, Context}; use tera::{Tera, Context};
use errors::{Result, ResultExt};


use errors::{Result, ResultExt};


lazy_static! { lazy_static! {
pub static ref GUTENBERG_TERA: Tera = { pub static ref GUTENBERG_TERA: Tera = {


+ 1
- 0
components/utils/Cargo.toml View File

@@ -5,6 +5,7 @@ authors = ["Vincent Prouillet <vincent@wearewizards.io>"]


[dependencies] [dependencies]
errors = { path = "../errors" } errors = { path = "../errors" }
tera = "0.10"




[dev-dependencies] [dev-dependencies]


+ 2
- 0
components/utils/src/lib.rs View File

@@ -3,6 +3,8 @@ extern crate errors;


#[cfg(test)] #[cfg(test)]
extern crate tempdir; extern crate tempdir;
extern crate tera;


pub mod fs; pub mod fs;
pub mod site; pub mod site;
pub mod templates;

+ 21
- 0
components/utils/src/templates.rs View File

@@ -0,0 +1,21 @@
use tera::{Tera, Context};

use errors::Result;

/// Renders the given template with the given context, but also ensures that, if the default file
/// is not found, it will look up for the equivalent template for the current theme if there is one
pub fn render_template(name: &str, tera: &Tera, context: &Context, theme: Option<String>) -> Result<String> {
if tera.templates.contains_key(name) {
return tera
.render(name, context)
.map_err(|e| e.into());
}

if let Some(ref t) = theme {
return tera
.render(&format!("{}/templates/{}", t, name), context)
.map_err(|e| e.into());
}

bail!("Tried to render `{}` but the template wasn't found", name)
}

+ 2
- 2
src/cmd/serve.rs View File

@@ -180,12 +180,12 @@ pub fn serve(interface: &str, port: &str, config_file: &str) -> Result<()> {
(ChangeKind::StaticFiles, p) => { (ChangeKind::StaticFiles, p) => {
if path.is_file() { if path.is_file() {
console::info(&format!("-> Static file changes detected {}", path.display())); console::info(&format!("-> Static file changes detected {}", path.display()));
rebuild_done_handling(&broadcaster, site.copy_static_file(&path), &p);
rebuild_done_handling(&broadcaster, site.copy_static_file(&path, &site.static_path), &p);
} }
}, },
(ChangeKind::Sass, p) => { (ChangeKind::Sass, p) => {
console::info(&format!("-> Sass file changed {}", path.display())); console::info(&format!("-> Sass file changed {}", path.display()));
rebuild_done_handling(&broadcaster, site.compile_sass(), &p);
rebuild_done_handling(&broadcaster, site.compile_sass(&site.base_path), &p);
}, },
}; };
console::report_elapsed_time(start); console::report_elapsed_time(start);


Loading…
Cancel
Save