|
|
@@ -3,8 +3,10 @@ use std::collections::HashMap; |
|
|
|
use std::default::Default; |
|
|
|
use std::fs::File; |
|
|
|
use std::io::prelude::*; |
|
|
|
use std::path::Path; |
|
|
|
|
|
|
|
// use pulldown_cmark as cmark; |
|
|
|
|
|
|
|
use pulldown_cmark as cmark; |
|
|
|
use regex::Regex; |
|
|
|
use tera::{Tera, Value, Context}; |
|
|
|
|
|
|
@@ -22,13 +24,22 @@ lazy_static! { |
|
|
|
pub struct Page { |
|
|
|
// .md filepath, excluding the content/ bit |
|
|
|
pub filepath: String, |
|
|
|
// the name of the .md file |
|
|
|
pub filename: String, |
|
|
|
// the directories above our .md file are called sections |
|
|
|
// for example a file at content/kb/solutions/blabla.md will have 2 sections: |
|
|
|
// `kb` and `solutions` |
|
|
|
pub sections: Vec<String>, |
|
|
|
|
|
|
|
// <title> of the page |
|
|
|
pub title: String, |
|
|
|
// The page slug |
|
|
|
pub slug: String, |
|
|
|
// the actual content of the page |
|
|
|
pub raw_content: String, |
|
|
|
// the HTML rendered of the page |
|
|
|
pub content: String, |
|
|
|
|
|
|
|
// tags, not to be confused with categories |
|
|
|
pub tags: Vec<String>, |
|
|
|
// whether this page should be public or not |
|
|
@@ -43,7 +54,7 @@ pub struct Page { |
|
|
|
pub category: Option<String>, |
|
|
|
// optional date if we want to order pages (ie blog post) |
|
|
|
pub date: Option<String>, |
|
|
|
// optional layout, if we want to specify which html to render for that page |
|
|
|
// optional layout, if we want to specify which tpl to render for that page |
|
|
|
pub layout: Option<String>, |
|
|
|
// description that appears when linked, e.g. on twitter |
|
|
|
pub description: Option<String>, |
|
|
@@ -54,9 +65,12 @@ impl Default for Page { |
|
|
|
fn default() -> Page { |
|
|
|
Page { |
|
|
|
filepath: "".to_string(), |
|
|
|
filename: "".to_string(), |
|
|
|
sections: vec![], |
|
|
|
|
|
|
|
title: "".to_string(), |
|
|
|
slug: "".to_string(), |
|
|
|
raw_content: "".to_string(), |
|
|
|
content: "".to_string(), |
|
|
|
tags: vec![], |
|
|
|
is_draft: false, |
|
|
@@ -90,37 +104,56 @@ impl Page { |
|
|
|
// 2. create our page, parse front matter and assign all of that |
|
|
|
let mut page = Page::default(); |
|
|
|
page.filepath = filepath.to_string(); |
|
|
|
page.content = content.to_string(); |
|
|
|
let path = Path::new(filepath); |
|
|
|
page.filename = path.file_stem().expect("Couldn't get file stem").to_string_lossy().to_string(); |
|
|
|
|
|
|
|
// find out if we have sections |
|
|
|
for section in path.parent().unwrap().components() { |
|
|
|
page.sections.push(section.as_ref().to_string_lossy().to_string()); |
|
|
|
} |
|
|
|
|
|
|
|
page.raw_content = content.to_string(); |
|
|
|
parse_front_matter(front_matter, &mut page) |
|
|
|
.chain_err(|| format!("Error when parsing front matter of file `{}`", filepath))?; |
|
|
|
|
|
|
|
page.content = { |
|
|
|
let mut html = String::new(); |
|
|
|
let parser = cmark::Parser::new(&page.raw_content); |
|
|
|
cmark::html::push_html(&mut html, parser); |
|
|
|
html |
|
|
|
}; |
|
|
|
|
|
|
|
Ok(page) |
|
|
|
} |
|
|
|
|
|
|
|
pub fn from_file(path: &str) -> Result<Page> { |
|
|
|
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Page> { |
|
|
|
let path = path.as_ref(); |
|
|
|
|
|
|
|
let mut content = String::new(); |
|
|
|
File::open(path) |
|
|
|
.chain_err(|| format!("Failed to open '{:?}'", path))? |
|
|
|
.chain_err(|| format!("Failed to open '{:?}'", path.display()))? |
|
|
|
.read_to_string(&mut content)?; |
|
|
|
|
|
|
|
Page::from_str(path, &content) |
|
|
|
// Remove the content string from name |
|
|
|
// Maybe get a path as an arg instead and use strip_prefix? |
|
|
|
Page::from_str(&path.strip_prefix("content").unwrap().to_string_lossy(), &content) |
|
|
|
} |
|
|
|
|
|
|
|
fn get_layout_name(&self) -> String { |
|
|
|
// TODO: handle themes |
|
|
|
match self.layout { |
|
|
|
Some(ref l) => l.to_string(), |
|
|
|
None => "_default/single.html".to_string() |
|
|
|
None => "single.html".to_string() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
pub fn render_html(&self, tera: &Tera, config: &Config) -> Result<String> { |
|
|
|
pub fn render_html(&mut self, tera: &Tera, config: &Config) -> Result<String> { |
|
|
|
let tpl = self.get_layout_name(); |
|
|
|
let mut context = Context::new(); |
|
|
|
context.add("site", config); |
|
|
|
context.add("page", self); |
|
|
|
// println!("{:?}", tera); |
|
|
|
tera.render(&tpl, context).chain_err(|| "") |
|
|
|
|
|
|
|
tera.render(&tpl, context) |
|
|
|
.chain_err(|| "Error while rendering template") |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
@@ -137,13 +170,40 @@ title = "Hello" |
|
|
|
slug = "hello-world" |
|
|
|
+++ |
|
|
|
Hello world"#; |
|
|
|
let res = Page::from_str("", content); |
|
|
|
let res = Page::from_str("post.md", content); |
|
|
|
assert!(res.is_ok()); |
|
|
|
let page = res.unwrap(); |
|
|
|
|
|
|
|
assert_eq!(page.title, "Hello".to_string()); |
|
|
|
assert_eq!(page.slug, "hello-world".to_string()); |
|
|
|
assert_eq!(page.content, "Hello world".to_string()); |
|
|
|
assert_eq!(page.raw_content, "Hello world".to_string()); |
|
|
|
assert_eq!(page.content, "<p>Hello world</p>\n".to_string()); |
|
|
|
} |
|
|
|
|
|
|
|
#[test] |
|
|
|
fn test_can_find_one_parent_directory() { |
|
|
|
let content = r#" |
|
|
|
title = "Hello" |
|
|
|
slug = "hello-world" |
|
|
|
+++ |
|
|
|
Hello world"#; |
|
|
|
let res = Page::from_str("posts/intro.md", content); |
|
|
|
assert!(res.is_ok()); |
|
|
|
let page = res.unwrap(); |
|
|
|
assert_eq!(page.sections, vec!["posts".to_string()]); |
|
|
|
} |
|
|
|
|
|
|
|
#[test] |
|
|
|
fn test_can_find_multiplie_parent_directories() { |
|
|
|
let content = r#" |
|
|
|
title = "Hello" |
|
|
|
slug = "hello-world" |
|
|
|
+++ |
|
|
|
Hello world"#; |
|
|
|
let res = Page::from_str("posts/intro/start.md", content); |
|
|
|
assert!(res.is_ok()); |
|
|
|
let page = res.unwrap(); |
|
|
|
assert_eq!(page.sections, vec!["posts".to_string(), "intro".to_string()]); |
|
|
|
} |
|
|
|
|
|
|
|
} |