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