@@ -34,3 +34,6 @@ | |||||
[submodule "sublime_syntaxes/Sublime-CMakeLists"] | [submodule "sublime_syntaxes/Sublime-CMakeLists"] | ||||
path = sublime_syntaxes/Sublime-CMakeLists | path = sublime_syntaxes/Sublime-CMakeLists | ||||
url = https://github.com/zyxar/Sublime-CMakeLists | url = https://github.com/zyxar/Sublime-CMakeLists | ||||
[submodule "sublime_syntaxes/Swift-for-f-ing-sublime"] | |||||
path = sublime_syntaxes/Swift-for-f-ing-sublime | |||||
url = git@github.com:colinta/Swift-for-f-ing-sublime.git |
@@ -1,5 +1,12 @@ | |||||
# Changelog | # Changelog | ||||
## 0.4.2 (unreleased) | |||||
- Add assets to section indexes | |||||
- Allow users to add custom highlighting syntaxes | |||||
- Add Swift, MiniZinc syntaxes and update others | |||||
- Handle post summaries better: no more cutting references | |||||
## 0.4.1 (2018-08-06) | ## 0.4.1 (2018-08-06) | ||||
- Fix live reload of a section content change getting no pages data | - Fix live reload of a section content change getting no pages data | ||||
@@ -1,6 +1,6 @@ | |||||
[package] | [package] | ||||
name = "gutenberg" | name = "gutenberg" | ||||
version = "0.4.1" | |||||
version = "0.4.2" | |||||
authors = ["Vincent Prouillet <prouillet.vincent@gmail.com>"] | authors = ["Vincent Prouillet <prouillet.vincent@gmail.com>"] | ||||
license = "MIT" | license = "MIT" | ||||
readme = "README.md" | readme = "README.md" | ||||
@@ -90,7 +90,7 @@ $ git submodule update --remote --merge | |||||
And finally from the root of the components/highlighting crate run the following command: | And finally from the root of the components/highlighting crate run the following command: | ||||
```bash | ```bash | ||||
$ cargo run --example generate_sublime synpack ../../sublime_syntaxes ../../sublime_syntaxes/newlines.packdump ../../sublime_syntaxes/nonewlines.packdump | |||||
$ cargo run --example generate_sublime synpack ../../sublime_syntaxes ../../sublime_syntaxes/newlines.packdump | |||||
``` | ``` | ||||
#### Adding a theme | #### Adding a theme | ||||
@@ -3,23 +3,22 @@ extern crate serde_derive; | |||||
extern crate toml; | extern crate toml; | ||||
#[macro_use] | #[macro_use] | ||||
extern crate errors; | extern crate errors; | ||||
extern crate highlighting; | |||||
extern crate chrono; | extern crate chrono; | ||||
extern crate globset; | extern crate globset; | ||||
extern crate highlighting; | |||||
use std::collections::HashMap; | use std::collections::HashMap; | ||||
use std::fs::File; | use std::fs::File; | ||||
use std::io::prelude::*; | use std::io::prelude::*; | ||||
use std::path::{Path, PathBuf}; | use std::path::{Path, PathBuf}; | ||||
use toml::Value as Toml; | |||||
use chrono::Utc; | use chrono::Utc; | ||||
use globset::{Glob, GlobSet, GlobSetBuilder}; | use globset::{Glob, GlobSet, GlobSetBuilder}; | ||||
use toml::Value as Toml; | |||||
use errors::{Result, ResultExt}; | use errors::{Result, ResultExt}; | ||||
use highlighting::THEME_SET; | use highlighting::THEME_SET; | ||||
mod theme; | mod theme; | ||||
use theme::Theme; | use theme::Theme; | ||||
@@ -27,7 +26,6 @@ use theme::Theme; | |||||
// We want a default base url for tests | // We want a default base url for tests | ||||
static DEFAULT_BASE_URL: &'static str = "http://a-website.com"; | static DEFAULT_BASE_URL: &'static str = "http://a-website.com"; | ||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] | #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] | ||||
#[serde(default)] | #[serde(default)] | ||||
pub struct Taxonomy { | pub struct Taxonomy { | ||||
@@ -101,12 +99,15 @@ pub struct Config { | |||||
/// Had to remove the PartialEq derive because GlobSet does not implement it. No impact | /// Had to remove the PartialEq derive because GlobSet does not implement it. No impact | ||||
/// because it's unused anyway (who wants to sort Configs?). | /// because it's unused anyway (who wants to sort Configs?). | ||||
pub ignored_content: Vec<String>, | pub ignored_content: Vec<String>, | ||||
#[serde(skip_serializing, skip_deserializing)] // not a typo, 2 are needed | |||||
#[serde(skip_serializing, skip_deserializing)] // not a typo, 2 are needed | |||||
pub ignored_content_globset: Option<GlobSet>, | pub ignored_content_globset: Option<GlobSet>, | ||||
/// Whether to check all external links for validity | /// Whether to check all external links for validity | ||||
pub check_external_links: bool, | pub check_external_links: bool, | ||||
/// A list of directories to search for additional `.sublime-syntax` files in. | |||||
pub extra_syntaxes: Vec<String>, | |||||
/// All user params set in [extra] in the config | /// All user params set in [extra] in the config | ||||
pub extra: HashMap<String, Toml>, | pub extra: HashMap<String, Toml>, | ||||
@@ -114,14 +115,13 @@ pub struct Config { | |||||
pub build_timestamp: Option<i64>, | pub build_timestamp: Option<i64>, | ||||
} | } | ||||
impl Config { | impl Config { | ||||
/// Parses a string containing TOML to our Config struct | /// Parses a string containing TOML to our Config struct | ||||
/// Any extra parameter will end up in the extra field | /// Any extra parameter will end up in the extra field | ||||
pub fn parse(content: &str) -> Result<Config> { | pub fn parse(content: &str) -> Result<Config> { | ||||
let mut config: Config = match toml::from_str(content) { | let mut config: Config = match toml::from_str(content) { | ||||
Ok(c) => c, | Ok(c) => c, | ||||
Err(e) => bail!(e) | |||||
Err(e) => bail!(e), | |||||
}; | }; | ||||
if config.base_url.is_empty() || config.base_url == DEFAULT_BASE_URL { | if config.base_url.is_empty() || config.base_url == DEFAULT_BASE_URL { | ||||
@@ -134,7 +134,6 @@ impl Config { | |||||
config.build_timestamp = Some(Utc::now().timestamp()); | config.build_timestamp = Some(Utc::now().timestamp()); | ||||
if !config.ignored_content.is_empty() { | if !config.ignored_content.is_empty() { | ||||
// Convert the file glob strings into a compiled glob set matcher. We want to do this once, | // Convert the file glob strings into a compiled glob set matcher. We want to do this once, | ||||
// at program initialization, rather than for every page, for example. We arrange for the | // at program initialization, rather than for every page, for example. We arrange for the | ||||
@@ -145,11 +144,19 @@ impl Config { | |||||
for pat in &config.ignored_content { | for pat in &config.ignored_content { | ||||
let glob = match Glob::new(pat) { | let glob = match Glob::new(pat) { | ||||
Ok(g) => g, | Ok(g) => g, | ||||
Err(e) => bail!("Invalid ignored_content glob pattern: {}, error = {}", pat, e) | |||||
Err(e) => bail!( | |||||
"Invalid ignored_content glob pattern: {}, error = {}", | |||||
pat, | |||||
e | |||||
), | |||||
}; | }; | ||||
glob_set_builder.add(glob); | glob_set_builder.add(glob); | ||||
} | } | ||||
config.ignored_content_globset = Some(glob_set_builder.build().expect("Bad ignored_content in config file.")); | |||||
config.ignored_content_globset = Some( | |||||
glob_set_builder | |||||
.build() | |||||
.expect("Bad ignored_content in config file."), | |||||
); | |||||
} | } | ||||
Ok(config) | Ok(config) | ||||
@@ -161,7 +168,12 @@ impl Config { | |||||
let path = path.as_ref(); | let path = path.as_ref(); | ||||
let file_name = path.file_name().unwrap(); | let file_name = path.file_name().unwrap(); | ||||
File::open(path) | File::open(path) | ||||
.chain_err(|| format!("No `{:?}` file found. Are you in the right directory?", file_name))? | |||||
.chain_err(|| { | |||||
format!( | |||||
"No `{:?}` file found. Are you in the right directory?", | |||||
file_name | |||||
) | |||||
})? | |||||
.read_to_string(&mut content)?; | .read_to_string(&mut content)?; | ||||
Config::parse(&content) | Config::parse(&content) | ||||
@@ -169,7 +181,11 @@ impl Config { | |||||
/// Makes a url, taking into account that the base url might have a trailing slash | /// Makes a url, taking into account that the base url might have a trailing slash | ||||
pub fn make_permalink(&self, path: &str) -> String { | pub fn make_permalink(&self, path: &str) -> String { | ||||
let trailing_bit = if path.ends_with('/') || path.is_empty() { "" } else { "/" }; | |||||
let trailing_bit = if path.ends_with('/') || path.is_empty() { | |||||
"" | |||||
} else { | |||||
"/" | |||||
}; | |||||
// Index section with a base url that has a trailing slash | // Index section with a base url that has a trailing slash | ||||
if self.base_url.ends_with('/') && path == "/" { | if self.base_url.ends_with('/') && path == "/" { | ||||
@@ -195,12 +211,16 @@ impl Config { | |||||
let original = self.extra.clone(); | let original = self.extra.clone(); | ||||
// 2. inject theme extra values | // 2. inject theme extra values | ||||
for (key, val) in &theme.extra { | for (key, val) in &theme.extra { | ||||
self.extra.entry(key.to_string()).or_insert_with(|| val.clone()); | |||||
self.extra | |||||
.entry(key.to_string()) | |||||
.or_insert_with(|| val.clone()); | |||||
} | } | ||||
// 3. overwrite with original config | // 3. overwrite with original config | ||||
for (key, val) in &original { | for (key, val) in &original { | ||||
self.extra.entry(key.to_string()).or_insert_with(|| val.clone()); | |||||
self.extra | |||||
.entry(key.to_string()) | |||||
.or_insert_with(|| val.clone()); | |||||
} | } | ||||
Ok(()) | Ok(()) | ||||
@@ -233,13 +253,13 @@ impl Default for Config { | |||||
ignored_content: Vec::new(), | ignored_content: Vec::new(), | ||||
ignored_content_globset: None, | ignored_content_globset: None, | ||||
translations: HashMap::new(), | translations: HashMap::new(), | ||||
extra_syntaxes: Vec::new(), | |||||
extra: HashMap::new(), | extra: HashMap::new(), | ||||
build_timestamp: Some(1), | build_timestamp: Some(1), | ||||
} | } | ||||
} | } | ||||
} | } | ||||
/// Get and parse the config. | /// Get and parse the config. | ||||
/// If it doesn't succeed, exit | /// If it doesn't succeed, exit | ||||
pub fn get_config(path: &Path, filename: &str) -> Config { | pub fn get_config(path: &Path, filename: &str) -> Config { | ||||
@@ -253,7 +273,6 @@ pub fn get_config(path: &Path, filename: &str) -> Config { | |||||
} | } | ||||
} | } | ||||
#[cfg(test)] | #[cfg(test)] | ||||
mod tests { | mod tests { | ||||
use super::{Config, Theme}; | use super::{Config, Theme}; | ||||
@@ -303,7 +322,16 @@ hello = "world" | |||||
let config = Config::parse(config); | let config = Config::parse(config); | ||||
assert!(config.is_ok()); | assert!(config.is_ok()); | ||||
assert_eq!(config.unwrap().extra.get("hello").unwrap().as_str().unwrap(), "world"); | |||||
assert_eq!( | |||||
config | |||||
.unwrap() | |||||
.extra | |||||
.get("hello") | |||||
.unwrap() | |||||
.as_str() | |||||
.unwrap(), | |||||
"world" | |||||
); | |||||
} | } | ||||
#[test] | #[test] | ||||
@@ -313,7 +341,6 @@ hello = "world" | |||||
assert_eq!(config.make_permalink(""), "http://vincent.is/"); | assert_eq!(config.make_permalink(""), "http://vincent.is/"); | ||||
} | } | ||||
#[test] | #[test] | ||||
fn can_make_url_index_page_with_railing_slash_url() { | fn can_make_url_index_page_with_railing_slash_url() { | ||||
let mut config = Config::default(); | let mut config = Config::default(); | ||||
@@ -339,7 +366,10 @@ hello = "world" | |||||
fn can_make_url_with_localhost() { | fn can_make_url_with_localhost() { | ||||
let mut config = Config::default(); | let mut config = Config::default(); | ||||
config.base_url = "http://127.0.0.1:1111".to_string(); | config.base_url = "http://127.0.0.1:1111".to_string(); | ||||
assert_eq!(config.make_permalink("/tags/rust"), "http://127.0.0.1:1111/tags/rust/"); | |||||
assert_eq!( | |||||
config.make_permalink("/tags/rust"), | |||||
"http://127.0.0.1:1111/tags/rust/" | |||||
); | |||||
} | } | ||||
#[test] | #[test] | ||||
@@ -7,6 +7,7 @@ extern crate front_matter; | |||||
extern crate config; | extern crate config; | ||||
use std::collections::HashMap; | use std::collections::HashMap; | ||||
use std::path::Path; | |||||
use config::Config; | use config::Config; | ||||
use tera::Tera; | use tera::Tera; | ||||
@@ -14,19 +15,16 @@ use front_matter::{SortBy, InsertAnchor}; | |||||
use content::{Page, sort_pages, populate_siblings}; | use content::{Page, sort_pages, populate_siblings}; | ||||
fn create_pages(number: usize, sort_by: SortBy) -> Vec<Page> { | |||||
fn create_pages(number: usize) -> Vec<Page> { | |||||
let mut pages = vec![]; | let mut pages = vec![]; | ||||
let config = Config::default(); | let config = Config::default(); | ||||
let tera = Tera::default(); | |||||
let mut tera = Tera::default(); | |||||
tera.add_raw_template("shortcodes/youtube.html", "hello"); | |||||
let permalinks = HashMap::new(); | let permalinks = HashMap::new(); | ||||
for i in 0..number { | for i in 0..number { | ||||
let mut page = Page::default(); | let mut page = Page::default(); | ||||
match sort_by { | |||||
SortBy::Weight => { page.meta.weight = Some(i); } | |||||
SortBy::Order => { page.meta.order = Some(i); } | |||||
_ => (), | |||||
}; | |||||
page.meta.weight = Some(i); | |||||
page.raw_content = r#" | page.raw_content = r#" | ||||
# Modus cognitius profanam ne duae virtutis mundi | # Modus cognitius profanam ne duae virtutis mundi | ||||
@@ -98,7 +96,7 @@ if __name__ == "__main__": | |||||
gen_site("basic-blog", [""], 250, paginate=True) | gen_site("basic-blog", [""], 250, paginate=True) | ||||
``` | ``` | ||||
"#.to_string(); | "#.to_string(); | ||||
page.render_markdown(&permalinks, &tera, &config, InsertAnchor::None).unwrap(); | |||||
page.render_markdown(&permalinks, &tera, &config, &Path::new(""), InsertAnchor::None).unwrap(); | |||||
pages.push(page); | pages.push(page); | ||||
} | } | ||||
@@ -111,34 +109,34 @@ if __name__ == "__main__": | |||||
#[bench] | #[bench] | ||||
fn bench_baseline_cloning(b: &mut test::Bencher) { | fn bench_baseline_cloning(b: &mut test::Bencher) { | ||||
let pages = create_pages(250, SortBy::Order); | |||||
let pages = create_pages(250); | |||||
b.iter(|| pages.clone()); | b.iter(|| pages.clone()); | ||||
} | } | ||||
#[bench] | #[bench] | ||||
fn bench_sorting_none(b: &mut test::Bencher) { | fn bench_sorting_none(b: &mut test::Bencher) { | ||||
let pages = create_pages(250, SortBy::Order); | |||||
b.iter(|| sort_pages(pages.clone(), SortBy::None)); | |||||
let pages = create_pages(250); | |||||
b.iter(|| sort_pages(pages.clone(), SortBy::Weight)); | |||||
} | } | ||||
#[bench] | #[bench] | ||||
fn bench_sorting_order(b: &mut test::Bencher) { | fn bench_sorting_order(b: &mut test::Bencher) { | ||||
let pages = create_pages(250, SortBy::Order); | |||||
b.iter(|| sort_pages(pages.clone(), SortBy::Order)); | |||||
let pages = create_pages(250); | |||||
b.iter(|| sort_pages(pages.clone(), SortBy::Weight)); | |||||
} | } | ||||
#[bench] | #[bench] | ||||
fn bench_populate_siblings(b: &mut test::Bencher) { | fn bench_populate_siblings(b: &mut test::Bencher) { | ||||
let pages = create_pages(250, SortBy::Order); | |||||
let (sorted_pages, _) = sort_pages(pages, SortBy::Order); | |||||
b.iter(|| populate_siblings(&sorted_pages.clone())); | |||||
let pages = create_pages(250); | |||||
let (sorted_pages, _) = sort_pages(pages, SortBy::Weight); | |||||
b.iter(|| populate_siblings(&sorted_pages.clone(), SortBy::Weight)); | |||||
} | } | ||||
#[bench] | #[bench] | ||||
fn bench_page_render_html(b: &mut test::Bencher) { | fn bench_page_render_html(b: &mut test::Bencher) { | ||||
let pages = create_pages(10, SortBy::Order); | |||||
let (mut sorted_pages, _) = sort_pages(pages, SortBy::Order); | |||||
sorted_pages = populate_siblings(&sorted_pages); | |||||
let pages = create_pages(10); | |||||
let (mut sorted_pages, _) = sort_pages(pages, SortBy::Weight); | |||||
sorted_pages = populate_siblings(&sorted_pages, SortBy::Weight); | |||||
let config = Config::default(); | let config = Config::default(); | ||||
let mut tera = Tera::default(); | let mut tera = Tera::default(); | ||||
@@ -166,30 +166,31 @@ impl Page { | |||||
/// We need access to all pages url to render links relative to content | /// We need access to all pages url to render links relative to content | ||||
/// so that can't happen at the same time as parsing | /// so that can't happen at the same time as parsing | ||||
pub fn render_markdown(&mut self, permalinks: &HashMap<String, String>, tera: &Tera, config: &Config, anchor_insert: InsertAnchor) -> Result<()> { | |||||
pub fn render_markdown( | |||||
&mut self, | |||||
permalinks: &HashMap<String, String>, | |||||
tera: &Tera, | |||||
config: &Config, | |||||
base_path: &Path, | |||||
anchor_insert: InsertAnchor, | |||||
) -> Result<()> { | |||||
let mut context = RenderContext::new( | let mut context = RenderContext::new( | ||||
tera, | tera, | ||||
config, | config, | ||||
&self.permalink, | &self.permalink, | ||||
permalinks, | permalinks, | ||||
base_path, | |||||
anchor_insert, | anchor_insert, | ||||
); | ); | ||||
context.tera_context.add("page", self); | context.tera_context.add("page", self); | ||||
let res = render_content( | |||||
&self.raw_content.replacen("<!-- more -->", "<a name=\"continue-reading\"></a>", 1), | |||||
&context, | |||||
).chain_err(|| format!("Failed to render content of {}", self.file.path.display()))?; | |||||
self.content = res.0; | |||||
self.toc = res.1; | |||||
if self.raw_content.contains("<!-- more -->") { | |||||
self.summary = Some({ | |||||
let summary = self.raw_content.splitn(2, "<!-- more -->").collect::<Vec<&str>>()[0]; | |||||
render_content(summary, &context) | |||||
.chain_err(|| format!("Failed to render content of {}", self.file.path.display()))?.0 | |||||
}) | |||||
} | |||||
let res = render_content(&self.raw_content, &context) | |||||
.chain_err(|| format!("Failed to render content of {}", self.file.path.display()))?; | |||||
self.summary = res.summary_len.map(|l| res.body[0..l].to_owned()); | |||||
self.content = res.body; | |||||
self.toc = res.toc; | |||||
Ok(()) | Ok(()) | ||||
} | } | ||||
@@ -310,7 +311,13 @@ Hello world"#; | |||||
let res = Page::parse(Path::new("post.md"), content, &Config::default()); | let res = Page::parse(Path::new("post.md"), content, &Config::default()); | ||||
assert!(res.is_ok()); | assert!(res.is_ok()); | ||||
let mut page = res.unwrap(); | let mut page = res.unwrap(); | ||||
page.render_markdown(&HashMap::default(), &Tera::default(), &Config::default(), InsertAnchor::None).unwrap(); | |||||
page.render_markdown( | |||||
&HashMap::default(), | |||||
&Tera::default(), | |||||
&Config::default(), | |||||
Path::new("something"), | |||||
InsertAnchor::None, | |||||
).unwrap(); | |||||
assert_eq!(page.meta.title.unwrap(), "Hello".to_string()); | assert_eq!(page.meta.title.unwrap(), "Hello".to_string()); | ||||
assert_eq!(page.meta.slug.unwrap(), "hello-world".to_string()); | assert_eq!(page.meta.slug.unwrap(), "hello-world".to_string()); | ||||
@@ -416,7 +423,13 @@ Hello world | |||||
let res = Page::parse(Path::new("hello.md"), &content, &config); | let res = Page::parse(Path::new("hello.md"), &content, &config); | ||||
assert!(res.is_ok()); | assert!(res.is_ok()); | ||||
let mut page = res.unwrap(); | let mut page = res.unwrap(); | ||||
page.render_markdown(&HashMap::default(), &Tera::default(), &config, InsertAnchor::None).unwrap(); | |||||
page.render_markdown( | |||||
&HashMap::default(), | |||||
&Tera::default(), | |||||
&config, | |||||
Path::new("something"), | |||||
InsertAnchor::None, | |||||
).unwrap(); | |||||
assert_eq!(page.summary, Some("<p>Hello world</p>\n".to_string())); | assert_eq!(page.summary, Some("<p>Hello world</p>\n".to_string())); | ||||
} | } | ||||
@@ -8,7 +8,7 @@ use serde::ser::{SerializeStruct, self}; | |||||
use config::Config; | 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, find_related_assets}; | |||||
use utils::templates::render_template; | use utils::templates::render_template; | ||||
use utils::site::get_reading_analytics; | use utils::site::get_reading_analytics; | ||||
use rendering::{RenderContext, Header, render_content}; | use rendering::{RenderContext, Header, render_content}; | ||||
@@ -33,6 +33,8 @@ pub struct Section { | |||||
pub raw_content: String, | pub raw_content: String, | ||||
/// The HTML rendered of the page | /// The HTML rendered of the page | ||||
pub content: String, | pub content: String, | ||||
/// All the non-md files we found next to the .md file | |||||
pub assets: Vec<PathBuf>, | |||||
/// All direct pages of that section | /// All direct pages of that section | ||||
pub pages: Vec<Page>, | pub pages: Vec<Page>, | ||||
/// All pages that cannot be sorted in this section | /// All pages that cannot be sorted in this section | ||||
@@ -54,6 +56,7 @@ impl Section { | |||||
components: vec![], | components: vec![], | ||||
permalink: "".to_string(), | permalink: "".to_string(), | ||||
raw_content: "".to_string(), | raw_content: "".to_string(), | ||||
assets: vec![], | |||||
content: "".to_string(), | content: "".to_string(), | ||||
pages: vec![], | pages: vec![], | ||||
ignored_pages: vec![], | ignored_pages: vec![], | ||||
@@ -79,8 +82,31 @@ impl Section { | |||||
pub fn from_file<P: AsRef<Path>>(path: P, config: &Config) -> Result<Section> { | pub fn from_file<P: AsRef<Path>>(path: P, config: &Config) -> Result<Section> { | ||||
let path = path.as_ref(); | let path = path.as_ref(); | ||||
let content = read_file(path)?; | let content = read_file(path)?; | ||||
let mut section = Section::parse(path, &content, config)?; | |||||
Section::parse(path, &content, config) | |||||
let parent_dir = path.parent().unwrap(); | |||||
let assets = find_related_assets(parent_dir); | |||||
if let Some(ref globset) = config.ignored_content_globset { | |||||
// `find_related_assets` only scans the immediate directory (it is not recursive) so our | |||||
// filtering only needs to work against the file_name component, not the full suffix. If | |||||
// `find_related_assets` was changed to also return files in subdirectories, we could | |||||
// use `PathBuf.strip_prefix` to remove the parent directory and then glob-filter | |||||
// against the remaining path. Note that the current behaviour effectively means that | |||||
// the `ignored_content` setting in the config file is limited to single-file glob | |||||
// patterns (no "**" patterns). | |||||
section.assets = assets.into_iter() | |||||
.filter(|path| | |||||
match path.file_name() { | |||||
None => true, | |||||
Some(file) => !globset.is_match(file) | |||||
} | |||||
).collect(); | |||||
} else { | |||||
section.assets = assets; | |||||
} | |||||
Ok(section) | |||||
} | } | ||||
pub fn get_template_name(&self) -> String { | pub fn get_template_name(&self) -> String { | ||||
@@ -97,12 +123,13 @@ impl Section { | |||||
/// We need access to all pages url to render links relative to content | /// We need access to all pages url to render links relative to content | ||||
/// so that can't happen at the same time as parsing | /// so that can't happen at the same time as parsing | ||||
pub fn render_markdown(&mut self, permalinks: &HashMap<String, String>, tera: &Tera, config: &Config) -> Result<()> { | |||||
pub fn render_markdown(&mut self, permalinks: &HashMap<String, String>, tera: &Tera, config: &Config, base_path: &Path) -> Result<()> { | |||||
let mut context = RenderContext::new( | let mut context = RenderContext::new( | ||||
tera, | tera, | ||||
config, | config, | ||||
&self.permalink, | &self.permalink, | ||||
permalinks, | permalinks, | ||||
base_path, | |||||
self.meta.insert_anchor_links, | self.meta.insert_anchor_links, | ||||
); | ); | ||||
@@ -110,8 +137,8 @@ impl Section { | |||||
let res = render_content(&self.raw_content, &context) | let res = render_content(&self.raw_content, &context) | ||||
.chain_err(|| format!("Failed to render content of {}", self.file.path.display()))?; | .chain_err(|| format!("Failed to render content of {}", self.file.path.display()))?; | ||||
self.content = res.0; | |||||
self.toc = res.1; | |||||
self.content = res.body; | |||||
self.toc = res.toc; | |||||
Ok(()) | Ok(()) | ||||
} | } | ||||
@@ -146,6 +173,15 @@ impl Section { | |||||
pub fn is_child_page(&self, path: &PathBuf) -> bool { | pub fn is_child_page(&self, path: &PathBuf) -> bool { | ||||
self.all_pages_path().contains(path) | self.all_pages_path().contains(path) | ||||
} | } | ||||
/// Creates a vectors of asset URLs. | |||||
fn serialize_assets(&self) -> Vec<String> { | |||||
self.assets.iter() | |||||
.filter_map(|asset| asset.file_name()) | |||||
.filter_map(|filename| filename.to_str()) | |||||
.map(|filename| self.path.clone() + filename) | |||||
.collect() | |||||
} | |||||
} | } | ||||
impl ser::Serialize for Section { | impl ser::Serialize for Section { | ||||
@@ -165,6 +201,8 @@ impl ser::Serialize for Section { | |||||
state.serialize_field("word_count", &word_count)?; | state.serialize_field("word_count", &word_count)?; | ||||
state.serialize_field("reading_time", &reading_time)?; | state.serialize_field("reading_time", &reading_time)?; | ||||
state.serialize_field("toc", &self.toc)?; | state.serialize_field("toc", &self.toc)?; | ||||
let assets = self.serialize_assets(); | |||||
state.serialize_field("assets", &assets)?; | |||||
state.end() | state.end() | ||||
} | } | ||||
} | } | ||||
@@ -179,6 +217,7 @@ impl Default for Section { | |||||
components: vec![], | components: vec![], | ||||
permalink: "".to_string(), | permalink: "".to_string(), | ||||
raw_content: "".to_string(), | raw_content: "".to_string(), | ||||
assets: vec![], | |||||
content: "".to_string(), | content: "".to_string(), | ||||
pages: vec![], | pages: vec![], | ||||
ignored_pages: vec![], | ignored_pages: vec![], | ||||
@@ -187,3 +226,69 @@ impl Default for Section { | |||||
} | } | ||||
} | } | ||||
} | } | ||||
#[cfg(test)] | |||||
mod tests { | |||||
use std::io::Write; | |||||
use std::fs::{File, create_dir}; | |||||
use tempfile::tempdir; | |||||
use globset::{Glob, GlobSetBuilder}; | |||||
use config::Config; | |||||
use super::Section; | |||||
#[test] | |||||
fn section_with_assets_gets_right_info() { | |||||
let tmp_dir = tempdir().expect("create temp dir"); | |||||
let path = tmp_dir.path(); | |||||
create_dir(&path.join("content")).expect("create content temp dir"); | |||||
create_dir(&path.join("content").join("posts")).expect("create posts temp dir"); | |||||
let nested_path = path.join("content").join("posts").join("with-assets"); | |||||
create_dir(&nested_path).expect("create nested temp dir"); | |||||
let mut f = File::create(nested_path.join("_index.md")).unwrap(); | |||||
f.write_all(b"+++\n+++\n").unwrap(); | |||||
File::create(nested_path.join("example.js")).unwrap(); | |||||
File::create(nested_path.join("graph.jpg")).unwrap(); | |||||
File::create(nested_path.join("fail.png")).unwrap(); | |||||
let res = Section::from_file( | |||||
nested_path.join("_index.md").as_path(), | |||||
&Config::default(), | |||||
); | |||||
assert!(res.is_ok()); | |||||
let section = res.unwrap(); | |||||
assert_eq!(section.assets.len(), 3); | |||||
assert_eq!(section.permalink, "http://a-website.com/posts/with-assets/"); | |||||
} | |||||
#[test] | |||||
fn section_with_ignored_assets_filters_out_correct_files() { | |||||
let tmp_dir = tempdir().expect("create temp dir"); | |||||
let path = tmp_dir.path(); | |||||
create_dir(&path.join("content")).expect("create content temp dir"); | |||||
create_dir(&path.join("content").join("posts")).expect("create posts temp dir"); | |||||
let nested_path = path.join("content").join("posts").join("with-assets"); | |||||
create_dir(&nested_path).expect("create nested temp dir"); | |||||
let mut f = File::create(nested_path.join("_index.md")).unwrap(); | |||||
f.write_all(b"+++\nslug=\"hey\"\n+++\n").unwrap(); | |||||
File::create(nested_path.join("example.js")).unwrap(); | |||||
File::create(nested_path.join("graph.jpg")).unwrap(); | |||||
File::create(nested_path.join("fail.png")).unwrap(); | |||||
let mut gsb = GlobSetBuilder::new(); | |||||
gsb.add(Glob::new("*.{js,png}").unwrap()); | |||||
let mut config = Config::default(); | |||||
config.ignored_content_globset = Some(gsb.build().unwrap()); | |||||
let res = Section::from_file( | |||||
nested_path.join("_index.md").as_path(), | |||||
&config, | |||||
); | |||||
assert!(res.is_ok()); | |||||
let page = res.unwrap(); | |||||
assert_eq!(page.assets.len(), 1); | |||||
assert_eq!(page.assets[0].file_name().unwrap().to_str(), Some("graph.jpg")); | |||||
} | |||||
} |
@@ -19,25 +19,20 @@ fn usage_and_exit() -> ! { | |||||
// Check README for more details | // Check README for more details | ||||
fn main() { | fn main() { | ||||
let mut args = env::args().skip(1); | let mut args = env::args().skip(1); | ||||
match (args.next(), args.next(), args.next(), args.next()) { | |||||
(Some(ref cmd), Some(ref package_dir), Some(ref packpath_newlines), Some(ref packpath_nonewlines)) if cmd == "synpack" => { | |||||
match (args.next(), args.next(), args.next()) { | |||||
(Some(ref cmd), Some(ref package_dir), Some(ref packpath_newlines)) if cmd == "synpack" => { | |||||
let mut ps = SyntaxSet::new(); | let mut ps = SyntaxSet::new(); | ||||
ps.load_plain_text_syntax(); | ps.load_plain_text_syntax(); | ||||
ps.load_syntaxes(package_dir, true).unwrap(); | ps.load_syntaxes(package_dir, true).unwrap(); | ||||
dump_to_file(&ps, packpath_newlines).unwrap(); | dump_to_file(&ps, packpath_newlines).unwrap(); | ||||
ps = SyntaxSet::new(); | |||||
ps.load_plain_text_syntax(); | |||||
ps.load_syntaxes(package_dir, false).unwrap(); | |||||
dump_to_file(&ps, packpath_nonewlines).unwrap(); | |||||
for s in ps.syntaxes() { | for s in ps.syntaxes() { | ||||
if !s.file_extensions.is_empty() { | if !s.file_extensions.is_empty() { | ||||
println!("- {} -> {:?}", s.name, s.file_extensions); | println!("- {} -> {:?}", s.name, s.file_extensions); | ||||
} | } | ||||
} | } | ||||
}, | }, | ||||
(Some(ref cmd), Some(ref theme_dir), Some(ref packpath), None) if cmd == "themepack" => { | |||||
(Some(ref cmd), Some(ref theme_dir), Some(ref packpath)) if cmd == "themepack" => { | |||||
let ts = ThemeSet::load_from_folder(theme_dir).unwrap(); | let ts = ThemeSet::load_from_folder(theme_dir).unwrap(); | ||||
for path in ts.themes.keys() { | for path in ts.themes.keys() { | ||||
println!("{:?}", path); | println!("{:?}", path); | ||||
@@ -2,16 +2,20 @@ | |||||
extern crate lazy_static; | extern crate lazy_static; | ||||
extern crate syntect; | extern crate syntect; | ||||
use std::cell::RefCell; | |||||
use std::path::Path; | |||||
use syntect::LoadingError; | |||||
use syntect::dumps::from_binary; | use syntect::dumps::from_binary; | ||||
use syntect::parsing::SyntaxSet; | use syntect::parsing::SyntaxSet; | ||||
use syntect::highlighting::{ThemeSet, Theme}; | use syntect::highlighting::{ThemeSet, Theme}; | ||||
use syntect::easy::HighlightLines; | use syntect::easy::HighlightLines; | ||||
thread_local! { | thread_local! { | ||||
pub static SYNTAX_SET: SyntaxSet = { | |||||
let mut ss: SyntaxSet = from_binary(include_bytes!("../../../sublime_syntaxes/newlines.packdump")); | |||||
ss.link_syntaxes(); | |||||
ss | |||||
/// A pair of the set and whether extras have been added to it. | |||||
pub static SYNTAX_SET: RefCell<(SyntaxSet, bool)> = { | |||||
let ss: SyntaxSet = from_binary(include_bytes!("../../../sublime_syntaxes/newlines.packdump")); | |||||
RefCell::new((ss, false)) | |||||
}; | }; | ||||
} | } | ||||
@@ -19,14 +23,22 @@ lazy_static! { | |||||
pub static ref THEME_SET: ThemeSet = from_binary(include_bytes!("../../../sublime_themes/all.themedump")); | pub static ref THEME_SET: ThemeSet = from_binary(include_bytes!("../../../sublime_themes/all.themedump")); | ||||
} | } | ||||
pub fn get_highlighter<'a>(theme: &'a Theme, info: &str, base_path: &Path, extra_syntaxes: &[String]) -> Result<HighlightLines<'a>, LoadingError> { | |||||
SYNTAX_SET.with(|rc| { | |||||
let (ss, extras_added) = &mut *rc.borrow_mut(); | |||||
if !*extras_added { | |||||
for dir in extra_syntaxes { | |||||
ss.load_syntaxes(base_path.join(dir), true)?; | |||||
} | |||||
ss.link_syntaxes(); | |||||
*extras_added = true; | |||||
} | |||||
pub fn get_highlighter<'a>(theme: &'a Theme, info: &str) -> HighlightLines<'a> { | |||||
SYNTAX_SET.with(|ss| { | |||||
let syntax = info | let syntax = info | ||||
.split(' ') | .split(' ') | ||||
.next() | .next() | ||||
.and_then(|lang| ss.find_syntax_by_token(lang)) | .and_then(|lang| ss.find_syntax_by_token(lang)) | ||||
.unwrap_or_else(|| ss.find_syntax_plain_text()); | .unwrap_or_else(|| ss.find_syntax_plain_text()); | ||||
HighlightLines::new(syntax, theme) | |||||
Ok(HighlightLines::new(syntax, theme)) | |||||
}) | }) | ||||
} | } |
@@ -2,11 +2,11 @@ extern crate reqwest; | |||||
#[macro_use] | #[macro_use] | ||||
extern crate lazy_static; | extern crate lazy_static; | ||||
use reqwest::header::{qitem, Accept, Headers}; | |||||
use reqwest::{mime, StatusCode}; | |||||
use std::collections::HashMap; | use std::collections::HashMap; | ||||
use std::error::Error; | use std::error::Error; | ||||
use std::sync::{Arc, RwLock}; | use std::sync::{Arc, RwLock}; | ||||
use reqwest::StatusCode; | |||||
#[derive(Clone, Debug, PartialEq)] | #[derive(Clone, Debug, PartialEq)] | ||||
pub struct LinkResult { | pub struct LinkResult { | ||||
@@ -54,19 +54,30 @@ pub fn check_url(url: &str) -> LinkResult { | |||||
} | } | ||||
} | } | ||||
let mut headers = Headers::new(); | |||||
headers.set(Accept(vec![qitem(mime::TEXT_HTML), qitem(mime::STAR_STAR)])); | |||||
let client = reqwest::Client::new(); | |||||
// Need to actually do the link checking | // Need to actually do the link checking | ||||
let res = match reqwest::get(url) { | |||||
Ok(response) => LinkResult { code: Some(response.status()), error: None }, | |||||
Err(e) => LinkResult { code: None, error: Some(e.description().to_string()) }, | |||||
let res = match client.get(url).headers(headers).send() { | |||||
Ok(response) => LinkResult { | |||||
code: Some(response.status()), | |||||
error: None, | |||||
}, | |||||
Err(e) => LinkResult { | |||||
code: None, | |||||
error: Some(e.description().to_string()), | |||||
}, | |||||
}; | }; | ||||
LINKS.write().unwrap().insert(url.to_string(), res.clone()); | LINKS.write().unwrap().insert(url.to_string(), res.clone()); | ||||
return res; | |||||
res | |||||
} | } | ||||
#[cfg(test)] | #[cfg(test)] | ||||
mod tests { | mod tests { | ||||
use super::{LINKS, check_url}; | |||||
use super::{check_url, LINKS}; | |||||
#[test] | #[test] | ||||
fn can_validate_ok_links() { | fn can_validate_ok_links() { | ||||
@@ -7,6 +7,7 @@ extern crate config; | |||||
extern crate front_matter; | extern crate front_matter; | ||||
use std::collections::HashMap; | use std::collections::HashMap; | ||||
use std::path::Path; | |||||
use tera::Tera; | use tera::Tera; | ||||
use rendering::{RenderContext, render_content, render_shortcodes}; | use rendering::{RenderContext, render_content, render_shortcodes}; | ||||
@@ -91,7 +92,7 @@ fn bench_render_content_with_highlighting(b: &mut test::Bencher) { | |||||
tera.add_raw_template("shortcodes/youtube.html", "{{id}}").unwrap(); | tera.add_raw_template("shortcodes/youtube.html", "{{id}}").unwrap(); | ||||
let permalinks_ctx = HashMap::new(); | let permalinks_ctx = HashMap::new(); | ||||
let config = Config::default(); | let config = Config::default(); | ||||
let context = RenderContext::new(&tera, &config, "", &permalinks_ctx, InsertAnchor::None); | |||||
let context = RenderContext::new(&tera, &config, "", &permalinks_ctx, Path::new(""), InsertAnchor::None); | |||||
b.iter(|| render_content(CONTENT, &context).unwrap()); | b.iter(|| render_content(CONTENT, &context).unwrap()); | ||||
} | } | ||||
@@ -102,7 +103,7 @@ fn bench_render_content_without_highlighting(b: &mut test::Bencher) { | |||||
let permalinks_ctx = HashMap::new(); | let permalinks_ctx = HashMap::new(); | ||||
let mut config = Config::default(); | let mut config = Config::default(); | ||||
config.highlight_code = false; | config.highlight_code = false; | ||||
let context = RenderContext::new(&tera, &config, "", &permalinks_ctx, InsertAnchor::None); | |||||
let context = RenderContext::new(&tera, &config, "", &permalinks_ctx, Path::new(""), InsertAnchor::None); | |||||
b.iter(|| render_content(CONTENT, &context).unwrap()); | b.iter(|| render_content(CONTENT, &context).unwrap()); | ||||
} | } | ||||
@@ -113,7 +114,7 @@ fn bench_render_content_no_shortcode(b: &mut test::Bencher) { | |||||
let mut config = Config::default(); | let mut config = Config::default(); | ||||
config.highlight_code = false; | config.highlight_code = false; | ||||
let permalinks_ctx = HashMap::new(); | let permalinks_ctx = HashMap::new(); | ||||
let context = RenderContext::new(&tera, &config, "", &permalinks_ctx, InsertAnchor::None); | |||||
let context = RenderContext::new(&tera, &config, "", &permalinks_ctx, Path::new(""), InsertAnchor::None); | |||||
b.iter(|| render_content(&content2, &context).unwrap()); | b.iter(|| render_content(&content2, &context).unwrap()); | ||||
} | } | ||||
@@ -124,7 +125,7 @@ fn bench_render_shortcodes_one_present(b: &mut test::Bencher) { | |||||
tera.add_raw_template("shortcodes/youtube.html", "{{id}}").unwrap(); | tera.add_raw_template("shortcodes/youtube.html", "{{id}}").unwrap(); | ||||
let config = Config::default(); | let config = Config::default(); | ||||
let permalinks_ctx = HashMap::new(); | let permalinks_ctx = HashMap::new(); | ||||
let context = RenderContext::new(&tera, &config, "", &permalinks_ctx, InsertAnchor::None); | |||||
let context = RenderContext::new(&tera, &config, "", &permalinks_ctx, Path::new(""), InsertAnchor::None); | |||||
b.iter(|| render_shortcodes(CONTENT, &context)); | b.iter(|| render_shortcodes(CONTENT, &context)); | ||||
} | } | ||||
@@ -1,4 +1,5 @@ | |||||
use std::collections::HashMap; | use std::collections::HashMap; | ||||
use std::path::Path; | |||||
use tera::{Tera, Context}; | use tera::{Tera, Context}; | ||||
use front_matter::InsertAnchor; | use front_matter::InsertAnchor; | ||||
@@ -13,6 +14,7 @@ pub struct RenderContext<'a> { | |||||
pub tera_context: Context, | pub tera_context: Context, | ||||
pub current_page_permalink: &'a str, | pub current_page_permalink: &'a str, | ||||
pub permalinks: &'a HashMap<String, String>, | pub permalinks: &'a HashMap<String, String>, | ||||
pub base_path: &'a Path, | |||||
pub insert_anchor: InsertAnchor, | pub insert_anchor: InsertAnchor, | ||||
} | } | ||||
@@ -22,6 +24,7 @@ impl<'a> RenderContext<'a> { | |||||
config: &'a Config, | config: &'a Config, | ||||
current_page_permalink: &'a str, | current_page_permalink: &'a str, | ||||
permalinks: &'a HashMap<String, String>, | permalinks: &'a HashMap<String, String>, | ||||
base_path: &'a Path, | |||||
insert_anchor: InsertAnchor, | insert_anchor: InsertAnchor, | ||||
) -> RenderContext<'a> { | ) -> RenderContext<'a> { | ||||
let mut tera_context = Context::new(); | let mut tera_context = Context::new(); | ||||
@@ -32,6 +35,7 @@ impl<'a> RenderContext<'a> { | |||||
current_page_permalink, | current_page_permalink, | ||||
permalinks, | permalinks, | ||||
insert_anchor, | insert_anchor, | ||||
base_path, | |||||
config, | config, | ||||
} | } | ||||
} | } | ||||
@@ -32,7 +32,7 @@ pub use table_of_contents::Header; | |||||
pub use shortcode::render_shortcodes; | pub use shortcode::render_shortcodes; | ||||
pub use context::RenderContext; | pub use context::RenderContext; | ||||
pub fn render_content(content: &str, context: &RenderContext) -> Result<(String, Vec<Header>)> { | |||||
pub fn render_content(content: &str, context: &RenderContext) -> Result<markdown::Rendered> { | |||||
// Don't do anything if there is nothing like a shortcode in the content | // Don't do anything if there is nothing like a shortcode in the content | ||||
if content.contains("{{") || content.contains("{%") { | if content.contains("{{") || content.contains("{%") { | ||||
let rendered = render_shortcodes(content, context)?; | let rendered = render_shortcodes(content, context)?; | ||||
@@ -1,4 +1,4 @@ | |||||
use std::borrow::Cow::Owned; | |||||
use std::borrow::Cow::{Owned, Borrowed}; | |||||
use pulldown_cmark as cmark; | use pulldown_cmark as cmark; | ||||
use self::cmark::{Parser, Event, Tag, Options, OPTION_ENABLE_TABLES, OPTION_ENABLE_FOOTNOTES}; | use self::cmark::{Parser, Event, Tag, Options, OPTION_ENABLE_TABLES, OPTION_ENABLE_FOOTNOTES}; | ||||
@@ -14,6 +14,15 @@ use link_checker::check_url; | |||||
use table_of_contents::{TempHeader, Header, make_table_of_contents}; | use table_of_contents::{TempHeader, Header, make_table_of_contents}; | ||||
use context::RenderContext; | use context::RenderContext; | ||||
const CONTINUE_READING: &str = "<p><a name=\"continue-reading\"></a></p>\n"; | |||||
#[derive(Debug)] | |||||
pub struct Rendered { | |||||
pub body: String, | |||||
pub summary_len: Option<usize>, | |||||
pub toc: Vec<Header> | |||||
} | |||||
// We might have cases where the slug is already present in our list of anchor | // We might have cases where the slug is already present in our list of anchor | ||||
// for example an article could have several titles named Example | // for example an article could have several titles named Example | ||||
// We add a counter after the slug if the slug is already present, which | // We add a counter after the slug if the slug is already present, which | ||||
@@ -36,8 +45,7 @@ fn is_colocated_asset_link(link: &str) -> bool { | |||||
&& !link.starts_with("mailto:") | && !link.starts_with("mailto:") | ||||
} | } | ||||
pub fn markdown_to_html(content: &str, context: &RenderContext) -> Result<(String, Vec<Header>)> { | |||||
pub fn markdown_to_html(content: &str, context: &RenderContext) -> Result<Rendered> { | |||||
// the rendered html | // the rendered html | ||||
let mut html = String::with_capacity(content.len()); | let mut html = String::with_capacity(content.len()); | ||||
// Set while parsing | // Set while parsing | ||||
@@ -57,6 +65,7 @@ pub fn markdown_to_html(content: &str, context: &RenderContext) -> Result<(Strin | |||||
let mut temp_header = TempHeader::default(); | let mut temp_header = TempHeader::default(); | ||||
let mut opts = Options::empty(); | let mut opts = Options::empty(); | ||||
let mut has_summary = false; | |||||
opts.insert(OPTION_ENABLE_TABLES); | opts.insert(OPTION_ENABLE_TABLES); | ||||
opts.insert(OPTION_ENABLE_FOOTNOTES); | opts.insert(OPTION_ENABLE_FOOTNOTES); | ||||
@@ -68,7 +77,7 @@ pub fn markdown_to_html(content: &str, context: &RenderContext) -> Result<(Strin | |||||
if in_header { | if in_header { | ||||
if header_created { | if header_created { | ||||
temp_header.push(&text); | temp_header.push(&text); | ||||
return Event::Html(Owned(String::new())); | |||||
return Event::Html(Borrowed("")); | |||||
} | } | ||||
let id = find_anchor(&anchors, slugify(&text), 0); | let id = find_anchor(&anchors, slugify(&text), 0); | ||||
anchors.push(id.clone()); | anchors.push(id.clone()); | ||||
@@ -78,7 +87,7 @@ pub fn markdown_to_html(content: &str, context: &RenderContext) -> Result<(Strin | |||||
// += as we might have some <code> or other things already there | // += as we might have some <code> or other things already there | ||||
temp_header.title += &text; | temp_header.title += &text; | ||||
header_created = true; | header_created = true; | ||||
return Event::Html(Owned(String::new())); | |||||
return Event::Html(Borrowed("")); | |||||
} | } | ||||
// if we are in the middle of a code block | // if we are in the middle of a code block | ||||
@@ -93,21 +102,27 @@ pub fn markdown_to_html(content: &str, context: &RenderContext) -> Result<(Strin | |||||
} | } | ||||
Event::Start(Tag::CodeBlock(ref info)) => { | Event::Start(Tag::CodeBlock(ref info)) => { | ||||
if !context.config.highlight_code { | if !context.config.highlight_code { | ||||
return Event::Html(Owned("<pre><code>".to_string())); | |||||
return Event::Html(Borrowed("<pre><code>")); | |||||
} | } | ||||
let theme = &THEME_SET.themes[&context.config.highlight_theme]; | let theme = &THEME_SET.themes[&context.config.highlight_theme]; | ||||
highlighter = Some(get_highlighter(&theme, info)); | |||||
match get_highlighter(&theme, info, context.base_path, &context.config.extra_syntaxes) { | |||||
Ok(h) => highlighter = Some(h), | |||||
Err(err) => { | |||||
error = Some(format!("Could not load syntax: {}", err).into()); | |||||
return Event::Html(Borrowed("")); | |||||
} | |||||
} | |||||
let snippet = start_coloured_html_snippet(theme); | let snippet = start_coloured_html_snippet(theme); | ||||
Event::Html(Owned(snippet)) | Event::Html(Owned(snippet)) | ||||
} | } | ||||
Event::End(Tag::CodeBlock(_)) => { | Event::End(Tag::CodeBlock(_)) => { | ||||
if !context.config.highlight_code { | if !context.config.highlight_code { | ||||
return Event::Html(Owned("</code></pre>\n".to_string())); | |||||
return Event::Html(Borrowed("</code></pre>\n")); | |||||
} | } | ||||
// reset highlight and close the code block | // reset highlight and close the code block | ||||
highlighter = None; | highlighter = None; | ||||
Event::Html(Owned("</pre>".to_string())) | |||||
Event::Html(Borrowed("</pre>")) | |||||
} | } | ||||
Event::Start(Tag::Image(src, title)) => { | Event::Start(Tag::Image(src, title)) => { | ||||
if is_colocated_asset_link(&src) { | if is_colocated_asset_link(&src) { | ||||
@@ -133,7 +148,7 @@ pub fn markdown_to_html(content: &str, context: &RenderContext) -> Result<(Strin | |||||
Ok(url) => url, | Ok(url) => url, | ||||
Err(_) => { | Err(_) => { | ||||
error = Some(format!("Relative link {} not found.", link).into()); | error = Some(format!("Relative link {} not found.", link).into()); | ||||
return Event::Html(Owned(String::new())); | |||||
return Event::Html(Borrowed("")); | |||||
} | } | ||||
} | } | ||||
} else if is_colocated_asset_link(&link) { | } else if is_colocated_asset_link(&link) { | ||||
@@ -161,7 +176,7 @@ pub fn markdown_to_html(content: &str, context: &RenderContext) -> Result<(Strin | |||||
format!("<a href=\"{}\" title=\"{}\">", fixed_link, title) | format!("<a href=\"{}\" title=\"{}\">", fixed_link, title) | ||||
}; | }; | ||||
temp_header.push(&html); | temp_header.push(&html); | ||||
return Event::Html(Owned(String::new())); | |||||
return Event::Html(Borrowed("")); | |||||
} | } | ||||
Event::Start(Tag::Link(Owned(fixed_link), title)) | Event::Start(Tag::Link(Owned(fixed_link), title)) | ||||
@@ -169,28 +184,28 @@ pub fn markdown_to_html(content: &str, context: &RenderContext) -> Result<(Strin | |||||
Event::End(Tag::Link(_, _)) => { | Event::End(Tag::Link(_, _)) => { | ||||
if in_header { | if in_header { | ||||
temp_header.push("</a>"); | temp_header.push("</a>"); | ||||
return Event::Html(Owned(String::new())); | |||||
return Event::Html(Borrowed("")); | |||||
} | } | ||||
event | event | ||||
} | } | ||||
Event::Start(Tag::Code) => { | Event::Start(Tag::Code) => { | ||||
if in_header { | if in_header { | ||||
temp_header.push("<code>"); | temp_header.push("<code>"); | ||||
return Event::Html(Owned(String::new())); | |||||
return Event::Html(Borrowed("")); | |||||
} | } | ||||
event | event | ||||
} | } | ||||
Event::End(Tag::Code) => { | Event::End(Tag::Code) => { | ||||
if in_header { | if in_header { | ||||
temp_header.push("</code>"); | temp_header.push("</code>"); | ||||
return Event::Html(Owned(String::new())); | |||||
return Event::Html(Borrowed("")); | |||||
} | } | ||||
event | event | ||||
} | } | ||||
Event::Start(Tag::Header(num)) => { | Event::Start(Tag::Header(num)) => { | ||||
in_header = true; | in_header = true; | ||||
temp_header = TempHeader::new(num); | temp_header = TempHeader::new(num); | ||||
Event::Html(Owned(String::new())) | |||||
Event::Html(Borrowed("")) | |||||
} | } | ||||
Event::End(Tag::Header(_)) => { | Event::End(Tag::Header(_)) => { | ||||
// End of a header, reset all the things and return the stringified | // End of a header, reset all the things and return the stringified | ||||
@@ -202,6 +217,10 @@ pub fn markdown_to_html(content: &str, context: &RenderContext) -> Result<(Strin | |||||
temp_header = TempHeader::default(); | temp_header = TempHeader::default(); | ||||
Event::Html(Owned(val)) | Event::Html(Owned(val)) | ||||
} | } | ||||
Event::Html(ref markup) if markup.contains("<!-- more -->") => { | |||||
has_summary = true; | |||||
Event::Html(Borrowed(CONTINUE_READING)) | |||||
} | |||||
_ => event, | _ => event, | ||||
} | } | ||||
}); | }); | ||||
@@ -209,11 +228,14 @@ pub fn markdown_to_html(content: &str, context: &RenderContext) -> Result<(Strin | |||||
cmark::html::push_html(&mut html, parser); | cmark::html::push_html(&mut html, parser); | ||||
} | } | ||||
match error { | |||||
Some(e) => Err(e), | |||||
None => Ok(( | |||||
html.replace("<p></p>", "").replace("</p></p>", "</p>"), | |||||
make_table_of_contents(&headers) | |||||
)), | |||||
if let Some(e) = error { | |||||
return Err(e) | |||||
} else { | |||||
html = html.replace("<p></p>", "").replace("</p></p>", "</p>"); | |||||
Ok(Rendered { | |||||
summary_len: if has_summary { html.find(CONTINUE_READING) } else { None }, | |||||
body: html, | |||||
toc: make_table_of_contents(&headers) | |||||
}) | |||||
} | } | ||||
} | } |
@@ -180,6 +180,8 @@ pub fn render_shortcodes(content: &str, context: &RenderContext) -> Result<Strin | |||||
#[cfg(test)] | #[cfg(test)] | ||||
mod tests { | mod tests { | ||||
use std::collections::HashMap; | use std::collections::HashMap; | ||||
use std::path::Path; | |||||
use tera::Tera; | use tera::Tera; | ||||
use config::Config; | use config::Config; | ||||
use front_matter::InsertAnchor; | use front_matter::InsertAnchor; | ||||
@@ -202,7 +204,7 @@ mod tests { | |||||
fn render_shortcodes(code: &str, tera: &Tera) -> String { | fn render_shortcodes(code: &str, tera: &Tera) -> String { | ||||
let config = Config::default(); | let config = Config::default(); | ||||
let permalinks = HashMap::new(); | let permalinks = HashMap::new(); | ||||
let context = RenderContext::new(&tera, &config, "", &permalinks, InsertAnchor::None); | |||||
let context = RenderContext::new(&tera, &config, "", &permalinks, Path::new("something"), InsertAnchor::None); | |||||
super::render_shortcodes(code, &context).unwrap() | super::render_shortcodes(code, &context).unwrap() | ||||
} | } | ||||
@@ -5,6 +5,7 @@ extern crate rendering; | |||||
extern crate config; | extern crate config; | ||||
use std::collections::HashMap; | use std::collections::HashMap; | ||||
use std::path::Path; | |||||
use tera::Tera; | use tera::Tera; | ||||
@@ -19,9 +20,9 @@ fn can_do_render_content_simple() { | |||||
let tera_ctx = Tera::default(); | let tera_ctx = Tera::default(); | ||||
let permalinks_ctx = HashMap::new(); | let permalinks_ctx = HashMap::new(); | ||||
let config = Config::default(); | let config = Config::default(); | ||||
let context = RenderContext::new(&tera_ctx, &config, "", &permalinks_ctx, InsertAnchor::None); | |||||
let context = RenderContext::new(&tera_ctx, &config, "", &permalinks_ctx, Path::new("something"), InsertAnchor::None); | |||||
let res = render_content("hello", &context).unwrap(); | let res = render_content("hello", &context).unwrap(); | ||||
assert_eq!(res.0, "<p>hello</p>\n"); | |||||
assert_eq!(res.body, "<p>hello</p>\n"); | |||||
} | } | ||||
#[test] | #[test] | ||||
@@ -30,10 +31,10 @@ fn doesnt_highlight_code_block_with_highlighting_off() { | |||||
let permalinks_ctx = HashMap::new(); | let permalinks_ctx = HashMap::new(); | ||||
let mut config = Config::default(); | let mut config = Config::default(); | ||||
config.highlight_code = false; | config.highlight_code = false; | ||||
let context = RenderContext::new(&tera_ctx, &config, "", &permalinks_ctx, InsertAnchor::None); | |||||
let context = RenderContext::new(&tera_ctx, &config, "", &permalinks_ctx, Path::new("something"), InsertAnchor::None); | |||||
let res = render_content("```\n$ gutenberg server\n```", &context).unwrap(); | let res = render_content("```\n$ gutenberg server\n```", &context).unwrap(); | ||||
assert_eq!( | assert_eq!( | ||||
res.0, | |||||
res.body, | |||||
"<pre><code>$ gutenberg server\n</code></pre>\n" | "<pre><code>$ gutenberg server\n</code></pre>\n" | ||||
); | ); | ||||
} | } | ||||
@@ -43,10 +44,10 @@ fn can_highlight_code_block_no_lang() { | |||||
let tera_ctx = Tera::default(); | let tera_ctx = Tera::default(); | ||||
let permalinks_ctx = HashMap::new(); | let permalinks_ctx = HashMap::new(); | ||||
let config = Config::default(); | let config = Config::default(); | ||||
let context = RenderContext::new(&tera_ctx, &config, "", &permalinks_ctx, InsertAnchor::None); | |||||
let context = RenderContext::new(&tera_ctx, &config, "", &permalinks_ctx, Path::new("something"), InsertAnchor::None); | |||||
let res = render_content("```\n$ gutenberg server\n$ ping\n```", &context).unwrap(); | let res = render_content("```\n$ gutenberg server\n$ ping\n```", &context).unwrap(); | ||||
assert_eq!( | assert_eq!( | ||||
res.0, | |||||
res.body, | |||||
"<pre style=\"background-color:#2b303b\">\n<span style=\"background-color:#2b303b;color:#c0c5ce;\">$ gutenberg server\n</span><span style=\"background-color:#2b303b;color:#c0c5ce;\">$ ping\n</span></pre>" | "<pre style=\"background-color:#2b303b\">\n<span style=\"background-color:#2b303b;color:#c0c5ce;\">$ gutenberg server\n</span><span style=\"background-color:#2b303b;color:#c0c5ce;\">$ ping\n</span></pre>" | ||||
); | ); | ||||
} | } | ||||
@@ -56,10 +57,10 @@ fn can_highlight_code_block_with_lang() { | |||||
let tera_ctx = Tera::default(); | let tera_ctx = Tera::default(); | ||||
let permalinks_ctx = HashMap::new(); | let permalinks_ctx = HashMap::new(); | ||||
let config = Config::default(); | let config = Config::default(); | ||||
let context = RenderContext::new(&tera_ctx, &config, "", &permalinks_ctx, InsertAnchor::None); | |||||
let context = RenderContext::new(&tera_ctx, &config, "", &permalinks_ctx, Path::new("something"), InsertAnchor::None); | |||||
let res = render_content("```python\nlist.append(1)\n```", &context).unwrap(); | let res = render_content("```python\nlist.append(1)\n```", &context).unwrap(); | ||||
assert_eq!( | assert_eq!( | ||||
res.0, | |||||
res.body, | |||||
"<pre style=\"background-color:#2b303b\">\n<span style=\"background-color:#2b303b;color:#c0c5ce;\">list.</span><span style=\"background-color:#2b303b;color:#bf616a;\">append</span><span style=\"background-color:#2b303b;color:#c0c5ce;\">(</span><span style=\"background-color:#2b303b;color:#d08770;\">1</span><span style=\"background-color:#2b303b;color:#c0c5ce;\">)\n</span></pre>" | "<pre style=\"background-color:#2b303b\">\n<span style=\"background-color:#2b303b;color:#c0c5ce;\">list.</span><span style=\"background-color:#2b303b;color:#bf616a;\">append</span><span style=\"background-color:#2b303b;color:#c0c5ce;\">(</span><span style=\"background-color:#2b303b;color:#d08770;\">1</span><span style=\"background-color:#2b303b;color:#c0c5ce;\">)\n</span></pre>" | ||||
); | ); | ||||
} | } | ||||
@@ -69,11 +70,11 @@ fn can_higlight_code_block_with_unknown_lang() { | |||||
let tera_ctx = Tera::default(); | let tera_ctx = Tera::default(); | ||||
let permalinks_ctx = HashMap::new(); | let permalinks_ctx = HashMap::new(); | ||||
let config = Config::default(); | let config = Config::default(); | ||||
let context = RenderContext::new(&tera_ctx, &config, "", &permalinks_ctx, InsertAnchor::None); | |||||
let context = RenderContext::new(&tera_ctx, &config, "", &permalinks_ctx, Path::new("something"), InsertAnchor::None); | |||||
let res = render_content("```yolo\nlist.append(1)\n```", &context).unwrap(); | let res = render_content("```yolo\nlist.append(1)\n```", &context).unwrap(); | ||||
// defaults to plain text | // defaults to plain text | ||||
assert_eq!( | assert_eq!( | ||||
res.0, | |||||
res.body, | |||||
"<pre style=\"background-color:#2b303b\">\n<span style=\"background-color:#2b303b;color:#c0c5ce;\">list.append(1)\n</span></pre>" | "<pre style=\"background-color:#2b303b\">\n<span style=\"background-color:#2b303b;color:#c0c5ce;\">list.append(1)\n</span></pre>" | ||||
); | ); | ||||
} | } | ||||
@@ -82,21 +83,21 @@ fn can_higlight_code_block_with_unknown_lang() { | |||||
fn can_render_shortcode() { | fn can_render_shortcode() { | ||||
let permalinks_ctx = HashMap::new(); | let permalinks_ctx = HashMap::new(); | ||||
let config = Config::default(); | let config = Config::default(); | ||||
let context = RenderContext::new(&GUTENBERG_TERA, &config, "", &permalinks_ctx, InsertAnchor::None); | |||||
let context = RenderContext::new(&GUTENBERG_TERA, &config, "", &permalinks_ctx, Path::new("something"), InsertAnchor::None); | |||||
let res = render_content(r#" | let res = render_content(r#" | ||||
Hello | Hello | ||||
{{ youtube(id="ub36ffWAqgQ") }} | {{ youtube(id="ub36ffWAqgQ") }} | ||||
"#, &context).unwrap(); | "#, &context).unwrap(); | ||||
assert!(res.0.contains("<p>Hello</p>\n<div >")); | |||||
assert!(res.0.contains(r#"<iframe src="https://www.youtube.com/embed/ub36ffWAqgQ""#)); | |||||
assert!(res.body.contains("<p>Hello</p>\n<div >")); | |||||
assert!(res.body.contains(r#"<iframe src="https://www.youtube.com/embed/ub36ffWAqgQ""#)); | |||||
} | } | ||||
#[test] | #[test] | ||||
fn can_render_shortcode_with_markdown_char_in_args_name() { | fn can_render_shortcode_with_markdown_char_in_args_name() { | ||||
let permalinks_ctx = HashMap::new(); | let permalinks_ctx = HashMap::new(); | ||||
let config = Config::default(); | let config = Config::default(); | ||||
let context = RenderContext::new(&GUTENBERG_TERA, &config, "", &permalinks_ctx, InsertAnchor::None); | |||||
let context = RenderContext::new(&GUTENBERG_TERA, &config, "", &permalinks_ctx, Path::new("something"), InsertAnchor::None); | |||||
let input = vec![ | let input = vec![ | ||||
"name", | "name", | ||||
"na_me", | "na_me", | ||||
@@ -105,7 +106,7 @@ fn can_render_shortcode_with_markdown_char_in_args_name() { | |||||
]; | ]; | ||||
for i in input { | for i in input { | ||||
let res = render_content(&format!("{{{{ youtube(id=\"hey\", {}=1) }}}}", i), &context).unwrap(); | let res = render_content(&format!("{{{{ youtube(id=\"hey\", {}=1) }}}}", i), &context).unwrap(); | ||||
assert!(res.0.contains(r#"<iframe src="https://www.youtube.com/embed/hey""#)); | |||||
assert!(res.body.contains(r#"<iframe src="https://www.youtube.com/embed/hey""#)); | |||||
} | } | ||||
} | } | ||||
@@ -113,7 +114,7 @@ fn can_render_shortcode_with_markdown_char_in_args_name() { | |||||
fn can_render_shortcode_with_markdown_char_in_args_value() { | fn can_render_shortcode_with_markdown_char_in_args_value() { | ||||
let permalinks_ctx = HashMap::new(); | let permalinks_ctx = HashMap::new(); | ||||
let config = Config::default(); | let config = Config::default(); | ||||
let context = RenderContext::new(&GUTENBERG_TERA, &config, "", &permalinks_ctx, InsertAnchor::None); | |||||
let context = RenderContext::new(&GUTENBERG_TERA, &config, "", &permalinks_ctx, Path::new("something"), InsertAnchor::None); | |||||
let input = vec![ | let input = vec![ | ||||
"ub36ffWAqgQ-hey", | "ub36ffWAqgQ-hey", | ||||
"ub36ffWAqgQ_hey", | "ub36ffWAqgQ_hey", | ||||
@@ -123,7 +124,7 @@ fn can_render_shortcode_with_markdown_char_in_args_value() { | |||||
]; | ]; | ||||
for i in input { | for i in input { | ||||
let res = render_content(&format!("{{{{ youtube(id=\"{}\") }}}}", i), &context).unwrap(); | let res = render_content(&format!("{{{{ youtube(id=\"{}\") }}}}", i), &context).unwrap(); | ||||
assert!(res.0.contains(&format!(r#"<iframe src="https://www.youtube.com/embed/{}""#, i))); | |||||
assert!(res.body.contains(&format!(r#"<iframe src="https://www.youtube.com/embed/{}""#, i))); | |||||
} | } | ||||
} | } | ||||
@@ -140,11 +141,11 @@ fn can_render_body_shortcode_with_markdown_char_in_name() { | |||||
for i in input { | for i in input { | ||||
tera.add_raw_template(&format!("shortcodes/{}.html", i), "<blockquote>{{ body }} - {{ author}}</blockquote>").unwrap(); | tera.add_raw_template(&format!("shortcodes/{}.html", i), "<blockquote>{{ body }} - {{ author}}</blockquote>").unwrap(); | ||||
let context = RenderContext::new(&tera, &config, "", &permalinks_ctx, InsertAnchor::None); | |||||
let context = RenderContext::new(&tera, &config, "", &permalinks_ctx, Path::new("something"), InsertAnchor::None); | |||||
let res = render_content(&format!("{{% {}(author=\"Bob\") %}}\nhey\n{{% end %}}", i), &context).unwrap(); | let res = render_content(&format!("{{% {}(author=\"Bob\") %}}\nhey\n{{% end %}}", i), &context).unwrap(); | ||||
println!("{:?}", res); | println!("{:?}", res); | ||||
assert!(res.0.contains("<blockquote>hey - Bob</blockquote>")); | |||||
assert!(res.body.contains("<blockquote>hey - Bob</blockquote>")); | |||||
} | } | ||||
} | } | ||||
@@ -169,11 +170,11 @@ Here is another paragraph. | |||||
tera.add_raw_template(&format!("shortcodes/{}.html", "figure"), shortcode).unwrap(); | tera.add_raw_template(&format!("shortcodes/{}.html", "figure"), shortcode).unwrap(); | ||||
let config = Config::default(); | let config = Config::default(); | ||||
let context = RenderContext::new(&tera, &config, "", &permalinks_ctx, InsertAnchor::None); | |||||
let context = RenderContext::new(&tera, &config, "", &permalinks_ctx, Path::new("something"), InsertAnchor::None); | |||||
let res = render_content(markdown_string, &context).unwrap(); | let res = render_content(markdown_string, &context).unwrap(); | ||||
println!("{:?}", res); | println!("{:?}", res); | ||||
assert_eq!(res.0, expected); | |||||
assert_eq!(res.body, expected); | |||||
} | } | ||||
#[test] | #[test] | ||||
@@ -202,18 +203,18 @@ Here is another paragraph. | |||||
tera.add_raw_template(&format!("shortcodes/{}.html", "figure"), shortcode).unwrap(); | tera.add_raw_template(&format!("shortcodes/{}.html", "figure"), shortcode).unwrap(); | ||||
let config = Config::default(); | let config = Config::default(); | ||||
let context = RenderContext::new(&tera, &config, "", &permalinks_ctx, InsertAnchor::None); | |||||
let context = RenderContext::new(&tera, &config, "", &permalinks_ctx, Path::new("something"), InsertAnchor::None); | |||||
let res = render_content(markdown_string, &context).unwrap(); | let res = render_content(markdown_string, &context).unwrap(); | ||||
println!("{:?}", res); | println!("{:?}", res); | ||||
assert_eq!(res.0, expected); | |||||
assert_eq!(res.body, expected); | |||||
} | } | ||||
#[test] | #[test] | ||||
fn can_render_several_shortcode_in_row() { | fn can_render_several_shortcode_in_row() { | ||||
let permalinks_ctx = HashMap::new(); | let permalinks_ctx = HashMap::new(); | ||||
let config = Config::default(); | let config = Config::default(); | ||||
let context = RenderContext::new(&GUTENBERG_TERA, &config, "", &permalinks_ctx, InsertAnchor::None); | |||||
let context = RenderContext::new(&GUTENBERG_TERA, &config, "", &permalinks_ctx, Path::new("something"), InsertAnchor::None); | |||||
let res = render_content(r#" | let res = render_content(r#" | ||||
Hello | Hello | ||||
@@ -228,11 +229,11 @@ Hello | |||||
{{ gist(url="https://gist.github.com/Keats/32d26f699dcc13ebd41b") }} | {{ gist(url="https://gist.github.com/Keats/32d26f699dcc13ebd41b") }} | ||||
"#, &context).unwrap(); | "#, &context).unwrap(); | ||||
assert!(res.0.contains("<p>Hello</p>\n<div >")); | |||||
assert!(res.0.contains(r#"<iframe src="https://www.youtube.com/embed/ub36ffWAqgQ""#)); | |||||
assert!(res.0.contains(r#"<iframe src="https://www.youtube.com/embed/ub36ffWAqgQ?autoplay=1""#)); | |||||
assert!(res.0.contains(r#"<iframe src="https://www.streamable.com/e/c0ic""#)); | |||||
assert!(res.0.contains(r#"//player.vimeo.com/video/210073083""#)); | |||||
assert!(res.body.contains("<p>Hello</p>\n<div >")); | |||||
assert!(res.body.contains(r#"<iframe src="https://www.youtube.com/embed/ub36ffWAqgQ""#)); | |||||
assert!(res.body.contains(r#"<iframe src="https://www.youtube.com/embed/ub36ffWAqgQ?autoplay=1""#)); | |||||
assert!(res.body.contains(r#"<iframe src="https://www.streamable.com/e/c0ic""#)); | |||||
assert!(res.body.contains(r#"//player.vimeo.com/video/210073083""#)); | |||||
} | } | ||||
#[test] | #[test] | ||||
@@ -240,9 +241,9 @@ fn doesnt_render_ignored_shortcodes() { | |||||
let permalinks_ctx = HashMap::new(); | let permalinks_ctx = HashMap::new(); | ||||
let mut config = Config::default(); | let mut config = Config::default(); | ||||
config.highlight_code = false; | config.highlight_code = false; | ||||
let context = RenderContext::new(&GUTENBERG_TERA, &config, "", &permalinks_ctx, InsertAnchor::None); | |||||
let context = RenderContext::new(&GUTENBERG_TERA, &config, "", &permalinks_ctx, Path::new("something"), InsertAnchor::None); | |||||
let res = render_content(r#"```{{/* youtube(id="w7Ft2ymGmfc") */}}```"#, &context).unwrap(); | let res = render_content(r#"```{{/* youtube(id="w7Ft2ymGmfc") */}}```"#, &context).unwrap(); | ||||
assert_eq!(res.0, "<p><code>{{ youtube(id="w7Ft2ymGmfc") }}</code></p>\n"); | |||||
assert_eq!(res.body, "<p><code>{{ youtube(id="w7Ft2ymGmfc") }}</code></p>\n"); | |||||
} | } | ||||
#[test] | #[test] | ||||
@@ -252,7 +253,7 @@ fn can_render_shortcode_with_body() { | |||||
tera.add_raw_template("shortcodes/quote.html", "<blockquote>{{ body }} - {{ author }}</blockquote>").unwrap(); | tera.add_raw_template("shortcodes/quote.html", "<blockquote>{{ body }} - {{ author }}</blockquote>").unwrap(); | ||||
let permalinks_ctx = HashMap::new(); | let permalinks_ctx = HashMap::new(); | ||||
let config = Config::default(); | let config = Config::default(); | ||||
let context = RenderContext::new(&tera, &config, "", &permalinks_ctx, InsertAnchor::None); | |||||
let context = RenderContext::new(&tera, &config, "", &permalinks_ctx, Path::new("something"), InsertAnchor::None); | |||||
let res = render_content(r#" | let res = render_content(r#" | ||||
Hello | Hello | ||||
@@ -260,7 +261,7 @@ Hello | |||||
A quote | A quote | ||||
{% end %} | {% end %} | ||||
"#, &context).unwrap(); | "#, &context).unwrap(); | ||||
assert_eq!(res.0, "<p>Hello</p>\n<blockquote>A quote - Keats</blockquote>\n"); | |||||
assert_eq!(res.body, "<p>Hello</p>\n<blockquote>A quote - Keats</blockquote>\n"); | |||||
} | } | ||||
#[test] | #[test] | ||||
@@ -268,7 +269,7 @@ fn errors_rendering_unknown_shortcode() { | |||||
let tera_ctx = Tera::default(); | let tera_ctx = Tera::default(); | ||||
let permalinks_ctx = HashMap::new(); | let permalinks_ctx = HashMap::new(); | ||||
let config = Config::default(); | let config = Config::default(); | ||||
let context = RenderContext::new(&tera_ctx, &config, "", &permalinks_ctx, InsertAnchor::None); | |||||
let context = RenderContext::new(&tera_ctx, &config, "", &permalinks_ctx, Path::new("something"), InsertAnchor::None); | |||||
let res = render_content("{{ hello(flash=true) }}", &context); | let res = render_content("{{ hello(flash=true) }}", &context); | ||||
assert!(res.is_err()); | assert!(res.is_err()); | ||||
} | } | ||||
@@ -279,14 +280,14 @@ fn can_make_valid_relative_link() { | |||||
permalinks.insert("pages/about.md".to_string(), "https://vincent.is/about".to_string()); | permalinks.insert("pages/about.md".to_string(), "https://vincent.is/about".to_string()); | ||||
let tera_ctx = Tera::default(); | let tera_ctx = Tera::default(); | ||||
let config = Config::default(); | let config = Config::default(); | ||||
let context = RenderContext::new(&tera_ctx, &config, "", &permalinks, InsertAnchor::None); | |||||
let context = RenderContext::new(&tera_ctx, &config, "", &permalinks, Path::new("something"), InsertAnchor::None); | |||||
let res = render_content( | let res = render_content( | ||||
r#"[rel link](./pages/about.md), [abs link](https://vincent.is/about)"#, | r#"[rel link](./pages/about.md), [abs link](https://vincent.is/about)"#, | ||||
&context | |||||
&context, | |||||
).unwrap(); | ).unwrap(); | ||||
assert!( | assert!( | ||||
res.0.contains(r#"<p><a href="https://vincent.is/about">rel link</a>, <a href="https://vincent.is/about">abs link</a></p>"#) | |||||
res.body.contains(r#"<p><a href="https://vincent.is/about">rel link</a>, <a href="https://vincent.is/about">abs link</a></p>"#) | |||||
); | ); | ||||
} | } | ||||
@@ -296,11 +297,11 @@ fn can_make_relative_links_with_anchors() { | |||||
permalinks.insert("pages/about.md".to_string(), "https://vincent.is/about".to_string()); | permalinks.insert("pages/about.md".to_string(), "https://vincent.is/about".to_string()); | ||||
let tera_ctx = Tera::default(); | let tera_ctx = Tera::default(); | ||||
let config = Config::default(); | let config = Config::default(); | ||||
let context = RenderContext::new(&tera_ctx, &config, "", &permalinks, InsertAnchor::None); | |||||
let context = RenderContext::new(&tera_ctx, &config, "", &permalinks, Path::new("something"), InsertAnchor::None); | |||||
let res = render_content(r#"[rel link](./pages/about.md#cv)"#, &context).unwrap(); | let res = render_content(r#"[rel link](./pages/about.md#cv)"#, &context).unwrap(); | ||||
assert!( | assert!( | ||||
res.0.contains(r#"<p><a href="https://vincent.is/about#cv">rel link</a></p>"#) | |||||
res.body.contains(r#"<p><a href="https://vincent.is/about#cv">rel link</a></p>"#) | |||||
); | ); | ||||
} | } | ||||
@@ -309,7 +310,7 @@ fn errors_relative_link_inexistant() { | |||||
let tera_ctx = Tera::default(); | let tera_ctx = Tera::default(); | ||||
let permalinks_ctx = HashMap::new(); | let permalinks_ctx = HashMap::new(); | ||||
let config = Config::default(); | let config = Config::default(); | ||||
let context = RenderContext::new(&tera_ctx, &config, "", &permalinks_ctx, InsertAnchor::None); | |||||
let context = RenderContext::new(&tera_ctx, &config, "", &permalinks_ctx, Path::new("something"), InsertAnchor::None); | |||||
let res = render_content("[rel link](./pages/about.md)", &context); | let res = render_content("[rel link](./pages/about.md)", &context); | ||||
assert!(res.is_err()); | assert!(res.is_err()); | ||||
} | } | ||||
@@ -319,9 +320,9 @@ fn can_add_id_to_headers() { | |||||
let tera_ctx = Tera::default(); | let tera_ctx = Tera::default(); | ||||
let permalinks_ctx = HashMap::new(); | let permalinks_ctx = HashMap::new(); | ||||
let config = Config::default(); | let config = Config::default(); | ||||
let context = RenderContext::new(&tera_ctx, &config, "", &permalinks_ctx, InsertAnchor::None); | |||||
let context = RenderContext::new(&tera_ctx, &config, "", &permalinks_ctx, Path::new("something"), InsertAnchor::None); | |||||
let res = render_content(r#"# Hello"#, &context).unwrap(); | let res = render_content(r#"# Hello"#, &context).unwrap(); | ||||
assert_eq!(res.0, "<h1 id=\"hello\">Hello</h1>\n"); | |||||
assert_eq!(res.body, "<h1 id=\"hello\">Hello</h1>\n"); | |||||
} | } | ||||
#[test] | #[test] | ||||
@@ -329,19 +330,19 @@ fn can_add_id_to_headers_same_slug() { | |||||
let tera_ctx = Tera::default(); | let tera_ctx = Tera::default(); | ||||
let permalinks_ctx = HashMap::new(); | let permalinks_ctx = HashMap::new(); | ||||
let config = Config::default(); | let config = Config::default(); | ||||
let context = RenderContext::new(&tera_ctx, &config, "", &permalinks_ctx, InsertAnchor::None); | |||||
let context = RenderContext::new(&tera_ctx, &config, "", &permalinks_ctx, Path::new("something"), InsertAnchor::None); | |||||
let res = render_content("# Hello\n# Hello", &context).unwrap(); | let res = render_content("# Hello\n# Hello", &context).unwrap(); | ||||
assert_eq!(res.0, "<h1 id=\"hello\">Hello</h1>\n<h1 id=\"hello-1\">Hello</h1>\n"); | |||||
assert_eq!(res.body, "<h1 id=\"hello\">Hello</h1>\n<h1 id=\"hello-1\">Hello</h1>\n"); | |||||
} | } | ||||
#[test] | #[test] | ||||
fn can_insert_anchor_left() { | fn can_insert_anchor_left() { | ||||
let permalinks_ctx = HashMap::new(); | let permalinks_ctx = HashMap::new(); | ||||
let config = Config::default(); | let config = Config::default(); | ||||
let context = RenderContext::new(&GUTENBERG_TERA, &config, "", &permalinks_ctx, InsertAnchor::Left); | |||||
let context = RenderContext::new(&GUTENBERG_TERA, &config, "", &permalinks_ctx, Path::new("something"), InsertAnchor::Left); | |||||
let res = render_content("# Hello", &context).unwrap(); | let res = render_content("# Hello", &context).unwrap(); | ||||
assert_eq!( | assert_eq!( | ||||
res.0, | |||||
res.body, | |||||
"<h1 id=\"hello\"><a class=\"gutenberg-anchor\" href=\"#hello\" aria-label=\"Anchor link for: hello\">đź”—</a>\nHello</h1>\n" | "<h1 id=\"hello\"><a class=\"gutenberg-anchor\" href=\"#hello\" aria-label=\"Anchor link for: hello\">đź”—</a>\nHello</h1>\n" | ||||
); | ); | ||||
} | } | ||||
@@ -350,10 +351,10 @@ fn can_insert_anchor_left() { | |||||
fn can_insert_anchor_right() { | fn can_insert_anchor_right() { | ||||
let permalinks_ctx = HashMap::new(); | let permalinks_ctx = HashMap::new(); | ||||
let config = Config::default(); | let config = Config::default(); | ||||
let context = RenderContext::new(&GUTENBERG_TERA, &config, "", &permalinks_ctx, InsertAnchor::Right); | |||||
let context = RenderContext::new(&GUTENBERG_TERA, &config, "", &permalinks_ctx, Path::new("something"), InsertAnchor::Right); | |||||
let res = render_content("# Hello", &context).unwrap(); | let res = render_content("# Hello", &context).unwrap(); | ||||
assert_eq!( | assert_eq!( | ||||
res.0, | |||||
res.body, | |||||
"<h1 id=\"hello\">Hello<a class=\"gutenberg-anchor\" href=\"#hello\" aria-label=\"Anchor link for: hello\">đź”—</a>\n</h1>\n" | "<h1 id=\"hello\">Hello<a class=\"gutenberg-anchor\" href=\"#hello\" aria-label=\"Anchor link for: hello\">đź”—</a>\n</h1>\n" | ||||
); | ); | ||||
} | } | ||||
@@ -363,10 +364,10 @@ fn can_insert_anchor_right() { | |||||
fn can_insert_anchor_with_exclamation_mark() { | fn can_insert_anchor_with_exclamation_mark() { | ||||
let permalinks_ctx = HashMap::new(); | let permalinks_ctx = HashMap::new(); | ||||
let config = Config::default(); | let config = Config::default(); | ||||
let context = RenderContext::new(&GUTENBERG_TERA, &config, "", &permalinks_ctx, InsertAnchor::Left); | |||||
let context = RenderContext::new(&GUTENBERG_TERA, &config, "", &permalinks_ctx, Path::new("something"), InsertAnchor::Left); | |||||
let res = render_content("# Hello!", &context).unwrap(); | let res = render_content("# Hello!", &context).unwrap(); | ||||
assert_eq!( | assert_eq!( | ||||
res.0, | |||||
res.body, | |||||
"<h1 id=\"hello\"><a class=\"gutenberg-anchor\" href=\"#hello\" aria-label=\"Anchor link for: hello\">đź”—</a>\nHello!</h1>\n" | "<h1 id=\"hello\"><a class=\"gutenberg-anchor\" href=\"#hello\" aria-label=\"Anchor link for: hello\">đź”—</a>\nHello!</h1>\n" | ||||
); | ); | ||||
} | } | ||||
@@ -376,10 +377,10 @@ fn can_insert_anchor_with_exclamation_mark() { | |||||
fn can_insert_anchor_with_link() { | fn can_insert_anchor_with_link() { | ||||
let permalinks_ctx = HashMap::new(); | let permalinks_ctx = HashMap::new(); | ||||
let config = Config::default(); | let config = Config::default(); | ||||
let context = RenderContext::new(&GUTENBERG_TERA, &config, "", &permalinks_ctx, InsertAnchor::Left); | |||||
let context = RenderContext::new(&GUTENBERG_TERA, &config, "", &permalinks_ctx, Path::new("something"), InsertAnchor::Left); | |||||
let res = render_content("## [Rust](https://rust-lang.org)", &context).unwrap(); | let res = render_content("## [Rust](https://rust-lang.org)", &context).unwrap(); | ||||
assert_eq!( | assert_eq!( | ||||
res.0, | |||||
res.body, | |||||
"<h2 id=\"rust\"><a class=\"gutenberg-anchor\" href=\"#rust\" aria-label=\"Anchor link for: rust\">đź”—</a>\n<a href=\"https://rust-lang.org\">Rust</a></h2>\n" | "<h2 id=\"rust\"><a class=\"gutenberg-anchor\" href=\"#rust\" aria-label=\"Anchor link for: rust\">đź”—</a>\n<a href=\"https://rust-lang.org\">Rust</a></h2>\n" | ||||
); | ); | ||||
} | } | ||||
@@ -388,10 +389,10 @@ fn can_insert_anchor_with_link() { | |||||
fn can_insert_anchor_with_other_special_chars() { | fn can_insert_anchor_with_other_special_chars() { | ||||
let permalinks_ctx = HashMap::new(); | let permalinks_ctx = HashMap::new(); | ||||
let config = Config::default(); | let config = Config::default(); | ||||
let context = RenderContext::new(&GUTENBERG_TERA, &config, "", &permalinks_ctx, InsertAnchor::Left); | |||||
let context = RenderContext::new(&GUTENBERG_TERA, &config, "", &permalinks_ctx, Path::new("something"), InsertAnchor::Left); | |||||
let res = render_content("# Hello*_()", &context).unwrap(); | let res = render_content("# Hello*_()", &context).unwrap(); | ||||
assert_eq!( | assert_eq!( | ||||
res.0, | |||||
res.body, | |||||
"<h1 id=\"hello\"><a class=\"gutenberg-anchor\" href=\"#hello\" aria-label=\"Anchor link for: hello\">đź”—</a>\nHello*_()</h1>\n" | "<h1 id=\"hello\"><a class=\"gutenberg-anchor\" href=\"#hello\" aria-label=\"Anchor link for: hello\">đź”—</a>\nHello*_()</h1>\n" | ||||
); | ); | ||||
} | } | ||||
@@ -405,7 +406,8 @@ fn can_make_toc() { | |||||
&config, | &config, | ||||
"https://mysite.com/something", | "https://mysite.com/something", | ||||
&permalinks_ctx, | &permalinks_ctx, | ||||
InsertAnchor::Left | |||||
Path::new("something"), | |||||
InsertAnchor::Left, | |||||
); | ); | ||||
let res = render_content(r#" | let res = render_content(r#" | ||||
@@ -418,21 +420,20 @@ fn can_make_toc() { | |||||
### Last one | ### Last one | ||||
"#, &context).unwrap(); | "#, &context).unwrap(); | ||||
let toc = res.1; | |||||
let toc = res.toc; | |||||
assert_eq!(toc.len(), 1); | assert_eq!(toc.len(), 1); | ||||
assert_eq!(toc[0].children.len(), 2); | assert_eq!(toc[0].children.len(), 2); | ||||
assert_eq!(toc[0].children[1].children.len(), 1); | assert_eq!(toc[0].children[1].children.len(), 1); | ||||
} | } | ||||
#[test] | #[test] | ||||
fn can_understand_backtick_in_titles() { | fn can_understand_backtick_in_titles() { | ||||
let permalinks_ctx = HashMap::new(); | let permalinks_ctx = HashMap::new(); | ||||
let config = Config::default(); | let config = Config::default(); | ||||
let context = RenderContext::new(&GUTENBERG_TERA, &config, "", &permalinks_ctx, InsertAnchor::None); | |||||
let context = RenderContext::new(&GUTENBERG_TERA, &config, "", &permalinks_ctx, Path::new("something"), InsertAnchor::None); | |||||
let res = render_content("# `Hello`", &context).unwrap(); | let res = render_content("# `Hello`", &context).unwrap(); | ||||
assert_eq!( | assert_eq!( | ||||
res.0, | |||||
res.body, | |||||
"<h1 id=\"hello\"><code>Hello</code></h1>\n" | "<h1 id=\"hello\"><code>Hello</code></h1>\n" | ||||
); | ); | ||||
} | } | ||||
@@ -441,10 +442,10 @@ fn can_understand_backtick_in_titles() { | |||||
fn can_understand_backtick_in_paragraphs() { | fn can_understand_backtick_in_paragraphs() { | ||||
let permalinks_ctx = HashMap::new(); | let permalinks_ctx = HashMap::new(); | ||||
let config = Config::default(); | let config = Config::default(); | ||||
let context = RenderContext::new(&GUTENBERG_TERA, &config, "", &permalinks_ctx, InsertAnchor::None); | |||||
let context = RenderContext::new(&GUTENBERG_TERA, &config, "", &permalinks_ctx, Path::new("something"), InsertAnchor::None); | |||||
let res = render_content("Hello `world`", &context).unwrap(); | let res = render_content("Hello `world`", &context).unwrap(); | ||||
assert_eq!( | assert_eq!( | ||||
res.0, | |||||
res.body, | |||||
"<p>Hello <code>world</code></p>\n" | "<p>Hello <code>world</code></p>\n" | ||||
); | ); | ||||
} | } | ||||
@@ -454,10 +455,10 @@ fn can_understand_backtick_in_paragraphs() { | |||||
fn can_understand_links_in_header() { | fn can_understand_links_in_header() { | ||||
let permalinks_ctx = HashMap::new(); | let permalinks_ctx = HashMap::new(); | ||||
let config = Config::default(); | let config = Config::default(); | ||||
let context = RenderContext::new(&GUTENBERG_TERA, &config, "", &permalinks_ctx, InsertAnchor::None); | |||||
let context = RenderContext::new(&GUTENBERG_TERA, &config, "", &permalinks_ctx, Path::new("something"), InsertAnchor::None); | |||||
let res = render_content("# [Rust](https://rust-lang.org)", &context).unwrap(); | let res = render_content("# [Rust](https://rust-lang.org)", &context).unwrap(); | ||||
assert_eq!( | assert_eq!( | ||||
res.0, | |||||
res.body, | |||||
"<h1 id=\"rust\"><a href=\"https://rust-lang.org\">Rust</a></h1>\n" | "<h1 id=\"rust\"><a href=\"https://rust-lang.org\">Rust</a></h1>\n" | ||||
); | ); | ||||
} | } | ||||
@@ -466,10 +467,10 @@ fn can_understand_links_in_header() { | |||||
fn can_understand_link_with_title_in_header() { | fn can_understand_link_with_title_in_header() { | ||||
let permalinks_ctx = HashMap::new(); | let permalinks_ctx = HashMap::new(); | ||||
let config = Config::default(); | let config = Config::default(); | ||||
let context = RenderContext::new(&GUTENBERG_TERA, &config, "", &permalinks_ctx, InsertAnchor::None); | |||||
let context = RenderContext::new(&GUTENBERG_TERA, &config, "", &permalinks_ctx, Path::new("something"), InsertAnchor::None); | |||||
let res = render_content("# [Rust](https://rust-lang.org \"Rust homepage\")", &context).unwrap(); | let res = render_content("# [Rust](https://rust-lang.org \"Rust homepage\")", &context).unwrap(); | ||||
assert_eq!( | assert_eq!( | ||||
res.0, | |||||
res.body, | |||||
"<h1 id=\"rust\"><a href=\"https://rust-lang.org\" title=\"Rust homepage\">Rust</a></h1>\n" | "<h1 id=\"rust\"><a href=\"https://rust-lang.org\" title=\"Rust homepage\">Rust</a></h1>\n" | ||||
); | ); | ||||
} | } | ||||
@@ -480,14 +481,14 @@ fn can_make_valid_relative_link_in_header() { | |||||
permalinks.insert("pages/about.md".to_string(), "https://vincent.is/about/".to_string()); | permalinks.insert("pages/about.md".to_string(), "https://vincent.is/about/".to_string()); | ||||
let tera_ctx = Tera::default(); | let tera_ctx = Tera::default(); | ||||
let config = Config::default(); | let config = Config::default(); | ||||
let context = RenderContext::new(&tera_ctx, &config, "", &permalinks, InsertAnchor::None); | |||||
let context = RenderContext::new(&tera_ctx, &config, "", &permalinks, Path::new("something"), InsertAnchor::None); | |||||
let res = render_content( | let res = render_content( | ||||
r#" # [rel link](./pages/about.md)"#, | r#" # [rel link](./pages/about.md)"#, | ||||
&context | |||||
&context, | |||||
).unwrap(); | ).unwrap(); | ||||
assert_eq!( | assert_eq!( | ||||
res.0, | |||||
res.body, | |||||
"<h1 id=\"rel-link\"><a href=\"https://vincent.is/about/\">rel link</a></h1>\n" | "<h1 id=\"rel-link\"><a href=\"https://vincent.is/about/\">rel link</a></h1>\n" | ||||
); | ); | ||||
} | } | ||||
@@ -496,10 +497,10 @@ fn can_make_valid_relative_link_in_header() { | |||||
fn can_make_permalinks_with_colocated_assets_for_link() { | fn can_make_permalinks_with_colocated_assets_for_link() { | ||||
let permalinks_ctx = HashMap::new(); | let permalinks_ctx = HashMap::new(); | ||||
let config = Config::default(); | let config = Config::default(); | ||||
let context = RenderContext::new(&GUTENBERG_TERA, &config, "https://vincent.is/about/", &permalinks_ctx, InsertAnchor::None); | |||||
let context = RenderContext::new(&GUTENBERG_TERA, &config, "https://vincent.is/about/", &permalinks_ctx, Path::new("something"), InsertAnchor::None); | |||||
let res = render_content("[an image](image.jpg)", &context).unwrap(); | let res = render_content("[an image](image.jpg)", &context).unwrap(); | ||||
assert_eq!( | assert_eq!( | ||||
res.0, | |||||
res.body, | |||||
"<p><a href=\"https://vincent.is/about/image.jpg\">an image</a></p>\n" | "<p><a href=\"https://vincent.is/about/image.jpg\">an image</a></p>\n" | ||||
); | ); | ||||
} | } | ||||
@@ -508,10 +509,10 @@ fn can_make_permalinks_with_colocated_assets_for_link() { | |||||
fn can_make_permalinks_with_colocated_assets_for_image() { | fn can_make_permalinks_with_colocated_assets_for_image() { | ||||
let permalinks_ctx = HashMap::new(); | let permalinks_ctx = HashMap::new(); | ||||
let config = Config::default(); | let config = Config::default(); | ||||
let context = RenderContext::new(&GUTENBERG_TERA, &config, "https://vincent.is/about/", &permalinks_ctx, InsertAnchor::None); | |||||
let context = RenderContext::new(&GUTENBERG_TERA, &config, "https://vincent.is/about/", &permalinks_ctx, Path::new("something"), InsertAnchor::None); | |||||
let res = render_content("![alt text](image.jpg)", &context).unwrap(); | let res = render_content("![alt text](image.jpg)", &context).unwrap(); | ||||
assert_eq!( | assert_eq!( | ||||
res.0, | |||||
res.body, | |||||
"<p><img src=\"https://vincent.is/about/image.jpg\" alt=\"alt text\" /></p>\n" | "<p><img src=\"https://vincent.is/about/image.jpg\" alt=\"alt text\" /></p>\n" | ||||
); | ); | ||||
} | } | ||||
@@ -520,7 +521,7 @@ fn can_make_permalinks_with_colocated_assets_for_image() { | |||||
fn markdown_doesnt_wrap_html_in_paragraph() { | fn markdown_doesnt_wrap_html_in_paragraph() { | ||||
let permalinks_ctx = HashMap::new(); | let permalinks_ctx = HashMap::new(); | ||||
let config = Config::default(); | let config = Config::default(); | ||||
let context = RenderContext::new(&GUTENBERG_TERA, &config, "https://vincent.is/about/", &permalinks_ctx, InsertAnchor::None); | |||||
let context = RenderContext::new(&GUTENBERG_TERA, &config, "https://vincent.is/about/", &permalinks_ctx, Path::new("something"), InsertAnchor::None); | |||||
let res = render_content(r#" | let res = render_content(r#" | ||||
Some text | Some text | ||||
@@ -533,7 +534,7 @@ Some text | |||||
</div> | </div> | ||||
"#, &context).unwrap(); | "#, &context).unwrap(); | ||||
assert_eq!( | assert_eq!( | ||||
res.0, | |||||
res.body, | |||||
"<p>Some text</p>\n<h1>Helo</h1>\n<div>\n<a href=\"mobx-flow.png\">\n <img src=\"mobx-flow.png\" alt=\"MobX flow\">\n </a>\n</div>\n" | "<p>Some text</p>\n<h1>Helo</h1>\n<div>\n<a href=\"mobx-flow.png\">\n <img src=\"mobx-flow.png\" alt=\"MobX flow\">\n </a>\n</div>\n" | ||||
); | ); | ||||
} | } | ||||
@@ -543,10 +544,10 @@ fn can_validate_valid_external_links() { | |||||
let permalinks_ctx = HashMap::new(); | let permalinks_ctx = HashMap::new(); | ||||
let mut config = Config::default(); | let mut config = Config::default(); | ||||
config.check_external_links = true; | config.check_external_links = true; | ||||
let context = RenderContext::new(&GUTENBERG_TERA, &config, "https://vincent.is/about/", &permalinks_ctx, InsertAnchor::None); | |||||
let context = RenderContext::new(&GUTENBERG_TERA, &config, "https://vincent.is/about/", &permalinks_ctx, Path::new("something"), InsertAnchor::None); | |||||
let res = render_content("[a link](http://google.com)", &context).unwrap(); | let res = render_content("[a link](http://google.com)", &context).unwrap(); | ||||
assert_eq!( | assert_eq!( | ||||
res.0, | |||||
res.body, | |||||
"<p><a href=\"http://google.com\">a link</a></p>\n" | "<p><a href=\"http://google.com\">a link</a></p>\n" | ||||
); | ); | ||||
} | } | ||||
@@ -556,9 +557,26 @@ fn can_show_error_message_for_invalid_external_links() { | |||||
let permalinks_ctx = HashMap::new(); | let permalinks_ctx = HashMap::new(); | ||||
let mut config = Config::default(); | let mut config = Config::default(); | ||||
config.check_external_links = true; | config.check_external_links = true; | ||||
let context = RenderContext::new(&GUTENBERG_TERA, &config, "https://vincent.is/about/", &permalinks_ctx, InsertAnchor::None); | |||||
let context = RenderContext::new(&GUTENBERG_TERA, &config, "https://vincent.is/about/", &permalinks_ctx, Path::new("something"), InsertAnchor::None); | |||||
let res = render_content("[a link](http://google.comy)", &context); | let res = render_content("[a link](http://google.comy)", &context); | ||||
assert!(res.is_err()); | assert!(res.is_err()); | ||||
let err = res.unwrap_err(); | let err = res.unwrap_err(); | ||||
assert!(err.description().contains("Link http://google.comy is not valid")); | assert!(err.description().contains("Link http://google.comy is not valid")); | ||||
} | } | ||||
#[test] | |||||
fn can_handle_summaries() { | |||||
let tera_ctx = Tera::default(); | |||||
let permalinks_ctx = HashMap::new(); | |||||
let config = Config::default(); | |||||
let context = RenderContext::new(&tera_ctx, &config, "", &permalinks_ctx, Path::new("something"), InsertAnchor::None); | |||||
let res = render_content("Hello [world]\n\n<!-- more -->\n\nBla bla\n\n[world]: https://vincent.is/about/", &context).unwrap(); | |||||
assert_eq!( | |||||
res.body, | |||||
"<p>Hello <a href=\"https://vincent.is/about/\">world</a></p>\n<p><a name=\"continue-reading\"></a></p>\n<p>Bla bla</p>\n" | |||||
); | |||||
assert_eq!( | |||||
res.summary_len, | |||||
Some("<p>Hello <a href=\"https://vincent.is/about/\">world</a></p>\n".len()) | |||||
); | |||||
} |
@@ -15,8 +15,10 @@ PAGE = """ | |||||
+++ | +++ | ||||
title = "Hello" | title = "Hello" | ||||
date = REPLACE_DATE | date = REPLACE_DATE | ||||
[taxonomies] | |||||
tags = REPLACE_TAG | tags = REPLACE_TAG | ||||
category = "REPLACE_CATEGORY" | |||||
categories = ["REPLACE_CATEGORY"] | |||||
+++ | +++ | ||||
# Modus cognitius profanam ne duae virtutis mundi | # Modus cognitius profanam ne duae virtutis mundi | ||||
@@ -103,10 +105,13 @@ def gen_skeleton(name, is_blog): | |||||
f.write(""" | f.write(""" | ||||
title = "My site" | title = "My site" | ||||
base_url = "https://replace-this-with-your-url.com" | base_url = "https://replace-this-with-your-url.com" | ||||
generate_tags_pages = true | |||||
generate_categories_pages = true | |||||
theme = "sample" | theme = "sample" | ||||
taxonomies = [ | |||||
{name = "tags", rss = true}, | |||||
{name = "categories"} | |||||
] | |||||
[extra.author] | [extra.author] | ||||
name = "Vincent Prouillet" | name = "Vincent Prouillet" | ||||
""") | """) | ||||
@@ -121,8 +126,8 @@ name = "Vincent Prouillet" | |||||
""") | """) | ||||
# Re-use the test templates | # Re-use the test templates | ||||
shutil.copytree("../test_site/templates", os.path.join(name, "templates")) | |||||
shutil.copytree("../test_site/themes", os.path.join(name, "themes")) | |||||
shutil.copytree("../../../test_site/templates", os.path.join(name, "templates")) | |||||
shutil.copytree("../../../test_site/themes", os.path.join(name, "themes")) | |||||
def gen_section(path, num_pages, is_blog): | def gen_section(path, num_pages, is_blog): | ||||
@@ -25,7 +25,7 @@ fn bench_loading_small_blog_with_syntax_highlighting(b: &mut test::Bencher) { | |||||
path.push("benches"); | path.push("benches"); | ||||
path.push("small-blog"); | path.push("small-blog"); | ||||
let mut site = Site::new(&path, "config.toml").unwrap(); | let mut site = Site::new(&path, "config.toml").unwrap(); | ||||
site.config.highlight_code = Some(true); | |||||
site.config.highlight_code = true; | |||||
b.iter(|| site.load().unwrap()); | b.iter(|| site.load().unwrap()); | ||||
} | } | ||||
@@ -46,7 +46,7 @@ fn bench_loading_small_blog_with_syntax_highlighting(b: &mut test::Bencher) { | |||||
// path.push("benches"); | // path.push("benches"); | ||||
// path.push("medium-blog"); | // path.push("medium-blog"); | ||||
// let mut site = Site::new(&path, "config.toml").unwrap(); | // let mut site = Site::new(&path, "config.toml").unwrap(); | ||||
// site.config.highlight_code = Some(true); | |||||
// site.config.highlight_code = true; | |||||
// | // | ||||
// b.iter(|| site.load().unwrap()); | // b.iter(|| site.load().unwrap()); | ||||
//} | //} | ||||
@@ -67,7 +67,7 @@ fn bench_loading_small_blog_with_syntax_highlighting(b: &mut test::Bencher) { | |||||
// path.push("benches"); | // path.push("benches"); | ||||
// path.push("big-blog"); | // path.push("big-blog"); | ||||
// let mut site = Site::new(&path, "config.toml").unwrap(); | // let mut site = Site::new(&path, "config.toml").unwrap(); | ||||
// site.config.highlight_code = Some(true); | |||||
// site.config.highlight_code = true; | |||||
// | // | ||||
// b.iter(|| site.load().unwrap()); | // b.iter(|| site.load().unwrap()); | ||||
//} | //} | ||||
@@ -88,7 +88,7 @@ fn bench_loading_small_blog_with_syntax_highlighting(b: &mut test::Bencher) { | |||||
// path.push("benches"); | // path.push("benches"); | ||||
// path.push("huge-blog"); | // path.push("huge-blog"); | ||||
// let mut site = Site::new(&path, "config.toml").unwrap(); | // let mut site = Site::new(&path, "config.toml").unwrap(); | ||||
// site.config.highlight_code = Some(true); | |||||
// site.config.highlight_code = true; | |||||
// | // | ||||
// b.iter(|| site.load().unwrap()); | // b.iter(|| site.load().unwrap()); | ||||
//} | //} | ||||
@@ -109,7 +109,7 @@ fn bench_loading_small_kb_with_syntax_highlighting(b: &mut test::Bencher) { | |||||
path.push("benches"); | path.push("benches"); | ||||
path.push("small-kb"); | path.push("small-kb"); | ||||
let mut site = Site::new(&path, "config.toml").unwrap(); | let mut site = Site::new(&path, "config.toml").unwrap(); | ||||
site.config.highlight_code = Some(true); | |||||
site.config.highlight_code = true; | |||||
b.iter(|| site.load().unwrap()); | b.iter(|| site.load().unwrap()); | ||||
} | } | ||||
@@ -1,12 +1,14 @@ | |||||
#![feature(test)] | #![feature(test)] | ||||
extern crate test; | extern crate test; | ||||
extern crate site; | extern crate site; | ||||
extern crate pagination; | |||||
extern crate tempfile; | extern crate tempfile; | ||||
use std::env; | use std::env; | ||||
use tempfile::tempdir; | use tempfile::tempdir; | ||||
use site::Site; | use site::Site; | ||||
use pagination::Paginator; | |||||
fn setup_site(name: &str) -> Site { | fn setup_site(name: &str) -> Site { | ||||
@@ -20,8 +22,8 @@ fn setup_site(name: &str) -> Site { | |||||
#[bench] | #[bench] | ||||
fn bench_render_aliases(b: &mut test::Bencher) { | fn bench_render_aliases(b: &mut test::Bencher) { | ||||
let mut site = setup_site("huge-blog"); | |||||
let tmp_dir = TempDir::new("benches").expect("create temp dir"); | |||||
let mut site = setup_site("small-blog"); | |||||
let tmp_dir = tempdir().expect("create temp dir"); | |||||
let public = &tmp_dir.path().join("public"); | let public = &tmp_dir.path().join("public"); | ||||
site.set_output_path(&public); | site.set_output_path(&public); | ||||
b.iter(|| site.render_aliases().unwrap()); | b.iter(|| site.render_aliases().unwrap()); | ||||
@@ -29,8 +31,8 @@ fn bench_render_aliases(b: &mut test::Bencher) { | |||||
#[bench] | #[bench] | ||||
fn bench_render_sitemap(b: &mut test::Bencher) { | fn bench_render_sitemap(b: &mut test::Bencher) { | ||||
let mut site = setup_site("huge-blog"); | |||||
let tmp_dir = TempDir::new("benches").expect("create temp dir"); | |||||
let mut site = setup_site("small-blog"); | |||||
let tmp_dir = tempdir().expect("create temp dir"); | |||||
let public = &tmp_dir.path().join("public"); | let public = &tmp_dir.path().join("public"); | ||||
site.set_output_path(&public); | site.set_output_path(&public); | ||||
b.iter(|| site.render_sitemap().unwrap()); | b.iter(|| site.render_sitemap().unwrap()); | ||||
@@ -38,29 +40,30 @@ fn bench_render_sitemap(b: &mut test::Bencher) { | |||||
#[bench] | #[bench] | ||||
fn bench_render_rss_feed(b: &mut test::Bencher) { | fn bench_render_rss_feed(b: &mut test::Bencher) { | ||||
let mut site = setup_site("huge-blog"); | |||||
let tmp_dir = TempDir::new("benches").expect("create temp dir"); | |||||
let mut site = setup_site("small-blog"); | |||||
let tmp_dir = tempdir().expect("create temp dir"); | |||||
let public = &tmp_dir.path().join("public"); | let public = &tmp_dir.path().join("public"); | ||||
site.set_output_path(&public); | site.set_output_path(&public); | ||||
b.iter(|| site.render_rss_feed().unwrap()); | |||||
b.iter(|| site.render_rss_feed(None, None).unwrap()); | |||||
} | } | ||||
#[bench] | #[bench] | ||||
fn bench_render_categories(b: &mut test::Bencher) { | |||||
let mut site = setup_site("huge-blog"); | |||||
let tmp_dir = TempDir::new("benches").expect("create temp dir"); | |||||
fn bench_render_taxonomies(b: &mut test::Bencher) { | |||||
let mut site = setup_site("small-blog"); | |||||
let tmp_dir = tempdir().expect("create temp dir"); | |||||
let public = &tmp_dir.path().join("public"); | let public = &tmp_dir.path().join("public"); | ||||
site.set_output_path(&public); | site.set_output_path(&public); | ||||
b.iter(|| site.render_categories().unwrap()); | |||||
b.iter(|| site.render_taxonomies().unwrap()); | |||||
} | } | ||||
#[bench] | #[bench] | ||||
fn bench_render_paginated(b: &mut test::Bencher) { | fn bench_render_paginated(b: &mut test::Bencher) { | ||||
let mut site = setup_site("medium-blog"); | |||||
let tmp_dir = TempDir::new("benches").expect("create temp dir"); | |||||
let mut site = setup_site("small-blog"); | |||||
let tmp_dir = tempdir().expect("create temp dir"); | |||||
let public = &tmp_dir.path().join("public"); | let public = &tmp_dir.path().join("public"); | ||||
site.set_output_path(&public); | site.set_output_path(&public); | ||||
let section = site.sections.values().collect::<Vec<_>>()[0]; | let section = site.sections.values().collect::<Vec<_>>()[0]; | ||||
let paginator = Paginator::from_section(§ion.pages, section); | |||||
b.iter(|| site.render_paginated(public, section)); | |||||
b.iter(|| site.render_paginated(public, &paginator)); | |||||
} | } |
@@ -187,7 +187,6 @@ impl Site { | |||||
section_entries | section_entries | ||||
.into_par_iter() | .into_par_iter() | ||||
.filter(|entry| entry.as_path().file_name().unwrap() == "_index.md") | |||||
.map(|entry| { | .map(|entry| { | ||||
let path = entry.as_path(); | let path = entry.as_path(); | ||||
Section::from_file(path, config) | Section::from_file(path, config) | ||||
@@ -200,7 +199,6 @@ impl Site { | |||||
page_entries | page_entries | ||||
.into_par_iter() | .into_par_iter() | ||||
.filter(|entry| entry.as_path().file_name().unwrap() != "_index.md") | |||||
.map(|entry| { | .map(|entry| { | ||||
let path = entry.as_path(); | let path = entry.as_path(); | ||||
Page::from_file(path, config) | Page::from_file(path, config) | ||||
@@ -216,7 +214,7 @@ impl Site { | |||||
} | } | ||||
// Insert a default index section if necessary so we don't need to create | // Insert a default index section if necessary so we don't need to create | ||||
// a _index.md to render the index page | |||||
// a _index.md to render the index page at the root of the site | |||||
let index_path = self.index_section_path(); | let index_path = self.index_section_path(); | ||||
if let Some(ref index_section) = self.sections.get(&index_path) { | if let Some(ref index_section) = self.sections.get(&index_path) { | ||||
if self.config.build_search_index && !index_section.meta.in_search_index { | if self.config.build_search_index && !index_section.meta.in_search_index { | ||||
@@ -260,6 +258,7 @@ impl Site { | |||||
let permalinks = &self.permalinks; | let permalinks = &self.permalinks; | ||||
let tera = &self.tera; | let tera = &self.tera; | ||||
let config = &self.config; | let config = &self.config; | ||||
let base_path = &self.base_path; | |||||
// TODO: avoid the duplication with function above for that part | // TODO: avoid the duplication with function above for that part | ||||
// This is needed in the first place because of silly borrow checker | // This is needed in the first place because of silly borrow checker | ||||
@@ -271,13 +270,13 @@ impl Site { | |||||
self.pages.par_iter_mut() | self.pages.par_iter_mut() | ||||
.map(|(_, page)| { | .map(|(_, page)| { | ||||
let insert_anchor = pages_insert_anchors[&page.file.path]; | let insert_anchor = pages_insert_anchors[&page.file.path]; | ||||
page.render_markdown(permalinks, tera, config, insert_anchor) | |||||
page.render_markdown(permalinks, tera, config, base_path, insert_anchor) | |||||
}) | }) | ||||
.fold(|| Ok(()), Result::and) | .fold(|| Ok(()), Result::and) | ||||
.reduce(|| Ok(()), Result::and)?; | .reduce(|| Ok(()), Result::and)?; | ||||
self.sections.par_iter_mut() | self.sections.par_iter_mut() | ||||
.map(|(_, section)| section.render_markdown(permalinks, tera, config)) | |||||
.map(|(_, section)| section.render_markdown(permalinks, tera, config, base_path)) | |||||
.fold(|| Ok(()), Result::and) | .fold(|| Ok(()), Result::and) | ||||
.reduce(|| Ok(()), Result::and)?; | .reduce(|| Ok(()), Result::and)?; | ||||
@@ -320,7 +319,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 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, &self.base_path, insert_anchor)?; | |||||
} | } | ||||
Ok(prev) | Ok(prev) | ||||
@@ -337,7 +336,7 @@ impl Site { | |||||
if render { | if render { | ||||
let 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, &self.base_path)?; | |||||
} | } | ||||
Ok(prev) | Ok(prev) | ||||
@@ -837,6 +836,12 @@ impl Site { | |||||
} | } | ||||
} | } | ||||
// Copy any asset we found previously into the same directory as the index.html | |||||
for asset in §ion.assets { | |||||
let asset_path = asset.as_path(); | |||||
copy(&asset_path, &output_path.join(asset_path.file_name().unwrap()))?; | |||||
} | |||||
if render_pages { | if render_pages { | ||||
section | section | ||||
.pages | .pages | ||||
@@ -19,7 +19,7 @@ fn can_parse_site() { | |||||
site.load().unwrap(); | site.load().unwrap(); | ||||
// Correct number of pages (sections are pages too) | // Correct number of pages (sections are pages too) | ||||
assert_eq!(site.pages.len(), 14); | |||||
assert_eq!(site.pages.len(), 15); | |||||
let posts_path = path.join("content").join("posts"); | let posts_path = path.join("content").join("posts"); | ||||
// Make sure we remove all the pwd + content from the sections | // Make sure we remove all the pwd + content from the sections | ||||
@@ -44,7 +44,7 @@ fn can_parse_site() { | |||||
let posts_section = &site.sections[&posts_path.join("_index.md")]; | let posts_section = &site.sections[&posts_path.join("_index.md")]; | ||||
assert_eq!(posts_section.subsections.len(), 1); | assert_eq!(posts_section.subsections.len(), 1); | ||||
assert_eq!(posts_section.pages.len(), 6); | |||||
assert_eq!(posts_section.pages.len(), 7); | |||||
let tutorials_section = &site.sections[&posts_path.join("tutorials").join("_index.md")]; | let tutorials_section = &site.sections[&posts_path.join("tutorials").join("_index.md")]; | ||||
assert_eq!(tutorials_section.subsections.len(), 2); | assert_eq!(tutorials_section.subsections.len(), 2); | ||||
@@ -321,22 +321,41 @@ fn can_build_site_with_pagination_for_section() { | |||||
"posts/page/1/index.html", | "posts/page/1/index.html", | ||||
"http-equiv=\"refresh\" content=\"0;url=https://replace-this-with-your-url.com/posts/\"" | "http-equiv=\"refresh\" content=\"0;url=https://replace-this-with-your-url.com/posts/\"" | ||||
)); | )); | ||||
assert!(file_contains!(public, "posts/index.html", "Num pagers: 3")); | |||||
assert!(file_contains!(public, "posts/index.html", "Num pagers: 4")); | |||||
assert!(file_contains!(public, "posts/index.html", "Page size: 2")); | assert!(file_contains!(public, "posts/index.html", "Page size: 2")); | ||||
assert!(file_contains!(public, "posts/index.html", "Current index: 1")); | assert!(file_contains!(public, "posts/index.html", "Current index: 1")); | ||||
assert!(!file_contains!(public, "posts/index.html", "has_prev")); | |||||
assert!(file_contains!(public, "posts/index.html", "has_next")); | assert!(file_contains!(public, "posts/index.html", "has_next")); | ||||
assert!(file_contains!(public, "posts/index.html", "First: https://replace-this-with-your-url.com/posts/")); | assert!(file_contains!(public, "posts/index.html", "First: https://replace-this-with-your-url.com/posts/")); | ||||
assert!(file_contains!(public, "posts/index.html", "Last: https://replace-this-with-your-url.com/posts/page/3/")); | |||||
assert!(file_contains!(public, "posts/index.html", "Last: https://replace-this-with-your-url.com/posts/page/4/")); | |||||
assert_eq!(file_contains!(public, "posts/index.html", "has_prev"), false); | assert_eq!(file_contains!(public, "posts/index.html", "has_prev"), false); | ||||
assert!(file_exists!(public, "posts/page/2/index.html")); | assert!(file_exists!(public, "posts/page/2/index.html")); | ||||
assert!(file_contains!(public, "posts/page/2/index.html", "Num pagers: 3")); | |||||
assert!(file_contains!(public, "posts/page/2/index.html", "Num pagers: 4")); | |||||
assert!(file_contains!(public, "posts/page/2/index.html", "Page size: 2")); | assert!(file_contains!(public, "posts/page/2/index.html", "Page size: 2")); | ||||
assert!(file_contains!(public, "posts/page/2/index.html", "Current index: 2")); | assert!(file_contains!(public, "posts/page/2/index.html", "Current index: 2")); | ||||
assert!(file_contains!(public, "posts/page/2/index.html", "has_prev")); | assert!(file_contains!(public, "posts/page/2/index.html", "has_prev")); | ||||
assert!(file_contains!(public, "posts/page/2/index.html", "has_next")); | assert!(file_contains!(public, "posts/page/2/index.html", "has_next")); | ||||
assert!(file_contains!(public, "posts/page/2/index.html", "First: https://replace-this-with-your-url.com/posts/")); | assert!(file_contains!(public, "posts/page/2/index.html", "First: https://replace-this-with-your-url.com/posts/")); | ||||
assert!(file_contains!(public, "posts/page/2/index.html", "Last: https://replace-this-with-your-url.com/posts/page/3/")); | |||||
assert!(file_contains!(public, "posts/page/2/index.html", "Last: https://replace-this-with-your-url.com/posts/page/4/")); | |||||
assert!(file_exists!(public, "posts/page/3/index.html")); | |||||
assert!(file_contains!(public, "posts/page/3/index.html", "Num pagers: 4")); | |||||
assert!(file_contains!(public, "posts/page/3/index.html", "Page size: 2")); | |||||
assert!(file_contains!(public, "posts/page/3/index.html", "Current index: 3")); | |||||
assert!(file_contains!(public, "posts/page/3/index.html", "has_prev")); | |||||
assert!(file_contains!(public, "posts/page/3/index.html", "has_next")); | |||||
assert!(file_contains!(public, "posts/page/3/index.html", "First: https://replace-this-with-your-url.com/posts/")); | |||||
assert!(file_contains!(public, "posts/page/3/index.html", "Last: https://replace-this-with-your-url.com/posts/page/4/")); | |||||
assert!(file_exists!(public, "posts/page/4/index.html")); | |||||
assert!(file_contains!(public, "posts/page/4/index.html", "Num pagers: 4")); | |||||
assert!(file_contains!(public, "posts/page/4/index.html", "Page size: 2")); | |||||
assert!(file_contains!(public, "posts/page/4/index.html", "Current index: 4")); | |||||
assert!(file_contains!(public, "posts/page/4/index.html", "has_prev")); | |||||
assert!(!file_contains!(public, "posts/page/4/index.html", "has_next")); | |||||
assert!(file_contains!(public, "posts/page/4/index.html", "First: https://replace-this-with-your-url.com/posts/")); | |||||
assert!(file_contains!(public, "posts/page/4/index.html", "Last: https://replace-this-with-your-url.com/posts/page/4/")); | |||||
} | } | ||||
#[test] | #[test] | ||||
@@ -397,10 +416,10 @@ fn can_build_rss_feed() { | |||||
assert!(Path::new(&public).exists()); | assert!(Path::new(&public).exists()); | ||||
assert!(file_exists!(public, "rss.xml")); | assert!(file_exists!(public, "rss.xml")); | ||||
// latest article is posts/simple.md | |||||
// latest article is posts/extra-syntax.md | |||||
assert!(file_contains!(public, "rss.xml", "Extra Syntax")); | |||||
// Next is posts/simple.md | |||||
assert!(file_contains!(public, "rss.xml", "Simple article with shortcodes")); | assert!(file_contains!(public, "rss.xml", "Simple article with shortcodes")); | ||||
// Next is posts/python.md | |||||
assert!(file_contains!(public, "rss.xml", "Python in posts")); | |||||
} | } | ||||
@@ -420,3 +439,20 @@ fn can_build_search_index() { | |||||
assert!(file_exists!(public, "elasticlunr.min.js")); | assert!(file_exists!(public, "elasticlunr.min.js")); | ||||
assert!(file_exists!(public, "search_index.en.js")); | assert!(file_exists!(public, "search_index.en.js")); | ||||
} | } | ||||
#[test] | |||||
fn can_build_with_extra_syntaxes() { | |||||
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.load().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!(&public.exists()); | |||||
assert!(file_exists!(public, "posts/extra-syntax/index.html")); | |||||
assert!(file_contains!(public, "posts/extra-syntax/index.html", | |||||
r#"<span style="background-color:#2b303b;color:#d08770;">test</span>"#)); | |||||
} |
@@ -40,18 +40,34 @@ While not shown in the example, sections can be nested indefinitely. | |||||
## Assets colocation | ## Assets colocation | ||||
The `content` directory is not limited to markup files though: it's natural to want to co-locate a page and some related | The `content` directory is not limited to markup files though: it's natural to want to co-locate a page and some related | ||||
assets. | |||||
assets, for instance images or spreadsheets. Gutenberg supports that pattern out of the box for both sections and pages. | |||||
Any non-markdown file you add in the page/section folder will be copied alongside the generated page when building the site, | |||||
which allows us to use a relative path to access them. | |||||
For pages to use assets colocation, they should not be placed directly in their section folder (such as `latest-experiment.md`), but as an `index.md` file | |||||
in a dedicated folder (`latest-experiment/index.md`), like so: | |||||
Gutenberg supports that pattern out of the box: create a folder, add a `index.md` file and as many non-markdown files as you want. | |||||
Those assets will be copied in the same folder when building the site which allows you to use a relative path to access them. | |||||
```bash | ```bash | ||||
└── with-assets | |||||
├── index.md | |||||
└── yavascript.js | |||||
└── research | |||||
├── latest-experiment | |||||
│ ├── index.md | |||||
│ └── yavascript.js | |||||
├── _index.md | |||||
└── research.jpg | |||||
``` | |||||
In this setup, you may access `research.jpg` from your 'research' section, | |||||
and `yavascript.js` from your 'latest-experiment' directly within the Markdown: | |||||
```markdown | |||||
Check out the complete program [here](yavascript.js). It's **really cool free-software**! | |||||
``` | ``` | ||||
By default, this page will get the folder name (`with-assets` in this case) as its slug. | |||||
By default, this page will get the folder name as its slug. So its permalink would be in the form of `https://example.com/research/latest-experiment/` | |||||
### Excluding files from assets | |||||
It is possible to ignore selected asset files using the | It is possible to ignore selected asset files using the | ||||
[ignored_content](./documentation/getting-started/configuration.md) setting in the config file. | [ignored_content](./documentation/getting-started/configuration.md) setting in the config file. | ||||
@@ -20,7 +20,7 @@ content directory `about.md` would also create a page at `[base_url]/about`. | |||||
As you can see, creating an `about.md` file is exactly equivalent to creating an | As you can see, creating an `about.md` file is exactly equivalent to creating an | ||||
`about/index.md` file. The only difference between the two methods is that creating | `about/index.md` file. The only difference between the two methods is that creating | ||||
the `about` folder allows you to use asset colocation, as discussed in the | the `about` folder allows you to use asset colocation, as discussed in the | ||||
[Overview](./documentation/content/overview.md) section of this documentation. | |||||
[Overview](./documentation/content/overview.md#assets-colocation) section of this documentation. | |||||
## Front-matter | ## Front-matter | ||||
@@ -14,6 +14,8 @@ not have any content or metadata. If you would like to add content or metadata, | |||||
`_index.md` file at the root of the `content` folder and edit it just as you would edit any other | `_index.md` file at the root of the `content` folder and edit it just as you would edit any other | ||||
`_index.md` file; your `index.html` template will then have access to that content and metadata. | `_index.md` file; your `index.html` template will then have access to that content and metadata. | ||||
Any non-Markdown file in the section folder is added to the `assets` collection of the section, as explained in the [Content Overview](./documentation/content/overview.md#assets-colocation). These files are then available from the Markdown using relative links. | |||||
## Front-matter | ## Front-matter | ||||
The `_index.md` file within a folder defines the content and metadata for that section. To set | The `_index.md` file within a folder defines the content and metadata for that section. To set | ||||
@@ -3,7 +3,7 @@ title = "Syntax Highlighting" | |||||
weight = 80 | weight = 80 | ||||
+++ | +++ | ||||
Gutenberg comes with built-in syntax highlighting but you first | |||||
Gutenberg comes with built-in syntax highlighting but you first | |||||
need to enable it in the [configuration](./documentation/getting-started/configuration.md). | need to enable it in the [configuration](./documentation/getting-started/configuration.md). | ||||
Once this is done, Gutenberg will automatically highlight all code blocks | Once this is done, Gutenberg will automatically highlight all code blocks | ||||
@@ -17,7 +17,7 @@ let highlight = true; | |||||
```` | ```` | ||||
You can replace the `rust` by the language you want to highlight or not put anything to get it | |||||
You can replace the `rust` by the language you want to highlight or not put anything to get it | |||||
interpreted as plain text. | interpreted as plain text. | ||||
Here is a full list of the supported languages and the short names you can use: | Here is a full list of the supported languages and the short names you can use: | ||||
@@ -27,12 +27,12 @@ Here is a full list of the supported languages and the short names you can use: | |||||
- Assembly x86 (NASM) -> ["asm", "inc", "nasm"] | - Assembly x86 (NASM) -> ["asm", "inc", "nasm"] | ||||
- Crystal -> ["cr"] | - Crystal -> ["cr"] | ||||
- Elixir -> ["ex", "exs"] | - Elixir -> ["ex", "exs"] | ||||
- Elm -> ["elm"] | |||||
- 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"] | ||||
- Jinja2 -> ["j2", "jinja2"] | - Jinja2 -> ["j2", "jinja2"] | ||||
- Julia -> ["jl"] | - Julia -> ["jl"] | ||||
- Kotlin -> ["kt", "kts"] | - Kotlin -> ["kt", "kts"] | ||||
- Less -> ["less", "css.less"] | - Less -> ["less", "css.less"] | ||||
- MiniZinc (MZN) -> ["mzn", "dzn"] | |||||
- Nim -> ["nim", "nims"] | - Nim -> ["nim", "nims"] | ||||
- ASP -> ["asa"] | - ASP -> ["asa"] | ||||
- HTML (ASP) -> ["asp"] | - HTML (ASP) -> ["asp"] | ||||
@@ -49,10 +49,17 @@ Here is a full list of the supported languages and the short names you can use: | |||||
- Diff -> ["diff", "patch"] | - Diff -> ["diff", "patch"] | ||||
- Erlang -> ["erl", "hrl", "Emakefile", "emakefile"] | - Erlang -> ["erl", "hrl", "Emakefile", "emakefile"] | ||||
- HTML (Erlang) -> ["yaws"] | - HTML (Erlang) -> ["yaws"] | ||||
- Git Attributes -> ["attributes", "gitattributes", ".gitattributes"] | |||||
- Git Commit -> ["COMMIT_EDITMSG", "MERGE_MSG", "TAG_EDITMSG"] | |||||
- Git Config -> ["gitconfig", ".gitconfig", ".gitmodules"] | |||||
- Git Ignore -> ["exclude", "gitignore", ".gitignore"] | |||||
- Git Link -> [".git"] | |||||
- Git Log -> ["gitlog"] | |||||
- Git Rebase Todo -> ["git-rebase-todo"] | |||||
- Go -> ["go"] | - Go -> ["go"] | ||||
- Graphviz (DOT) -> ["dot", "DOT", "gv"] | - Graphviz (DOT) -> ["dot", "DOT", "gv"] | ||||
- Groovy -> ["groovy", "gvy", "gradle"] | |||||
- HTML -> ["html", "htm", "shtml", "xhtml", "inc", "tmpl", "tpl"] | |||||
- Groovy -> ["groovy", "gvy", "gradle", "Jenkinsfile"] | |||||
- HTML -> ["html", "htm", "shtml", "xhtml"] | |||||
- Haskell -> ["hs"] | - Haskell -> ["hs"] | ||||
- Literate Haskell -> ["lhs"] | - Literate Haskell -> ["lhs"] | ||||
- Java Server Page (JSP) -> ["jsp"] | - Java Server Page (JSP) -> ["jsp"] | ||||
@@ -65,7 +72,7 @@ Here is a full list of the supported languages and the short names you can use: | |||||
- TeX -> ["sty", "cls"] | - TeX -> ["sty", "cls"] | ||||
- Lisp -> ["lisp", "cl", "clisp", "l", "mud", "el", "scm", "ss", "lsp", "fasl"] | - Lisp -> ["lisp", "cl", "clisp", "l", "mud", "el", "scm", "ss", "lsp", "fasl"] | ||||
- Lua -> ["lua"] | - Lua -> ["lua"] | ||||
- Makefile -> ["make", "GNUmakefile", "makefile", "Makefile", "OCamlMakefile", "mak", "mk"] | |||||
- Makefile -> ["make", "GNUmakefile", "makefile", "Makefile", "makefile.am", "Makefile.am", "makefile.in", "Makefile.in", "OCamlMakefile", "mak", "mk"] | |||||
- Markdown -> ["md", "mdown", "markdown", "markdn"] | - Markdown -> ["md", "mdown", "markdown", "markdn"] | ||||
- MATLAB -> ["matlab"] | - MATLAB -> ["matlab"] | ||||
- OCaml -> ["ml", "mli"] | - OCaml -> ["ml", "mli"] | ||||
@@ -90,19 +97,45 @@ Here is a full list of the supported languages and the short names you can use: | |||||
- Rust -> ["rs"] | - Rust -> ["rs"] | ||||
- SQL -> ["sql", "ddl", "dml"] | - SQL -> ["sql", "ddl", "dml"] | ||||
- Scala -> ["scala", "sbt"] | - Scala -> ["scala", "sbt"] | ||||
- Bourne Again Shell (bash) -> ["sh", "bash", "zsh", "fish", ".bash_aliases", ".bash_completions", ".bash_functions", ".bash_login", ".bash_logout", ".bash_profile", ".bash_variables", ".bashrc", ".profile", ".textmate_init"] | |||||
- Bourne Again Shell (bash) -> ["sh", "bash", "zsh", "fish", ".bash_aliases", ".bash_completions", ".bash_functions", ".bash_login", ".bash_logout", ".bash_profile", ".bash_variables", ".bashrc", ".profile", ".textmate_init", ".zshrc"] | |||||
- HTML (Tcl) -> ["adp"] | - HTML (Tcl) -> ["adp"] | ||||
- Tcl -> ["tcl"] | - Tcl -> ["tcl"] | ||||
- Textile -> ["textile"] | - Textile -> ["textile"] | ||||
- XML -> ["xml", "xsd", "xslt", "tld", "dtml", "rss", "opml", "svg"] | - XML -> ["xml", "xsd", "xslt", "tld", "dtml", "rss", "opml", "svg"] | ||||
- YAML -> ["yaml", "yml", "sublime-syntax"] | - YAML -> ["yaml", "yml", "sublime-syntax"] | ||||
- SWI-Prolog -> ["pro"] | - SWI-Prolog -> ["pro"] | ||||
- CMake C Header -> ["h.in"] | |||||
- CMake C++ Header -> ["hh.in", "hpp.in", "hxx.in", "h++.in"] | |||||
- CMake -> ["CMakeLists.txt", "cmake"] | |||||
- CMakeCache -> ["CMakeCache.txt"] | |||||
- Generic Config -> ["cfg", "conf", "config", "ini", "pro", "mak", "mk", "Doxyfile", "inputrc", ".inputrc", "dircolors", ".dircolors", "gitmodules", ".gitmodules", "gitignore", ".gitignore", "gitattributes", ".gitattributes"] | - Generic Config -> ["cfg", "conf", "config", "ini", "pro", "mak", "mk", "Doxyfile", "inputrc", ".inputrc", "dircolors", ".dircolors", "gitmodules", ".gitmodules", "gitignore", ".gitignore", "gitattributes", ".gitattributes"] | ||||
- Elm -> ["elm"] | |||||
- Linker Script -> ["ld"] | - Linker Script -> ["ld"] | ||||
- TOML -> ["toml", "tml"] | - TOML -> ["toml", "tml"] | ||||
- TypeScript -> ["ts"] | - TypeScript -> ["ts"] | ||||
- TypeScriptReact -> ["tsx"] | - TypeScriptReact -> ["tsx"] | ||||
- VimL -> ["vim"] | - VimL -> ["vim"] | ||||
- TOML -> ["toml", "tml", "Cargo.lock", "Gopkg.lock"] | |||||
``` | ``` | ||||
If you want to highlight a language not on that list, please open an issue or a pull request on the [Gutenberg repo](https://github.com/Keats/gutenberg). | If you want to highlight a language not on that list, please open an issue or a pull request on the [Gutenberg repo](https://github.com/Keats/gutenberg). | ||||
Alternatively, the `extra_syntaxes` config option can be used to add additional syntax files. | |||||
If your site source is laid out as follows: | |||||
``` | |||||
. | |||||
├── config.toml | |||||
├── content/ | |||||
│  └── ... | |||||
├── static/ | |||||
│  └── ... | |||||
├── syntaxes/ | |||||
│  ├── Sublime-Language1/ | |||||
│  │  └── lang1.sublime-syntax | |||||
│  └── lang2.sublime-syntax | |||||
└── templates/ | |||||
└── ... | |||||
``` | |||||
you would set your `extra_syntaxes` to `["syntaxes", "syntaxes/Sublime-Language1"]` in order to load `lang1.sublime-syntax` and `lang2.sublime-syntax`. |
@@ -70,6 +70,9 @@ check_external_links = false | |||||
# ignored_content = ["*.{graphml,xlsx}", "temp.*"] | # ignored_content = ["*.{graphml,xlsx}", "temp.*"] | ||||
ignored_content = [] | ignored_content = [] | ||||
# A list of directories to search for additional `.sublime-syntax` files in. | |||||
extra_syntaxes = [] | |||||
# Optional translation object. The key if present should be a language code | # Optional translation object. The key if present should be a language code | ||||
[translations] | [translations] | ||||
@@ -77,6 +77,8 @@ word_count: Number; | |||||
reading_time: Number; | reading_time: Number; | ||||
// See the Table of contents section below for more details | // See the Table of contents section below for more details | ||||
toc: Array<Header>; | toc: Array<Header>; | ||||
// Paths of colocated assets, relative to the content directory | |||||
assets: Array<String>; | |||||
``` | ``` | ||||
## Table of contents | ## Table of contents | ||||
@@ -1 +1 @@ | |||||
Subproject commit 6f0da0ace6d881648dbfb2c3c1a3799ff7d5c54a | |||||
Subproject commit d3ddfe7b51e01140db209f57af3bc47d04d9ac0a |
@@ -0,0 +1,65 @@ | |||||
%YAML 1.2 | |||||
--- | |||||
# http://www.sublimetext.com/docs/3/syntax.html | |||||
name: MiniZinc (MZN) | |||||
file_extensions: | |||||
- mzn | |||||
- dzn | |||||
scope: source.mzn | |||||
contexts: | |||||
main: | |||||
- match: \%.* | |||||
scope: comment.line.percentage.mzn | |||||
- match: /\* | |||||
push: | |||||
- meta_scope: comment.block.mzn | |||||
- match: \*/ | |||||
pop: true | |||||
- match: \'.*?\' | |||||
scope: string.quoted.single.mzn | |||||
- match: \".*?\" | |||||
scope: string.quoted.double.mzn | |||||
- match: \b(ann|annotation|any|constraint|function|in|include|list|of|op|output|minimize|maximize|par|predicate|record|satisfy|solve|test|type|var)\b | |||||
scope: keyword.control.mzn | |||||
- match: \b(array|set|bool|enum|float|int|string|tuple)\b | |||||
scope: storage.type.mzn | |||||
- match: \b(for|forall|if|then|else|endif|where)\b | |||||
scope: keyword.control.mzn | |||||
- match: \b(abort|abs|acosh|array_intersect|array_union|array1d|array2d|array3d|array4d|array5d|array6d|asin|assert|atan|bool2int|card|ceil|concat|cos|cosh|dom|dom_array|dom_size|fix|exp|floor|index_set|index_set_1of2|index_set_2of2|index_set_1of3|index_set_2of3|index_set_3of3|int2float|is_fixed|join|lb|lb_array|length|ln|log|log2|log10|min|max|pow|product|round|set2array|show|show_int|show_float|sin|sinh|sqrt|sum|tan|tanh|trace|ub|ub_array)\b | |||||
scope: entity.name.function.mzn | |||||
- match: \b(circuit|disjoint|maximum|maximum_arg|member|minimum|minimum_arg|network_flow|network_flow_cost|partition_set|range|roots|sliding_sum|subcircuit|sum_pred)\b | |||||
scope: support.function.mzn | |||||
- match: \b(alldifferent|all_different|all_disjoint|all_equal|alldifferent_except_0|nvalue|symmetric_all_different)\b | |||||
scope: support.function.mzn | |||||
- match: \b(lex2|lex_greater|lex_greatereq|lex_less|lex_lesseq|strict_lex2|value_precede|value_precede_chain)\b | |||||
scope: support.function.mzn | |||||
- match: \b(arg_sort|decreasing|increasing|sort)\b | |||||
scope: support.function.mzn | |||||
- match: \b(int_set_channel|inverse|inverse_set|link_set_to_booleans)\b | |||||
scope: support.function.mzn | |||||
- match: \b(among|at_least|at_most|at_most1|count|count_eq|count_geq|count_gt|count_leq|count_lt|count_neq|distribute|exactly|global_cardinality|global_cardinality_closed|global_cardinality_low_up|global_cardinality_low_up_closed)\b | |||||
scope: support.function.mzn | |||||
- match: \b(bin_packing|bin_packing_capa|bin_packing_load|diffn|diffn_k|diffn_nonstrict|diffn_nonstrict_k|geost|geost_bb|geost_smallest_bb|knapsack)\b | |||||
scope: support.function.mzn | |||||
- match: \b(alternative|cumulative|disjunctive|disjunctive_strict|span)\b | |||||
scope: support.function.mzn | |||||
- match: \b(regular|regular_nfa|table)\b | |||||
scope: support.function.mzn | |||||
- match: \b(not|\+|-)\b | |||||
scope: keyword.operator.math.mzn | |||||
- match: \b(<->|->|<-|\\/|xor|/\\)\b | |||||
scope: keyword.operator.logical.mzn | |||||
- match: \b(<|>|<=|>=|==|=|!=)\b | |||||
scope: keyword.operator.math.mzn | |||||
- match: \b(\+|-|\*|/|div|mod)\b | |||||
scope: keyword.operator.math.mzn | |||||
- match: \b(in|subset|superset|union|diff|symdiff|intersect)\b | |||||
scope: keyword.operator.sets.mzn | |||||
- match: \|\.\.|\+\+ | |||||
scope: keyword.operator.math.mzn | |||||
- match: \b(true|false)\b | |||||
scope: constant.language.mzn | |||||
- match: '\b([_A-Za-z])(\w*)\b' | |||||
scope: variable.other.mzn | |||||
- match: '([+-]?)\d+(\.[^\.]\d*)?([eE][-+]?\d+)?' | |||||
scope: constant.numeric.mzn |
@@ -1 +1 @@ | |||||
Subproject commit 1cb4c3ec368c751d6f7ecfa16fe02ceff23a1f6b | |||||
Subproject commit 289782ff2e4cb58de171579c7fc86fe00d280619 |
@@ -1 +1 @@ | |||||
Subproject commit ff9a800a4ca942edd095de553ca05fba03b02275 | |||||
Subproject commit aba96a0862369e9f960bb63a38e2d7563ea6475e |
@@ -1 +1 @@ | |||||
Subproject commit a9055b118c991601c3a0876730c8918beb422c84 | |||||
Subproject commit 3c90f249ee6e4daa1d25a2dd9cda53071e42a076 |
@@ -1 +1 @@ | |||||
Subproject commit 1deb0745d7cfd069bdd5652878e321019b1ed229 | |||||
Subproject commit 36f8239551f09ed354a6872a6cc2fd0168883446 |
@@ -10,5 +10,7 @@ taxonomies = [ | |||||
{name = "categories", rss = true}, | {name = "categories", rss = true}, | ||||
] | ] | ||||
extra_syntaxes = ["syntaxes"] | |||||
[extra.author] | [extra.author] | ||||
name = "Vincent Prouillet" | name = "Vincent Prouillet" |
@@ -3,4 +3,5 @@ title = "Posts" | |||||
paginate_by = 2 | paginate_by = 2 | ||||
template = "section_paginated.html" | template = "section_paginated.html" | ||||
insert_anchor_links = "left" | insert_anchor_links = "left" | ||||
sort_by = "date" | |||||
+++ | +++ |
@@ -0,0 +1,9 @@ | |||||
+++ | |||||
title = "Extra Syntax" | |||||
description = "" | |||||
date = 2018-08-14 | |||||
+++ | |||||
```test-syntax | |||||
This is a test code snippet. | |||||
``` |
@@ -1,6 +1,7 @@ | |||||
+++ | +++ | ||||
title = "With assets" | title = "With assets" | ||||
description = "hey there" | description = "hey there" | ||||
date = 2015-03-01 | |||||
+++ | +++ | ||||
Hello world | Hello world |
@@ -0,0 +1,10 @@ | |||||
%YAML 1.2 | |||||
--- | |||||
file_extensions: | |||||
- test-syntax | |||||
scope: source.test | |||||
contexts: | |||||
main: | |||||
- match: "test" | |||||
scope: constant.language.test |
@@ -3,6 +3,6 @@ | |||||
{% block content %} | {% block content %} | ||||
{{ page.content | safe }} | {{ page.content | safe }} | ||||
{% if page.previous %}Previous article: {{ page.previous.permalink }}{% endif %} | |||||
{% if page.next %}Next article: {{ page.next.permalink }}{% endif %} | |||||
{% if page.earlier %}Previous article: {{ page.earlier.permalink }}{% endif %} | |||||
{% if page.later %}Next article: {{ page.later.permalink }}{% endif %} | |||||
{% endblock content %} | {% endblock content %} |
@@ -1,3 +1,3 @@ | |||||
{% for tag in tags %} | |||||
{% for tag in terms %} | |||||
{{ tag.name }} {{ tag.slug }} {{ tag.pages | length }} | {{ tag.name }} {{ tag.slug }} {{ tag.pages | length }} | ||||
{% endfor %} | {% endfor %} |
@@ -1,6 +1,6 @@ | |||||
Tag: {{ tag.name }} | |||||
Tag: {{ term.name }} | |||||
{% for page in tag.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 }}">{{ page.title }}</a></h3> | ||||
</article> | </article> |