Browse Source

Move insert_anchor to section and allow left/right

index-subcmd
Vincent Prouillet 7 years ago
parent
commit
dbe4a1d517
16 changed files with 259 additions and 106 deletions
  1. +1
    -0
      CHANGELOG.md
  2. +5
    -3
      Cargo.lock
  3. +1
    -1
      src/bin/rebuild.rs
  4. +14
    -15
      src/content/page.rs
  5. +7
    -5
      src/content/section.rs
  6. +1
    -1
      src/front_matter/mod.rs
  7. +17
    -0
      src/front_matter/section.rs
  8. +1
    -1
      src/lib.rs
  9. +33
    -0
      src/rendering/context.rs
  10. +124
    -76
      src/rendering/markdown.rs
  11. +1
    -0
      src/rendering/mod.rs
  12. +30
    -0
      src/rendering/short_code.rs
  13. +21
    -3
      src/site.rs
  14. +1
    -0
      test_site/content/posts/_index.md
  15. +2
    -0
      test_site/content/posts/fixed-slug.md
  16. +0
    -1
      tests/site.rs

+ 1
- 0
CHANGELOG.md View File

@@ -6,6 +6,7 @@
- Change the single item template context for categories/tags
- Add a `get_url` global Tera function
- 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)



+ 5
- 3
Cargo.lock View File

@@ -56,7 +56,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"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)",
"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)",
"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)",
@@ -187,10 +187,12 @@ dependencies = [

[[package]]
name = "cpp_demangle"
version = "0.2.1"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
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)",
"glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[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 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 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 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"


+ 1
- 1
src/bin/rebuild.rs View File

@@ -7,7 +7,7 @@ use gutenberg::errors::Result;
/// 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> {
for section in site.sections.values() {
if section.is_child_page(page) {
if section.is_child_page(&page.file.path) {
return Some(section)
}
}


+ 14
- 15
src/content/page.rs View File

@@ -4,14 +4,15 @@ use std::path::{Path, PathBuf};
use std::result::Result as StdResult;


use tera::{Tera, Context};
use tera::{Tera, Context as TeraContext};
use serde::ser::{SerializeStruct, self};
use slug::slugify;

use errors::{Result, ResultExt};
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::context::Context;
use fs::{read_file};
use content::utils::{find_related_assets, get_reading_analytics};
use content::file_info::FileInfo;
@@ -112,13 +113,13 @@ impl Page {

/// We need access to all pages url to render links relative to content
/// 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 -->") {
self.summary = Some({
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()
};

let mut context = Context::new();
let mut context = TeraContext::new();
context.add("config", config);
context.add("page", self);
context.add("current_url", &self.permalink);
@@ -195,6 +196,7 @@ mod tests {

use config::Config;
use super::Page;
use front_matter::InsertAnchor;


#[test]
@@ -209,7 +211,7 @@ Hello world"#;
let res = Page::parse(Path::new("post.md"), content, &Config::default());
assert!(res.is_ok());
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.slug.unwrap(), "hello-world".to_string());
@@ -228,8 +230,7 @@ Hello world"#;
conf.base_url = "http://hello.com/".to_string();
let res = Page::parse(Path::new("content/posts/intro/start.md"), content, &conf);
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.permalink, "http://hello.com/posts/intro/hello-world");
}
@@ -244,8 +245,7 @@ Hello world"#;
let config = Config::default();
let res = Page::parse(Path::new("start.md"), content, &config);
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.permalink, config.make_permalink("hello-world"));
}
@@ -268,8 +268,7 @@ Hello world"#;
let config = Config::default();
let res = Page::parse(Path::new(" file with space.md"), "+++\n+++", &config);
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.permalink, config.make_permalink(&page.slug));
}
@@ -285,7 +284,7 @@ Hello world
let res = Page::parse(Path::new("hello.md"), &content, &config);
assert!(res.is_ok());
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()));
}



+ 7
- 5
src/content/section.rs View File

@@ -2,7 +2,7 @@ use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::result::Result as StdResult;

use tera::{Tera, Context};
use tera::{Tera, Context as TeraContext};
use serde::ser::{SerializeStruct, self};

use config::Config;
@@ -10,6 +10,7 @@ use front_matter::{SectionFrontMatter, split_section_content};
use errors::{Result, ResultExt};
use fs::{read_file};
use rendering::markdown::markdown_to_html;
use rendering::context::Context;
use content::Page;
use content::file_info::FileInfo;

@@ -85,7 +86,8 @@ impl Section {
/// We need access to all pages url to render links relative to content
/// 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)?;
let context = Context::new(tera, config, permalinks, self.meta.insert_anchor.unwrap());
self.content = markdown_to_html(&self.raw_content, &context)?;
Ok(())
}

@@ -93,7 +95,7 @@ impl Section {
pub fn render_html(&self, sections: HashMap<String, Section>, tera: &Tera, config: &Config) -> Result<String> {
let tpl_name = self.get_template_name();

let mut context = Context::new();
let mut context = TeraContext::new();
context.add("config", config);
context.add("section", self);
context.add("current_url", &self.permalink);
@@ -120,8 +122,8 @@ impl 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)
}
}



+ 1
- 1
src/front_matter/mod.rs View File

@@ -8,7 +8,7 @@ mod page;
mod section;

pub use self::page::PageFrontMatter;
pub use self::section::{SectionFrontMatter};
pub use self::section::{SectionFrontMatter, InsertAnchor};

lazy_static! {
static ref PAGE_RE: Regex = Regex::new(r"^[[:space:]]*\+\+\+\r?\n((?s).*?(?-s))\+\+\+\r?\n?((?s).*(?-s))$").unwrap();


+ 17
- 0
src/front_matter/section.rs View File

@@ -9,6 +9,14 @@ use content::SortBy;
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
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
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`.
#[serde(skip_serializing)]
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`.
/// 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
@@ -56,6 +68,10 @@ impl SectionFrontMatter {
f.sort_by = Some(SortBy::None);
}

if f.insert_anchor.is_none() {
f.insert_anchor = Some(InsertAnchor::None);
}

Ok(f)
}

@@ -87,6 +103,7 @@ impl Default for SectionFrontMatter {
paginate_by: None,
paginate_path: Some(DEFAULT_PAGINATE_PATH.to_string()),
render: Some(true),
insert_anchor: Some(InsertAnchor::None),
extra: None,
}
}


+ 1
- 1
src/lib.rs View File

@@ -31,6 +31,6 @@ mod templates;

pub use site::{Site};
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 fs::{create_file};

+ 33
- 0
src/rendering/context.rs View 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
}
}

+ 124
- 76
src/rendering/markdown.rs View File

@@ -1,5 +1,4 @@
use std::borrow::Cow::Owned;
use std::collections::HashMap;

use pulldown_cmark as cmark;
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::parsing::SyntaxSet;
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 site::resolve_internal_link;
use front_matter::InsertAnchor;
use rendering::context::Context;
use rendering::highlighting::THEME_SET;
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
// 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
// to be highlighted. It could potentially have false positive if the content
// 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("```")
} else {
false
};
let highlight_theme = config.highlight_theme.clone().unwrap();
// Set while parsing
let mut error = 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) {
let (name, args) = parse_shortcode(&text);
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))),
Err(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 text.trim() == "{% end %}" {
added_shortcode = true;
match shortcode.render(tera) {
match shortcode.render(context.tera) {
Ok(s) => return Event::Html(Owned(format!("</p>{}", s))),
Err(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);
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 {
String::new()
};
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
@@ -170,7 +175,7 @@ pub fn markdown_to_html(content: &str, permalinks: &HashMap<String, String>, ter
if !should_highlight {
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
.split(' ')
.next()
@@ -195,7 +200,7 @@ pub fn markdown_to_html(content: &str, permalinks: &HashMap<String, String>, ter
return Event::Html(Owned("".to_owned()));
}
if link.starts_with("./") {
match resolve_internal_link(link, permalinks) {
match resolve_internal_link(link, context.permalinks) {
Ok(url) => {
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 {
use std::collections::HashMap;

use templates::GUTENBERG_TERA;
use tera::Tera;

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]
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");
}

#[test]
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!(
res,
"<pre><code>$ gutenberg server\n</code></pre>\n"
@@ -316,7 +308,11 @@ mod tests {

#[test]
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!(
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>"
@@ -325,7 +321,11 @@ mod tests {

#[test]
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!(
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>"
@@ -334,7 +334,11 @@ mod tests {

#[test]
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
assert_eq!(
res,
@@ -344,17 +348,23 @@ mod tests {

#[test]
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#"
Hello

{{ youtube(id="ub36ffWAqgQ") }}
"#, &HashMap::new(), &GUTENBERG_TERA, &Config::default()).unwrap();
"#, &context).unwrap();
assert!(res.contains("<p>Hello</p>\n<div >"));
assert!(res.contains(r#"<iframe src="https://www.youtube.com/embed/ub36ffWAqgQ""#));
}

#[test]
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#"
Hello

@@ -366,7 +376,7 @@ Hello

{{ 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(r#"<iframe src="https://www.youtube.com/embed/ub36ffWAqgQ""#));
assert!(res.contains(r#"<iframe src="https://www.youtube.com/embed/ub36ffWAqgQ?autoplay=1""#));
@@ -375,7 +385,10 @@ Hello

#[test]
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=&quot;w7Ft2ymGmfc&quot;) }}</code></p>\n");
}

@@ -384,18 +397,26 @@ Hello
let mut tera = Tera::default();
tera.extend(&GUTENBERG_TERA).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#"
Hello
{% quote(author="Keats") %}
A quote
{% end %}
"#, &HashMap::new(), &tera, &Config::default()).unwrap();
"#, &context).unwrap();
assert_eq!(res, "<p>Hello\n</p><blockquote>A quote - Keats</blockquote>");
}

#[test]
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());
}

@@ -403,11 +424,12 @@ A quote
fn can_make_valid_relative_link() {
let mut permalinks = HashMap::new();
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(
r#"[rel link](./pages/about.md), [abs link](https://vincent.is/about)"#,
&permalinks,
&GUTENBERG_TERA,
&Config::default()
&context
).unwrap();

assert!(
@@ -419,12 +441,10 @@ A quote
fn can_make_relative_links_with_anchors() {
let mut permalinks = HashMap::new();
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!(
res.contains(r#"<p><a href="https://vincent.is/about#cv">rel link</a></p>"#)
@@ -433,39 +453,65 @@ A quote

#[test]
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());
}

#[test]
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");
}

#[test]
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");
}

#[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!(
res,
"<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
#[test]
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!(
res,
"<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
#[test]
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!(
res,
"<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]
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!(
res,
"<h1 id=\"hello\"><a class=\"anchor\" href=\"#hello\" aria-label=\"Anchor link for: hello\">đź”—</a>\nHello*_()</h1>\n"


+ 1
- 0
src/rendering/mod.rs View File

@@ -1,3 +1,4 @@
pub mod highlighting;
pub mod markdown;
pub mod short_code;
pub mod context;

+ 30
- 0
src/rendering/short_code.rs View File

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


#[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");
}

}

+ 21
- 3
src/site.rs View File

@@ -11,7 +11,7 @@ use config::{Config, get_config};
use fs::{create_file, create_directory, ensure_directory_exists};
use content::{Page, Section, Paginator, SortBy, Taxonomy, populate_previous_and_next_pages, sort_pages};
use templates::{GUTENBERG_TERA, global_fns, render_redirect_template};
use front_matter::InsertAnchor;


#[derive(Debug)]
@@ -112,9 +112,16 @@ impl Site {
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
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
for section in self.sections.values_mut() {
@@ -145,8 +152,9 @@ impl Site {
let prev = self.pages.insert(page.file.path.clone(), page);

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();
page.render_markdown(&self.permalinks, &self.tera, &self.config)?;
page.render_markdown(&self.permalinks, &self.tera, &self.config, insert_anchor)?;
}

Ok(prev)
@@ -169,6 +177,15 @@ impl Site {
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
/// as well as the pages for each section
pub fn populate_sections(&mut self) {
@@ -299,6 +316,7 @@ impl Site {
create_directory(&current_path)?;
}
}
println!("Rendering page");

// Make sure the folder exists
create_directory(&current_path)?;


+ 1
- 0
test_site/content/posts/_index.md View File

@@ -2,4 +2,5 @@
title = "Posts"
paginate_by = 2
template = "section_paginated.html"
insert_anchor = "left"
+++

+ 2
- 0
test_site/content/posts/fixed-slug.md View File

@@ -8,3 +8,5 @@ date = "2017-01-01"
A simple page with a slug defined

# Title

Hey

+ 0
- 1
tests/site.rs View File

@@ -277,7 +277,6 @@ fn can_build_site_and_insert_anchor_links() {
let mut path = env::current_dir().unwrap().to_path_buf();
path.push("test_site");
let mut site = Site::new(&path, "config.toml").unwrap();
site.config.insert_anchor_links = Some(true);
site.load().unwrap();
let tmp_dir = TempDir::new("example").expect("create temp dir");
let public = &tmp_dir.path().join("public");


Loading…
Cancel
Save