Yaml frontmatter (#990)
* Accept dates presented as strings Still, if we find a string we involve the TOML parser to make sure the content of said string would be a correct TOML date. In a pure TOML world this isn't exactly relevant, but this will come in handy when using a YAML deserializer. Signed-off-by: Kevin Ottens <ervin@ipsquad.net> * Add serde_yaml and test-case dependencies This will be necessary for the YAML frontmatter handling and corresponding unit tests. Signed-off-by: Kevin Ottens <ervin@ipsquad.net> * Add YAML front matter handling Signed-off-by: Kevin Ottens <ervin@ipsquad.net> * Switch RawFrontMatter enum to wrap &str instead of String Signed-off-by: Kevin Ottens <ervin@ipsquad.net> * Update the documentation to mention YAML frontmatter This is just a light update on purpose. There would be no point in pushing YAML too much, this is mainly here to help people with a backlog of posts to transition. Signed-off-by: Kevin Ottens <ervin@ipsquad.net>
This commit is contained in:
parent
f20c52b872
commit
4754cb5792
26
Cargo.lock
generated
26
Cargo.lock
generated
|
@ -641,7 +641,9 @@ dependencies = [
|
||||||
"regex",
|
"regex",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
|
"serde_yaml",
|
||||||
"tera",
|
"tera",
|
||||||
|
"test-case",
|
||||||
"toml",
|
"toml",
|
||||||
"utils",
|
"utils",
|
||||||
]
|
]
|
||||||
|
@ -2341,6 +2343,18 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_yaml"
|
||||||
|
version = "0.8.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f7baae0a99f1a324984bcdc5f0718384c1f69775f1c7eec8b859b71b443e3fd7"
|
||||||
|
dependencies = [
|
||||||
|
"dtoa",
|
||||||
|
"linked-hash-map",
|
||||||
|
"serde",
|
||||||
|
"yaml-rust",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha-1"
|
name = "sha-1"
|
||||||
version = "0.8.2"
|
version = "0.8.2"
|
||||||
|
@ -2625,6 +2639,18 @@ dependencies = [
|
||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "test-case"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "199464148b42bcf3da8b2a56f6ee87ca68f47402496d1268849291ec9fb463c8"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "textwrap"
|
name = "textwrap"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
|
|
|
@ -9,9 +9,14 @@ tera = "1"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
serde = "1"
|
serde = "1"
|
||||||
serde_derive = "1"
|
serde_derive = "1"
|
||||||
|
serde_yaml = "0.8"
|
||||||
toml = "0.5"
|
toml = "0.5"
|
||||||
regex = "1"
|
regex = "1"
|
||||||
lazy_static = "1"
|
lazy_static = "1"
|
||||||
|
|
||||||
errors = { path = "../errors" }
|
errors = { path = "../errors" }
|
||||||
utils = { path = "../utils" }
|
utils = { path = "../utils" }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
test-case = "1.0"
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,9 @@ use serde_derive::{Deserialize, Serialize};
|
||||||
|
|
||||||
use errors::{bail, Error, Result};
|
use errors::{bail, Error, Result};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
use serde_yaml;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use toml;
|
||||||
|
|
||||||
mod page;
|
mod page;
|
||||||
mod section;
|
mod section;
|
||||||
|
@ -12,8 +14,31 @@ pub use page::PageFrontMatter;
|
||||||
pub use section::SectionFrontMatter;
|
pub use section::SectionFrontMatter;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref PAGE_RE: Regex =
|
static ref TOML_RE: Regex =
|
||||||
Regex::new(r"^[[:space:]]*\+\+\+(\r?\n(?s).*?(?-s))\+\+\+\r?\n?((?s).*(?-s))$").unwrap();
|
Regex::new(r"^[[:space:]]*\+\+\+(\r?\n(?s).*?(?-s))\+\+\+\r?\n?((?s).*(?-s))$").unwrap();
|
||||||
|
static ref YAML_RE: Regex =
|
||||||
|
Regex::new(r"^[[:space:]]*---(\r?\n(?s).*?(?-s))---\r?\n?((?s).*(?-s))$").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum RawFrontMatter<'a> {
|
||||||
|
Toml(&'a str),
|
||||||
|
Yaml(&'a str),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawFrontMatter<'_> {
|
||||||
|
fn deserialize<T>(&self) -> Result<T>
|
||||||
|
where
|
||||||
|
T: serde::de::DeserializeOwned,
|
||||||
|
{
|
||||||
|
let f: T = match self {
|
||||||
|
RawFrontMatter::Toml(s) => toml::from_str(s)?,
|
||||||
|
RawFrontMatter::Yaml(s) => match serde_yaml::from_str(s) {
|
||||||
|
Ok(d) => d,
|
||||||
|
Err(e) => bail!(format!("YAML deserialize error: {:?}", e)),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
Ok(f)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
@ -37,20 +62,30 @@ pub enum InsertAnchor {
|
||||||
|
|
||||||
/// Split a file between the front matter and its content
|
/// Split a file between the front matter and its content
|
||||||
/// Will return an error if the front matter wasn't found
|
/// Will return an error if the front matter wasn't found
|
||||||
fn split_content<'c>(file_path: &Path, content: &'c str) -> Result<(&'c str, &'c str)> {
|
fn split_content<'c>(file_path: &Path, content: &'c str) -> Result<(RawFrontMatter<'c>, &'c str)> {
|
||||||
if !PAGE_RE.is_match(content) {
|
let (re, is_toml) = if TOML_RE.is_match(content) {
|
||||||
|
(&TOML_RE as &Regex, true)
|
||||||
|
} else if YAML_RE.is_match(content) {
|
||||||
|
(&YAML_RE as &Regex, false)
|
||||||
|
} else {
|
||||||
bail!(
|
bail!(
|
||||||
"Couldn't find front matter in `{}`. Did you forget to add `+++`?",
|
"Couldn't find front matter in `{}`. Did you forget to add `+++` or `---`?",
|
||||||
file_path.to_string_lossy()
|
file_path.to_string_lossy()
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
// 2. extract the front matter and the content
|
// 2. extract the front matter and the content
|
||||||
let caps = PAGE_RE.captures(content).unwrap();
|
let caps = re.captures(content).unwrap();
|
||||||
// caps[0] is the full match
|
// caps[0] is the full match
|
||||||
// caps[1] => front matter
|
// caps[1] => front matter
|
||||||
// caps[2] => content
|
// caps[2] => content
|
||||||
Ok((caps.get(1).unwrap().as_str(), caps.get(2).unwrap().as_str()))
|
let front_matter = caps.get(1).unwrap().as_str();
|
||||||
|
let content = caps.get(2).unwrap().as_str();
|
||||||
|
if is_toml {
|
||||||
|
Ok((RawFrontMatter::Toml(front_matter), content))
|
||||||
|
} else {
|
||||||
|
Ok((RawFrontMatter::Yaml(front_matter), content))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Split a file between the front matter and its content.
|
/// Split a file between the front matter and its content.
|
||||||
|
@ -88,71 +123,125 @@ pub fn split_page_content<'c>(
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use test_case::test_case;
|
||||||
|
|
||||||
use super::{split_page_content, split_section_content};
|
use super::{split_page_content, split_section_content};
|
||||||
|
|
||||||
#[test]
|
#[test_case(r#"
|
||||||
fn can_split_page_content_valid() {
|
|
||||||
let content = r#"
|
|
||||||
+++
|
+++
|
||||||
title = "Title"
|
title = "Title"
|
||||||
description = "hey there"
|
description = "hey there"
|
||||||
date = 2002-10-12
|
date = 2002-10-12
|
||||||
+++
|
+++
|
||||||
Hello
|
Hello
|
||||||
"#;
|
"#; "toml")]
|
||||||
|
#[test_case(r#"
|
||||||
|
---
|
||||||
|
title: Title
|
||||||
|
description: hey there
|
||||||
|
date: 2002-10-12
|
||||||
|
---
|
||||||
|
Hello
|
||||||
|
"#; "yaml")]
|
||||||
|
fn can_split_page_content_valid(content: &str) {
|
||||||
let (front_matter, content) = split_page_content(Path::new(""), content).unwrap();
|
let (front_matter, content) = split_page_content(Path::new(""), content).unwrap();
|
||||||
assert_eq!(content, "Hello\n");
|
assert_eq!(content, "Hello\n");
|
||||||
assert_eq!(front_matter.title.unwrap(), "Title");
|
assert_eq!(front_matter.title.unwrap(), "Title");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test_case(r#"
|
||||||
fn can_split_section_content_valid() {
|
|
||||||
let content = r#"
|
|
||||||
+++
|
+++
|
||||||
paginate_by = 10
|
paginate_by = 10
|
||||||
+++
|
+++
|
||||||
Hello
|
Hello
|
||||||
"#;
|
"#; "toml")]
|
||||||
|
#[test_case(r#"
|
||||||
|
---
|
||||||
|
paginate_by: 10
|
||||||
|
---
|
||||||
|
Hello
|
||||||
|
"#; "yaml")]
|
||||||
|
fn can_split_section_content_valid(content: &str) {
|
||||||
let (front_matter, content) = split_section_content(Path::new(""), content).unwrap();
|
let (front_matter, content) = split_section_content(Path::new(""), content).unwrap();
|
||||||
assert_eq!(content, "Hello\n");
|
assert_eq!(content, "Hello\n");
|
||||||
assert!(front_matter.is_paginated());
|
assert!(front_matter.is_paginated());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test_case(r#"
|
||||||
fn can_split_content_with_only_frontmatter_valid() {
|
|
||||||
let content = r#"
|
|
||||||
+++
|
+++
|
||||||
title = "Title"
|
title = "Title"
|
||||||
description = "hey there"
|
description = "hey there"
|
||||||
date = 2002-10-12
|
date = 2002-10-12
|
||||||
+++"#;
|
+++"#; "toml")]
|
||||||
|
#[test_case(r#"
|
||||||
|
---
|
||||||
|
title: Title
|
||||||
|
description: hey there
|
||||||
|
date: 2002-10-12
|
||||||
|
---"#; "yaml")]
|
||||||
|
fn can_split_content_with_only_frontmatter_valid(content: &str) {
|
||||||
let (front_matter, content) = split_page_content(Path::new(""), content).unwrap();
|
let (front_matter, content) = split_page_content(Path::new(""), content).unwrap();
|
||||||
assert_eq!(content, "");
|
assert_eq!(content, "");
|
||||||
assert_eq!(front_matter.title.unwrap(), "Title");
|
assert_eq!(front_matter.title.unwrap(), "Title");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test_case(r#"
|
||||||
fn can_split_content_lazily() {
|
|
||||||
let content = r#"
|
|
||||||
+++
|
+++
|
||||||
title = "Title"
|
title = "Title"
|
||||||
description = "hey there"
|
description = "hey there"
|
||||||
date = 2002-10-02T15:00:00Z
|
date = 2002-10-02T15:00:00Z
|
||||||
+++
|
+++
|
||||||
+++"#;
|
+++"#, "+++"; "toml with pluses in content")]
|
||||||
let (front_matter, content) = split_page_content(Path::new(""), content).unwrap();
|
#[test_case(r#"
|
||||||
assert_eq!(content, "+++");
|
|
||||||
assert_eq!(front_matter.title.unwrap(), "Title");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn errors_if_cannot_locate_frontmatter() {
|
|
||||||
let content = r#"
|
|
||||||
+++
|
+++
|
||||||
title = "Title"
|
title = "Title"
|
||||||
description = "hey there"
|
description = "hey there"
|
||||||
date = 2002-10-12"#;
|
date = 2002-10-02T15:00:00Z
|
||||||
|
+++
|
||||||
|
---"#, "---"; "toml with minuses in content")]
|
||||||
|
#[test_case(r#"
|
||||||
|
---
|
||||||
|
title: Title
|
||||||
|
description: hey there
|
||||||
|
date: 2002-10-02T15:00:00Z
|
||||||
|
---
|
||||||
|
+++"#, "+++"; "yaml with pluses in content")]
|
||||||
|
#[test_case(r#"
|
||||||
|
---
|
||||||
|
title: Title
|
||||||
|
description: hey there
|
||||||
|
date: 2002-10-02T15:00:00Z
|
||||||
|
---
|
||||||
|
---"#, "---"; "yaml with minuses in content")]
|
||||||
|
fn can_split_content_lazily(content: &str, expected: &str) {
|
||||||
|
let (front_matter, content) = split_page_content(Path::new(""), content).unwrap();
|
||||||
|
assert_eq!(content, expected);
|
||||||
|
assert_eq!(front_matter.title.unwrap(), "Title");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test_case(r#"
|
||||||
|
+++
|
||||||
|
title = "Title"
|
||||||
|
description = "hey there"
|
||||||
|
date = 2002-10-12"#; "toml")]
|
||||||
|
#[test_case(r#"
|
||||||
|
+++
|
||||||
|
title = "Title"
|
||||||
|
description = "hey there"
|
||||||
|
date = 2002-10-12
|
||||||
|
---"#; "toml unmatched")]
|
||||||
|
#[test_case(r#"
|
||||||
|
---
|
||||||
|
title: Title
|
||||||
|
description: hey there
|
||||||
|
date: 2002-10-12"#; "yaml")]
|
||||||
|
#[test_case(r#"
|
||||||
|
---
|
||||||
|
title: Title
|
||||||
|
description: hey there
|
||||||
|
date: 2002-10-12
|
||||||
|
+++"#; "yaml unmatched")]
|
||||||
|
fn errors_if_cannot_locate_frontmatter(content: &str) {
|
||||||
let res = split_page_content(Path::new(""), content);
|
let res = split_page_content(Path::new(""), content);
|
||||||
assert!(res.is_err());
|
assert!(res.is_err());
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,8 @@ use tera::{Map, Value};
|
||||||
use errors::{bail, Result};
|
use errors::{bail, Result};
|
||||||
use utils::de::{fix_toml_dates, from_toml_datetime};
|
use utils::de::{fix_toml_dates, from_toml_datetime};
|
||||||
|
|
||||||
|
use crate::RawFrontMatter;
|
||||||
|
|
||||||
/// The front matter of every page
|
/// The front matter of every page
|
||||||
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
@ -69,11 +71,8 @@ fn parse_datetime(d: &str) -> Option<NaiveDateTime> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PageFrontMatter {
|
impl PageFrontMatter {
|
||||||
pub fn parse(toml: &str) -> Result<PageFrontMatter> {
|
pub fn parse(raw: &RawFrontMatter) -> Result<PageFrontMatter> {
|
||||||
let mut f: PageFrontMatter = match toml::from_str(toml) {
|
let mut f: PageFrontMatter = raw.deserialize()?;
|
||||||
Ok(d) => d,
|
|
||||||
Err(e) => bail!(e),
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(ref slug) = f.slug {
|
if let Some(ref slug) = f.slug {
|
||||||
if slug == "" {
|
if slug == "" {
|
||||||
|
@ -140,21 +139,27 @@ impl Default for PageFrontMatter {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::PageFrontMatter;
|
use super::PageFrontMatter;
|
||||||
|
use super::RawFrontMatter;
|
||||||
use tera::to_value;
|
use tera::to_value;
|
||||||
|
use test_case::test_case;
|
||||||
|
|
||||||
#[test]
|
#[test_case(&RawFrontMatter::Toml(r#" "#); "toml")]
|
||||||
fn can_have_empty_front_matter() {
|
#[test_case(&RawFrontMatter::Toml(r#" "#); "yaml")]
|
||||||
let content = r#" "#;
|
fn can_have_empty_front_matter(content: &RawFrontMatter) {
|
||||||
let res = PageFrontMatter::parse(content);
|
let res = PageFrontMatter::parse(content);
|
||||||
println!("{:?}", res);
|
println!("{:?}", res);
|
||||||
assert!(res.is_ok());
|
assert!(res.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test_case(&RawFrontMatter::Toml(r#"
|
||||||
fn can_parse_valid_front_matter() {
|
title = "Hello"
|
||||||
let content = r#"
|
description = "hey there"
|
||||||
title = "Hello"
|
"#); "toml")]
|
||||||
description = "hey there""#;
|
#[test_case(&RawFrontMatter::Yaml(r#"
|
||||||
|
title: Hello
|
||||||
|
description: hey there
|
||||||
|
"#); "yaml")]
|
||||||
|
fn can_parse_valid_front_matter(content: &RawFrontMatter) {
|
||||||
let res = PageFrontMatter::parse(content);
|
let res = PageFrontMatter::parse(content);
|
||||||
assert!(res.is_ok());
|
assert!(res.is_ok());
|
||||||
let res = res.unwrap();
|
let res = res.unwrap();
|
||||||
|
@ -162,183 +167,281 @@ mod tests {
|
||||||
assert_eq!(res.description.unwrap(), "hey there".to_string())
|
assert_eq!(res.description.unwrap(), "hey there".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test_case(&RawFrontMatter::Toml(r#"title = |\n"#); "toml")]
|
||||||
fn errors_with_invalid_front_matter() {
|
#[test_case(&RawFrontMatter::Yaml(r#"title: |\n"#); "yaml")]
|
||||||
let content = r#"title = 1\n"#;
|
fn errors_with_invalid_front_matter(content: &RawFrontMatter) {
|
||||||
let res = PageFrontMatter::parse(content);
|
let res = PageFrontMatter::parse(content);
|
||||||
assert!(res.is_err());
|
assert!(res.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test_case(&RawFrontMatter::Toml(r#"
|
||||||
fn errors_on_present_but_empty_slug() {
|
title = "Hello"
|
||||||
let content = r#"
|
description = "hey there"
|
||||||
title = "Hello"
|
slug = ""
|
||||||
description = "hey there"
|
"#); "toml")]
|
||||||
slug = """#;
|
#[test_case(&RawFrontMatter::Yaml(r#"
|
||||||
|
title: Hello
|
||||||
|
description: hey there
|
||||||
|
slug: ""
|
||||||
|
"#); "yaml")]
|
||||||
|
fn errors_on_present_but_empty_slug(content: &RawFrontMatter) {
|
||||||
let res = PageFrontMatter::parse(content);
|
let res = PageFrontMatter::parse(content);
|
||||||
assert!(res.is_err());
|
assert!(res.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test_case(&RawFrontMatter::Toml(r#"
|
||||||
fn errors_on_present_but_empty_path() {
|
title = "Hello"
|
||||||
let content = r#"
|
description = "hey there"
|
||||||
title = "Hello"
|
path = ""
|
||||||
description = "hey there"
|
"#); "toml")]
|
||||||
path = """#;
|
#[test_case(&RawFrontMatter::Yaml(r#"
|
||||||
|
title: Hello
|
||||||
|
description: hey there
|
||||||
|
path: ""
|
||||||
|
"#); "yaml")]
|
||||||
|
fn errors_on_present_but_empty_path(content: &RawFrontMatter) {
|
||||||
let res = PageFrontMatter::parse(content);
|
let res = PageFrontMatter::parse(content);
|
||||||
assert!(res.is_err());
|
assert!(res.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test_case(&RawFrontMatter::Toml(r#"
|
||||||
fn can_parse_date_yyyy_mm_dd() {
|
title = "Hello"
|
||||||
let content = r#"
|
description = "hey there"
|
||||||
title = "Hello"
|
date = 2016-10-10
|
||||||
description = "hey there"
|
"#); "toml")]
|
||||||
date = 2016-10-10
|
#[test_case(&RawFrontMatter::Yaml(r#"
|
||||||
"#;
|
title: Hello
|
||||||
|
description: hey there
|
||||||
|
date: 2016-10-10
|
||||||
|
"#); "yaml")]
|
||||||
|
fn can_parse_date_yyyy_mm_dd(content: &RawFrontMatter) {
|
||||||
let res = PageFrontMatter::parse(content).unwrap();
|
let res = PageFrontMatter::parse(content).unwrap();
|
||||||
assert!(res.datetime.is_some());
|
assert!(res.datetime.is_some());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test_case(&RawFrontMatter::Toml(r#"
|
||||||
fn can_parse_date_rfc3339() {
|
title = "Hello"
|
||||||
let content = r#"
|
description = "hey there"
|
||||||
title = "Hello"
|
date = 2002-10-02T15:00:00Z
|
||||||
description = "hey there"
|
"#); "toml")]
|
||||||
date = 2002-10-02T15:00:00Z
|
#[test_case(&RawFrontMatter::Yaml(r#"
|
||||||
"#;
|
title: Hello
|
||||||
|
description: hey there
|
||||||
|
date: 2002-10-02T15:00:00Z
|
||||||
|
"#); "yaml")]
|
||||||
|
fn can_parse_date_rfc3339(content: &RawFrontMatter) {
|
||||||
let res = PageFrontMatter::parse(content).unwrap();
|
let res = PageFrontMatter::parse(content).unwrap();
|
||||||
assert!(res.datetime.is_some());
|
assert!(res.datetime.is_some());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test_case(&RawFrontMatter::Toml(r#"
|
||||||
fn can_parse_date_rfc3339_without_timezone() {
|
title = "Hello"
|
||||||
let content = r#"
|
description = "hey there"
|
||||||
title = "Hello"
|
date = 2002-10-02T15:00:00
|
||||||
description = "hey there"
|
"#); "toml")]
|
||||||
date = 2002-10-02T15:00:00
|
#[test_case(&RawFrontMatter::Yaml(r#"
|
||||||
"#;
|
title: Hello
|
||||||
|
description: hey there
|
||||||
|
date: 2002-10-02T15:00:00
|
||||||
|
"#); "yaml")]
|
||||||
|
fn can_parse_date_rfc3339_without_timezone(content: &RawFrontMatter) {
|
||||||
let res = PageFrontMatter::parse(content).unwrap();
|
let res = PageFrontMatter::parse(content).unwrap();
|
||||||
assert!(res.datetime.is_some());
|
assert!(res.datetime.is_some());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test_case(&RawFrontMatter::Toml(r#"
|
||||||
fn can_parse_date_rfc3339_with_space() {
|
title = "Hello"
|
||||||
let content = r#"
|
description = "hey there"
|
||||||
title = "Hello"
|
date = 2002-10-02 15:00:00+02:00
|
||||||
description = "hey there"
|
"#); "toml")]
|
||||||
date = 2002-10-02 15:00:00+02:00
|
#[test_case(&RawFrontMatter::Yaml(r#"
|
||||||
"#;
|
title: Hello
|
||||||
|
description: hey there
|
||||||
|
date: 2002-10-02 15:00:00+02:00
|
||||||
|
"#); "yaml")]
|
||||||
|
fn can_parse_date_rfc3339_with_space(content: &RawFrontMatter) {
|
||||||
let res = PageFrontMatter::parse(content).unwrap();
|
let res = PageFrontMatter::parse(content).unwrap();
|
||||||
assert!(res.datetime.is_some());
|
assert!(res.datetime.is_some());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test_case(&RawFrontMatter::Toml(r#"
|
||||||
fn can_parse_date_rfc3339_with_space_without_timezone() {
|
title = "Hello"
|
||||||
let content = r#"
|
description = "hey there"
|
||||||
title = "Hello"
|
date = 2002-10-02 15:00:00
|
||||||
description = "hey there"
|
"#); "toml")]
|
||||||
date = 2002-10-02 15:00:00
|
#[test_case(&RawFrontMatter::Yaml(r#"
|
||||||
"#;
|
title: Hello
|
||||||
|
description: hey there
|
||||||
|
date: 2002-10-02 15:00:00
|
||||||
|
"#); "yaml")]
|
||||||
|
fn can_parse_date_rfc3339_with_space_without_timezone(content: &RawFrontMatter) {
|
||||||
let res = PageFrontMatter::parse(content).unwrap();
|
let res = PageFrontMatter::parse(content).unwrap();
|
||||||
assert!(res.datetime.is_some());
|
assert!(res.datetime.is_some());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test_case(&RawFrontMatter::Toml(r#"
|
||||||
fn can_parse_date_rfc3339_with_microseconds() {
|
title = "Hello"
|
||||||
let content = r#"
|
description = "hey there"
|
||||||
title = "Hello"
|
date = 2002-10-02T15:00:00.123456Z
|
||||||
description = "hey there"
|
"#); "toml")]
|
||||||
date = 2002-10-02T15:00:00.123456Z
|
#[test_case(&RawFrontMatter::Yaml(r#"
|
||||||
"#;
|
title: Hello
|
||||||
|
description: hey there
|
||||||
|
date: 2002-10-02T15:00:00.123456Z
|
||||||
|
"#); "yaml")]
|
||||||
|
fn can_parse_date_rfc3339_with_microseconds(content: &RawFrontMatter) {
|
||||||
let res = PageFrontMatter::parse(content).unwrap();
|
let res = PageFrontMatter::parse(content).unwrap();
|
||||||
assert!(res.datetime.is_some());
|
assert!(res.datetime.is_some());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test_case(&RawFrontMatter::Toml(r#"
|
||||||
fn cannot_parse_random_date_format() {
|
title = "Hello"
|
||||||
let content = r#"
|
description = "hey there"
|
||||||
title = "Hello"
|
date = 2002/10/12
|
||||||
description = "hey there"
|
"#); "toml")]
|
||||||
date = 2002/10/12"#;
|
#[test_case(&RawFrontMatter::Yaml(r#"
|
||||||
|
title: Hello
|
||||||
|
description: hey there
|
||||||
|
date: 2002/10/12
|
||||||
|
"#); "yaml")]
|
||||||
|
fn cannot_parse_random_date_format(content: &RawFrontMatter) {
|
||||||
let res = PageFrontMatter::parse(content);
|
let res = PageFrontMatter::parse(content);
|
||||||
assert!(res.is_err());
|
assert!(res.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test_case(&RawFrontMatter::Toml(r#"
|
||||||
fn cannot_parse_invalid_date_format() {
|
title = "Hello"
|
||||||
let content = r#"
|
description = "hey there"
|
||||||
title = "Hello"
|
date = 2002-14-01
|
||||||
description = "hey there"
|
"#); "toml")]
|
||||||
date = 2002-14-01"#;
|
#[test_case(&RawFrontMatter::Yaml(r#"
|
||||||
|
title: Hello
|
||||||
|
description: hey there
|
||||||
|
date: 2002-14-01
|
||||||
|
"#); "yaml")]
|
||||||
|
fn cannot_parse_invalid_date_format(content: &RawFrontMatter) {
|
||||||
let res = PageFrontMatter::parse(content);
|
let res = PageFrontMatter::parse(content);
|
||||||
assert!(res.is_err());
|
assert!(res.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test_case(&RawFrontMatter::Toml(r#"
|
||||||
fn cannot_parse_date_as_string() {
|
title = "Hello"
|
||||||
let content = r#"
|
description = "hey there"
|
||||||
title = "Hello"
|
date = "2016-10-10"
|
||||||
description = "hey there"
|
"#); "toml")]
|
||||||
date = "2002-14-01""#;
|
#[test_case(&RawFrontMatter::Yaml(r#"
|
||||||
|
title: Hello
|
||||||
|
description: hey there
|
||||||
|
date: "2016-10-10"
|
||||||
|
"#); "yaml")]
|
||||||
|
fn can_parse_valid_date_as_string(content: &RawFrontMatter) {
|
||||||
|
let res = PageFrontMatter::parse(content).unwrap();
|
||||||
|
assert!(res.date.is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test_case(&RawFrontMatter::Toml(r#"
|
||||||
|
title = "Hello"
|
||||||
|
description = "hey there"
|
||||||
|
date = "2002-14-01"
|
||||||
|
"#); "toml")]
|
||||||
|
#[test_case(&RawFrontMatter::Yaml(r#"
|
||||||
|
title: Hello
|
||||||
|
description: hey there
|
||||||
|
date: "2002-14-01"
|
||||||
|
"#); "yaml")]
|
||||||
|
fn cannot_parse_invalid_date_as_string(content: &RawFrontMatter) {
|
||||||
let res = PageFrontMatter::parse(content);
|
let res = PageFrontMatter::parse(content);
|
||||||
assert!(res.is_err());
|
assert!(res.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test_case(&RawFrontMatter::Toml(r#"
|
||||||
fn can_parse_dates_in_extra() {
|
title = "Hello"
|
||||||
let content = r#"
|
description = "hey there"
|
||||||
title = "Hello"
|
|
||||||
description = "hey there"
|
|
||||||
|
|
||||||
[extra]
|
[extra]
|
||||||
some-date = 2002-14-01"#;
|
some-date = 2002-14-01
|
||||||
|
"#); "toml")]
|
||||||
|
#[test_case(&RawFrontMatter::Yaml(r#"
|
||||||
|
title: Hello
|
||||||
|
description: hey there
|
||||||
|
|
||||||
|
extra:
|
||||||
|
some-date: 2002-14-01
|
||||||
|
"#); "yaml")]
|
||||||
|
fn can_parse_dates_in_extra(content: &RawFrontMatter) {
|
||||||
let res = PageFrontMatter::parse(content);
|
let res = PageFrontMatter::parse(content);
|
||||||
println!("{:?}", res);
|
println!("{:?}", res);
|
||||||
assert!(res.is_ok());
|
assert!(res.is_ok());
|
||||||
assert_eq!(res.unwrap().extra["some-date"], to_value("2002-14-01").unwrap());
|
assert_eq!(res.unwrap().extra["some-date"], to_value("2002-14-01").unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test_case(&RawFrontMatter::Toml(r#"
|
||||||
fn can_parse_nested_dates_in_extra() {
|
title = "Hello"
|
||||||
let content = r#"
|
description = "hey there"
|
||||||
title = "Hello"
|
|
||||||
description = "hey there"
|
|
||||||
|
|
||||||
[extra.something]
|
[extra.something]
|
||||||
some-date = 2002-14-01"#;
|
some-date = 2002-14-01
|
||||||
|
"#); "toml")]
|
||||||
|
#[test_case(&RawFrontMatter::Yaml(r#"
|
||||||
|
title: Hello
|
||||||
|
description: hey there
|
||||||
|
|
||||||
|
extra:
|
||||||
|
something:
|
||||||
|
some-date: 2002-14-01
|
||||||
|
"#); "yaml")]
|
||||||
|
fn can_parse_nested_dates_in_extra(content: &RawFrontMatter) {
|
||||||
let res = PageFrontMatter::parse(content);
|
let res = PageFrontMatter::parse(content);
|
||||||
println!("{:?}", res);
|
println!("{:?}", res);
|
||||||
assert!(res.is_ok());
|
assert!(res.is_ok());
|
||||||
assert_eq!(res.unwrap().extra["something"]["some-date"], to_value("2002-14-01").unwrap());
|
assert_eq!(res.unwrap().extra["something"]["some-date"], to_value("2002-14-01").unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test_case(&RawFrontMatter::Toml(r#"
|
||||||
fn can_parse_fully_nested_dates_in_extra() {
|
title = "Hello"
|
||||||
let content = r#"
|
description = "hey there"
|
||||||
title = "Hello"
|
|
||||||
description = "hey there"
|
|
||||||
|
|
||||||
[extra]
|
[extra]
|
||||||
date_example = 2020-05-04
|
date_example = 2020-05-04
|
||||||
[[extra.questions]]
|
[[extra.questions]]
|
||||||
date = 2020-05-03
|
date = 2020-05-03
|
||||||
name = "Who is the prime minister of Uganda?""#;
|
name = "Who is the prime minister of Uganda?"
|
||||||
|
"#); "toml")]
|
||||||
|
#[test_case(&RawFrontMatter::Yaml(r#"
|
||||||
|
title: Hello
|
||||||
|
description: hey there
|
||||||
|
|
||||||
|
extra:
|
||||||
|
date_example: 2020-05-04
|
||||||
|
questions:
|
||||||
|
- date: 2020-05-03
|
||||||
|
name: "Who is the prime minister of Uganda?"
|
||||||
|
"#); "yaml")]
|
||||||
|
fn can_parse_fully_nested_dates_in_extra(content: &RawFrontMatter) {
|
||||||
let res = PageFrontMatter::parse(content);
|
let res = PageFrontMatter::parse(content);
|
||||||
println!("{:?}", res);
|
println!("{:?}", res);
|
||||||
assert!(res.is_ok());
|
assert!(res.is_ok());
|
||||||
assert_eq!(res.unwrap().extra["questions"][0]["date"], to_value("2020-05-03").unwrap());
|
assert_eq!(res.unwrap().extra["questions"][0]["date"], to_value("2020-05-03").unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test_case(&RawFrontMatter::Toml(r#"
|
||||||
fn can_parse_taxonomies() {
|
|
||||||
let content = r#"
|
|
||||||
title = "Hello World"
|
title = "Hello World"
|
||||||
|
|
||||||
[taxonomies]
|
[taxonomies]
|
||||||
tags = ["Rust", "JavaScript"]
|
tags = ["Rust", "JavaScript"]
|
||||||
categories = ["Dev"]
|
categories = ["Dev"]
|
||||||
"#;
|
"#); "toml")]
|
||||||
|
#[test_case(&RawFrontMatter::Yaml(r#"
|
||||||
|
title: Hello World
|
||||||
|
|
||||||
|
taxonomies:
|
||||||
|
tags:
|
||||||
|
- Rust
|
||||||
|
- JavaScript
|
||||||
|
categories:
|
||||||
|
- Dev
|
||||||
|
"#); "yaml")]
|
||||||
|
fn can_parse_taxonomies(content: &RawFrontMatter) {
|
||||||
let res = PageFrontMatter::parse(content);
|
let res = PageFrontMatter::parse(content);
|
||||||
println!("{:?}", res);
|
println!("{:?}", res);
|
||||||
assert!(res.is_ok());
|
assert!(res.is_ok());
|
||||||
|
|
|
@ -2,9 +2,11 @@ use serde_derive::{Deserialize, Serialize};
|
||||||
use tera::{Map, Value};
|
use tera::{Map, Value};
|
||||||
|
|
||||||
use super::{InsertAnchor, SortBy};
|
use super::{InsertAnchor, SortBy};
|
||||||
use errors::{bail, Result};
|
use errors::Result;
|
||||||
use utils::de::fix_toml_dates;
|
use utils::de::fix_toml_dates;
|
||||||
|
|
||||||
|
use crate::RawFrontMatter;
|
||||||
|
|
||||||
static DEFAULT_PAGINATE_PATH: &str = "page";
|
static DEFAULT_PAGINATE_PATH: &str = "page";
|
||||||
|
|
||||||
/// The front matter of every section
|
/// The front matter of every section
|
||||||
|
@ -73,11 +75,8 @@ pub struct SectionFrontMatter {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SectionFrontMatter {
|
impl SectionFrontMatter {
|
||||||
pub fn parse(toml: &str) -> Result<SectionFrontMatter> {
|
pub fn parse(raw: &RawFrontMatter) -> Result<SectionFrontMatter> {
|
||||||
let mut f: SectionFrontMatter = match toml::from_str(toml) {
|
let mut f: SectionFrontMatter = raw.deserialize()?;
|
||||||
Ok(d) => d,
|
|
||||||
Err(e) => bail!(e),
|
|
||||||
};
|
|
||||||
|
|
||||||
f.extra = match fix_toml_dates(f.extra) {
|
f.extra = match fix_toml_dates(f.extra) {
|
||||||
Value::Object(o) => o,
|
Value::Object(o) => o,
|
||||||
|
|
|
@ -1,12 +1,32 @@
|
||||||
use serde::{Deserialize, Deserializer};
|
use serde::{Deserialize, Deserializer};
|
||||||
|
use serde_derive::Deserialize;
|
||||||
use tera::{Map, Value};
|
use tera::{Map, Value};
|
||||||
|
|
||||||
/// Used as an attribute when we want to convert from TOML to a string date
|
/// Used as an attribute when we want to convert from TOML to a string date
|
||||||
|
/// If a TOML datetime isn't present, it will accept a string and push it through
|
||||||
|
/// TOML's date time parser to ensure only valid dates are accepted.
|
||||||
|
/// Inspired by this proposal: https://github.com/alexcrichton/toml-rs/issues/269
|
||||||
pub fn from_toml_datetime<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
|
pub fn from_toml_datetime<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
|
||||||
where
|
where
|
||||||
D: Deserializer<'de>,
|
D: Deserializer<'de>,
|
||||||
{
|
{
|
||||||
toml::value::Datetime::deserialize(deserializer).map(|s| Some(s.to_string()))
|
use serde::de::Error;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
enum MaybeDatetime {
|
||||||
|
Datetime(toml::value::Datetime),
|
||||||
|
String(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
match MaybeDatetime::deserialize(deserializer)? {
|
||||||
|
MaybeDatetime::Datetime(d) => Ok(Some(d.to_string())),
|
||||||
|
MaybeDatetime::String(s) => match toml::value::Datetime::from_str(&s) {
|
||||||
|
Ok(d) => Ok(Some(d.to_string())),
|
||||||
|
Err(e) => Err(D::Error::custom(e)),
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns key/value for a converted date from TOML.
|
/// Returns key/value for a converted date from TOML.
|
||||||
|
|
|
@ -79,6 +79,9 @@ by triple pluses (`+++`).
|
||||||
|
|
||||||
Although none of the front matter variables are mandatory, the opening and closing `+++` are required.
|
Although none of the front matter variables are mandatory, the opening and closing `+++` are required.
|
||||||
|
|
||||||
|
Note that even though the use of TOML is encouraged, YAML front matter is also supported to ease porting
|
||||||
|
legacy content. In this case the embedded metadata must be enclosed by triple minuses (`---`).
|
||||||
|
|
||||||
Here is an example page with all the available variables. The values provided below are the
|
Here is an example page with all the available variables. The values provided below are the
|
||||||
default values.
|
default values.
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,9 @@ to your templates through the `section.content` variable.
|
||||||
|
|
||||||
Although none of the front matter variables are mandatory, the opening and closing `+++` are required.
|
Although none of the front matter variables are mandatory, the opening and closing `+++` are required.
|
||||||
|
|
||||||
|
Note that even though the use of TOML is encouraged, YAML front matter is also supported to ease porting
|
||||||
|
legacy content. In this case the embedded metadata must be enclosed by triple minuses (`---`).
|
||||||
|
|
||||||
Here is an example `_index.md` with all the available variables. The values provided below are the
|
Here is an example `_index.md` with all the available variables. The values provided below are the
|
||||||
default values.
|
default values.
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue