@@ -6,6 +6,7 @@ | |||||
- Change the single item template context for categories/tags | - Change the single item template context for categories/tags | ||||
- Add a `get_url` global Tera function | - Add a `get_url` global Tera function | ||||
- Add a config option to control how many articles to show in RSS feed | - Add a config option to control how many articles to show in RSS feed | ||||
- Move `insert_anchor_links` from config to being a section option | |||||
## 0.0.5 (2017-05-15) | ## 0.0.5 (2017-05-15) | ||||
@@ -56,7 +56,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||||
dependencies = [ | dependencies = [ | ||||
"backtrace-sys 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", | "backtrace-sys 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", | "cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"cpp_demangle 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"cpp_demangle 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"dbghelp-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", | "dbghelp-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", | "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
@@ -187,10 +187,12 @@ dependencies = [ | |||||
[[package]] | [[package]] | ||||
name = "cpp_demangle" | name = "cpp_demangle" | ||||
version = "0.2.1" | |||||
version = "0.2.2" | |||||
source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
dependencies = [ | dependencies = [ | ||||
"clap 2.24.2 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"fixedbitset 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", | "fixedbitset 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
] | ] | ||||
[[package]] | [[package]] | ||||
@@ -1100,7 +1102,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||||
"checksum clap 2.24.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6b8f69e518f967224e628896b54e41ff6acfb4dcfefc5076325c36525dac900f" | "checksum clap 2.24.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6b8f69e518f967224e628896b54e41ff6acfb4dcfefc5076325c36525dac900f" | ||||
"checksum cmake 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)" = "92278eb79412c8f75cfc89e707a1bb3a6490b68f7f2e78d15c774f30fe701122" | "checksum cmake 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)" = "92278eb79412c8f75cfc89e707a1bb3a6490b68f7f2e78d15c774f30fe701122" | ||||
"checksum conduit-mime-types 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "95ca30253581af809925ef68c2641cc140d6183f43e12e0af4992d53768bd7b8" | "checksum conduit-mime-types 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "95ca30253581af809925ef68c2641cc140d6183f43e12e0af4992d53768bd7b8" | ||||
"checksum cpp_demangle 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ae1040e8145a72a251c1ccdd1d3d4b4ad175acc363da8a9c21a21cbb5b1f9056" | |||||
"checksum cpp_demangle 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2692e985e8b489736612f206c8b2d071767c05ac9343a654dd5ebd791a9035d5" | |||||
"checksum dbghelp-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "97590ba53bcb8ac28279161ca943a924d1fd4a8fb3fa63302591647c4fc5b850" | "checksum dbghelp-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "97590ba53bcb8ac28279161ca943a924d1fd4a8fb3fa63302591647c4fc5b850" | ||||
"checksum dtoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "80c8b71fd71146990a9742fc06dcbbde19161a267e0ad4e572c35162f4578c90" | "checksum dtoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "80c8b71fd71146990a9742fc06dcbbde19161a267e0ad4e572c35162f4578c90" | ||||
"checksum error 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "a6e606f14042bb87cc02ef6a14db6c90ab92ed6f62d87e69377bc759fd7987cc" | "checksum error 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "a6e606f14042bb87cc02ef6a14db6c90ab92ed6f62d87e69377bc759fd7987cc" | ||||
@@ -7,7 +7,7 @@ use gutenberg::errors::Result; | |||||
/// Finds the section that contains the page given if there is one | /// Finds the section that contains the page given if there is one | ||||
pub fn find_parent_section<'a>(site: &'a Site, page: &Page) -> Option<&'a Section> { | pub fn find_parent_section<'a>(site: &'a Site, page: &Page) -> Option<&'a Section> { | ||||
for section in site.sections.values() { | for section in site.sections.values() { | ||||
if section.is_child_page(page) { | |||||
if section.is_child_page(&page.file.path) { | |||||
return Some(section) | return Some(section) | ||||
} | } | ||||
} | } | ||||
@@ -4,14 +4,15 @@ use std::path::{Path, PathBuf}; | |||||
use std::result::Result as StdResult; | use std::result::Result as StdResult; | ||||
use tera::{Tera, Context}; | |||||
use tera::{Tera, Context as TeraContext}; | |||||
use serde::ser::{SerializeStruct, self}; | use serde::ser::{SerializeStruct, self}; | ||||
use slug::slugify; | use slug::slugify; | ||||
use errors::{Result, ResultExt}; | use errors::{Result, ResultExt}; | ||||
use config::Config; | use config::Config; | ||||
use front_matter::{PageFrontMatter, split_page_content}; | |||||
use front_matter::{PageFrontMatter, InsertAnchor, split_page_content}; | |||||
use rendering::markdown::markdown_to_html; | use rendering::markdown::markdown_to_html; | ||||
use rendering::context::Context; | |||||
use fs::{read_file}; | use fs::{read_file}; | ||||
use content::utils::{find_related_assets, get_reading_analytics}; | use content::utils::{find_related_assets, get_reading_analytics}; | ||||
use content::file_info::FileInfo; | use content::file_info::FileInfo; | ||||
@@ -112,13 +113,13 @@ 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) -> Result<()> { | |||||
self.content = markdown_to_html(&self.raw_content, permalinks, tera, config)?; | |||||
pub fn render_markdown(&mut self, permalinks: &HashMap<String, String>, tera: &Tera, config: &Config, anchor_insert: InsertAnchor) -> Result<()> { | |||||
let context = Context::new(tera, config, permalinks, anchor_insert); | |||||
self.content = markdown_to_html(&self.raw_content, &context)?; | |||||
if self.raw_content.contains("<!-- more -->") { | if self.raw_content.contains("<!-- more -->") { | ||||
self.summary = Some({ | self.summary = Some({ | ||||
let summary = self.raw_content.splitn(2, "<!-- more -->").collect::<Vec<&str>>()[0]; | let summary = self.raw_content.splitn(2, "<!-- more -->").collect::<Vec<&str>>()[0]; | ||||
markdown_to_html(summary, permalinks, tera, config)? | |||||
markdown_to_html(summary, &context)? | |||||
}) | }) | ||||
} | } | ||||
@@ -132,7 +133,7 @@ impl Page { | |||||
None => "page.html".to_string() | None => "page.html".to_string() | ||||
}; | }; | ||||
let mut context = Context::new(); | |||||
let mut context = TeraContext::new(); | |||||
context.add("config", config); | context.add("config", config); | ||||
context.add("page", self); | context.add("page", self); | ||||
context.add("current_url", &self.permalink); | context.add("current_url", &self.permalink); | ||||
@@ -195,6 +196,7 @@ mod tests { | |||||
use config::Config; | use config::Config; | ||||
use super::Page; | use super::Page; | ||||
use front_matter::InsertAnchor; | |||||
#[test] | #[test] | ||||
@@ -209,7 +211,7 @@ 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()).unwrap(); | |||||
page.render_markdown(&HashMap::default(), &Tera::default(), &Config::default(), 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()); | ||||
@@ -228,8 +230,7 @@ Hello world"#; | |||||
conf.base_url = "http://hello.com/".to_string(); | conf.base_url = "http://hello.com/".to_string(); | ||||
let res = Page::parse(Path::new("content/posts/intro/start.md"), content, &conf); | let res = Page::parse(Path::new("content/posts/intro/start.md"), content, &conf); | ||||
assert!(res.is_ok()); | assert!(res.is_ok()); | ||||
let mut page = res.unwrap(); | |||||
page.render_markdown(&HashMap::default(), &Tera::default(), &Config::default()).unwrap(); | |||||
let page = res.unwrap(); | |||||
assert_eq!(page.path, "posts/intro/hello-world"); | assert_eq!(page.path, "posts/intro/hello-world"); | ||||
assert_eq!(page.permalink, "http://hello.com/posts/intro/hello-world"); | assert_eq!(page.permalink, "http://hello.com/posts/intro/hello-world"); | ||||
} | } | ||||
@@ -244,8 +245,7 @@ Hello world"#; | |||||
let config = Config::default(); | let config = Config::default(); | ||||
let res = Page::parse(Path::new("start.md"), content, &config); | let res = Page::parse(Path::new("start.md"), content, &config); | ||||
assert!(res.is_ok()); | assert!(res.is_ok()); | ||||
let mut page = res.unwrap(); | |||||
page.render_markdown(&HashMap::default(), &Tera::default(), &config).unwrap(); | |||||
let page = res.unwrap(); | |||||
assert_eq!(page.path, "hello-world"); | assert_eq!(page.path, "hello-world"); | ||||
assert_eq!(page.permalink, config.make_permalink("hello-world")); | assert_eq!(page.permalink, config.make_permalink("hello-world")); | ||||
} | } | ||||
@@ -268,8 +268,7 @@ Hello world"#; | |||||
let config = Config::default(); | let config = Config::default(); | ||||
let res = Page::parse(Path::new(" file with space.md"), "+++\n+++", &config); | let res = Page::parse(Path::new(" file with space.md"), "+++\n+++", &config); | ||||
assert!(res.is_ok()); | assert!(res.is_ok()); | ||||
let mut page = res.unwrap(); | |||||
page.render_markdown(&HashMap::default(), &Tera::default(), &config).unwrap(); | |||||
let page = res.unwrap(); | |||||
assert_eq!(page.slug, "file-with-space"); | assert_eq!(page.slug, "file-with-space"); | ||||
assert_eq!(page.permalink, config.make_permalink(&page.slug)); | assert_eq!(page.permalink, config.make_permalink(&page.slug)); | ||||
} | } | ||||
@@ -285,7 +284,7 @@ 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).unwrap(); | |||||
page.render_markdown(&HashMap::default(), &Tera::default(), &config, 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())); | ||||
} | } | ||||
@@ -2,7 +2,7 @@ use std::collections::HashMap; | |||||
use std::path::{Path, PathBuf}; | use std::path::{Path, PathBuf}; | ||||
use std::result::Result as StdResult; | use std::result::Result as StdResult; | ||||
use tera::{Tera, Context}; | |||||
use tera::{Tera, Context as TeraContext}; | |||||
use serde::ser::{SerializeStruct, self}; | use serde::ser::{SerializeStruct, self}; | ||||
use config::Config; | use config::Config; | ||||
@@ -10,6 +10,7 @@ use front_matter::{SectionFrontMatter, split_section_content}; | |||||
use errors::{Result, ResultExt}; | use errors::{Result, ResultExt}; | ||||
use fs::{read_file}; | use fs::{read_file}; | ||||
use rendering::markdown::markdown_to_html; | use rendering::markdown::markdown_to_html; | ||||
use rendering::context::Context; | |||||
use content::Page; | use content::Page; | ||||
use content::file_info::FileInfo; | use content::file_info::FileInfo; | ||||
@@ -85,7 +86,8 @@ 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) -> Result<()> { | ||||
self.content = markdown_to_html(&self.raw_content, permalinks, tera, config)?; | |||||
let context = Context::new(tera, config, permalinks, self.meta.insert_anchor.unwrap()); | |||||
self.content = markdown_to_html(&self.raw_content, &context)?; | |||||
Ok(()) | Ok(()) | ||||
} | } | ||||
@@ -93,7 +95,7 @@ impl Section { | |||||
pub fn render_html(&self, sections: HashMap<String, Section>, tera: &Tera, config: &Config) -> Result<String> { | pub fn render_html(&self, sections: HashMap<String, Section>, tera: &Tera, config: &Config) -> Result<String> { | ||||
let tpl_name = self.get_template_name(); | let tpl_name = self.get_template_name(); | ||||
let mut context = Context::new(); | |||||
let mut context = TeraContext::new(); | |||||
context.add("config", config); | context.add("config", config); | ||||
context.add("section", self); | context.add("section", self); | ||||
context.add("current_url", &self.permalink); | context.add("current_url", &self.permalink); | ||||
@@ -120,8 +122,8 @@ impl Section { | |||||
} | } | ||||
/// Whether the page given belongs to that section | /// Whether the page given belongs to that section | ||||
pub fn is_child_page(&self, page: &Page) -> bool { | |||||
self.all_pages_path().contains(&page.file.path) | |||||
pub fn is_child_page(&self, path: &PathBuf) -> bool { | |||||
self.all_pages_path().contains(path) | |||||
} | } | ||||
} | } | ||||
@@ -8,7 +8,7 @@ mod page; | |||||
mod section; | mod section; | ||||
pub use self::page::PageFrontMatter; | pub use self::page::PageFrontMatter; | ||||
pub use self::section::{SectionFrontMatter}; | |||||
pub use self::section::{SectionFrontMatter, InsertAnchor}; | |||||
lazy_static! { | lazy_static! { | ||||
static ref PAGE_RE: Regex = Regex::new(r"^[[:space:]]*\+\+\+\r?\n((?s).*?(?-s))\+\+\+\r?\n?((?s).*(?-s))$").unwrap(); | static ref PAGE_RE: Regex = Regex::new(r"^[[:space:]]*\+\+\+\r?\n((?s).*?(?-s))\+\+\+\r?\n?((?s).*(?-s))$").unwrap(); | ||||
@@ -9,6 +9,14 @@ use content::SortBy; | |||||
static DEFAULT_PAGINATE_PATH: &'static str = "page"; | static DEFAULT_PAGINATE_PATH: &'static str = "page"; | ||||
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] | |||||
#[serde(rename_all = "lowercase")] | |||||
pub enum InsertAnchor { | |||||
Left, | |||||
Right, | |||||
None, | |||||
} | |||||
/// The front matter of every section | /// The front matter of every section | ||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] | ||||
pub struct SectionFrontMatter { | pub struct SectionFrontMatter { | ||||
@@ -28,6 +36,10 @@ pub struct SectionFrontMatter { | |||||
/// Path to be used by pagination: the page number will be appended after it. Defaults to `page`. | /// Path to be used by pagination: the page number will be appended after it. Defaults to `page`. | ||||
#[serde(skip_serializing)] | #[serde(skip_serializing)] | ||||
pub paginate_path: Option<String>, | pub paginate_path: Option<String>, | ||||
/// Whether to insert a link for each header like in Github READMEs. Defaults to false | |||||
/// The default template can be overridden by creating a `anchor-link.html` template and CSS will need to be | |||||
/// written if you turn that on. | |||||
pub insert_anchor: Option<InsertAnchor>, | |||||
/// Whether to render that section or not. Defaults to `true`. | /// Whether to render that section or not. Defaults to `true`. | ||||
/// Useful when the section is only there to organize things but is not meant | /// Useful when the section is only there to organize things but is not meant | ||||
/// to be used directly, like a posts section in a personal site | /// to be used directly, like a posts section in a personal site | ||||
@@ -56,6 +68,10 @@ impl SectionFrontMatter { | |||||
f.sort_by = Some(SortBy::None); | f.sort_by = Some(SortBy::None); | ||||
} | } | ||||
if f.insert_anchor.is_none() { | |||||
f.insert_anchor = Some(InsertAnchor::None); | |||||
} | |||||
Ok(f) | Ok(f) | ||||
} | } | ||||
@@ -87,6 +103,7 @@ impl Default for SectionFrontMatter { | |||||
paginate_by: None, | paginate_by: None, | ||||
paginate_path: Some(DEFAULT_PAGINATE_PATH.to_string()), | paginate_path: Some(DEFAULT_PAGINATE_PATH.to_string()), | ||||
render: Some(true), | render: Some(true), | ||||
insert_anchor: Some(InsertAnchor::None), | |||||
extra: None, | extra: None, | ||||
} | } | ||||
} | } | ||||
@@ -31,6 +31,6 @@ mod templates; | |||||
pub use site::{Site}; | pub use site::{Site}; | ||||
pub use config::{Config, get_config}; | pub use config::{Config, get_config}; | ||||
pub use front_matter::{PageFrontMatter, SectionFrontMatter, split_page_content, split_section_content}; | |||||
pub use front_matter::{PageFrontMatter, SectionFrontMatter, InsertAnchor, split_page_content, split_section_content}; | |||||
pub use content::{Page, Section, SortBy, sort_pages, populate_previous_and_next_pages}; | pub use content::{Page, Section, SortBy, sort_pages, populate_previous_and_next_pages}; | ||||
pub use fs::{create_file}; | pub use fs::{create_file}; |
@@ -0,0 +1,33 @@ | |||||
use std::collections::HashMap; | |||||
use tera::Tera; | |||||
use config::Config; | |||||
use front_matter::InsertAnchor; | |||||
/// All the information from the gutenberg site that is needed to render HTML from markdown | |||||
#[derive(Debug)] | |||||
pub struct Context<'a> { | |||||
pub tera: &'a Tera, | |||||
pub highlight_code: bool, | |||||
pub highlight_theme: String, | |||||
pub permalinks: &'a HashMap<String, String>, | |||||
pub insert_anchor: InsertAnchor, | |||||
} | |||||
impl<'a> Context<'a> { | |||||
pub fn new(tera: &'a Tera, config: &'a Config, permalinks: &'a HashMap<String, String>, insert_anchor: InsertAnchor) -> Context<'a> { | |||||
Context { | |||||
tera, | |||||
permalinks, | |||||
insert_anchor, | |||||
highlight_code: config.highlight_code.unwrap(), | |||||
highlight_theme: config.highlight_theme.clone().unwrap(), | |||||
} | |||||
} | |||||
pub fn should_insert_anchor(&self) -> bool { | |||||
self.insert_anchor != InsertAnchor::None | |||||
} | |||||
} |
@@ -1,5 +1,4 @@ | |||||
use std::borrow::Cow::Owned; | use std::borrow::Cow::Owned; | ||||
use std::collections::HashMap; | |||||
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}; | ||||
@@ -9,11 +8,12 @@ use syntect::dumps::from_binary; | |||||
use syntect::easy::HighlightLines; | use syntect::easy::HighlightLines; | ||||
use syntect::parsing::SyntaxSet; | use syntect::parsing::SyntaxSet; | ||||
use syntect::html::{start_coloured_html_snippet, styles_to_coloured_html, IncludeBackground}; | use syntect::html::{start_coloured_html_snippet, styles_to_coloured_html, IncludeBackground}; | ||||
use tera::{Tera, Context}; | |||||
use tera::{Context as TeraContext}; | |||||
use config::Config; | |||||
use errors::{Result}; | use errors::{Result}; | ||||
use site::resolve_internal_link; | use site::resolve_internal_link; | ||||
use front_matter::InsertAnchor; | |||||
use rendering::context::Context; | |||||
use rendering::highlighting::THEME_SET; | use rendering::highlighting::THEME_SET; | ||||
use rendering::short_code::{ShortCode, parse_shortcode, render_simple_shortcode}; | use rendering::short_code::{ShortCode, parse_shortcode, render_simple_shortcode}; | ||||
@@ -36,18 +36,18 @@ lazy_static!{ | |||||
}; | }; | ||||
} | } | ||||
pub fn markdown_to_html(content: &str, permalinks: &HashMap<String, String>, tera: &Tera, config: &Config) -> Result<String> { | |||||
pub fn markdown_to_html(content: &str, context: &Context) -> Result<String> { | |||||
// We try to be smart about highlighting code as it can be time-consuming | // We try to be smart about highlighting code as it can be time-consuming | ||||
// If the global config disables it, then we do nothing. However, | // If the global config disables it, then we do nothing. However, | ||||
// if we see a code block in the content, we assume that this page needs | // if we see a code block in the content, we assume that this page needs | ||||
// to be highlighted. It could potentially have false positive if the content | // to be highlighted. It could potentially have false positive if the content | ||||
// has ``` in it but that seems kind of unlikely | // has ``` in it but that seems kind of unlikely | ||||
let should_highlight = if config.highlight_code.unwrap() { | |||||
let should_highlight = if context.highlight_code { | |||||
content.contains("```") | content.contains("```") | ||||
} else { | } else { | ||||
false | false | ||||
}; | }; | ||||
let highlight_theme = config.highlight_theme.clone().unwrap(); | |||||
// Set while parsing | // Set while parsing | ||||
let mut error = None; | let mut error = None; | ||||
let mut highlighter: Option<HighlightLines> = None; | let mut highlighter: Option<HighlightLines> = None; | ||||
@@ -105,7 +105,7 @@ pub fn markdown_to_html(content: &str, permalinks: &HashMap<String, String>, ter | |||||
if shortcode_block.is_none() && text.starts_with("{{") && text.ends_with("}}") && SHORTCODE_RE.is_match(&text) { | if shortcode_block.is_none() && text.starts_with("{{") && text.ends_with("}}") && SHORTCODE_RE.is_match(&text) { | ||||
let (name, args) = parse_shortcode(&text); | let (name, args) = parse_shortcode(&text); | ||||
added_shortcode = true; | added_shortcode = true; | ||||
match render_simple_shortcode(tera, &name, &args) { | |||||
match render_simple_shortcode(context.tera, &name, &args) { | |||||
Ok(s) => return Event::Html(Owned(format!("</p>{}", s))), | Ok(s) => return Event::Html(Owned(format!("</p>{}", s))), | ||||
Err(e) => { | Err(e) => { | ||||
error = Some(e); | error = Some(e); | ||||
@@ -131,7 +131,7 @@ pub fn markdown_to_html(content: &str, permalinks: &HashMap<String, String>, ter | |||||
if let Some(ref mut shortcode) = shortcode_block { | if let Some(ref mut shortcode) = shortcode_block { | ||||
if text.trim() == "{% end %}" { | if text.trim() == "{% end %}" { | ||||
added_shortcode = true; | added_shortcode = true; | ||||
match shortcode.render(tera) { | |||||
match shortcode.render(context.tera) { | |||||
Ok(s) => return Event::Html(Owned(format!("</p>{}", s))), | Ok(s) => return Event::Html(Owned(format!("</p>{}", s))), | ||||
Err(e) => { | Err(e) => { | ||||
error = Some(e); | error = Some(e); | ||||
@@ -151,15 +151,20 @@ pub fn markdown_to_html(content: &str, permalinks: &HashMap<String, String>, ter | |||||
} | } | ||||
let id = find_anchor(&anchors, slugify(&text), 0); | let id = find_anchor(&anchors, slugify(&text), 0); | ||||
anchors.push(id.clone()); | anchors.push(id.clone()); | ||||
let anchor_link = if config.insert_anchor_links.unwrap() { | |||||
let mut context = Context::new(); | |||||
context.add("id", &id); | |||||
tera.render("anchor-link.html", &context).unwrap() | |||||
let anchor_link = if context.should_insert_anchor() { | |||||
let mut c = TeraContext::new(); | |||||
c.add("id", &id); | |||||
context.tera.render("anchor-link.html", &c).unwrap() | |||||
} else { | } else { | ||||
String::new() | String::new() | ||||
}; | }; | ||||
header_already_inserted = true; | header_already_inserted = true; | ||||
return Event::Html(Owned(format!(r#"id="{}">{}{}"#, id, anchor_link, text))); | |||||
let event = match context.insert_anchor { | |||||
InsertAnchor::Left => Event::Html(Owned(format!(r#"id="{}">{}{}"#, id, anchor_link, text))), | |||||
InsertAnchor::Right => Event::Html(Owned(format!(r#"id="{}">{}{}"#, id, text, anchor_link))), | |||||
InsertAnchor::None => Event::Html(Owned(format!(r#"id="{}">{}"#, id, text))) | |||||
}; | |||||
return event; | |||||
} | } | ||||
// Business as usual | // Business as usual | ||||
@@ -170,7 +175,7 @@ pub fn markdown_to_html(content: &str, permalinks: &HashMap<String, String>, ter | |||||
if !should_highlight { | if !should_highlight { | ||||
return Event::Html(Owned("<pre><code>".to_owned())); | return Event::Html(Owned("<pre><code>".to_owned())); | ||||
} | } | ||||
let theme = &THEME_SET.themes[&highlight_theme]; | |||||
let theme = &THEME_SET.themes[&context.highlight_theme]; | |||||
let syntax = info | let syntax = info | ||||
.split(' ') | .split(' ') | ||||
.next() | .next() | ||||
@@ -195,7 +200,7 @@ pub fn markdown_to_html(content: &str, permalinks: &HashMap<String, String>, ter | |||||
return Event::Html(Owned("".to_owned())); | return Event::Html(Owned("".to_owned())); | ||||
} | } | ||||
if link.starts_with("./") { | if link.starts_with("./") { | ||||
match resolve_internal_link(link, permalinks) { | |||||
match resolve_internal_link(link, context.permalinks) { | |||||
Ok(url) => { | Ok(url) => { | ||||
return Event::Start(Tag::Link(Owned(url), title.clone())); | return Event::Start(Tag::Link(Owned(url), title.clone())); | ||||
}, | }, | ||||
@@ -268,46 +273,33 @@ pub fn markdown_to_html(content: &str, permalinks: &HashMap<String, String>, ter | |||||
mod tests { | mod tests { | ||||
use std::collections::HashMap; | use std::collections::HashMap; | ||||
use templates::GUTENBERG_TERA; | |||||
use tera::Tera; | use tera::Tera; | ||||
use config::Config; | use config::Config; | ||||
use super::{markdown_to_html, parse_shortcode}; | |||||
#[test] | |||||
fn can_parse_simple_shortcode_one_arg() { | |||||
let (name, args) = parse_shortcode(r#"{{ youtube(id="w7Ft2ymGmfc") }}"#); | |||||
assert_eq!(name, "youtube"); | |||||
assert_eq!(args["id"], "w7Ft2ymGmfc"); | |||||
} | |||||
use front_matter::InsertAnchor; | |||||
use templates::GUTENBERG_TERA; | |||||
use rendering::context::Context; | |||||
#[test] | |||||
fn can_parse_simple_shortcode_several_arg() { | |||||
let (name, args) = parse_shortcode(r#"{{ youtube(id="w7Ft2ymGmfc", autoplay=true) }}"#); | |||||
assert_eq!(name, "youtube"); | |||||
assert_eq!(args["id"], "w7Ft2ymGmfc"); | |||||
assert_eq!(args["autoplay"], "true"); | |||||
} | |||||
#[test] | |||||
fn can_parse_block_shortcode_several_arg() { | |||||
let (name, args) = parse_shortcode(r#"{% youtube(id="w7Ft2ymGmfc", autoplay=true) %}"#); | |||||
assert_eq!(name, "youtube"); | |||||
assert_eq!(args["id"], "w7Ft2ymGmfc"); | |||||
assert_eq!(args["autoplay"], "true"); | |||||
} | |||||
use super::markdown_to_html; | |||||
#[test] | #[test] | ||||
fn can_do_markdown_to_html_simple() { | fn can_do_markdown_to_html_simple() { | ||||
let res = markdown_to_html("hello", &HashMap::new(), &Tera::default(), &Config::default()).unwrap(); | |||||
let tera_ctx = Tera::default(); | |||||
let permalinks_ctx = HashMap::new(); | |||||
let config_ctx = Config::default(); | |||||
let context = Context::new(&tera_ctx, &config_ctx, &permalinks_ctx, InsertAnchor::None); | |||||
let res = markdown_to_html("hello", &context).unwrap(); | |||||
assert_eq!(res, "<p>hello</p>\n"); | assert_eq!(res, "<p>hello</p>\n"); | ||||
} | } | ||||
#[test] | #[test] | ||||
fn doesnt_highlight_code_block_with_highlighting_off() { | fn doesnt_highlight_code_block_with_highlighting_off() { | ||||
let mut config = Config::default(); | |||||
config.highlight_code = Some(false); | |||||
let res = markdown_to_html("```\n$ gutenberg server\n```", &HashMap::new(), &Tera::default(), &config).unwrap(); | |||||
let tera_ctx = Tera::default(); | |||||
let permalinks_ctx = HashMap::new(); | |||||
let config_ctx = Config::default(); | |||||
let mut context = Context::new(&tera_ctx, &config_ctx, &permalinks_ctx, InsertAnchor::None); | |||||
context.highlight_code = false; | |||||
let res = markdown_to_html("```\n$ gutenberg server\n```", &context).unwrap(); | |||||
assert_eq!( | assert_eq!( | ||||
res, | res, | ||||
"<pre><code>$ gutenberg server\n</code></pre>\n" | "<pre><code>$ gutenberg server\n</code></pre>\n" | ||||
@@ -316,7 +308,11 @@ mod tests { | |||||
#[test] | #[test] | ||||
fn can_highlight_code_block_no_lang() { | fn can_highlight_code_block_no_lang() { | ||||
let res = markdown_to_html("```\n$ gutenberg server\n$ ping\n```", &HashMap::new(), &Tera::default(), &Config::default()).unwrap(); | |||||
let tera_ctx = Tera::default(); | |||||
let permalinks_ctx = HashMap::new(); | |||||
let config_ctx = Config::default(); | |||||
let context = Context::new(&tera_ctx, &config_ctx, &permalinks_ctx, InsertAnchor::None); | |||||
let res = markdown_to_html("```\n$ gutenberg server\n$ ping\n```", &context).unwrap(); | |||||
assert_eq!( | assert_eq!( | ||||
res, | res, | ||||
"<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>" | ||||
@@ -325,7 +321,11 @@ mod tests { | |||||
#[test] | #[test] | ||||
fn can_highlight_code_block_with_lang() { | fn can_highlight_code_block_with_lang() { | ||||
let res = markdown_to_html("```python\nlist.append(1)\n```", &HashMap::new(), &Tera::default(), &Config::default()).unwrap(); | |||||
let tera_ctx = Tera::default(); | |||||
let permalinks_ctx = HashMap::new(); | |||||
let config_ctx = Config::default(); | |||||
let context = Context::new(&tera_ctx, &config_ctx, &permalinks_ctx, InsertAnchor::None); | |||||
let res = markdown_to_html("```python\nlist.append(1)\n```", &context).unwrap(); | |||||
assert_eq!( | assert_eq!( | ||||
res, | res, | ||||
"<pre style=\"background-color:#2b303b\">\n<span style=\"background-color:#2b303b;color:#c0c5ce;\">list</span><span style=\"background-color:#2b303b;color:#c0c5ce;\">.</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;\">)</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:#c0c5ce;\">.</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;\">)</span><span style=\"background-color:#2b303b;color:#c0c5ce;\">\n</span></pre>" | ||||
@@ -334,7 +334,11 @@ mod tests { | |||||
#[test] | #[test] | ||||
fn can_higlight_code_block_with_unknown_lang() { | fn can_higlight_code_block_with_unknown_lang() { | ||||
let res = markdown_to_html("```yolo\nlist.append(1)\n```", &HashMap::new(), &Tera::default(), &Config::default()).unwrap(); | |||||
let tera_ctx = Tera::default(); | |||||
let permalinks_ctx = HashMap::new(); | |||||
let config_ctx = Config::default(); | |||||
let context = Context::new(&tera_ctx, &config_ctx, &permalinks_ctx, InsertAnchor::None); | |||||
let res = markdown_to_html("```yolo\nlist.append(1)\n```", &context).unwrap(); | |||||
// defaults to plain text | // defaults to plain text | ||||
assert_eq!( | assert_eq!( | ||||
res, | res, | ||||
@@ -344,17 +348,23 @@ mod tests { | |||||
#[test] | #[test] | ||||
fn can_render_shortcode() { | fn can_render_shortcode() { | ||||
let permalinks_ctx = HashMap::new(); | |||||
let config_ctx = Config::default(); | |||||
let context = Context::new(&GUTENBERG_TERA, &config_ctx, &permalinks_ctx, InsertAnchor::None); | |||||
let res = markdown_to_html(r#" | let res = markdown_to_html(r#" | ||||
Hello | Hello | ||||
{{ youtube(id="ub36ffWAqgQ") }} | {{ youtube(id="ub36ffWAqgQ") }} | ||||
"#, &HashMap::new(), &GUTENBERG_TERA, &Config::default()).unwrap(); | |||||
"#, &context).unwrap(); | |||||
assert!(res.contains("<p>Hello</p>\n<div >")); | assert!(res.contains("<p>Hello</p>\n<div >")); | ||||
assert!(res.contains(r#"<iframe src="https://www.youtube.com/embed/ub36ffWAqgQ""#)); | assert!(res.contains(r#"<iframe src="https://www.youtube.com/embed/ub36ffWAqgQ""#)); | ||||
} | } | ||||
#[test] | #[test] | ||||
fn can_render_several_shortcode_in_row() { | fn can_render_several_shortcode_in_row() { | ||||
let permalinks_ctx = HashMap::new(); | |||||
let config_ctx = Config::default(); | |||||
let context = Context::new(&GUTENBERG_TERA, &config_ctx, &permalinks_ctx, InsertAnchor::None); | |||||
let res = markdown_to_html(r#" | let res = markdown_to_html(r#" | ||||
Hello | Hello | ||||
@@ -366,7 +376,7 @@ Hello | |||||
{{ gist(url="https://gist.github.com/Keats/32d26f699dcc13ebd41b") }} | {{ gist(url="https://gist.github.com/Keats/32d26f699dcc13ebd41b") }} | ||||
"#, &HashMap::new(), &GUTENBERG_TERA, &Config::default()).unwrap(); | |||||
"#, &context).unwrap(); | |||||
assert!(res.contains("<p>Hello</p>\n<div >")); | assert!(res.contains("<p>Hello</p>\n<div >")); | ||||
assert!(res.contains(r#"<iframe src="https://www.youtube.com/embed/ub36ffWAqgQ""#)); | assert!(res.contains(r#"<iframe src="https://www.youtube.com/embed/ub36ffWAqgQ""#)); | ||||
assert!(res.contains(r#"<iframe src="https://www.youtube.com/embed/ub36ffWAqgQ?autoplay=1""#)); | assert!(res.contains(r#"<iframe src="https://www.youtube.com/embed/ub36ffWAqgQ?autoplay=1""#)); | ||||
@@ -375,7 +385,10 @@ Hello | |||||
#[test] | #[test] | ||||
fn doesnt_render_shortcode_in_code_block() { | fn doesnt_render_shortcode_in_code_block() { | ||||
let res = markdown_to_html(r#"```{{ youtube(id="w7Ft2ymGmfc") }}```"#, &HashMap::new(), &GUTENBERG_TERA, &Config::default()).unwrap(); | |||||
let permalinks_ctx = HashMap::new(); | |||||
let config_ctx = Config::default(); | |||||
let context = Context::new(&GUTENBERG_TERA, &config_ctx, &permalinks_ctx, InsertAnchor::None); | |||||
let res = markdown_to_html(r#"```{{ youtube(id="w7Ft2ymGmfc") }}```"#, &context).unwrap(); | |||||
assert_eq!(res, "<p><code>{{ youtube(id="w7Ft2ymGmfc") }}</code></p>\n"); | assert_eq!(res, "<p><code>{{ youtube(id="w7Ft2ymGmfc") }}</code></p>\n"); | ||||
} | } | ||||
@@ -384,18 +397,26 @@ Hello | |||||
let mut tera = Tera::default(); | let mut tera = Tera::default(); | ||||
tera.extend(&GUTENBERG_TERA).unwrap(); | tera.extend(&GUTENBERG_TERA).unwrap(); | ||||
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 config_ctx = Config::default(); | |||||
let context = Context::new(&tera, &config_ctx, &permalinks_ctx, InsertAnchor::None); | |||||
let res = markdown_to_html(r#" | let res = markdown_to_html(r#" | ||||
Hello | Hello | ||||
{% quote(author="Keats") %} | {% quote(author="Keats") %} | ||||
A quote | A quote | ||||
{% end %} | {% end %} | ||||
"#, &HashMap::new(), &tera, &Config::default()).unwrap(); | |||||
"#, &context).unwrap(); | |||||
assert_eq!(res, "<p>Hello\n</p><blockquote>A quote - Keats</blockquote>"); | assert_eq!(res, "<p>Hello\n</p><blockquote>A quote - Keats</blockquote>"); | ||||
} | } | ||||
#[test] | #[test] | ||||
fn errors_rendering_unknown_shortcode() { | fn errors_rendering_unknown_shortcode() { | ||||
let res = markdown_to_html("{{ hello(flash=true) }}", &HashMap::new(), &Tera::default(), &Config::default()); | |||||
let tera_ctx = Tera::default(); | |||||
let permalinks_ctx = HashMap::new(); | |||||
let config_ctx = Config::default(); | |||||
let context = Context::new(&tera_ctx, &config_ctx, &permalinks_ctx, InsertAnchor::None); | |||||
let res = markdown_to_html("{{ hello(flash=true) }}", &context); | |||||
assert!(res.is_err()); | assert!(res.is_err()); | ||||
} | } | ||||
@@ -403,11 +424,12 @@ A quote | |||||
fn can_make_valid_relative_link() { | fn can_make_valid_relative_link() { | ||||
let mut permalinks = HashMap::new(); | let mut permalinks = HashMap::new(); | ||||
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 config_ctx = Config::default(); | |||||
let context = Context::new(&tera_ctx, &config_ctx, &permalinks, InsertAnchor::None); | |||||
let res = markdown_to_html( | let res = markdown_to_html( | ||||
r#"[rel link](./pages/about.md), [abs link](https://vincent.is/about)"#, | r#"[rel link](./pages/about.md), [abs link](https://vincent.is/about)"#, | ||||
&permalinks, | |||||
&GUTENBERG_TERA, | |||||
&Config::default() | |||||
&context | |||||
).unwrap(); | ).unwrap(); | ||||
assert!( | assert!( | ||||
@@ -419,12 +441,10 @@ A quote | |||||
fn can_make_relative_links_with_anchors() { | fn can_make_relative_links_with_anchors() { | ||||
let mut permalinks = HashMap::new(); | let mut permalinks = HashMap::new(); | ||||
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 res = markdown_to_html( | |||||
r#"[rel link](./pages/about.md#cv)"#, | |||||
&permalinks, | |||||
&GUTENBERG_TERA, | |||||
&Config::default() | |||||
).unwrap(); | |||||
let tera_ctx = Tera::default(); | |||||
let config_ctx = Config::default(); | |||||
let context = Context::new(&tera_ctx, &config_ctx, &permalinks, InsertAnchor::None); | |||||
let res = markdown_to_html(r#"[rel link](./pages/about.md#cv)"#, &context).unwrap(); | |||||
assert!( | assert!( | ||||
res.contains(r#"<p><a href="https://vincent.is/about#cv">rel link</a></p>"#) | res.contains(r#"<p><a href="https://vincent.is/about#cv">rel link</a></p>"#) | ||||
@@ -433,39 +453,65 @@ A quote | |||||
#[test] | #[test] | ||||
fn errors_relative_link_inexistant() { | fn errors_relative_link_inexistant() { | ||||
let res = markdown_to_html("[rel link](./pages/about.md)", &HashMap::new(), &Tera::default(), &Config::default()); | |||||
let tera_ctx = Tera::default(); | |||||
let permalinks_ctx = HashMap::new(); | |||||
let config_ctx = Config::default(); | |||||
let context = Context::new(&tera_ctx, &config_ctx, &permalinks_ctx, InsertAnchor::None); | |||||
let res = markdown_to_html("[rel link](./pages/about.md)", &context); | |||||
assert!(res.is_err()); | assert!(res.is_err()); | ||||
} | } | ||||
#[test] | #[test] | ||||
fn can_add_id_to_headers() { | fn can_add_id_to_headers() { | ||||
let res = markdown_to_html(r#"# Hello"#, &HashMap::new(), &GUTENBERG_TERA, &Config::default()).unwrap(); | |||||
let tera_ctx = Tera::default(); | |||||
let permalinks_ctx = HashMap::new(); | |||||
let config_ctx = Config::default(); | |||||
let context = Context::new(&tera_ctx, &config_ctx, &permalinks_ctx, InsertAnchor::None); | |||||
let res = markdown_to_html(r#"# Hello"#, &context).unwrap(); | |||||
assert_eq!(res, "<h1 id=\"hello\">Hello</h1>\n"); | assert_eq!(res, "<h1 id=\"hello\">Hello</h1>\n"); | ||||
} | } | ||||
#[test] | #[test] | ||||
fn can_add_id_to_headers_same_slug() { | fn can_add_id_to_headers_same_slug() { | ||||
let res = markdown_to_html("# Hello\n# Hello", &HashMap::new(), &GUTENBERG_TERA, &Config::default()).unwrap(); | |||||
let tera_ctx = Tera::default(); | |||||
let permalinks_ctx = HashMap::new(); | |||||
let config_ctx = Config::default(); | |||||
let context = Context::new(&tera_ctx, &config_ctx, &permalinks_ctx, InsertAnchor::None); | |||||
let res = markdown_to_html("# Hello\n# Hello", &context).unwrap(); | |||||
assert_eq!(res, "<h1 id=\"hello\">Hello</h1>\n<h1 id=\"hello-1\">Hello</h1>\n"); | assert_eq!(res, "<h1 id=\"hello\">Hello</h1>\n<h1 id=\"hello-1\">Hello</h1>\n"); | ||||
} | } | ||||
#[test] | #[test] | ||||
fn can_insert_anchor() { | |||||
let mut config = Config::default(); | |||||
config.insert_anchor_links = Some(true); | |||||
let res = markdown_to_html("# Hello", &HashMap::new(), &GUTENBERG_TERA, &config).unwrap(); | |||||
fn can_insert_anchor_left() { | |||||
let permalinks_ctx = HashMap::new(); | |||||
let config_ctx = Config::default(); | |||||
let context = Context::new(&GUTENBERG_TERA, &config_ctx, &permalinks_ctx, InsertAnchor::Left); | |||||
let res = markdown_to_html("# Hello", &context).unwrap(); | |||||
assert_eq!( | assert_eq!( | ||||
res, | res, | ||||
"<h1 id=\"hello\"><a class=\"anchor\" href=\"#hello\" aria-label=\"Anchor link for: hello\">đź”—</a>\nHello</h1>\n" | "<h1 id=\"hello\"><a class=\"anchor\" href=\"#hello\" aria-label=\"Anchor link for: hello\">đź”—</a>\nHello</h1>\n" | ||||
); | ); | ||||
} | } | ||||
#[test] | |||||
fn can_insert_anchor_right() { | |||||
let permalinks_ctx = HashMap::new(); | |||||
let config_ctx = Config::default(); | |||||
let context = Context::new(&GUTENBERG_TERA, &config_ctx, &permalinks_ctx, InsertAnchor::Right); | |||||
let res = markdown_to_html("# Hello", &context).unwrap(); | |||||
assert_eq!( | |||||
res, | |||||
"<h1 id=\"hello\">Hello<a class=\"anchor\" href=\"#hello\" aria-label=\"Anchor link for: hello\">đź”—</a>\n</h1>\n" | |||||
); | |||||
} | |||||
// See https://github.com/Keats/gutenberg/issues/42 | // See https://github.com/Keats/gutenberg/issues/42 | ||||
#[test] | #[test] | ||||
fn can_insert_anchor_with_exclamation_mark() { | fn can_insert_anchor_with_exclamation_mark() { | ||||
let mut config = Config::default(); | |||||
config.insert_anchor_links = Some(true); | |||||
let res = markdown_to_html("# Hello!", &HashMap::new(), &GUTENBERG_TERA, &config).unwrap(); | |||||
let permalinks_ctx = HashMap::new(); | |||||
let config_ctx = Config::default(); | |||||
let context = Context::new(&GUTENBERG_TERA, &config_ctx, &permalinks_ctx, InsertAnchor::Left); | |||||
let res = markdown_to_html("# Hello!", &context).unwrap(); | |||||
assert_eq!( | assert_eq!( | ||||
res, | res, | ||||
"<h1 id=\"hello\"><a class=\"anchor\" href=\"#hello\" aria-label=\"Anchor link for: hello\">đź”—</a>\nHello!</h1>\n" | "<h1 id=\"hello\"><a class=\"anchor\" href=\"#hello\" aria-label=\"Anchor link for: hello\">đź”—</a>\nHello!</h1>\n" | ||||
@@ -475,9 +521,10 @@ A quote | |||||
// See https://github.com/Keats/gutenberg/issues/53 | // See https://github.com/Keats/gutenberg/issues/53 | ||||
#[test] | #[test] | ||||
fn can_insert_anchor_with_link() { | fn can_insert_anchor_with_link() { | ||||
let mut config = Config::default(); | |||||
config.insert_anchor_links = Some(true); | |||||
let res = markdown_to_html("## [](#xresources)Xresources", &HashMap::new(), &GUTENBERG_TERA, &config).unwrap(); | |||||
let permalinks_ctx = HashMap::new(); | |||||
let config_ctx = Config::default(); | |||||
let context = Context::new(&GUTENBERG_TERA, &config_ctx, &permalinks_ctx, InsertAnchor::Left); | |||||
let res = markdown_to_html("## [](#xresources)Xresources", &context).unwrap(); | |||||
assert_eq!( | assert_eq!( | ||||
res, | res, | ||||
"<h2 id=\"xresources\"><a class=\"anchor\" href=\"#xresources\" aria-label=\"Anchor link for: xresources\">đź”—</a>\nXresources</h2>\n" | "<h2 id=\"xresources\"><a class=\"anchor\" href=\"#xresources\" aria-label=\"Anchor link for: xresources\">đź”—</a>\nXresources</h2>\n" | ||||
@@ -486,9 +533,10 @@ A quote | |||||
#[test] | #[test] | ||||
fn can_insert_anchor_with_other_special_chars() { | fn can_insert_anchor_with_other_special_chars() { | ||||
let mut config = Config::default(); | |||||
config.insert_anchor_links = Some(true); | |||||
let res = markdown_to_html("# Hello*_()", &HashMap::new(), &GUTENBERG_TERA, &config).unwrap(); | |||||
let permalinks_ctx = HashMap::new(); | |||||
let config_ctx = Config::default(); | |||||
let context = Context::new(&GUTENBERG_TERA, &config_ctx, &permalinks_ctx, InsertAnchor::Left); | |||||
let res = markdown_to_html("# Hello*_()", &context).unwrap(); | |||||
assert_eq!( | assert_eq!( | ||||
res, | res, | ||||
"<h1 id=\"hello\"><a class=\"anchor\" href=\"#hello\" aria-label=\"Anchor link for: hello\">đź”—</a>\nHello*_()</h1>\n" | "<h1 id=\"hello\"><a class=\"anchor\" href=\"#hello\" aria-label=\"Anchor link for: hello\">đź”—</a>\nHello*_()</h1>\n" | ||||
@@ -1,3 +1,4 @@ | |||||
pub mod highlighting; | pub mod highlighting; | ||||
pub mod markdown; | pub mod markdown; | ||||
pub mod short_code; | pub mod short_code; | ||||
pub mod context; |
@@ -69,3 +69,33 @@ pub fn render_simple_shortcode(tera: &Tera, name: &str, args: &HashMap<String, S | |||||
tera.render(&tpl_name, &context).chain_err(|| format!("Failed to render {} shortcode", name)) | tera.render(&tpl_name, &context).chain_err(|| format!("Failed to render {} shortcode", name)) | ||||
} | } | ||||
#[cfg(test)] | |||||
mod tests { | |||||
use super::{parse_shortcode}; | |||||
#[test] | |||||
fn can_parse_simple_shortcode_one_arg() { | |||||
let (name, args) = parse_shortcode(r#"{{ youtube(id="w7Ft2ymGmfc") }}"#); | |||||
assert_eq!(name, "youtube"); | |||||
assert_eq!(args["id"], "w7Ft2ymGmfc"); | |||||
} | |||||
#[test] | |||||
fn can_parse_simple_shortcode_several_arg() { | |||||
let (name, args) = parse_shortcode(r#"{{ youtube(id="w7Ft2ymGmfc", autoplay=true) }}"#); | |||||
assert_eq!(name, "youtube"); | |||||
assert_eq!(args["id"], "w7Ft2ymGmfc"); | |||||
assert_eq!(args["autoplay"], "true"); | |||||
} | |||||
#[test] | |||||
fn can_parse_block_shortcode_several_arg() { | |||||
let (name, args) = parse_shortcode(r#"{% youtube(id="w7Ft2ymGmfc", autoplay=true) %}"#); | |||||
assert_eq!(name, "youtube"); | |||||
assert_eq!(args["id"], "w7Ft2ymGmfc"); | |||||
assert_eq!(args["autoplay"], "true"); | |||||
} | |||||
} |
@@ -11,7 +11,7 @@ use config::{Config, get_config}; | |||||
use fs::{create_file, create_directory, ensure_directory_exists}; | use fs::{create_file, create_directory, ensure_directory_exists}; | ||||
use content::{Page, Section, Paginator, SortBy, Taxonomy, populate_previous_and_next_pages, sort_pages}; | use content::{Page, Section, Paginator, SortBy, Taxonomy, populate_previous_and_next_pages, sort_pages}; | ||||
use templates::{GUTENBERG_TERA, global_fns, render_redirect_template}; | use templates::{GUTENBERG_TERA, global_fns, render_redirect_template}; | ||||
use front_matter::InsertAnchor; | |||||
#[derive(Debug)] | #[derive(Debug)] | ||||
@@ -112,9 +112,16 @@ impl Site { | |||||
self.sections.insert(index_path, index_section); | self.sections.insert(index_path, index_section); | ||||
} | } | ||||
// Silly thing needed to make the borrow checker happy | |||||
let mut pages_insert_anchors = HashMap::new(); | |||||
for page in self.pages.values() { | |||||
pages_insert_anchors.insert(page.file.path.clone(), self.find_parent_section_insert_anchor(&page.file.parent.clone())); | |||||
} | |||||
// TODO: make that parallel | // TODO: make that parallel | ||||
for page in self.pages.values_mut() { | for page in self.pages.values_mut() { | ||||
page.render_markdown(&self.permalinks, &self.tera, &self.config)?; | |||||
let insert_anchor = pages_insert_anchors[&page.file.path]; | |||||
page.render_markdown(&self.permalinks, &self.tera, &self.config, insert_anchor)?; | |||||
} | } | ||||
// TODO: make that parallel | // TODO: make that parallel | ||||
for section in self.sections.values_mut() { | for section in self.sections.values_mut() { | ||||
@@ -145,8 +152,9 @@ impl Site { | |||||
let prev = self.pages.insert(page.file.path.clone(), page); | let prev = self.pages.insert(page.file.path.clone(), page); | ||||
if render { | if render { | ||||
let insert_anchor = self.find_parent_section_insert_anchor(&self.pages[path].file.parent); | |||||
let mut page = self.pages.get_mut(path).unwrap(); | let mut page = self.pages.get_mut(path).unwrap(); | ||||
page.render_markdown(&self.permalinks, &self.tera, &self.config)?; | |||||
page.render_markdown(&self.permalinks, &self.tera, &self.config, insert_anchor)?; | |||||
} | } | ||||
Ok(prev) | Ok(prev) | ||||
@@ -169,6 +177,15 @@ impl Site { | |||||
Ok(prev) | Ok(prev) | ||||
} | } | ||||
/// Finds the insert_anchor for the parent section of the directory at `path`. | |||||
/// Defaults to `AnchorInsert::None` if no parent section found | |||||
pub fn find_parent_section_insert_anchor(&self, parent_path: &PathBuf) -> InsertAnchor { | |||||
match self.sections.get(&parent_path.join("_index.md")) { | |||||
Some(ref s) => s.meta.insert_anchor.unwrap(), | |||||
None => InsertAnchor::None | |||||
} | |||||
} | |||||
/// Find out the direct subsections of each subsection if there are some | /// Find out the direct subsections of each subsection if there are some | ||||
/// as well as the pages for each section | /// as well as the pages for each section | ||||
pub fn populate_sections(&mut self) { | pub fn populate_sections(&mut self) { | ||||
@@ -299,6 +316,7 @@ impl Site { | |||||
create_directory(¤t_path)?; | create_directory(¤t_path)?; | ||||
} | } | ||||
} | } | ||||
println!("Rendering page"); | |||||
// Make sure the folder exists | // Make sure the folder exists | ||||
create_directory(¤t_path)?; | create_directory(¤t_path)?; | ||||
@@ -2,4 +2,5 @@ | |||||
title = "Posts" | title = "Posts" | ||||
paginate_by = 2 | paginate_by = 2 | ||||
template = "section_paginated.html" | template = "section_paginated.html" | ||||
insert_anchor = "left" | |||||
+++ | +++ |
@@ -8,3 +8,5 @@ date = "2017-01-01" | |||||
A simple page with a slug defined | A simple page with a slug defined | ||||
# Title | # Title | ||||
Hey |
@@ -277,7 +277,6 @@ fn can_build_site_and_insert_anchor_links() { | |||||
let mut path = env::current_dir().unwrap().to_path_buf(); | let mut path = env::current_dir().unwrap().to_path_buf(); | ||||
path.push("test_site"); | path.push("test_site"); | ||||
let mut site = Site::new(&path, "config.toml").unwrap(); | let mut site = Site::new(&path, "config.toml").unwrap(); | ||||
site.config.insert_anchor_links = Some(true); | |||||
site.load().unwrap(); | site.load().unwrap(); | ||||
let tmp_dir = TempDir::new("example").expect("create temp dir"); | let tmp_dir = TempDir::new("example").expect("create temp dir"); | ||||
let public = &tmp_dir.path().join("public"); | let public = &tmp_dir.path().join("public"); | ||||