2019-12-21 21:52:39 +00:00
|
|
|
use lazy_static::lazy_static;
|
|
|
|
use serde_derive::{Deserialize, Serialize};
|
2017-05-13 04:01:38 +00:00
|
|
|
|
2019-12-21 21:52:39 +00:00
|
|
|
use errors::{bail, Error, Result};
|
2018-10-31 07:18:57 +00:00
|
|
|
use regex::Regex;
|
2020-12-08 18:18:14 +00:00
|
|
|
use serde_yaml;
|
2018-10-31 07:18:57 +00:00
|
|
|
use std::path::Path;
|
2020-12-08 18:18:14 +00:00
|
|
|
use toml;
|
2017-05-13 04:01:38 +00:00
|
|
|
|
|
|
|
mod page;
|
|
|
|
mod section;
|
|
|
|
|
2017-07-01 07:47:41 +00:00
|
|
|
pub use page::PageFrontMatter;
|
|
|
|
pub use section::SectionFrontMatter;
|
2017-05-13 04:01:38 +00:00
|
|
|
|
|
|
|
lazy_static! {
|
2020-12-08 18:18:14 +00:00
|
|
|
static ref TOML_RE: Regex =
|
2020-08-18 11:14:27 +00:00
|
|
|
Regex::new(r"^[[:space:]]*\+\+\+(\r?\n(?s).*?(?-s))\+\+\+\r?\n?((?s).*(?-s))$").unwrap();
|
2020-12-08 18:18:14 +00:00
|
|
|
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)
|
|
|
|
}
|
2017-05-13 04:01:38 +00:00
|
|
|
}
|
|
|
|
|
2017-07-01 07:47:41 +00:00
|
|
|
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
|
|
|
|
#[serde(rename_all = "lowercase")]
|
|
|
|
pub enum SortBy {
|
2017-09-27 14:37:17 +00:00
|
|
|
/// Most recent to oldest
|
2017-07-01 07:47:41 +00:00
|
|
|
Date,
|
2021-01-20 14:35:25 +00:00
|
|
|
/// Sort by title
|
|
|
|
Title,
|
2017-09-27 14:37:17 +00:00
|
|
|
/// Lower weight comes first
|
2017-07-01 07:47:41 +00:00
|
|
|
Weight,
|
2017-09-27 14:37:17 +00:00
|
|
|
/// No sorting
|
2017-07-01 07:47:41 +00:00
|
|
|
None,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
|
|
|
|
#[serde(rename_all = "lowercase")]
|
|
|
|
pub enum InsertAnchor {
|
|
|
|
Left,
|
|
|
|
Right,
|
|
|
|
None,
|
|
|
|
}
|
|
|
|
|
2017-05-13 04:01:38 +00:00
|
|
|
/// Split a file between the front matter and its content
|
|
|
|
/// Will return an error if the front matter wasn't found
|
2020-12-08 18:18:14 +00:00
|
|
|
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 {
|
2018-10-31 07:18:57 +00:00
|
|
|
bail!(
|
2020-12-08 18:18:14 +00:00
|
|
|
"Couldn't find front matter in `{}`. Did you forget to add `+++` or `---`?",
|
2018-10-31 07:18:57 +00:00
|
|
|
file_path.to_string_lossy()
|
|
|
|
);
|
2020-12-08 18:18:14 +00:00
|
|
|
};
|
2017-05-13 04:01:38 +00:00
|
|
|
|
|
|
|
// 2. extract the front matter and the content
|
2020-12-08 18:18:14 +00:00
|
|
|
let caps = re.captures(content).unwrap();
|
2017-05-13 04:01:38 +00:00
|
|
|
// caps[0] is the full match
|
|
|
|
// caps[1] => front matter
|
|
|
|
// caps[2] => content
|
2020-12-08 18:18:14 +00:00
|
|
|
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))
|
|
|
|
}
|
2017-05-13 04:01:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Split a file between the front matter and its content.
|
2017-05-15 03:23:19 +00:00
|
|
|
/// Returns a parsed `SectionFrontMatter` and the rest of the content
|
2020-08-18 11:14:27 +00:00
|
|
|
pub fn split_section_content<'c>(
|
2018-10-31 07:18:57 +00:00
|
|
|
file_path: &Path,
|
2020-08-18 11:14:27 +00:00
|
|
|
content: &'c str,
|
|
|
|
) -> Result<(SectionFrontMatter, &'c str)> {
|
2017-05-13 04:01:38 +00:00
|
|
|
let (front_matter, content) = split_content(file_path, content)?;
|
2019-01-11 19:29:46 +00:00
|
|
|
let meta = SectionFrontMatter::parse(&front_matter).map_err(|e| {
|
2019-02-09 18:54:46 +00:00
|
|
|
Error::chain(
|
|
|
|
format!("Error when parsing front matter of section `{}`", file_path.to_string_lossy()),
|
|
|
|
e,
|
|
|
|
)
|
2018-10-31 07:18:57 +00:00
|
|
|
})?;
|
2017-05-13 04:01:38 +00:00
|
|
|
Ok((meta, content))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Split a file between the front matter and its content
|
2017-05-15 03:23:19 +00:00
|
|
|
/// Returns a parsed `PageFrontMatter` and the rest of the content
|
2020-09-01 19:00:21 +00:00
|
|
|
pub fn split_page_content<'c>(
|
|
|
|
file_path: &Path,
|
|
|
|
content: &'c str,
|
|
|
|
) -> Result<(PageFrontMatter, &'c str)> {
|
2017-05-13 04:01:38 +00:00
|
|
|
let (front_matter, content) = split_content(file_path, content)?;
|
2019-01-11 19:29:46 +00:00
|
|
|
let meta = PageFrontMatter::parse(&front_matter).map_err(|e| {
|
2019-02-09 18:54:46 +00:00
|
|
|
Error::chain(
|
|
|
|
format!("Error when parsing front matter of page `{}`", file_path.to_string_lossy()),
|
|
|
|
e,
|
|
|
|
)
|
2018-10-31 07:18:57 +00:00
|
|
|
})?;
|
2017-05-13 04:01:38 +00:00
|
|
|
Ok((meta, content))
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use std::path::Path;
|
2020-12-08 18:18:14 +00:00
|
|
|
use test_case::test_case;
|
2017-05-13 04:01:38 +00:00
|
|
|
|
2018-10-31 07:18:57 +00:00
|
|
|
use super::{split_page_content, split_section_content};
|
2017-05-13 04:01:38 +00:00
|
|
|
|
2020-12-08 18:18:14 +00:00
|
|
|
#[test_case(r#"
|
2017-05-13 04:01:38 +00:00
|
|
|
+++
|
|
|
|
title = "Title"
|
|
|
|
description = "hey there"
|
2018-01-14 17:03:57 +00:00
|
|
|
date = 2002-10-12
|
2017-05-13 04:01:38 +00:00
|
|
|
+++
|
|
|
|
Hello
|
2020-12-08 18:18:14 +00:00
|
|
|
"#; "toml")]
|
|
|
|
#[test_case(r#"
|
|
|
|
---
|
|
|
|
title: Title
|
|
|
|
description: hey there
|
|
|
|
date: 2002-10-12
|
|
|
|
---
|
|
|
|
Hello
|
|
|
|
"#; "yaml")]
|
|
|
|
fn can_split_page_content_valid(content: &str) {
|
2017-05-13 04:01:38 +00:00
|
|
|
let (front_matter, content) = split_page_content(Path::new(""), content).unwrap();
|
|
|
|
assert_eq!(content, "Hello\n");
|
|
|
|
assert_eq!(front_matter.title.unwrap(), "Title");
|
|
|
|
}
|
|
|
|
|
2020-12-08 18:18:14 +00:00
|
|
|
#[test_case(r#"
|
2017-05-13 04:01:38 +00:00
|
|
|
+++
|
|
|
|
paginate_by = 10
|
|
|
|
+++
|
|
|
|
Hello
|
2020-12-08 18:18:14 +00:00
|
|
|
"#; "toml")]
|
|
|
|
#[test_case(r#"
|
|
|
|
---
|
|
|
|
paginate_by: 10
|
|
|
|
---
|
|
|
|
Hello
|
|
|
|
"#; "yaml")]
|
|
|
|
fn can_split_section_content_valid(content: &str) {
|
2017-05-13 04:01:38 +00:00
|
|
|
let (front_matter, content) = split_section_content(Path::new(""), content).unwrap();
|
|
|
|
assert_eq!(content, "Hello\n");
|
|
|
|
assert!(front_matter.is_paginated());
|
|
|
|
}
|
|
|
|
|
2020-12-08 18:18:14 +00:00
|
|
|
#[test_case(r#"
|
2017-05-13 04:01:38 +00:00
|
|
|
+++
|
|
|
|
title = "Title"
|
|
|
|
description = "hey there"
|
2018-01-14 17:03:57 +00:00
|
|
|
date = 2002-10-12
|
2020-12-08 18:18:14 +00:00
|
|
|
+++"#; "toml")]
|
|
|
|
#[test_case(r#"
|
|
|
|
---
|
|
|
|
title: Title
|
|
|
|
description: hey there
|
|
|
|
date: 2002-10-12
|
|
|
|
---"#; "yaml")]
|
|
|
|
fn can_split_content_with_only_frontmatter_valid(content: &str) {
|
2017-05-13 04:01:38 +00:00
|
|
|
let (front_matter, content) = split_page_content(Path::new(""), content).unwrap();
|
|
|
|
assert_eq!(content, "");
|
|
|
|
assert_eq!(front_matter.title.unwrap(), "Title");
|
|
|
|
}
|
|
|
|
|
2020-12-08 18:18:14 +00:00
|
|
|
#[test_case(r#"
|
2017-05-13 04:01:38 +00:00
|
|
|
+++
|
|
|
|
title = "Title"
|
|
|
|
description = "hey there"
|
2018-01-14 17:03:57 +00:00
|
|
|
date = 2002-10-02T15:00:00Z
|
2017-05-13 04:01:38 +00:00
|
|
|
+++
|
2020-12-08 18:18:14 +00:00
|
|
|
+++"#, "+++"; "toml with pluses in content")]
|
|
|
|
#[test_case(r#"
|
|
|
|
+++
|
|
|
|
title = "Title"
|
|
|
|
description = "hey there"
|
|
|
|
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) {
|
2017-05-13 04:01:38 +00:00
|
|
|
let (front_matter, content) = split_page_content(Path::new(""), content).unwrap();
|
2020-12-08 18:18:14 +00:00
|
|
|
assert_eq!(content, expected);
|
2017-05-13 04:01:38 +00:00
|
|
|
assert_eq!(front_matter.title.unwrap(), "Title");
|
|
|
|
}
|
|
|
|
|
2020-12-08 18:18:14 +00:00
|
|
|
#[test_case(r#"
|
2017-05-13 04:01:38 +00:00
|
|
|
+++
|
|
|
|
title = "Title"
|
|
|
|
description = "hey there"
|
2020-12-08 18:18:14 +00:00
|
|
|
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) {
|
2017-05-13 04:01:38 +00:00
|
|
|
let res = split_page_content(Path::new(""), content);
|
|
|
|
assert!(res.is_err());
|
|
|
|
}
|
|
|
|
}
|