use crate::error::NoteError;
use crate::error::FRONT_MATTER_ERROR_MAX_LINES;
use std::ops::Deref;
use std::ops::DerefMut;
use std::str;
use tera::Value;
#[derive(Debug, Eq, PartialEq)]
pub struct FrontMatter(pub tera::Map<String, tera::Value>);
pub(crate) fn all_leaves(val: &Value, f: &dyn Fn(&Value) -> bool) -> bool {
match &val {
Value::Array(a) => {
for i in a.iter() {
if !all_leaves(i, &f) {
return false;
}
}
}
Value::Object(map) => {
for (_, v) in map {
if !all_leaves(v, &f) {
return false;
}
}
}
_ => {
return f(val);
}
}
true
}
impl TryFrom<&str> for FrontMatter {
type Error = NoteError;
fn try_from(header: &str) -> Result<FrontMatter, NoteError> {
let map: tera::Map<String, tera::Value> =
serde_yaml::from_str(header).map_err(|e| NoteError::InvalidFrontMatterYaml {
front_matter: header
.lines()
.enumerate()
.map(|(n, s)| format!("{:03}: {}\n", n + 1, s))
.take(FRONT_MATTER_ERROR_MAX_LINES)
.collect::<String>(),
source_error: e,
})?;
let fm = FrontMatter(map);
Ok(fm)
}
}
impl Deref for FrontMatter {
type Target = tera::Map<String, tera::Value>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for FrontMatter {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
#[cfg(test)]
mod tests {
use crate::config::TMPL_VAR_FM_ALL;
use crate::front_matter::FrontMatter;
use serde_json::json;
use tera::Value;
#[test]
fn test_deserialize() {
use super::FrontMatter;
use serde_json::json;
let input = "# document start
title: The book
subtitle: you always wanted
author: It's me
date: 2020-04-21
lang: en
revision: '1.0'
sort_tag: 20200420-21_22
file_ext: md
height: 1.23
count: 2
neg: -1
flag: true
numbers:
- 1
- 3
- 5
";
let mut expected = tera::Map::new();
expected.insert("title".to_string(), Value::String("The book".to_string()));
expected.insert(
"subtitle".to_string(),
Value::String("you always wanted".to_string()),
);
expected.insert("author".to_string(), Value::String("It\'s me".to_string()));
expected.insert("date".to_string(), Value::String("2020-04-21".to_string()));
expected.insert("lang".to_string(), Value::String("en".to_string()));
expected.insert("revision".to_string(), Value::String("1.0".to_string()));
expected.insert(
"sort_tag".to_string(),
Value::String("20200420-21_22".to_string()),
);
expected.insert("file_ext".to_string(), Value::String("md".to_string()));
expected.insert("height".to_string(), json!(1.23)); expected.insert("count".to_string(), json!(2)); expected.insert("neg".to_string(), json!(-1)); expected.insert("flag".to_string(), json!(true)); expected.insert("numbers".to_string(), json!([1, 3, 5]));
let expected_front_matter = FrontMatter(expected);
assert_eq!(expected_front_matter, FrontMatter::try_from(input).unwrap());
}
#[test]
fn test_register_front_matter() {
use super::FrontMatter;
use crate::context::Context;
use serde_json::json;
use std::path::Path;
use tera::Value;
let mut tmp = tera::Map::new();
tmp.insert("file_ext".to_string(), Value::String("md".to_string())); tmp.insert("height".to_string(), json!(1.23)); tmp.insert("count".to_string(), json!(2)); tmp.insert("neg".to_string(), json!(-1)); tmp.insert("flag".to_string(), json!(true)); tmp.insert("numbers".to_string(), json!([1, 3, 5])); let mut tmp2 = tera::Map::new();
tmp2.insert("fm_file_ext".to_string(), Value::String("md".to_string())); tmp2.insert("fm_height".to_string(), json!(1.23)); tmp2.insert("fm_count".to_string(), json!(2)); tmp2.insert("fm_neg".to_string(), json!(-1)); tmp2.insert("fm_flag".to_string(), json!(true)); tmp2.insert("fm_numbers".to_string(), json!([1, 3, 5]));
let input1 = Context::from(Path::new("a/b/test.md")).unwrap();
let input2 = FrontMatter(tmp);
let mut expected = Context::from(Path::new("a/b/test.md")).unwrap();
tmp2.remove("fm_numbers");
tmp2.insert("fm_numbers".to_string(), json!([1, 3, 5])); let tmp2 = tera::Value::from(tmp2);
expected.insert(TMPL_VAR_FM_ALL, &tmp2); let expected = expected.insert_front_matter(&FrontMatter::try_from("").unwrap());
let result = input1.insert_front_matter(&input2);
assert_eq!(result, expected);
}
#[test]
fn test_try_from_content() {
use crate::content::Content;
use crate::content::ContentString;
use serde_json::json;
let raw = "\u{feff}---\ntitle: \"My day\"\nsubtitle: \"Note\"\n---\nBody";
let content = ContentString::from_string(raw.to_string(), "doc".to_string());
assert!(!content.is_empty());
assert!(!content.borrow_dependent().header.is_empty());
let front_matter = FrontMatter::try_from(content.header()).unwrap();
assert_eq!(front_matter.get("title"), Some(&json!("My day")));
assert_eq!(front_matter.get("subtitle"), Some(&json!("Note")));
}
#[test]
fn test_all_leaves() {
use super::all_leaves;
let input = json!({
"first": "tmp: test",
"second": [
"string(a)",
"string(b)"
],});
assert!(all_leaves(&input, &|v| matches!(v, Value::String(..))));
let input = json!({
"first": "tmp: test",
"second": [
1234,
"string(b)"
],});
assert!(!all_leaves(&input, &|v| matches!(v, Value::String(..))));
let input = json!({
"first": "tmp: test",
"second": [
"string(a)",
false
],});
assert!(!all_leaves(&input, &|v| matches!(v, Value::String(..))));
let input = json!({
"first": "tmp: test",
"second": [
"string(a)",
"string(b)"
],});
assert!(all_leaves(&input, &|v| matches!(v, Value::String(..))
&& v.as_str() != Some("")));
let input = json!({
"first": "tmp: test",
"second": [
"string(a)",
""
],});
assert!(!all_leaves(&input, &|v| matches!(v, Value::String(..))
&& v.as_str() != Some("")));
}
}