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",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_yaml",
|
||||
"tera",
|
||||
"test-case",
|
||||
"toml",
|
||||
"utils",
|
||||
]
|
||||
|
@ -2341,6 +2343,18 @@ dependencies = [
|
|||
"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]]
|
||||
name = "sha-1"
|
||||
version = "0.8.2"
|
||||
|
@ -2625,6 +2639,18 @@ dependencies = [
|
|||
"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]]
|
||||
name = "textwrap"
|
||||
version = "0.11.0"
|
||||
|
|
|
@ -9,9 +9,14 @@ tera = "1"
|
|||
chrono = "0.4"
|
||||
serde = "1"
|
||||
serde_derive = "1"
|
||||
serde_yaml = "0.8"
|
||||
toml = "0.5"
|
||||
regex = "1"
|
||||
lazy_static = "1"
|
||||
|
||||
errors = { path = "../errors" }
|
||||
utils = { path = "../utils" }
|
||||
|
||||
[dev-dependencies]
|
||||
test-case = "1.0"
|
||||
|
||||
|
|
|
@ -3,7 +3,9 @@ use serde_derive::{Deserialize, Serialize};
|
|||
|
||||
use errors::{bail, Error, Result};
|
||||
use regex::Regex;
|
||||
use serde_yaml;
|
||||
use std::path::Path;
|
||||
use toml;
|
||||
|
||||
mod page;
|
||||
mod section;
|
||||
|
@ -12,8 +14,31 @@ pub use page::PageFrontMatter;
|
|||
pub use section::SectionFrontMatter;
|
||||
|
||||
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();
|
||||
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)]
|
||||
|
@ -37,20 +62,30 @@ pub enum InsertAnchor {
|
|||
|
||||
/// Split a file between the front matter and its content
|
||||
/// 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)> {
|
||||
if !PAGE_RE.is_match(content) {
|
||||
fn split_content<'c>(file_path: &Path, content: &'c str) -> Result<(RawFrontMatter<'c>, &'c str)> {
|
||||
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!(
|
||||
"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()
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// 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[1] => front matter
|
||||
// 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.
|
||||
|
@ -88,71 +123,125 @@ pub fn split_page_content<'c>(
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::path::Path;
|
||||
use test_case::test_case;
|
||||
|
||||
use super::{split_page_content, split_section_content};
|
||||
|
||||
#[test]
|
||||
fn can_split_page_content_valid() {
|
||||
let content = r#"
|
||||
#[test_case(r#"
|
||||
+++
|
||||
title = "Title"
|
||||
description = "hey there"
|
||||
date = 2002-10-12
|
||||
+++
|
||||
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();
|
||||
assert_eq!(content, "Hello\n");
|
||||
assert_eq!(front_matter.title.unwrap(), "Title");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_split_section_content_valid() {
|
||||
let content = r#"
|
||||
#[test_case(r#"
|
||||
+++
|
||||
paginate_by = 10
|
||||
+++
|
||||
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();
|
||||
assert_eq!(content, "Hello\n");
|
||||
assert!(front_matter.is_paginated());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_split_content_with_only_frontmatter_valid() {
|
||||
let content = r#"
|
||||
#[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
|
||||
---"#; "yaml")]
|
||||
fn can_split_content_with_only_frontmatter_valid(content: &str) {
|
||||
let (front_matter, content) = split_page_content(Path::new(""), content).unwrap();
|
||||
assert_eq!(content, "");
|
||||
assert_eq!(front_matter.title.unwrap(), "Title");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_split_content_lazily() {
|
||||
let content = r#"
|
||||
#[test_case(r#"
|
||||
+++
|
||||
title = "Title"
|
||||
description = "hey there"
|
||||
date = 2002-10-02T15:00:00Z
|
||||
+++
|
||||
+++"#;
|
||||
let (front_matter, content) = split_page_content(Path::new(""), content).unwrap();
|
||||
assert_eq!(content, "+++");
|
||||
assert_eq!(front_matter.title.unwrap(), "Title");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn errors_if_cannot_locate_frontmatter() {
|
||||
let content = r#"
|
||||
+++"#, "+++"; "toml with pluses in content")]
|
||||
#[test_case(r#"
|
||||
+++
|
||||
title = "Title"
|
||||
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);
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
|
|
@ -7,6 +7,8 @@ use tera::{Map, Value};
|
|||
use errors::{bail, Result};
|
||||
use utils::de::{fix_toml_dates, from_toml_datetime};
|
||||
|
||||
use crate::RawFrontMatter;
|
||||
|
||||
/// The front matter of every page
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
||||
#[serde(default)]
|
||||
|
@ -69,11 +71,8 @@ fn parse_datetime(d: &str) -> Option<NaiveDateTime> {
|
|||
}
|
||||
|
||||
impl PageFrontMatter {
|
||||
pub fn parse(toml: &str) -> Result<PageFrontMatter> {
|
||||
let mut f: PageFrontMatter = match toml::from_str(toml) {
|
||||
Ok(d) => d,
|
||||
Err(e) => bail!(e),
|
||||
};
|
||||
pub fn parse(raw: &RawFrontMatter) -> Result<PageFrontMatter> {
|
||||
let mut f: PageFrontMatter = raw.deserialize()?;
|
||||
|
||||
if let Some(ref slug) = f.slug {
|
||||
if slug == "" {
|
||||
|
@ -140,21 +139,27 @@ impl Default for PageFrontMatter {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::PageFrontMatter;
|
||||
use super::RawFrontMatter;
|
||||
use tera::to_value;
|
||||
use test_case::test_case;
|
||||
|
||||
#[test]
|
||||
fn can_have_empty_front_matter() {
|
||||
let content = r#" "#;
|
||||
#[test_case(&RawFrontMatter::Toml(r#" "#); "toml")]
|
||||
#[test_case(&RawFrontMatter::Toml(r#" "#); "yaml")]
|
||||
fn can_have_empty_front_matter(content: &RawFrontMatter) {
|
||||
let res = PageFrontMatter::parse(content);
|
||||
println!("{:?}", res);
|
||||
assert!(res.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_parse_valid_front_matter() {
|
||||
let content = r#"
|
||||
title = "Hello"
|
||||
description = "hey there""#;
|
||||
#[test_case(&RawFrontMatter::Toml(r#"
|
||||
title = "Hello"
|
||||
description = "hey there"
|
||||
"#); "toml")]
|
||||
#[test_case(&RawFrontMatter::Yaml(r#"
|
||||
title: Hello
|
||||
description: hey there
|
||||
"#); "yaml")]
|
||||
fn can_parse_valid_front_matter(content: &RawFrontMatter) {
|
||||
let res = PageFrontMatter::parse(content);
|
||||
assert!(res.is_ok());
|
||||
let res = res.unwrap();
|
||||
|
@ -162,183 +167,281 @@ mod tests {
|
|||
assert_eq!(res.description.unwrap(), "hey there".to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn errors_with_invalid_front_matter() {
|
||||
let content = r#"title = 1\n"#;
|
||||
#[test_case(&RawFrontMatter::Toml(r#"title = |\n"#); "toml")]
|
||||
#[test_case(&RawFrontMatter::Yaml(r#"title: |\n"#); "yaml")]
|
||||
fn errors_with_invalid_front_matter(content: &RawFrontMatter) {
|
||||
let res = PageFrontMatter::parse(content);
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn errors_on_present_but_empty_slug() {
|
||||
let content = r#"
|
||||
title = "Hello"
|
||||
description = "hey there"
|
||||
slug = """#;
|
||||
#[test_case(&RawFrontMatter::Toml(r#"
|
||||
title = "Hello"
|
||||
description = "hey there"
|
||||
slug = ""
|
||||
"#); "toml")]
|
||||
#[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);
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn errors_on_present_but_empty_path() {
|
||||
let content = r#"
|
||||
title = "Hello"
|
||||
description = "hey there"
|
||||
path = """#;
|
||||
#[test_case(&RawFrontMatter::Toml(r#"
|
||||
title = "Hello"
|
||||
description = "hey there"
|
||||
path = ""
|
||||
"#); "toml")]
|
||||
#[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);
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_parse_date_yyyy_mm_dd() {
|
||||
let content = r#"
|
||||
title = "Hello"
|
||||
description = "hey there"
|
||||
date = 2016-10-10
|
||||
"#;
|
||||
#[test_case(&RawFrontMatter::Toml(r#"
|
||||
title = "Hello"
|
||||
description = "hey there"
|
||||
date = 2016-10-10
|
||||
"#); "toml")]
|
||||
#[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();
|
||||
assert!(res.datetime.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_parse_date_rfc3339() {
|
||||
let content = r#"
|
||||
title = "Hello"
|
||||
description = "hey there"
|
||||
date = 2002-10-02T15:00:00Z
|
||||
"#;
|
||||
#[test_case(&RawFrontMatter::Toml(r#"
|
||||
title = "Hello"
|
||||
description = "hey there"
|
||||
date = 2002-10-02T15:00:00Z
|
||||
"#); "toml")]
|
||||
#[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();
|
||||
assert!(res.datetime.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_parse_date_rfc3339_without_timezone() {
|
||||
let content = r#"
|
||||
title = "Hello"
|
||||
description = "hey there"
|
||||
date = 2002-10-02T15:00:00
|
||||
"#;
|
||||
#[test_case(&RawFrontMatter::Toml(r#"
|
||||
title = "Hello"
|
||||
description = "hey there"
|
||||
date = 2002-10-02T15:00:00
|
||||
"#); "toml")]
|
||||
#[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();
|
||||
assert!(res.datetime.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_parse_date_rfc3339_with_space() {
|
||||
let content = r#"
|
||||
title = "Hello"
|
||||
description = "hey there"
|
||||
date = 2002-10-02 15:00:00+02:00
|
||||
"#;
|
||||
#[test_case(&RawFrontMatter::Toml(r#"
|
||||
title = "Hello"
|
||||
description = "hey there"
|
||||
date = 2002-10-02 15:00:00+02:00
|
||||
"#); "toml")]
|
||||
#[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();
|
||||
assert!(res.datetime.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_parse_date_rfc3339_with_space_without_timezone() {
|
||||
let content = r#"
|
||||
title = "Hello"
|
||||
description = "hey there"
|
||||
date = 2002-10-02 15:00:00
|
||||
"#;
|
||||
#[test_case(&RawFrontMatter::Toml(r#"
|
||||
title = "Hello"
|
||||
description = "hey there"
|
||||
date = 2002-10-02 15:00:00
|
||||
"#); "toml")]
|
||||
#[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();
|
||||
assert!(res.datetime.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_parse_date_rfc3339_with_microseconds() {
|
||||
let content = r#"
|
||||
title = "Hello"
|
||||
description = "hey there"
|
||||
date = 2002-10-02T15:00:00.123456Z
|
||||
"#;
|
||||
#[test_case(&RawFrontMatter::Toml(r#"
|
||||
title = "Hello"
|
||||
description = "hey there"
|
||||
date = 2002-10-02T15:00:00.123456Z
|
||||
"#); "toml")]
|
||||
#[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();
|
||||
assert!(res.datetime.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cannot_parse_random_date_format() {
|
||||
let content = r#"
|
||||
title = "Hello"
|
||||
description = "hey there"
|
||||
date = 2002/10/12"#;
|
||||
#[test_case(&RawFrontMatter::Toml(r#"
|
||||
title = "Hello"
|
||||
description = "hey there"
|
||||
date = 2002/10/12
|
||||
"#); "toml")]
|
||||
#[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);
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cannot_parse_invalid_date_format() {
|
||||
let content = r#"
|
||||
title = "Hello"
|
||||
description = "hey there"
|
||||
date = 2002-14-01"#;
|
||||
#[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_format(content: &RawFrontMatter) {
|
||||
let res = PageFrontMatter::parse(content);
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cannot_parse_date_as_string() {
|
||||
let content = r#"
|
||||
title = "Hello"
|
||||
description = "hey there"
|
||||
date = "2002-14-01""#;
|
||||
#[test_case(&RawFrontMatter::Toml(r#"
|
||||
title = "Hello"
|
||||
description = "hey there"
|
||||
date = "2016-10-10"
|
||||
"#); "toml")]
|
||||
#[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);
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_parse_dates_in_extra() {
|
||||
let content = r#"
|
||||
title = "Hello"
|
||||
description = "hey there"
|
||||
#[test_case(&RawFrontMatter::Toml(r#"
|
||||
title = "Hello"
|
||||
description = "hey there"
|
||||
|
||||
[extra]
|
||||
some-date = 2002-14-01"#;
|
||||
[extra]
|
||||
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);
|
||||
println!("{:?}", res);
|
||||
assert!(res.is_ok());
|
||||
assert_eq!(res.unwrap().extra["some-date"], to_value("2002-14-01").unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_parse_nested_dates_in_extra() {
|
||||
let content = r#"
|
||||
title = "Hello"
|
||||
description = "hey there"
|
||||
#[test_case(&RawFrontMatter::Toml(r#"
|
||||
title = "Hello"
|
||||
description = "hey there"
|
||||
|
||||
[extra.something]
|
||||
some-date = 2002-14-01"#;
|
||||
[extra.something]
|
||||
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);
|
||||
println!("{:?}", res);
|
||||
assert!(res.is_ok());
|
||||
assert_eq!(res.unwrap().extra["something"]["some-date"], to_value("2002-14-01").unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_parse_fully_nested_dates_in_extra() {
|
||||
let content = r#"
|
||||
title = "Hello"
|
||||
description = "hey there"
|
||||
#[test_case(&RawFrontMatter::Toml(r#"
|
||||
title = "Hello"
|
||||
description = "hey there"
|
||||
|
||||
[extra]
|
||||
date_example = 2020-05-04
|
||||
[[extra.questions]]
|
||||
date = 2020-05-03
|
||||
name = "Who is the prime minister of Uganda?""#;
|
||||
[extra]
|
||||
date_example = 2020-05-04
|
||||
[[extra.questions]]
|
||||
date = 2020-05-03
|
||||
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);
|
||||
println!("{:?}", res);
|
||||
assert!(res.is_ok());
|
||||
assert_eq!(res.unwrap().extra["questions"][0]["date"], to_value("2020-05-03").unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_parse_taxonomies() {
|
||||
let content = r#"
|
||||
#[test_case(&RawFrontMatter::Toml(r#"
|
||||
title = "Hello World"
|
||||
|
||||
[taxonomies]
|
||||
tags = ["Rust", "JavaScript"]
|
||||
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);
|
||||
println!("{:?}", res);
|
||||
assert!(res.is_ok());
|
||||
|
|
|
@ -2,9 +2,11 @@ use serde_derive::{Deserialize, Serialize};
|
|||
use tera::{Map, Value};
|
||||
|
||||
use super::{InsertAnchor, SortBy};
|
||||
use errors::{bail, Result};
|
||||
use errors::Result;
|
||||
use utils::de::fix_toml_dates;
|
||||
|
||||
use crate::RawFrontMatter;
|
||||
|
||||
static DEFAULT_PAGINATE_PATH: &str = "page";
|
||||
|
||||
/// The front matter of every section
|
||||
|
@ -73,11 +75,8 @@ pub struct SectionFrontMatter {
|
|||
}
|
||||
|
||||
impl SectionFrontMatter {
|
||||
pub fn parse(toml: &str) -> Result<SectionFrontMatter> {
|
||||
let mut f: SectionFrontMatter = match toml::from_str(toml) {
|
||||
Ok(d) => d,
|
||||
Err(e) => bail!(e),
|
||||
};
|
||||
pub fn parse(raw: &RawFrontMatter) -> Result<SectionFrontMatter> {
|
||||
let mut f: SectionFrontMatter = raw.deserialize()?;
|
||||
|
||||
f.extra = match fix_toml_dates(f.extra) {
|
||||
Value::Object(o) => o,
|
||||
|
|
|
@ -1,12 +1,32 @@
|
|||
use serde::{Deserialize, Deserializer};
|
||||
use serde_derive::Deserialize;
|
||||
use tera::{Map, Value};
|
||||
|
||||
/// 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>
|
||||
where
|
||||
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.
|
||||
|
|
|
@ -79,6 +79,9 @@ by triple pluses (`+++`).
|
|||
|
||||
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
|
||||
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.
|
||||
|
||||
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
|
||||
default values.
|
||||
|
||||
|
|
Loading…
Reference in a new issue