diff --git a/Cargo.lock b/Cargo.lock index 60a502e5..1f500828 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/components/front_matter/Cargo.toml b/components/front_matter/Cargo.toml index eb3f8a6f..d368e90c 100644 --- a/components/front_matter/Cargo.toml +++ b/components/front_matter/Cargo.toml @@ -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" + diff --git a/components/front_matter/src/lib.rs b/components/front_matter/src/lib.rs index f0866797..834747b3 100644 --- a/components/front_matter/src/lib.rs +++ b/components/front_matter/src/lib.rs @@ -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(&self) -> Result + 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()); } diff --git a/components/front_matter/src/page.rs b/components/front_matter/src/page.rs index f38f1a2e..da39a5e7 100644 --- a/components/front_matter/src/page.rs +++ b/components/front_matter/src/page.rs @@ -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 { } impl PageFrontMatter { - pub fn parse(toml: &str) -> Result { - let mut f: PageFrontMatter = match toml::from_str(toml) { - Ok(d) => d, - Err(e) => bail!(e), - }; + pub fn parse(raw: &RawFrontMatter) -> Result { + 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()); diff --git a/components/front_matter/src/section.rs b/components/front_matter/src/section.rs index f2360a42..063ba014 100644 --- a/components/front_matter/src/section.rs +++ b/components/front_matter/src/section.rs @@ -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 { - let mut f: SectionFrontMatter = match toml::from_str(toml) { - Ok(d) => d, - Err(e) => bail!(e), - }; + pub fn parse(raw: &RawFrontMatter) -> Result { + let mut f: SectionFrontMatter = raw.deserialize()?; f.extra = match fix_toml_dates(f.extra) { Value::Object(o) => o, diff --git a/components/utils/src/de.rs b/components/utils/src/de.rs index 654eaee1..e294e92a 100644 --- a/components/utils/src/de.rs +++ b/components/utils/src/de.rs @@ -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, 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. diff --git a/docs/content/documentation/content/page.md b/docs/content/documentation/content/page.md index d04da579..ac6ac242 100644 --- a/docs/content/documentation/content/page.md +++ b/docs/content/documentation/content/page.md @@ -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. diff --git a/docs/content/documentation/content/section.md b/docs/content/documentation/content/section.md index 00ea8f57..fd9f9916 100644 --- a/docs/content/documentation/content/section.md +++ b/docs/content/documentation/content/section.md @@ -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.