commit
014ce878f8
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -34,3 +34,6 @@
|
||||||
[submodule "sublime_syntaxes/Sublime-CMakeLists"]
|
[submodule "sublime_syntaxes/Sublime-CMakeLists"]
|
||||||
path = sublime_syntaxes/Sublime-CMakeLists
|
path = sublime_syntaxes/Sublime-CMakeLists
|
||||||
url = https://github.com/zyxar/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
|
# 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)
|
## 0.4.1 (2018-08-06)
|
||||||
|
|
||||||
- Fix live reload of a section content change getting no pages data
|
- Fix live reload of a section content change getting no pages data
|
||||||
|
|
959
Cargo.lock
generated
959
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "gutenberg"
|
name = "gutenberg"
|
||||||
version = "0.4.1"
|
version = "0.4.2"
|
||||||
authors = ["Vincent Prouillet <prouillet.vincent@gmail.com>"]
|
authors = ["Vincent Prouillet <prouillet.vincent@gmail.com>"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
readme = "README.md"
|
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:
|
And finally from the root of the components/highlighting crate run the following command:
|
||||||
|
|
||||||
```bash
|
```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
|
#### Adding a theme
|
||||||
|
|
|
@ -3,23 +3,22 @@ extern crate serde_derive;
|
||||||
extern crate toml;
|
extern crate toml;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate errors;
|
extern crate errors;
|
||||||
extern crate highlighting;
|
|
||||||
extern crate chrono;
|
extern crate chrono;
|
||||||
extern crate globset;
|
extern crate globset;
|
||||||
|
extern crate highlighting;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use toml::Value as Toml;
|
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use globset::{Glob, GlobSet, GlobSetBuilder};
|
use globset::{Glob, GlobSet, GlobSetBuilder};
|
||||||
|
use toml::Value as Toml;
|
||||||
|
|
||||||
use errors::{Result, ResultExt};
|
use errors::{Result, ResultExt};
|
||||||
use highlighting::THEME_SET;
|
use highlighting::THEME_SET;
|
||||||
|
|
||||||
|
|
||||||
mod theme;
|
mod theme;
|
||||||
|
|
||||||
use theme::Theme;
|
use theme::Theme;
|
||||||
|
@ -27,7 +26,6 @@ use theme::Theme;
|
||||||
// We want a default base url for tests
|
// We want a default base url for tests
|
||||||
static DEFAULT_BASE_URL: &'static str = "http://a-website.com";
|
static DEFAULT_BASE_URL: &'static str = "http://a-website.com";
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct Taxonomy {
|
pub struct Taxonomy {
|
||||||
|
@ -101,12 +99,15 @@ pub struct Config {
|
||||||
/// Had to remove the PartialEq derive because GlobSet does not implement it. No impact
|
/// Had to remove the PartialEq derive because GlobSet does not implement it. No impact
|
||||||
/// because it's unused anyway (who wants to sort Configs?).
|
/// because it's unused anyway (who wants to sort Configs?).
|
||||||
pub ignored_content: Vec<String>,
|
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>,
|
pub ignored_content_globset: Option<GlobSet>,
|
||||||
|
|
||||||
/// Whether to check all external links for validity
|
/// Whether to check all external links for validity
|
||||||
pub check_external_links: bool,
|
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
|
/// All user params set in [extra] in the config
|
||||||
pub extra: HashMap<String, Toml>,
|
pub extra: HashMap<String, Toml>,
|
||||||
|
|
||||||
|
@ -114,14 +115,13 @@ pub struct Config {
|
||||||
pub build_timestamp: Option<i64>,
|
pub build_timestamp: Option<i64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
/// Parses a string containing TOML to our Config struct
|
/// Parses a string containing TOML to our Config struct
|
||||||
/// Any extra parameter will end up in the extra field
|
/// Any extra parameter will end up in the extra field
|
||||||
pub fn parse(content: &str) -> Result<Config> {
|
pub fn parse(content: &str) -> Result<Config> {
|
||||||
let mut config: Config = match toml::from_str(content) {
|
let mut config: Config = match toml::from_str(content) {
|
||||||
Ok(c) => c,
|
Ok(c) => c,
|
||||||
Err(e) => bail!(e)
|
Err(e) => bail!(e),
|
||||||
};
|
};
|
||||||
|
|
||||||
if config.base_url.is_empty() || config.base_url == DEFAULT_BASE_URL {
|
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());
|
config.build_timestamp = Some(Utc::now().timestamp());
|
||||||
|
|
||||||
|
|
||||||
if !config.ignored_content.is_empty() {
|
if !config.ignored_content.is_empty() {
|
||||||
// Convert the file glob strings into a compiled glob set matcher. We want to do this once,
|
// 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
|
// 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 {
|
for pat in &config.ignored_content {
|
||||||
let glob = match Glob::new(pat) {
|
let glob = match Glob::new(pat) {
|
||||||
Ok(g) => g,
|
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);
|
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)
|
Ok(config)
|
||||||
|
@ -161,7 +168,12 @@ impl Config {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
let file_name = path.file_name().unwrap();
|
let file_name = path.file_name().unwrap();
|
||||||
File::open(path)
|
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)?;
|
.read_to_string(&mut content)?;
|
||||||
|
|
||||||
Config::parse(&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
|
/// Makes a url, taking into account that the base url might have a trailing slash
|
||||||
pub fn make_permalink(&self, path: &str) -> String {
|
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
|
// Index section with a base url that has a trailing slash
|
||||||
if self.base_url.ends_with('/') && path == "/" {
|
if self.base_url.ends_with('/') && path == "/" {
|
||||||
|
@ -195,12 +211,16 @@ impl Config {
|
||||||
let original = self.extra.clone();
|
let original = self.extra.clone();
|
||||||
// 2. inject theme extra values
|
// 2. inject theme extra values
|
||||||
for (key, val) in &theme.extra {
|
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
|
// 3. overwrite with original config
|
||||||
for (key, val) in &original {
|
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(())
|
Ok(())
|
||||||
|
@ -233,13 +253,13 @@ impl Default for Config {
|
||||||
ignored_content: Vec::new(),
|
ignored_content: Vec::new(),
|
||||||
ignored_content_globset: None,
|
ignored_content_globset: None,
|
||||||
translations: HashMap::new(),
|
translations: HashMap::new(),
|
||||||
|
extra_syntaxes: Vec::new(),
|
||||||
extra: HashMap::new(),
|
extra: HashMap::new(),
|
||||||
build_timestamp: Some(1),
|
build_timestamp: Some(1),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Get and parse the config.
|
/// Get and parse the config.
|
||||||
/// If it doesn't succeed, exit
|
/// If it doesn't succeed, exit
|
||||||
pub fn get_config(path: &Path, filename: &str) -> Config {
|
pub fn get_config(path: &Path, filename: &str) -> Config {
|
||||||
|
@ -253,7 +273,6 @@ pub fn get_config(path: &Path, filename: &str) -> Config {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{Config, Theme};
|
use super::{Config, Theme};
|
||||||
|
@ -303,7 +322,16 @@ hello = "world"
|
||||||
|
|
||||||
let config = Config::parse(config);
|
let config = Config::parse(config);
|
||||||
assert!(config.is_ok());
|
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]
|
#[test]
|
||||||
|
@ -313,7 +341,6 @@ hello = "world"
|
||||||
assert_eq!(config.make_permalink(""), "http://vincent.is/");
|
assert_eq!(config.make_permalink(""), "http://vincent.is/");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn can_make_url_index_page_with_railing_slash_url() {
|
fn can_make_url_index_page_with_railing_slash_url() {
|
||||||
let mut config = Config::default();
|
let mut config = Config::default();
|
||||||
|
@ -339,7 +366,10 @@ hello = "world"
|
||||||
fn can_make_url_with_localhost() {
|
fn can_make_url_with_localhost() {
|
||||||
let mut config = Config::default();
|
let mut config = Config::default();
|
||||||
config.base_url = "http://127.0.0.1:1111".to_string();
|
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]
|
#[test]
|
||||||
|
|
|
@ -7,6 +7,7 @@ extern crate front_matter;
|
||||||
extern crate config;
|
extern crate config;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
use config::Config;
|
use config::Config;
|
||||||
use tera::Tera;
|
use tera::Tera;
|
||||||
|
@ -14,19 +15,16 @@ use front_matter::{SortBy, InsertAnchor};
|
||||||
use content::{Page, sort_pages, populate_siblings};
|
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 mut pages = vec![];
|
||||||
let config = Config::default();
|
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();
|
let permalinks = HashMap::new();
|
||||||
|
|
||||||
for i in 0..number {
|
for i in 0..number {
|
||||||
let mut page = Page::default();
|
let mut page = Page::default();
|
||||||
match sort_by {
|
page.meta.weight = Some(i);
|
||||||
SortBy::Weight => { page.meta.weight = Some(i); }
|
|
||||||
SortBy::Order => { page.meta.order = Some(i); }
|
|
||||||
_ => (),
|
|
||||||
};
|
|
||||||
page.raw_content = r#"
|
page.raw_content = r#"
|
||||||
# Modus cognitius profanam ne duae virtutis mundi
|
# Modus cognitius profanam ne duae virtutis mundi
|
||||||
|
|
||||||
|
@ -98,7 +96,7 @@ if __name__ == "__main__":
|
||||||
gen_site("basic-blog", [""], 250, paginate=True)
|
gen_site("basic-blog", [""], 250, paginate=True)
|
||||||
```
|
```
|
||||||
"#.to_string();
|
"#.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);
|
pages.push(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,34 +109,34 @@ if __name__ == "__main__":
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
fn bench_baseline_cloning(b: &mut test::Bencher) {
|
fn bench_baseline_cloning(b: &mut test::Bencher) {
|
||||||
let pages = create_pages(250, SortBy::Order);
|
let pages = create_pages(250);
|
||||||
b.iter(|| pages.clone());
|
b.iter(|| pages.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
fn bench_sorting_none(b: &mut test::Bencher) {
|
fn bench_sorting_none(b: &mut test::Bencher) {
|
||||||
let pages = create_pages(250, SortBy::Order);
|
let pages = create_pages(250);
|
||||||
b.iter(|| sort_pages(pages.clone(), SortBy::None));
|
b.iter(|| sort_pages(pages.clone(), SortBy::Weight));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
fn bench_sorting_order(b: &mut test::Bencher) {
|
fn bench_sorting_order(b: &mut test::Bencher) {
|
||||||
let pages = create_pages(250, SortBy::Order);
|
let pages = create_pages(250);
|
||||||
b.iter(|| sort_pages(pages.clone(), SortBy::Order));
|
b.iter(|| sort_pages(pages.clone(), SortBy::Weight));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
fn bench_populate_siblings(b: &mut test::Bencher) {
|
fn bench_populate_siblings(b: &mut test::Bencher) {
|
||||||
let pages = create_pages(250, SortBy::Order);
|
let pages = create_pages(250);
|
||||||
let (sorted_pages, _) = sort_pages(pages, SortBy::Order);
|
let (sorted_pages, _) = sort_pages(pages, SortBy::Weight);
|
||||||
b.iter(|| populate_siblings(&sorted_pages.clone()));
|
b.iter(|| populate_siblings(&sorted_pages.clone(), SortBy::Weight));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
fn bench_page_render_html(b: &mut test::Bencher) {
|
fn bench_page_render_html(b: &mut test::Bencher) {
|
||||||
let pages = create_pages(10, SortBy::Order);
|
let pages = create_pages(10);
|
||||||
let (mut sorted_pages, _) = sort_pages(pages, SortBy::Order);
|
let (mut sorted_pages, _) = sort_pages(pages, SortBy::Weight);
|
||||||
sorted_pages = populate_siblings(&sorted_pages);
|
sorted_pages = populate_siblings(&sorted_pages, SortBy::Weight);
|
||||||
|
|
||||||
let config = Config::default();
|
let config = Config::default();
|
||||||
let mut tera = Tera::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
|
/// We need access to all pages url to render links relative to content
|
||||||
/// so that can't happen at the same time as parsing
|
/// so that can't happen at the same time as parsing
|
||||||
pub fn render_markdown(&mut self, permalinks: &HashMap<String, String>, tera: &Tera, config: &Config, 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(
|
let mut context = RenderContext::new(
|
||||||
tera,
|
tera,
|
||||||
config,
|
config,
|
||||||
&self.permalink,
|
&self.permalink,
|
||||||
permalinks,
|
permalinks,
|
||||||
|
base_path,
|
||||||
anchor_insert,
|
anchor_insert,
|
||||||
);
|
);
|
||||||
|
|
||||||
context.tera_context.add("page", self);
|
context.tera_context.add("page", self);
|
||||||
|
|
||||||
let res = render_content(
|
let res = render_content(&self.raw_content, &context)
|
||||||
&self.raw_content.replacen("<!-- more -->", "<a name=\"continue-reading\"></a>", 1),
|
.chain_err(|| format!("Failed to render content of {}", self.file.path.display()))?;
|
||||||
&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.0;
|
self.content = res.body;
|
||||||
self.toc = res.1;
|
self.toc = res.toc;
|
||||||
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
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -310,7 +311,13 @@ Hello world"#;
|
||||||
let res = Page::parse(Path::new("post.md"), content, &Config::default());
|
let res = Page::parse(Path::new("post.md"), content, &Config::default());
|
||||||
assert!(res.is_ok());
|
assert!(res.is_ok());
|
||||||
let mut page = res.unwrap();
|
let mut page = res.unwrap();
|
||||||
page.render_markdown(&HashMap::default(), &Tera::default(), &Config::default(), 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.title.unwrap(), "Hello".to_string());
|
||||||
assert_eq!(page.meta.slug.unwrap(), "hello-world".to_string());
|
assert_eq!(page.meta.slug.unwrap(), "hello-world".to_string());
|
||||||
|
@ -416,7 +423,13 @@ Hello world
|
||||||
let res = Page::parse(Path::new("hello.md"), &content, &config);
|
let res = Page::parse(Path::new("hello.md"), &content, &config);
|
||||||
assert!(res.is_ok());
|
assert!(res.is_ok());
|
||||||
let mut page = res.unwrap();
|
let mut page = res.unwrap();
|
||||||
page.render_markdown(&HashMap::default(), &Tera::default(), &config, 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()));
|
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 config::Config;
|
||||||
use front_matter::{SectionFrontMatter, split_section_content};
|
use front_matter::{SectionFrontMatter, split_section_content};
|
||||||
use errors::{Result, ResultExt};
|
use errors::{Result, ResultExt};
|
||||||
use utils::fs::read_file;
|
use utils::fs::{read_file, find_related_assets};
|
||||||
use utils::templates::render_template;
|
use utils::templates::render_template;
|
||||||
use utils::site::get_reading_analytics;
|
use utils::site::get_reading_analytics;
|
||||||
use rendering::{RenderContext, Header, render_content};
|
use rendering::{RenderContext, Header, render_content};
|
||||||
|
@ -33,6 +33,8 @@ pub struct Section {
|
||||||
pub raw_content: String,
|
pub raw_content: String,
|
||||||
/// The HTML rendered of the page
|
/// The HTML rendered of the page
|
||||||
pub content: String,
|
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
|
/// All direct pages of that section
|
||||||
pub pages: Vec<Page>,
|
pub pages: Vec<Page>,
|
||||||
/// All pages that cannot be sorted in this section
|
/// All pages that cannot be sorted in this section
|
||||||
|
@ -54,6 +56,7 @@ impl Section {
|
||||||
components: vec![],
|
components: vec![],
|
||||||
permalink: "".to_string(),
|
permalink: "".to_string(),
|
||||||
raw_content: "".to_string(),
|
raw_content: "".to_string(),
|
||||||
|
assets: vec![],
|
||||||
content: "".to_string(),
|
content: "".to_string(),
|
||||||
pages: vec![],
|
pages: vec![],
|
||||||
ignored_pages: vec![],
|
ignored_pages: vec![],
|
||||||
|
@ -79,8 +82,31 @@ impl Section {
|
||||||
pub fn from_file<P: AsRef<Path>>(path: P, config: &Config) -> Result<Section> {
|
pub fn from_file<P: AsRef<Path>>(path: P, config: &Config) -> Result<Section> {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
let content = read_file(path)?;
|
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 {
|
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
|
/// We need access to all pages url to render links relative to content
|
||||||
/// so that can't happen at the same time as parsing
|
/// so that can't happen at the same time as parsing
|
||||||
pub fn render_markdown(&mut self, permalinks: &HashMap<String, String>, tera: &Tera, config: &Config) -> Result<()> {
|
pub fn render_markdown(&mut self, permalinks: &HashMap<String, String>, tera: &Tera, config: &Config, base_path: &Path) -> Result<()> {
|
||||||
let mut context = RenderContext::new(
|
let mut context = RenderContext::new(
|
||||||
tera,
|
tera,
|
||||||
config,
|
config,
|
||||||
&self.permalink,
|
&self.permalink,
|
||||||
permalinks,
|
permalinks,
|
||||||
|
base_path,
|
||||||
self.meta.insert_anchor_links,
|
self.meta.insert_anchor_links,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -110,8 +137,8 @@ impl Section {
|
||||||
|
|
||||||
let res = render_content(&self.raw_content, &context)
|
let res = render_content(&self.raw_content, &context)
|
||||||
.chain_err(|| format!("Failed to render content of {}", self.file.path.display()))?;
|
.chain_err(|| format!("Failed to render content of {}", self.file.path.display()))?;
|
||||||
self.content = res.0;
|
self.content = res.body;
|
||||||
self.toc = res.1;
|
self.toc = res.toc;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,6 +173,15 @@ impl Section {
|
||||||
pub fn is_child_page(&self, path: &PathBuf) -> bool {
|
pub fn is_child_page(&self, path: &PathBuf) -> bool {
|
||||||
self.all_pages_path().contains(path)
|
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 {
|
impl ser::Serialize for Section {
|
||||||
|
@ -165,6 +201,8 @@ impl ser::Serialize for Section {
|
||||||
state.serialize_field("word_count", &word_count)?;
|
state.serialize_field("word_count", &word_count)?;
|
||||||
state.serialize_field("reading_time", &reading_time)?;
|
state.serialize_field("reading_time", &reading_time)?;
|
||||||
state.serialize_field("toc", &self.toc)?;
|
state.serialize_field("toc", &self.toc)?;
|
||||||
|
let assets = self.serialize_assets();
|
||||||
|
state.serialize_field("assets", &assets)?;
|
||||||
state.end()
|
state.end()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -179,6 +217,7 @@ impl Default for Section {
|
||||||
components: vec![],
|
components: vec![],
|
||||||
permalink: "".to_string(),
|
permalink: "".to_string(),
|
||||||
raw_content: "".to_string(),
|
raw_content: "".to_string(),
|
||||||
|
assets: vec![],
|
||||||
content: "".to_string(),
|
content: "".to_string(),
|
||||||
pages: vec![],
|
pages: vec![],
|
||||||
ignored_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
|
// Check README for more details
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut args = env::args().skip(1);
|
let mut args = env::args().skip(1);
|
||||||
match (args.next(), args.next(), args.next(), args.next()) {
|
match (args.next(), args.next(), args.next()) {
|
||||||
(Some(ref cmd), Some(ref package_dir), Some(ref packpath_newlines), Some(ref packpath_nonewlines)) if cmd == "synpack" => {
|
(Some(ref cmd), Some(ref package_dir), Some(ref packpath_newlines)) if cmd == "synpack" => {
|
||||||
let mut ps = SyntaxSet::new();
|
let mut ps = SyntaxSet::new();
|
||||||
ps.load_plain_text_syntax();
|
ps.load_plain_text_syntax();
|
||||||
ps.load_syntaxes(package_dir, true).unwrap();
|
ps.load_syntaxes(package_dir, true).unwrap();
|
||||||
dump_to_file(&ps, packpath_newlines).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() {
|
for s in ps.syntaxes() {
|
||||||
if !s.file_extensions.is_empty() {
|
if !s.file_extensions.is_empty() {
|
||||||
println!("- {} -> {:?}", s.name, s.file_extensions);
|
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();
|
let ts = ThemeSet::load_from_folder(theme_dir).unwrap();
|
||||||
for path in ts.themes.keys() {
|
for path in ts.themes.keys() {
|
||||||
println!("{:?}", path);
|
println!("{:?}", path);
|
||||||
|
|
|
@ -2,16 +2,20 @@
|
||||||
extern crate lazy_static;
|
extern crate lazy_static;
|
||||||
extern crate syntect;
|
extern crate syntect;
|
||||||
|
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use syntect::LoadingError;
|
||||||
use syntect::dumps::from_binary;
|
use syntect::dumps::from_binary;
|
||||||
use syntect::parsing::SyntaxSet;
|
use syntect::parsing::SyntaxSet;
|
||||||
use syntect::highlighting::{ThemeSet, Theme};
|
use syntect::highlighting::{ThemeSet, Theme};
|
||||||
use syntect::easy::HighlightLines;
|
use syntect::easy::HighlightLines;
|
||||||
|
|
||||||
thread_local! {
|
thread_local! {
|
||||||
pub static SYNTAX_SET: SyntaxSet = {
|
/// A pair of the set and whether extras have been added to it.
|
||||||
let mut ss: SyntaxSet = from_binary(include_bytes!("../../../sublime_syntaxes/newlines.packdump"));
|
pub static SYNTAX_SET: RefCell<(SyntaxSet, bool)> = {
|
||||||
ss.link_syntaxes();
|
let ss: SyntaxSet = from_binary(include_bytes!("../../../sublime_syntaxes/newlines.packdump"));
|
||||||
ss
|
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 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
|
let syntax = info
|
||||||
.split(' ')
|
.split(' ')
|
||||||
.next()
|
.next()
|
||||||
.and_then(|lang| ss.find_syntax_by_token(lang))
|
.and_then(|lang| ss.find_syntax_by_token(lang))
|
||||||
.unwrap_or_else(|| ss.find_syntax_plain_text());
|
.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]
|
#[macro_use]
|
||||||
extern crate lazy_static;
|
extern crate lazy_static;
|
||||||
|
|
||||||
|
use reqwest::header::{qitem, Accept, Headers};
|
||||||
|
use reqwest::{mime, StatusCode};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
use reqwest::StatusCode;
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct LinkResult {
|
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
|
// Need to actually do the link checking
|
||||||
let res = match reqwest::get(url) {
|
let res = match client.get(url).headers(headers).send() {
|
||||||
Ok(response) => LinkResult { code: Some(response.status()), error: None },
|
Ok(response) => LinkResult {
|
||||||
Err(e) => LinkResult { code: None, error: Some(e.description().to_string()) },
|
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());
|
LINKS.write().unwrap().insert(url.to_string(), res.clone());
|
||||||
return res;
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{LINKS, check_url};
|
use super::{check_url, LINKS};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn can_validate_ok_links() {
|
fn can_validate_ok_links() {
|
||||||
|
|
|
@ -7,6 +7,7 @@ extern crate config;
|
||||||
extern crate front_matter;
|
extern crate front_matter;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
use tera::Tera;
|
use tera::Tera;
|
||||||
use rendering::{RenderContext, render_content, render_shortcodes};
|
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();
|
tera.add_raw_template("shortcodes/youtube.html", "{{id}}").unwrap();
|
||||||
let permalinks_ctx = HashMap::new();
|
let permalinks_ctx = HashMap::new();
|
||||||
let config = Config::default();
|
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());
|
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 permalinks_ctx = HashMap::new();
|
||||||
let mut config = Config::default();
|
let mut config = Config::default();
|
||||||
config.highlight_code = false;
|
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());
|
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();
|
let mut config = Config::default();
|
||||||
config.highlight_code = false;
|
config.highlight_code = false;
|
||||||
let permalinks_ctx = HashMap::new();
|
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());
|
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();
|
tera.add_raw_template("shortcodes/youtube.html", "{{id}}").unwrap();
|
||||||
let config = Config::default();
|
let config = Config::default();
|
||||||
let permalinks_ctx = HashMap::new();
|
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));
|
b.iter(|| render_shortcodes(CONTENT, &context));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
use tera::{Tera, Context};
|
use tera::{Tera, Context};
|
||||||
use front_matter::InsertAnchor;
|
use front_matter::InsertAnchor;
|
||||||
|
@ -13,6 +14,7 @@ pub struct RenderContext<'a> {
|
||||||
pub tera_context: Context,
|
pub tera_context: Context,
|
||||||
pub current_page_permalink: &'a str,
|
pub current_page_permalink: &'a str,
|
||||||
pub permalinks: &'a HashMap<String, String>,
|
pub permalinks: &'a HashMap<String, String>,
|
||||||
|
pub base_path: &'a Path,
|
||||||
pub insert_anchor: InsertAnchor,
|
pub insert_anchor: InsertAnchor,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,6 +24,7 @@ impl<'a> RenderContext<'a> {
|
||||||
config: &'a Config,
|
config: &'a Config,
|
||||||
current_page_permalink: &'a str,
|
current_page_permalink: &'a str,
|
||||||
permalinks: &'a HashMap<String, String>,
|
permalinks: &'a HashMap<String, String>,
|
||||||
|
base_path: &'a Path,
|
||||||
insert_anchor: InsertAnchor,
|
insert_anchor: InsertAnchor,
|
||||||
) -> RenderContext<'a> {
|
) -> RenderContext<'a> {
|
||||||
let mut tera_context = Context::new();
|
let mut tera_context = Context::new();
|
||||||
|
@ -32,6 +35,7 @@ impl<'a> RenderContext<'a> {
|
||||||
current_page_permalink,
|
current_page_permalink,
|
||||||
permalinks,
|
permalinks,
|
||||||
insert_anchor,
|
insert_anchor,
|
||||||
|
base_path,
|
||||||
config,
|
config,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ pub use table_of_contents::Header;
|
||||||
pub use shortcode::render_shortcodes;
|
pub use shortcode::render_shortcodes;
|
||||||
pub use context::RenderContext;
|
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
|
// Don't do anything if there is nothing like a shortcode in the content
|
||||||
if content.contains("{{") || content.contains("{%") {
|
if content.contains("{{") || content.contains("{%") {
|
||||||
let rendered = render_shortcodes(content, context)?;
|
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 pulldown_cmark as cmark;
|
||||||
use self::cmark::{Parser, Event, Tag, Options, OPTION_ENABLE_TABLES, OPTION_ENABLE_FOOTNOTES};
|
use self::cmark::{Parser, Event, Tag, Options, OPTION_ENABLE_TABLES, OPTION_ENABLE_FOOTNOTES};
|
||||||
|
@ -14,6 +14,15 @@ use link_checker::check_url;
|
||||||
use table_of_contents::{TempHeader, Header, make_table_of_contents};
|
use table_of_contents::{TempHeader, Header, make_table_of_contents};
|
||||||
use context::RenderContext;
|
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
|
// 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
|
// for example an article could have several titles named Example
|
||||||
// We add a counter after the slug if the slug is already present, which
|
// 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:")
|
&& !link.starts_with("mailto:")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn markdown_to_html(content: &str, context: &RenderContext) -> Result<Rendered> {
|
||||||
pub fn markdown_to_html(content: &str, context: &RenderContext) -> Result<(String, Vec<Header>)> {
|
|
||||||
// the rendered html
|
// the rendered html
|
||||||
let mut html = String::with_capacity(content.len());
|
let mut html = String::with_capacity(content.len());
|
||||||
// Set while parsing
|
// 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 temp_header = TempHeader::default();
|
||||||
|
|
||||||
let mut opts = Options::empty();
|
let mut opts = Options::empty();
|
||||||
|
let mut has_summary = false;
|
||||||
opts.insert(OPTION_ENABLE_TABLES);
|
opts.insert(OPTION_ENABLE_TABLES);
|
||||||
opts.insert(OPTION_ENABLE_FOOTNOTES);
|
opts.insert(OPTION_ENABLE_FOOTNOTES);
|
||||||
|
|
||||||
|
@ -68,7 +77,7 @@ pub fn markdown_to_html(content: &str, context: &RenderContext) -> Result<(Strin
|
||||||
if in_header {
|
if in_header {
|
||||||
if header_created {
|
if header_created {
|
||||||
temp_header.push(&text);
|
temp_header.push(&text);
|
||||||
return Event::Html(Owned(String::new()));
|
return Event::Html(Borrowed(""));
|
||||||
}
|
}
|
||||||
let id = find_anchor(&anchors, slugify(&text), 0);
|
let id = find_anchor(&anchors, slugify(&text), 0);
|
||||||
anchors.push(id.clone());
|
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
|
// += as we might have some <code> or other things already there
|
||||||
temp_header.title += &text;
|
temp_header.title += &text;
|
||||||
header_created = true;
|
header_created = true;
|
||||||
return Event::Html(Owned(String::new()));
|
return Event::Html(Borrowed(""));
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we are in the middle of a code block
|
// 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)) => {
|
Event::Start(Tag::CodeBlock(ref info)) => {
|
||||||
if !context.config.highlight_code {
|
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];
|
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);
|
let snippet = start_coloured_html_snippet(theme);
|
||||||
Event::Html(Owned(snippet))
|
Event::Html(Owned(snippet))
|
||||||
}
|
}
|
||||||
Event::End(Tag::CodeBlock(_)) => {
|
Event::End(Tag::CodeBlock(_)) => {
|
||||||
if !context.config.highlight_code {
|
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
|
// reset highlight and close the code block
|
||||||
highlighter = None;
|
highlighter = None;
|
||||||
Event::Html(Owned("</pre>".to_string()))
|
Event::Html(Borrowed("</pre>"))
|
||||||
}
|
}
|
||||||
Event::Start(Tag::Image(src, title)) => {
|
Event::Start(Tag::Image(src, title)) => {
|
||||||
if is_colocated_asset_link(&src) {
|
if is_colocated_asset_link(&src) {
|
||||||
|
@ -133,7 +148,7 @@ pub fn markdown_to_html(content: &str, context: &RenderContext) -> Result<(Strin
|
||||||
Ok(url) => url,
|
Ok(url) => url,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
error = Some(format!("Relative link {} not found.", link).into());
|
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) {
|
} 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)
|
format!("<a href=\"{}\" title=\"{}\">", fixed_link, title)
|
||||||
};
|
};
|
||||||
temp_header.push(&html);
|
temp_header.push(&html);
|
||||||
return Event::Html(Owned(String::new()));
|
return Event::Html(Borrowed(""));
|
||||||
}
|
}
|
||||||
|
|
||||||
Event::Start(Tag::Link(Owned(fixed_link), title))
|
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(_, _)) => {
|
Event::End(Tag::Link(_, _)) => {
|
||||||
if in_header {
|
if in_header {
|
||||||
temp_header.push("</a>");
|
temp_header.push("</a>");
|
||||||
return Event::Html(Owned(String::new()));
|
return Event::Html(Borrowed(""));
|
||||||
}
|
}
|
||||||
event
|
event
|
||||||
}
|
}
|
||||||
Event::Start(Tag::Code) => {
|
Event::Start(Tag::Code) => {
|
||||||
if in_header {
|
if in_header {
|
||||||
temp_header.push("<code>");
|
temp_header.push("<code>");
|
||||||
return Event::Html(Owned(String::new()));
|
return Event::Html(Borrowed(""));
|
||||||
}
|
}
|
||||||
event
|
event
|
||||||
}
|
}
|
||||||
Event::End(Tag::Code) => {
|
Event::End(Tag::Code) => {
|
||||||
if in_header {
|
if in_header {
|
||||||
temp_header.push("</code>");
|
temp_header.push("</code>");
|
||||||
return Event::Html(Owned(String::new()));
|
return Event::Html(Borrowed(""));
|
||||||
}
|
}
|
||||||
event
|
event
|
||||||
}
|
}
|
||||||
Event::Start(Tag::Header(num)) => {
|
Event::Start(Tag::Header(num)) => {
|
||||||
in_header = true;
|
in_header = true;
|
||||||
temp_header = TempHeader::new(num);
|
temp_header = TempHeader::new(num);
|
||||||
Event::Html(Owned(String::new()))
|
Event::Html(Borrowed(""))
|
||||||
}
|
}
|
||||||
Event::End(Tag::Header(_)) => {
|
Event::End(Tag::Header(_)) => {
|
||||||
// End of a header, reset all the things and return the stringified
|
// 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();
|
temp_header = TempHeader::default();
|
||||||
Event::Html(Owned(val))
|
Event::Html(Owned(val))
|
||||||
}
|
}
|
||||||
|
Event::Html(ref markup) if markup.contains("<!-- more -->") => {
|
||||||
|
has_summary = true;
|
||||||
|
Event::Html(Borrowed(CONTINUE_READING))
|
||||||
|
}
|
||||||
_ => event,
|
_ => event,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -209,11 +228,14 @@ pub fn markdown_to_html(content: &str, context: &RenderContext) -> Result<(Strin
|
||||||
cmark::html::push_html(&mut html, parser);
|
cmark::html::push_html(&mut html, parser);
|
||||||
}
|
}
|
||||||
|
|
||||||
match error {
|
if let Some(e) = error {
|
||||||
Some(e) => Err(e),
|
return Err(e)
|
||||||
None => Ok((
|
} else {
|
||||||
html.replace("<p></p>", "").replace("</p></p>", "</p>"),
|
html = html.replace("<p></p>", "").replace("</p></p>", "</p>");
|
||||||
make_table_of_contents(&headers)
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
use tera::Tera;
|
use tera::Tera;
|
||||||
use config::Config;
|
use config::Config;
|
||||||
use front_matter::InsertAnchor;
|
use front_matter::InsertAnchor;
|
||||||
|
@ -202,7 +204,7 @@ mod tests {
|
||||||
fn render_shortcodes(code: &str, tera: &Tera) -> String {
|
fn render_shortcodes(code: &str, tera: &Tera) -> String {
|
||||||
let config = Config::default();
|
let config = Config::default();
|
||||||
let permalinks = HashMap::new();
|
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()
|
super::render_shortcodes(code, &context).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ extern crate rendering;
|
||||||
extern crate config;
|
extern crate config;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
use tera::Tera;
|
use tera::Tera;
|
||||||
|
|
||||||
|
@ -19,9 +20,9 @@ fn can_do_render_content_simple() {
|
||||||
let tera_ctx = Tera::default();
|
let tera_ctx = Tera::default();
|
||||||
let permalinks_ctx = HashMap::new();
|
let permalinks_ctx = HashMap::new();
|
||||||
let config = Config::default();
|
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();
|
let res = render_content("hello", &context).unwrap();
|
||||||
assert_eq!(res.0, "<p>hello</p>\n");
|
assert_eq!(res.body, "<p>hello</p>\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -30,10 +31,10 @@ fn doesnt_highlight_code_block_with_highlighting_off() {
|
||||||
let permalinks_ctx = HashMap::new();
|
let permalinks_ctx = HashMap::new();
|
||||||
let mut config = Config::default();
|
let mut config = Config::default();
|
||||||
config.highlight_code = false;
|
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();
|
let res = render_content("```\n$ gutenberg server\n```", &context).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
res.0,
|
res.body,
|
||||||
"<pre><code>$ gutenberg server\n</code></pre>\n"
|
"<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 tera_ctx = Tera::default();
|
||||||
let permalinks_ctx = HashMap::new();
|
let permalinks_ctx = HashMap::new();
|
||||||
let config = Config::default();
|
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();
|
let res = render_content("```\n$ gutenberg server\n$ ping\n```", &context).unwrap();
|
||||||
assert_eq!(
|
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>"
|
"<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 tera_ctx = Tera::default();
|
||||||
let permalinks_ctx = HashMap::new();
|
let permalinks_ctx = HashMap::new();
|
||||||
let config = Config::default();
|
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();
|
let res = render_content("```python\nlist.append(1)\n```", &context).unwrap();
|
||||||
assert_eq!(
|
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>"
|
"<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 tera_ctx = Tera::default();
|
||||||
let permalinks_ctx = HashMap::new();
|
let permalinks_ctx = HashMap::new();
|
||||||
let config = Config::default();
|
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();
|
let res = render_content("```yolo\nlist.append(1)\n```", &context).unwrap();
|
||||||
// defaults to plain text
|
// defaults to plain text
|
||||||
assert_eq!(
|
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>"
|
"<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() {
|
fn can_render_shortcode() {
|
||||||
let permalinks_ctx = HashMap::new();
|
let permalinks_ctx = HashMap::new();
|
||||||
let config = Config::default();
|
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#"
|
let res = render_content(r#"
|
||||||
Hello
|
Hello
|
||||||
|
|
||||||
{{ youtube(id="ub36ffWAqgQ") }}
|
{{ youtube(id="ub36ffWAqgQ") }}
|
||||||
"#, &context).unwrap();
|
"#, &context).unwrap();
|
||||||
assert!(res.0.contains("<p>Hello</p>\n<div >"));
|
assert!(res.body.contains("<p>Hello</p>\n<div >"));
|
||||||
assert!(res.0.contains(r#"<iframe src="https://www.youtube.com/embed/ub36ffWAqgQ""#));
|
assert!(res.body.contains(r#"<iframe src="https://www.youtube.com/embed/ub36ffWAqgQ""#));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn can_render_shortcode_with_markdown_char_in_args_name() {
|
fn can_render_shortcode_with_markdown_char_in_args_name() {
|
||||||
let permalinks_ctx = HashMap::new();
|
let permalinks_ctx = HashMap::new();
|
||||||
let config = Config::default();
|
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![
|
let input = vec![
|
||||||
"name",
|
"name",
|
||||||
"na_me",
|
"na_me",
|
||||||
|
@ -105,7 +106,7 @@ fn can_render_shortcode_with_markdown_char_in_args_name() {
|
||||||
];
|
];
|
||||||
for i in input {
|
for i in input {
|
||||||
let res = render_content(&format!("{{{{ youtube(id=\"hey\", {}=1) }}}}", i), &context).unwrap();
|
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() {
|
fn can_render_shortcode_with_markdown_char_in_args_value() {
|
||||||
let permalinks_ctx = HashMap::new();
|
let permalinks_ctx = HashMap::new();
|
||||||
let config = Config::default();
|
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![
|
let input = vec![
|
||||||
"ub36ffWAqgQ-hey",
|
"ub36ffWAqgQ-hey",
|
||||||
"ub36ffWAqgQ_hey",
|
"ub36ffWAqgQ_hey",
|
||||||
|
@ -123,7 +124,7 @@ fn can_render_shortcode_with_markdown_char_in_args_value() {
|
||||||
];
|
];
|
||||||
for i in input {
|
for i in input {
|
||||||
let res = render_content(&format!("{{{{ youtube(id=\"{}\") }}}}", i), &context).unwrap();
|
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 {
|
for i in input {
|
||||||
tera.add_raw_template(&format!("shortcodes/{}.html", i), "<blockquote>{{ body }} - {{ author}}</blockquote>").unwrap();
|
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();
|
let res = render_content(&format!("{{% {}(author=\"Bob\") %}}\nhey\n{{% end %}}", i), &context).unwrap();
|
||||||
println!("{:?}", res);
|
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();
|
tera.add_raw_template(&format!("shortcodes/{}.html", "figure"), shortcode).unwrap();
|
||||||
let config = Config::default();
|
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();
|
let res = render_content(markdown_string, &context).unwrap();
|
||||||
println!("{:?}", res);
|
println!("{:?}", res);
|
||||||
assert_eq!(res.0, expected);
|
assert_eq!(res.body, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -202,18 +203,18 @@ Here is another paragraph.
|
||||||
|
|
||||||
tera.add_raw_template(&format!("shortcodes/{}.html", "figure"), shortcode).unwrap();
|
tera.add_raw_template(&format!("shortcodes/{}.html", "figure"), shortcode).unwrap();
|
||||||
let config = Config::default();
|
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();
|
let res = render_content(markdown_string, &context).unwrap();
|
||||||
println!("{:?}", res);
|
println!("{:?}", res);
|
||||||
assert_eq!(res.0, expected);
|
assert_eq!(res.body, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn can_render_several_shortcode_in_row() {
|
fn can_render_several_shortcode_in_row() {
|
||||||
let permalinks_ctx = HashMap::new();
|
let permalinks_ctx = HashMap::new();
|
||||||
let config = Config::default();
|
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#"
|
let res = render_content(r#"
|
||||||
Hello
|
Hello
|
||||||
|
|
||||||
|
@ -228,11 +229,11 @@ Hello
|
||||||
{{ gist(url="https://gist.github.com/Keats/32d26f699dcc13ebd41b") }}
|
{{ gist(url="https://gist.github.com/Keats/32d26f699dcc13ebd41b") }}
|
||||||
|
|
||||||
"#, &context).unwrap();
|
"#, &context).unwrap();
|
||||||
assert!(res.0.contains("<p>Hello</p>\n<div >"));
|
assert!(res.body.contains("<p>Hello</p>\n<div >"));
|
||||||
assert!(res.0.contains(r#"<iframe src="https://www.youtube.com/embed/ub36ffWAqgQ""#));
|
assert!(res.body.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.body.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.body.contains(r#"<iframe src="https://www.streamable.com/e/c0ic""#));
|
||||||
assert!(res.0.contains(r#"//player.vimeo.com/video/210073083""#));
|
assert!(res.body.contains(r#"//player.vimeo.com/video/210073083""#));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -240,9 +241,9 @@ fn doesnt_render_ignored_shortcodes() {
|
||||||
let permalinks_ctx = HashMap::new();
|
let permalinks_ctx = HashMap::new();
|
||||||
let mut config = Config::default();
|
let mut config = Config::default();
|
||||||
config.highlight_code = false;
|
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();
|
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]
|
#[test]
|
||||||
|
@ -252,7 +253,7 @@ fn can_render_shortcode_with_body() {
|
||||||
tera.add_raw_template("shortcodes/quote.html", "<blockquote>{{ body }} - {{ author }}</blockquote>").unwrap();
|
tera.add_raw_template("shortcodes/quote.html", "<blockquote>{{ body }} - {{ author }}</blockquote>").unwrap();
|
||||||
let permalinks_ctx = HashMap::new();
|
let permalinks_ctx = HashMap::new();
|
||||||
let config = Config::default();
|
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#"
|
let res = render_content(r#"
|
||||||
Hello
|
Hello
|
||||||
|
@ -260,7 +261,7 @@ Hello
|
||||||
A quote
|
A quote
|
||||||
{% end %}
|
{% end %}
|
||||||
"#, &context).unwrap();
|
"#, &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]
|
#[test]
|
||||||
|
@ -268,7 +269,7 @@ fn errors_rendering_unknown_shortcode() {
|
||||||
let tera_ctx = Tera::default();
|
let tera_ctx = Tera::default();
|
||||||
let permalinks_ctx = HashMap::new();
|
let permalinks_ctx = HashMap::new();
|
||||||
let config = Config::default();
|
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);
|
let res = render_content("{{ hello(flash=true) }}", &context);
|
||||||
assert!(res.is_err());
|
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());
|
permalinks.insert("pages/about.md".to_string(), "https://vincent.is/about".to_string());
|
||||||
let tera_ctx = Tera::default();
|
let tera_ctx = Tera::default();
|
||||||
let config = Config::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(
|
let res = render_content(
|
||||||
r#"[rel link](./pages/about.md), [abs link](https://vincent.is/about)"#,
|
r#"[rel link](./pages/about.md), [abs link](https://vincent.is/about)"#,
|
||||||
&context
|
&context,
|
||||||
).unwrap();
|
).unwrap();
|
||||||
|
|
||||||
assert!(
|
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());
|
permalinks.insert("pages/about.md".to_string(), "https://vincent.is/about".to_string());
|
||||||
let tera_ctx = Tera::default();
|
let tera_ctx = Tera::default();
|
||||||
let config = Config::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();
|
let res = render_content(r#"[rel link](./pages/about.md#cv)"#, &context).unwrap();
|
||||||
|
|
||||||
assert!(
|
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 tera_ctx = Tera::default();
|
||||||
let permalinks_ctx = HashMap::new();
|
let permalinks_ctx = HashMap::new();
|
||||||
let config = Config::default();
|
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);
|
let res = render_content("[rel link](./pages/about.md)", &context);
|
||||||
assert!(res.is_err());
|
assert!(res.is_err());
|
||||||
}
|
}
|
||||||
|
@ -319,9 +320,9 @@ fn can_add_id_to_headers() {
|
||||||
let tera_ctx = Tera::default();
|
let tera_ctx = Tera::default();
|
||||||
let permalinks_ctx = HashMap::new();
|
let permalinks_ctx = HashMap::new();
|
||||||
let config = Config::default();
|
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();
|
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]
|
#[test]
|
||||||
|
@ -329,19 +330,19 @@ fn can_add_id_to_headers_same_slug() {
|
||||||
let tera_ctx = Tera::default();
|
let tera_ctx = Tera::default();
|
||||||
let permalinks_ctx = HashMap::new();
|
let permalinks_ctx = HashMap::new();
|
||||||
let config = Config::default();
|
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();
|
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]
|
#[test]
|
||||||
fn can_insert_anchor_left() {
|
fn can_insert_anchor_left() {
|
||||||
let permalinks_ctx = HashMap::new();
|
let permalinks_ctx = HashMap::new();
|
||||||
let config = Config::default();
|
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();
|
let res = render_content("# Hello", &context).unwrap();
|
||||||
assert_eq!(
|
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"
|
"<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() {
|
fn can_insert_anchor_right() {
|
||||||
let permalinks_ctx = HashMap::new();
|
let permalinks_ctx = HashMap::new();
|
||||||
let config = Config::default();
|
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();
|
let res = render_content("# Hello", &context).unwrap();
|
||||||
assert_eq!(
|
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"
|
"<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() {
|
fn can_insert_anchor_with_exclamation_mark() {
|
||||||
let permalinks_ctx = HashMap::new();
|
let permalinks_ctx = HashMap::new();
|
||||||
let config = Config::default();
|
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();
|
let res = render_content("# Hello!", &context).unwrap();
|
||||||
assert_eq!(
|
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"
|
"<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() {
|
fn can_insert_anchor_with_link() {
|
||||||
let permalinks_ctx = HashMap::new();
|
let permalinks_ctx = HashMap::new();
|
||||||
let config = Config::default();
|
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();
|
let res = render_content("## [Rust](https://rust-lang.org)", &context).unwrap();
|
||||||
assert_eq!(
|
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"
|
"<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() {
|
fn can_insert_anchor_with_other_special_chars() {
|
||||||
let permalinks_ctx = HashMap::new();
|
let permalinks_ctx = HashMap::new();
|
||||||
let config = Config::default();
|
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();
|
let res = render_content("# Hello*_()", &context).unwrap();
|
||||||
assert_eq!(
|
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"
|
"<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,
|
&config,
|
||||||
"https://mysite.com/something",
|
"https://mysite.com/something",
|
||||||
&permalinks_ctx,
|
&permalinks_ctx,
|
||||||
InsertAnchor::Left
|
Path::new("something"),
|
||||||
|
InsertAnchor::Left,
|
||||||
);
|
);
|
||||||
|
|
||||||
let res = render_content(r#"
|
let res = render_content(r#"
|
||||||
|
@ -418,21 +420,20 @@ fn can_make_toc() {
|
||||||
### Last one
|
### Last one
|
||||||
"#, &context).unwrap();
|
"#, &context).unwrap();
|
||||||
|
|
||||||
let toc = res.1;
|
let toc = res.toc;
|
||||||
assert_eq!(toc.len(), 1);
|
assert_eq!(toc.len(), 1);
|
||||||
assert_eq!(toc[0].children.len(), 2);
|
assert_eq!(toc[0].children.len(), 2);
|
||||||
assert_eq!(toc[0].children[1].children.len(), 1);
|
assert_eq!(toc[0].children[1].children.len(), 1);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn can_understand_backtick_in_titles() {
|
fn can_understand_backtick_in_titles() {
|
||||||
let permalinks_ctx = HashMap::new();
|
let permalinks_ctx = HashMap::new();
|
||||||
let config = Config::default();
|
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();
|
let res = render_content("# `Hello`", &context).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
res.0,
|
res.body,
|
||||||
"<h1 id=\"hello\"><code>Hello</code></h1>\n"
|
"<h1 id=\"hello\"><code>Hello</code></h1>\n"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -441,10 +442,10 @@ fn can_understand_backtick_in_titles() {
|
||||||
fn can_understand_backtick_in_paragraphs() {
|
fn can_understand_backtick_in_paragraphs() {
|
||||||
let permalinks_ctx = HashMap::new();
|
let permalinks_ctx = HashMap::new();
|
||||||
let config = Config::default();
|
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();
|
let res = render_content("Hello `world`", &context).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
res.0,
|
res.body,
|
||||||
"<p>Hello <code>world</code></p>\n"
|
"<p>Hello <code>world</code></p>\n"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -454,10 +455,10 @@ fn can_understand_backtick_in_paragraphs() {
|
||||||
fn can_understand_links_in_header() {
|
fn can_understand_links_in_header() {
|
||||||
let permalinks_ctx = HashMap::new();
|
let permalinks_ctx = HashMap::new();
|
||||||
let config = Config::default();
|
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();
|
let res = render_content("# [Rust](https://rust-lang.org)", &context).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
res.0,
|
res.body,
|
||||||
"<h1 id=\"rust\"><a href=\"https://rust-lang.org\">Rust</a></h1>\n"
|
"<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() {
|
fn can_understand_link_with_title_in_header() {
|
||||||
let permalinks_ctx = HashMap::new();
|
let permalinks_ctx = HashMap::new();
|
||||||
let config = Config::default();
|
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();
|
let res = render_content("# [Rust](https://rust-lang.org \"Rust homepage\")", &context).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
res.0,
|
res.body,
|
||||||
"<h1 id=\"rust\"><a href=\"https://rust-lang.org\" title=\"Rust homepage\">Rust</a></h1>\n"
|
"<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());
|
permalinks.insert("pages/about.md".to_string(), "https://vincent.is/about/".to_string());
|
||||||
let tera_ctx = Tera::default();
|
let tera_ctx = Tera::default();
|
||||||
let config = Config::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(
|
let res = render_content(
|
||||||
r#" # [rel link](./pages/about.md)"#,
|
r#" # [rel link](./pages/about.md)"#,
|
||||||
&context
|
&context,
|
||||||
).unwrap();
|
).unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
res.0,
|
res.body,
|
||||||
"<h1 id=\"rel-link\"><a href=\"https://vincent.is/about/\">rel link</a></h1>\n"
|
"<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() {
|
fn can_make_permalinks_with_colocated_assets_for_link() {
|
||||||
let permalinks_ctx = HashMap::new();
|
let permalinks_ctx = HashMap::new();
|
||||||
let config = Config::default();
|
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();
|
let res = render_content("[an image](image.jpg)", &context).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
res.0,
|
res.body,
|
||||||
"<p><a href=\"https://vincent.is/about/image.jpg\">an image</a></p>\n"
|
"<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() {
|
fn can_make_permalinks_with_colocated_assets_for_image() {
|
||||||
let permalinks_ctx = HashMap::new();
|
let permalinks_ctx = HashMap::new();
|
||||||
let config = Config::default();
|
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();
|
let res = render_content("![alt text](image.jpg)", &context).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
res.0,
|
res.body,
|
||||||
"<p><img src=\"https://vincent.is/about/image.jpg\" alt=\"alt text\" /></p>\n"
|
"<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() {
|
fn markdown_doesnt_wrap_html_in_paragraph() {
|
||||||
let permalinks_ctx = HashMap::new();
|
let permalinks_ctx = HashMap::new();
|
||||||
let config = Config::default();
|
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#"
|
let res = render_content(r#"
|
||||||
Some text
|
Some text
|
||||||
|
|
||||||
|
@ -533,7 +534,7 @@ Some text
|
||||||
</div>
|
</div>
|
||||||
"#, &context).unwrap();
|
"#, &context).unwrap();
|
||||||
assert_eq!(
|
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"
|
"<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 permalinks_ctx = HashMap::new();
|
||||||
let mut config = Config::default();
|
let mut config = Config::default();
|
||||||
config.check_external_links = true;
|
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();
|
let res = render_content("[a link](http://google.com)", &context).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
res.0,
|
res.body,
|
||||||
"<p><a href=\"http://google.com\">a link</a></p>\n"
|
"<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 permalinks_ctx = HashMap::new();
|
||||||
let mut config = Config::default();
|
let mut config = Config::default();
|
||||||
config.check_external_links = true;
|
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);
|
let res = render_content("[a link](http://google.comy)", &context);
|
||||||
assert!(res.is_err());
|
assert!(res.is_err());
|
||||||
let err = res.unwrap_err();
|
let err = res.unwrap_err();
|
||||||
assert!(err.description().contains("Link http://google.comy is not valid"));
|
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"
|
title = "Hello"
|
||||||
date = REPLACE_DATE
|
date = REPLACE_DATE
|
||||||
|
|
||||||
|
[taxonomies]
|
||||||
tags = REPLACE_TAG
|
tags = REPLACE_TAG
|
||||||
category = "REPLACE_CATEGORY"
|
categories = ["REPLACE_CATEGORY"]
|
||||||
+++
|
+++
|
||||||
|
|
||||||
# Modus cognitius profanam ne duae virtutis mundi
|
# Modus cognitius profanam ne duae virtutis mundi
|
||||||
|
@ -103,10 +105,13 @@ def gen_skeleton(name, is_blog):
|
||||||
f.write("""
|
f.write("""
|
||||||
title = "My site"
|
title = "My site"
|
||||||
base_url = "https://replace-this-with-your-url.com"
|
base_url = "https://replace-this-with-your-url.com"
|
||||||
generate_tags_pages = true
|
|
||||||
generate_categories_pages = true
|
|
||||||
theme = "sample"
|
theme = "sample"
|
||||||
|
|
||||||
|
taxonomies = [
|
||||||
|
{name = "tags", rss = true},
|
||||||
|
{name = "categories"}
|
||||||
|
]
|
||||||
|
|
||||||
[extra.author]
|
[extra.author]
|
||||||
name = "Vincent Prouillet"
|
name = "Vincent Prouillet"
|
||||||
""")
|
""")
|
||||||
|
@ -121,8 +126,8 @@ name = "Vincent Prouillet"
|
||||||
""")
|
""")
|
||||||
|
|
||||||
# Re-use the test templates
|
# Re-use the test templates
|
||||||
shutil.copytree("../test_site/templates", os.path.join(name, "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/themes", os.path.join(name, "themes"))
|
||||||
|
|
||||||
|
|
||||||
def gen_section(path, num_pages, is_blog):
|
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("benches");
|
||||||
path.push("small-blog");
|
path.push("small-blog");
|
||||||
let mut site = Site::new(&path, "config.toml").unwrap();
|
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());
|
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("benches");
|
||||||
// path.push("medium-blog");
|
// path.push("medium-blog");
|
||||||
// let mut site = Site::new(&path, "config.toml").unwrap();
|
// 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());
|
// 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("benches");
|
||||||
// path.push("big-blog");
|
// path.push("big-blog");
|
||||||
// let mut site = Site::new(&path, "config.toml").unwrap();
|
// 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());
|
// 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("benches");
|
||||||
// path.push("huge-blog");
|
// path.push("huge-blog");
|
||||||
// let mut site = Site::new(&path, "config.toml").unwrap();
|
// 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());
|
// 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("benches");
|
||||||
path.push("small-kb");
|
path.push("small-kb");
|
||||||
let mut site = Site::new(&path, "config.toml").unwrap();
|
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());
|
b.iter(|| site.load().unwrap());
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
#![feature(test)]
|
#![feature(test)]
|
||||||
extern crate test;
|
extern crate test;
|
||||||
extern crate site;
|
extern crate site;
|
||||||
|
extern crate pagination;
|
||||||
extern crate tempfile;
|
extern crate tempfile;
|
||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
use tempfile::tempdir;
|
use tempfile::tempdir;
|
||||||
use site::Site;
|
use site::Site;
|
||||||
|
use pagination::Paginator;
|
||||||
|
|
||||||
|
|
||||||
fn setup_site(name: &str) -> Site {
|
fn setup_site(name: &str) -> Site {
|
||||||
|
@ -20,8 +22,8 @@ fn setup_site(name: &str) -> Site {
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
fn bench_render_aliases(b: &mut test::Bencher) {
|
fn bench_render_aliases(b: &mut test::Bencher) {
|
||||||
let mut site = setup_site("huge-blog");
|
let mut site = setup_site("small-blog");
|
||||||
let tmp_dir = TempDir::new("benches").expect("create temp dir");
|
let tmp_dir = tempdir().expect("create temp dir");
|
||||||
let public = &tmp_dir.path().join("public");
|
let public = &tmp_dir.path().join("public");
|
||||||
site.set_output_path(&public);
|
site.set_output_path(&public);
|
||||||
b.iter(|| site.render_aliases().unwrap());
|
b.iter(|| site.render_aliases().unwrap());
|
||||||
|
@ -29,8 +31,8 @@ fn bench_render_aliases(b: &mut test::Bencher) {
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
fn bench_render_sitemap(b: &mut test::Bencher) {
|
fn bench_render_sitemap(b: &mut test::Bencher) {
|
||||||
let mut site = setup_site("huge-blog");
|
let mut site = setup_site("small-blog");
|
||||||
let tmp_dir = TempDir::new("benches").expect("create temp dir");
|
let tmp_dir = tempdir().expect("create temp dir");
|
||||||
let public = &tmp_dir.path().join("public");
|
let public = &tmp_dir.path().join("public");
|
||||||
site.set_output_path(&public);
|
site.set_output_path(&public);
|
||||||
b.iter(|| site.render_sitemap().unwrap());
|
b.iter(|| site.render_sitemap().unwrap());
|
||||||
|
@ -38,29 +40,30 @@ fn bench_render_sitemap(b: &mut test::Bencher) {
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
fn bench_render_rss_feed(b: &mut test::Bencher) {
|
fn bench_render_rss_feed(b: &mut test::Bencher) {
|
||||||
let mut site = setup_site("huge-blog");
|
let mut site = setup_site("small-blog");
|
||||||
let tmp_dir = TempDir::new("benches").expect("create temp dir");
|
let tmp_dir = tempdir().expect("create temp dir");
|
||||||
let public = &tmp_dir.path().join("public");
|
let public = &tmp_dir.path().join("public");
|
||||||
site.set_output_path(&public);
|
site.set_output_path(&public);
|
||||||
b.iter(|| site.render_rss_feed().unwrap());
|
b.iter(|| site.render_rss_feed(None, None).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
fn bench_render_categories(b: &mut test::Bencher) {
|
fn bench_render_taxonomies(b: &mut test::Bencher) {
|
||||||
let mut site = setup_site("huge-blog");
|
let mut site = setup_site("small-blog");
|
||||||
let tmp_dir = TempDir::new("benches").expect("create temp dir");
|
let tmp_dir = tempdir().expect("create temp dir");
|
||||||
let public = &tmp_dir.path().join("public");
|
let public = &tmp_dir.path().join("public");
|
||||||
site.set_output_path(&public);
|
site.set_output_path(&public);
|
||||||
b.iter(|| site.render_categories().unwrap());
|
b.iter(|| site.render_taxonomies().unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
fn bench_render_paginated(b: &mut test::Bencher) {
|
fn bench_render_paginated(b: &mut test::Bencher) {
|
||||||
let mut site = setup_site("medium-blog");
|
let mut site = setup_site("small-blog");
|
||||||
let tmp_dir = TempDir::new("benches").expect("create temp dir");
|
let tmp_dir = tempdir().expect("create temp dir");
|
||||||
let public = &tmp_dir.path().join("public");
|
let public = &tmp_dir.path().join("public");
|
||||||
site.set_output_path(&public);
|
site.set_output_path(&public);
|
||||||
let section = site.sections.values().collect::<Vec<_>>()[0];
|
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
|
section_entries
|
||||||
.into_par_iter()
|
.into_par_iter()
|
||||||
.filter(|entry| entry.as_path().file_name().unwrap() == "_index.md")
|
|
||||||
.map(|entry| {
|
.map(|entry| {
|
||||||
let path = entry.as_path();
|
let path = entry.as_path();
|
||||||
Section::from_file(path, config)
|
Section::from_file(path, config)
|
||||||
|
@ -200,7 +199,6 @@ impl Site {
|
||||||
|
|
||||||
page_entries
|
page_entries
|
||||||
.into_par_iter()
|
.into_par_iter()
|
||||||
.filter(|entry| entry.as_path().file_name().unwrap() != "_index.md")
|
|
||||||
.map(|entry| {
|
.map(|entry| {
|
||||||
let path = entry.as_path();
|
let path = entry.as_path();
|
||||||
Page::from_file(path, config)
|
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
|
// 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();
|
let index_path = self.index_section_path();
|
||||||
if let Some(ref index_section) = self.sections.get(&index_path) {
|
if let Some(ref index_section) = self.sections.get(&index_path) {
|
||||||
if self.config.build_search_index && !index_section.meta.in_search_index {
|
if self.config.build_search_index && !index_section.meta.in_search_index {
|
||||||
|
@ -260,6 +258,7 @@ impl Site {
|
||||||
let permalinks = &self.permalinks;
|
let permalinks = &self.permalinks;
|
||||||
let tera = &self.tera;
|
let tera = &self.tera;
|
||||||
let config = &self.config;
|
let config = &self.config;
|
||||||
|
let base_path = &self.base_path;
|
||||||
|
|
||||||
// TODO: avoid the duplication with function above for that part
|
// TODO: avoid the duplication with function above for that part
|
||||||
// This is needed in the first place because of silly borrow checker
|
// This is needed in the first place because of silly borrow checker
|
||||||
|
@ -271,13 +270,13 @@ impl Site {
|
||||||
self.pages.par_iter_mut()
|
self.pages.par_iter_mut()
|
||||||
.map(|(_, page)| {
|
.map(|(_, page)| {
|
||||||
let insert_anchor = pages_insert_anchors[&page.file.path];
|
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)
|
.fold(|| Ok(()), Result::and)
|
||||||
.reduce(|| Ok(()), Result::and)?;
|
.reduce(|| Ok(()), Result::and)?;
|
||||||
|
|
||||||
self.sections.par_iter_mut()
|
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)
|
.fold(|| Ok(()), Result::and)
|
||||||
.reduce(|| Ok(()), Result::and)?;
|
.reduce(|| Ok(()), Result::and)?;
|
||||||
|
|
||||||
|
@ -320,7 +319,7 @@ impl Site {
|
||||||
if render {
|
if render {
|
||||||
let insert_anchor = self.find_parent_section_insert_anchor(&self.pages[&path].file.parent);
|
let insert_anchor = self.find_parent_section_insert_anchor(&self.pages[&path].file.parent);
|
||||||
let page = self.pages.get_mut(&path).unwrap();
|
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)
|
Ok(prev)
|
||||||
|
@ -337,7 +336,7 @@ impl Site {
|
||||||
|
|
||||||
if render {
|
if render {
|
||||||
let section = self.sections.get_mut(&path).unwrap();
|
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)
|
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 {
|
if render_pages {
|
||||||
section
|
section
|
||||||
.pages
|
.pages
|
||||||
|
|
|
@ -19,7 +19,7 @@ fn can_parse_site() {
|
||||||
site.load().unwrap();
|
site.load().unwrap();
|
||||||
|
|
||||||
// Correct number of pages (sections are pages too)
|
// 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");
|
let posts_path = path.join("content").join("posts");
|
||||||
|
|
||||||
// Make sure we remove all the pwd + content from the sections
|
// 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")];
|
let posts_section = &site.sections[&posts_path.join("_index.md")];
|
||||||
assert_eq!(posts_section.subsections.len(), 1);
|
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")];
|
let tutorials_section = &site.sections[&posts_path.join("tutorials").join("_index.md")];
|
||||||
assert_eq!(tutorials_section.subsections.len(), 2);
|
assert_eq!(tutorials_section.subsections.len(), 2);
|
||||||
|
@ -321,22 +321,41 @@ fn can_build_site_with_pagination_for_section() {
|
||||||
"posts/page/1/index.html",
|
"posts/page/1/index.html",
|
||||||
"http-equiv=\"refresh\" content=\"0;url=https://replace-this-with-your-url.com/posts/\""
|
"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", "Page size: 2"));
|
||||||
assert!(file_contains!(public, "posts/index.html", "Current index: 1"));
|
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", "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", "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_eq!(file_contains!(public, "posts/index.html", "has_prev"), false);
|
||||||
|
|
||||||
assert!(file_exists!(public, "posts/page/2/index.html"));
|
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", "Page size: 2"));
|
||||||
assert!(file_contains!(public, "posts/page/2/index.html", "Current index: 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_prev"));
|
||||||
assert!(file_contains!(public, "posts/page/2/index.html", "has_next"));
|
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", "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]
|
#[test]
|
||||||
|
@ -397,10 +416,10 @@ fn can_build_rss_feed() {
|
||||||
|
|
||||||
assert!(Path::new(&public).exists());
|
assert!(Path::new(&public).exists());
|
||||||
assert!(file_exists!(public, "rss.xml"));
|
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"));
|
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, "elasticlunr.min.js"));
|
||||||
assert!(file_exists!(public, "search_index.en.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
|
## 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
|
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
|
```bash
|
||||||
└── with-assets
|
└── research
|
||||||
├── index.md
|
├── latest-experiment
|
||||||
└── yavascript.js
|
│ ├── index.md
|
||||||
|
│ └── yavascript.js
|
||||||
|
├── _index.md
|
||||||
|
└── research.jpg
|
||||||
```
|
```
|
||||||
|
|
||||||
By default, this page will get the folder name (`with-assets` in this case) as its slug.
|
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 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
|
It is possible to ignore selected asset files using the
|
||||||
[ignored_content](./documentation/getting-started/configuration.md) setting in the config file.
|
[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
|
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
|
`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
|
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
|
## 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 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.
|
`_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
|
## Front-matter
|
||||||
|
|
||||||
The `_index.md` file within a folder defines the content and metadata for that section. To set
|
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
|
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).
|
need to enable it in the [configuration](./documentation/getting-started/configuration.md).
|
||||||
|
|
||||||
Once this is done, Gutenberg will automatically highlight all code blocks
|
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.
|
interpreted as plain text.
|
||||||
|
|
||||||
Here is a full list of the supported languages and the short names you can use:
|
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"]
|
- Assembly x86 (NASM) -> ["asm", "inc", "nasm"]
|
||||||
- Crystal -> ["cr"]
|
- Crystal -> ["cr"]
|
||||||
- Elixir -> ["ex", "exs"]
|
- Elixir -> ["ex", "exs"]
|
||||||
- Elm -> ["elm"]
|
|
||||||
- Handlebars -> ["handlebars", "handlebars.html", "hbr", "hbrs", "hbs", "hdbs", "hjs", "mu", "mustache", "rac", "stache", "template", "tmpl"]
|
- Handlebars -> ["handlebars", "handlebars.html", "hbr", "hbrs", "hbs", "hdbs", "hjs", "mu", "mustache", "rac", "stache", "template", "tmpl"]
|
||||||
- Jinja2 -> ["j2", "jinja2"]
|
- Jinja2 -> ["j2", "jinja2"]
|
||||||
- Julia -> ["jl"]
|
- Julia -> ["jl"]
|
||||||
- Kotlin -> ["kt", "kts"]
|
- Kotlin -> ["kt", "kts"]
|
||||||
- Less -> ["less", "css.less"]
|
- Less -> ["less", "css.less"]
|
||||||
|
- MiniZinc (MZN) -> ["mzn", "dzn"]
|
||||||
- Nim -> ["nim", "nims"]
|
- Nim -> ["nim", "nims"]
|
||||||
- ASP -> ["asa"]
|
- ASP -> ["asa"]
|
||||||
- HTML (ASP) -> ["asp"]
|
- 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"]
|
- Diff -> ["diff", "patch"]
|
||||||
- Erlang -> ["erl", "hrl", "Emakefile", "emakefile"]
|
- Erlang -> ["erl", "hrl", "Emakefile", "emakefile"]
|
||||||
- HTML (Erlang) -> ["yaws"]
|
- 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"]
|
- Go -> ["go"]
|
||||||
- Graphviz (DOT) -> ["dot", "DOT", "gv"]
|
- Graphviz (DOT) -> ["dot", "DOT", "gv"]
|
||||||
- Groovy -> ["groovy", "gvy", "gradle"]
|
- Groovy -> ["groovy", "gvy", "gradle", "Jenkinsfile"]
|
||||||
- HTML -> ["html", "htm", "shtml", "xhtml", "inc", "tmpl", "tpl"]
|
- HTML -> ["html", "htm", "shtml", "xhtml"]
|
||||||
- Haskell -> ["hs"]
|
- Haskell -> ["hs"]
|
||||||
- Literate Haskell -> ["lhs"]
|
- Literate Haskell -> ["lhs"]
|
||||||
- Java Server Page (JSP) -> ["jsp"]
|
- 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"]
|
- TeX -> ["sty", "cls"]
|
||||||
- Lisp -> ["lisp", "cl", "clisp", "l", "mud", "el", "scm", "ss", "lsp", "fasl"]
|
- Lisp -> ["lisp", "cl", "clisp", "l", "mud", "el", "scm", "ss", "lsp", "fasl"]
|
||||||
- Lua -> ["lua"]
|
- 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"]
|
- Markdown -> ["md", "mdown", "markdown", "markdn"]
|
||||||
- MATLAB -> ["matlab"]
|
- MATLAB -> ["matlab"]
|
||||||
- OCaml -> ["ml", "mli"]
|
- 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"]
|
- Rust -> ["rs"]
|
||||||
- SQL -> ["sql", "ddl", "dml"]
|
- SQL -> ["sql", "ddl", "dml"]
|
||||||
- Scala -> ["scala", "sbt"]
|
- 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"]
|
- HTML (Tcl) -> ["adp"]
|
||||||
- Tcl -> ["tcl"]
|
- Tcl -> ["tcl"]
|
||||||
- Textile -> ["textile"]
|
- Textile -> ["textile"]
|
||||||
- XML -> ["xml", "xsd", "xslt", "tld", "dtml", "rss", "opml", "svg"]
|
- XML -> ["xml", "xsd", "xslt", "tld", "dtml", "rss", "opml", "svg"]
|
||||||
- YAML -> ["yaml", "yml", "sublime-syntax"]
|
- YAML -> ["yaml", "yml", "sublime-syntax"]
|
||||||
- SWI-Prolog -> ["pro"]
|
- 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"]
|
- 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"]
|
- Linker Script -> ["ld"]
|
||||||
- TOML -> ["toml", "tml"]
|
- TOML -> ["toml", "tml"]
|
||||||
- TypeScript -> ["ts"]
|
- TypeScript -> ["ts"]
|
||||||
- TypeScriptReact -> ["tsx"]
|
- TypeScriptReact -> ["tsx"]
|
||||||
- VimL -> ["vim"]
|
- 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).
|
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 = ["*.{graphml,xlsx}", "temp.*"]
|
||||||
ignored_content = []
|
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
|
# Optional translation object. The key if present should be a language code
|
||||||
[translations]
|
[translations]
|
||||||
|
|
||||||
|
|
|
@ -77,6 +77,8 @@ word_count: Number;
|
||||||
reading_time: Number;
|
reading_time: Number;
|
||||||
// See the Table of contents section below for more details
|
// See the Table of contents section below for more details
|
||||||
toc: Array<Header>;
|
toc: Array<Header>;
|
||||||
|
// Paths of colocated assets, relative to the content directory
|
||||||
|
assets: Array<String>;
|
||||||
```
|
```
|
||||||
|
|
||||||
## Table of contents
|
## Table of contents
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 6f0da0ace6d881648dbfb2c3c1a3799ff7d5c54a
|
Subproject commit d3ddfe7b51e01140db209f57af3bc47d04d9ac0a
|
65
sublime_syntaxes/MZN.sublime-syntax
Normal file
65
sublime_syntaxes/MZN.sublime-syntax
Normal file
|
@ -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
|
Binary file not shown.
Binary file not shown.
|
@ -1 +1 @@
|
||||||
Subproject commit 1deb0745d7cfd069bdd5652878e321019b1ed229
|
Subproject commit 36f8239551f09ed354a6872a6cc2fd0168883446
|
|
@ -10,5 +10,7 @@ taxonomies = [
|
||||||
{name = "categories", rss = true},
|
{name = "categories", rss = true},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
extra_syntaxes = ["syntaxes"]
|
||||||
|
|
||||||
[extra.author]
|
[extra.author]
|
||||||
name = "Vincent Prouillet"
|
name = "Vincent Prouillet"
|
||||||
|
|
|
@ -3,4 +3,5 @@ title = "Posts"
|
||||||
paginate_by = 2
|
paginate_by = 2
|
||||||
template = "section_paginated.html"
|
template = "section_paginated.html"
|
||||||
insert_anchor_links = "left"
|
insert_anchor_links = "left"
|
||||||
|
sort_by = "date"
|
||||||
+++
|
+++
|
||||||
|
|
9
test_site/content/posts/extra_syntax.md
Normal file
9
test_site/content/posts/extra_syntax.md
Normal file
|
@ -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"
|
title = "With assets"
|
||||||
description = "hey there"
|
description = "hey there"
|
||||||
|
date = 2015-03-01
|
||||||
+++
|
+++
|
||||||
|
|
||||||
Hello world
|
Hello world
|
||||||
|
|
10
test_site/syntaxes/test.sublime-syntax
Normal file
10
test_site/syntaxes/test.sublime-syntax
Normal file
|
@ -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 %}
|
{% block content %}
|
||||||
{{ page.content | safe }}
|
{{ page.content | safe }}
|
||||||
|
|
||||||
{% if page.previous %}Previous article: {{ page.previous.permalink }}{% endif %}
|
{% if page.earlier %}Previous article: {{ page.earlier.permalink }}{% endif %}
|
||||||
{% if page.next %}Next article: {{ page.next.permalink }}{% endif %}
|
{% if page.later %}Next article: {{ page.later.permalink }}{% endif %}
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
{% for tag in tags %}
|
{% for tag in terms %}
|
||||||
{{ tag.name }} {{ tag.slug }} {{ tag.pages | length }}
|
{{ tag.name }} {{ tag.slug }} {{ tag.pages | length }}
|
||||||
{% endfor %}
|
{% endfor %}
|
|
@ -1,6 +1,6 @@
|
||||||
Tag: {{ tag.name }}
|
Tag: {{ term.name }}
|
||||||
|
|
||||||
{% for page in tag.pages %}
|
{% for page in term.pages %}
|
||||||
<article>
|
<article>
|
||||||
<h3 class="post__title"><a href="{{ page.permalink }}">{{ page.title }}</a></h3>
|
<h3 class="post__title"><a href="{{ page.permalink }}">{{ page.title }}</a></h3>
|
||||||
</article>
|
</article>
|
Loading…
Reference in a new issue