@@ -123,14 +123,18 @@ which template will be used to render that section. | |||
Sections will also automatically pick up their subsections, allowing you to make some complex pages layout and | |||
table of contents. | |||
A special case is the `_index.md` at the root of the `content` directory which represents the homepage. It is only there | |||
to control pagination and sorting of the homepage. | |||
### Code highlighting themes | |||
Code highlighting can be turned on by setting `highlight_code = true` in `config.toml`. | |||
When turned on, all text between backticks will be highlighted, like the example below. | |||
```rust | |||
let site = Site::new(); | |||
``` | |||
```rust | |||
let site = Site::new(); | |||
``` | |||
If the name of the language is not given, it will default to plain-text highlighting. | |||
Gutenberg uses Sublime Text themes for syntax highlighting. It comes with the following theme | |||
@@ -37,7 +37,7 @@ lazy_static!{ | |||
}; | |||
} | |||
/// A ShortCode that has a body | |||
/// A shortcode that has a body | |||
/// Called by having some content like {% ... %} body {% end %} | |||
/// We need the struct to hold the data while we're processing the markdown | |||
#[derive(Debug)] | |||
@@ -62,7 +62,7 @@ impl ShortCode { | |||
pub fn render(&self, tera: &Tera) -> Result<String> { | |||
let mut context = Context::new(); | |||
for (key, value) in self.args.iter() { | |||
for (key, value) in &self.args { | |||
context.add(key, value); | |||
} | |||
context.add("body", &self.body); | |||
@@ -132,7 +132,7 @@ pub fn markdown_to_html(content: &str, permalinks: &HashMap<String, String>, ter | |||
// for example an article could have several titles named Example | |||
// We add a counter after the slug if the slug is already present, which | |||
// means we will have example, example-1, example-2 etc | |||
fn find_anchor(anchors: &Vec<String>, name: String, level: u8) -> String { | |||
fn find_anchor(anchors: &[String], name: String, level: u8) -> String { | |||
if level == 0 && !anchors.contains(&name) { | |||
return name.to_string(); | |||
} | |||
@@ -164,16 +164,14 @@ pub fn markdown_to_html(content: &str, permalinks: &HashMap<String, String>, ter | |||
} | |||
// Shortcode without body | |||
if shortcode_block.is_none() && text.starts_with("{{") && text.ends_with("}}") { | |||
if SHORTCODE_RE.is_match(&text) { | |||
let (name, args) = parse_shortcode(&text); | |||
added_shortcode = true; | |||
match render_simple_shortcode(tera, &name, &args) { | |||
Ok(s) => return Event::Html(Owned(format!("</p>{}", s))), | |||
Err(e) => { | |||
error = Some(e); | |||
return Event::Html(Owned("".to_string())); | |||
} | |||
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) { | |||
Ok(s) => return Event::Html(Owned(format!("</p>{}", s))), | |||
Err(e) => { | |||
error = Some(e); | |||
return Event::Html(Owned("".to_string())); | |||
} | |||
} | |||
// non-matching will be returned normally below | |||
@@ -277,7 +275,7 @@ pub fn markdown_to_html(content: &str, permalinks: &HashMap<String, String>, ter | |||
}; | |||
} | |||
return Event::Start(Tag::Link(link.clone(), title.clone())); | |||
Event::Start(Tag::Link(link.clone(), title.clone())) | |||
}, | |||
// need to know when we are in a code block to disable shortcodes in them | |||
Event::Start(Tag::Code) => { | |||
@@ -291,7 +289,7 @@ pub fn markdown_to_html(content: &str, permalinks: &HashMap<String, String>, ter | |||
Event::Start(Tag::Header(num)) => { | |||
in_header = true; | |||
// ugly eh | |||
return Event::Html(Owned(format!("<h{} ", num))); | |||
Event::Html(Owned(format!("<h{} ", num))) | |||
}, | |||
Event::End(Tag::Header(_)) => { | |||
in_header = false; | |||
@@ -152,22 +152,21 @@ impl Page { | |||
// Pages with custom urls exists outside of sections | |||
if let Some(ref u) = page.meta.url { | |||
page.path = u.trim().to_string(); | |||
} else { | |||
if !page.components.is_empty() { | |||
// If we have a folder with an asset, don't consider it as a component | |||
if page.file_name == "index" { | |||
page.components.pop(); | |||
// also set parent_path to grandparent instead | |||
page.parent_path = page.parent_path.parent().unwrap().to_path_buf(); | |||
} | |||
// Don't add a trailing slash to sections | |||
page.path = format!("{}/{}", page.components.join("/"), page.slug); | |||
} else { | |||
page.path = page.slug.clone(); | |||
} else if !page.components.is_empty() { | |||
// If we have a folder with an asset, don't consider it as a component | |||
if page.file_name == "index" { | |||
page.components.pop(); | |||
// also set parent_path to grandparent instead | |||
page.parent_path = page.parent_path.parent().unwrap().to_path_buf(); | |||
} | |||
// Don't add a trailing slash to sections | |||
page.path = format!("{}/{}", page.components.join("/"), page.slug); | |||
} else { | |||
page.path = page.slug.clone(); | |||
} | |||
page.permalink = config.make_permalink(&page.path); | |||
Ok(page) | |||
@@ -64,6 +64,7 @@ pub struct Site { | |||
pub config: Config, | |||
pub pages: HashMap<PathBuf, Page>, | |||
pub sections: BTreeMap<PathBuf, Section>, | |||
pub index: Option<Section>, | |||
pub tera: Tera, | |||
live_reload: bool, | |||
output_path: PathBuf, | |||
@@ -91,6 +92,7 @@ impl Site { | |||
config: get_config(path, config_file), | |||
pages: HashMap::new(), | |||
sections: BTreeMap::new(), | |||
index: None, | |||
tera: tera, | |||
live_reload: false, | |||
output_path: path.join("public"), | |||
@@ -117,16 +119,21 @@ impl Site { | |||
/// Reads all .md files in the `content` directory and create pages/sections | |||
/// out of them | |||
pub fn load(&mut self) -> Result<()> { | |||
let path = self.base_path.to_string_lossy().replace("\\", "/"); | |||
let content_glob = format!("{}/{}", path, "content/**/*.md"); | |||
let base_path = self.base_path.to_string_lossy().replace("\\", "/"); | |||
let content_glob = format!("{}/{}", base_path, "content/**/*.md"); | |||
// TODO: make that parallel, that's the main bottleneck | |||
// `add_section` and `add_page` can't be used in the parallel version afaik | |||
for entry in glob(&content_glob).unwrap().filter_map(|e| e.ok()) { | |||
let path = entry.as_path(); | |||
if path.file_name().unwrap() == "_index.md" { | |||
self.add_section(path)?; | |||
// Index section | |||
if path.parent().unwrap() == self.base_path.join("content") { | |||
self.index = Some(Section::from_file(path, &self.config)?); | |||
} else { | |||
// all the other sections | |||
self.add_section(path)?; | |||
} | |||
} else { | |||
self.add_page(path)?; | |||
} | |||
@@ -0,0 +1,4 @@ | |||
+++ | |||
title = "Home" | |||
description = "" | |||
+++ |
@@ -53,10 +53,10 @@ authors = ["Bob", "Alice"]"#; | |||
assert_eq!(res.title, "Hello".to_string()); | |||
assert_eq!(res.slug.unwrap(), "hello-world".to_string()); | |||
let extra = res.extra.unwrap(); | |||
assert_eq!(extra.get("language").unwrap(), &to_value("en").unwrap()); | |||
assert_eq!(extra["language"], to_value("en").unwrap()); | |||
assert_eq!( | |||
extra.get("authors").unwrap(), | |||
&to_value(["Bob".to_string(), "Alice".to_string()]).unwrap() | |||
extra["authors"], | |||
to_value(["Bob".to_string(), "Alice".to_string()]).unwrap() | |||
); | |||
} | |||
@@ -22,6 +22,9 @@ fn test_can_parse_site() { | |||
assert_eq!(site.pages.len(), 10); | |||
let posts_path = path.join("content").join("posts"); | |||
// We have an index page | |||
assert!(site.index.is_some()); | |||
// Make sure we remove all the pwd + content from the sections | |||
let basic = &site.pages[&posts_path.join("simple.md")]; | |||
assert_eq!(basic.components, vec!["posts".to_string()]); | |||