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