use tera::Value;
use crate::config::Assertion;
use crate::config::FILENAME_ROOT_PATH_MARKER;
use crate::config::LIB_CFG;
#[cfg(feature = "viewer")]
use crate::config::TMPL_HTML_VAR_DOC_ERROR;
#[cfg(feature = "viewer")]
use crate::config::TMPL_HTML_VAR_DOC_TEXT;
use crate::config::TMPL_HTML_VAR_EXPORTER_DOC_CSS;
use crate::config::TMPL_HTML_VAR_EXPORTER_HIGHLIGHTING_CSS;
use crate::config::TMPL_HTML_VAR_VIEWER_DOC_CSS_PATH;
use crate::config::TMPL_HTML_VAR_VIEWER_DOC_CSS_PATH_VALUE;
use crate::config::TMPL_HTML_VAR_VIEWER_DOC_JS;
use crate::config::TMPL_HTML_VAR_VIEWER_HIGHLIGHTING_CSS_PATH;
use crate::config::TMPL_HTML_VAR_VIEWER_HIGHLIGHTING_CSS_PATH_VALUE;
use crate::config::TMPL_VAR_BODY;
use crate::config::TMPL_VAR_CURRENT_SCHEME;
use crate::config::TMPL_VAR_DIR_PATH;
use crate::config::TMPL_VAR_DOC_FILE_DATE;
use crate::config::TMPL_VAR_EXTENSION_DEFAULT;
use crate::config::TMPL_VAR_FM_;
use crate::config::TMPL_VAR_FM_ALL;
use crate::config::TMPL_VAR_FM_SCHEME;
use crate::config::TMPL_VAR_FORCE_LANG;
use crate::config::TMPL_VAR_HEADER;
use crate::config::TMPL_VAR_LANG;
use crate::config::TMPL_VAR_PATH;
use crate::config::TMPL_VAR_ROOT_PATH;
use crate::config::TMPL_VAR_SCHEME_SYNC_DEFAULT;
use crate::config::TMPL_VAR_USERNAME;
use crate::content::Content;
use crate::error::FileError;
use crate::error::LibCfgError;
use crate::error::NoteError;
use crate::filename::Extension;
use crate::filename::NotePath;
use crate::filename::NotePathStr;
use crate::filter::name;
use crate::front_matter::FrontMatter;
use crate::front_matter::all_leaves;
use crate::settings::SETTINGS;
use std::borrow::Cow;
use std::fs::File;
use std::marker::PhantomData;
use std::matches;
use std::ops::Deref;
use std::path::Path;
use std::path::PathBuf;
use std::time::SystemTime;
pub trait ContextState {}
#[derive(Debug, PartialEq, Clone)]
pub struct Invalid;
#[derive(Debug, PartialEq, Clone)]
pub struct HasSettings;
#[derive(Debug, PartialEq, Clone)]
pub(crate) struct ReadyForFilenameTemplate;
#[derive(Debug, PartialEq, Clone)]
pub(crate) struct HasExistingContent;
#[derive(Debug, PartialEq, Clone)]
pub(crate) struct ReadyForContentTemplate;
#[derive(Debug, PartialEq, Clone)]
pub(crate) struct ReadyForHtmlTemplate;
#[cfg(feature = "viewer")]
#[derive(Debug, PartialEq, Clone)]
pub(crate) struct ReadyForHtmlErrorTemplate;
impl ContextState for Invalid {}
impl ContextState for HasSettings {}
impl ContextState for ReadyForFilenameTemplate {}
impl ContextState for HasExistingContent {}
impl ContextState for ReadyForContentTemplate {}
impl ContextState for ReadyForHtmlTemplate {}
#[cfg(feature = "viewer")]
impl ContextState for ReadyForHtmlErrorTemplate {}
#[derive(Clone, Debug, PartialEq)]
pub struct Context<S: ContextState + ?Sized> {
ct: tera::Context,
path: PathBuf,
dir_path: PathBuf,
root_path: PathBuf,
doc_file_date: Option<SystemTime>,
_marker: PhantomData<S>,
}
impl<S: ContextState> Context<S> {
pub fn get_path(&self) -> &Path {
self.path.as_path()
}
pub fn get_dir_path(&self) -> &Path {
self.dir_path.as_path()
}
pub fn get_root_path(&self) -> &Path {
self.root_path.as_path()
}
pub fn get_doc_file_date(&self) -> Option<SystemTime> {
self.doc_file_date
}
pub fn from_context_path(context: &Context<S>) -> Context<HasSettings> {
let mut new_context = Context {
ct: tera::Context::new(),
path: context.path.clone(),
dir_path: context.dir_path.clone(),
root_path: context.root_path.clone(),
doc_file_date: context.doc_file_date,
_marker: PhantomData,
};
new_context.sync_paths_to_map();
new_context.insert_config_vars();
new_context.insert_settings();
new_context
}
fn sync_paths_to_map(&mut self) {
self.ct.insert(TMPL_VAR_PATH, &self.path);
self.ct.insert(TMPL_VAR_DIR_PATH, &self.dir_path);
self.ct.insert(TMPL_VAR_ROOT_PATH, &self.root_path);
if let Some(time) = self.doc_file_date {
self.ct.insert(
TMPL_VAR_DOC_FILE_DATE,
&time
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap_or_default()
.as_secs(),
)
} else {
self.ct.remove(TMPL_VAR_DOC_FILE_DATE);
};
}
fn insert_config_vars(&mut self) {
let lib_cfg = LIB_CFG.read_recursive();
self.ct.insert(
TMPL_VAR_SCHEME_SYNC_DEFAULT,
lib_cfg.scheme_sync_default.as_str(),
);
}
fn insert_settings(&mut self) {
let settings = SETTINGS.read_recursive();
self.ct.insert(
TMPL_VAR_EXTENSION_DEFAULT,
settings.extension_default.as_str(),
);
{
let lib_cfg = LIB_CFG.read_recursive();
self.ct.insert(
TMPL_VAR_CURRENT_SCHEME,
&lib_cfg.scheme[settings.current_scheme].name,
);
}
self.ct.insert(TMPL_VAR_USERNAME, &settings.author);
self.ct.insert(TMPL_VAR_LANG, &settings.lang);
self.ct.insert(TMPL_VAR_FORCE_LANG, &settings.force_lang);
}
fn insert_front_matter2(&mut self, fm: &FrontMatter) {
let mut fm_all_map = self
.ct
.remove(TMPL_VAR_FM_ALL)
.and_then(|v| {
if let tera::Value::Object(map) = v {
Some(map)
} else {
None
}
})
.unwrap_or_default();
let localized_scheme_names: Vec<String> = LIB_CFG
.read_recursive()
.scheme
.iter()
.map(|s| {
s.tmpl
.fm_var
.localization
.iter()
.find_map(|(k, v)| (k == TMPL_VAR_FM_SCHEME).then_some(v.to_owned()))
})
.collect::<Option<Vec<String>>>()
.unwrap_or_default();
let localized_scheme: Option<(usize, &str)> = localized_scheme_names
.iter()
.enumerate()
.find_map(|(i, k)| fm.0.get(k).and_then(|s| s.as_str()).map(|s| (i, s)));
let scheme = if let Some((scheme_idx, scheme_name)) = localized_scheme {
{
log::trace!(
"Found `scheme: {}` with index=={} in front matter",
scheme_name,
scheme_idx,
);
scheme_idx
}
} else {
SETTINGS.read_recursive().current_scheme
};
let scheme = &LIB_CFG.read_recursive().scheme[scheme];
let vars = &scheme.tmpl.fm_var.localization;
for (key, value) in fm.iter() {
let fm_key = vars.iter().find(|&l| &l.1 == key).map_or_else(
|| {
let mut s = TMPL_VAR_FM_.to_string();
s.push_str(key);
Cow::Owned(s)
},
|l| Cow::Borrowed(&l.0),
);
fm_all_map.insert(fm_key.to_string(), value.clone());
}
self.ct.insert(TMPL_VAR_FM_ALL, &fm_all_map);
}
#[cfg(test)]
pub(crate) fn insert(&mut self, key: &str, val: &tera::Value) {
self.ct.insert(key, val);
}
fn insert_raw_text_from_existing_content(&mut self, content: &impl Content) {
let mut map = tera::Map::new();
map.insert(TMPL_VAR_HEADER.to_string(), content.header().into());
map.insert(TMPL_VAR_BODY.to_string(), content.body().into());
self.ct.insert(content.name(), &tera::Value::from(map));
}
fn insert_front_matter_and_raw_text_from_existing_content2(
&mut self,
clipboards: &Vec<&impl Content>,
) -> Result<(), NoteError> {
for &clip in clipboards {
self.insert_raw_text_from_existing_content(clip);
if !clip.header().is_empty() {
let input_fm = FrontMatter::try_from(clip.header());
match input_fm {
Ok(ref fm) => {
log::trace!(
"Input stream \"{}\" generates the front matter variables:\n{:#?}",
clip.name(),
&fm
)
}
Err(ref e) => {
if !clip.header().is_empty() {
return Err(NoteError::InvalidInputYaml {
tmpl_var: clip.name().to_string(),
source_str: e.to_string(),
});
}
}
};
if let Ok(fm) = input_fm {
self.insert_front_matter2(&fm);
}
}
}
Ok(())
}
}
impl Context<Invalid> {
pub fn from(path: &Path) -> Result<Context<HasSettings>, FileError> {
let path = path.to_path_buf();
let dir_path = if path.is_dir() {
path.clone()
} else {
path.parent()
.unwrap_or_else(|| Path::new("./"))
.to_path_buf()
};
let mut root_path = Path::new("");
for anc in dir_path.ancestors() {
root_path = anc;
let mut p = anc.to_owned();
p.push(Path::new(FILENAME_ROOT_PATH_MARKER));
if p.is_file() {
break;
}
}
let root_path = root_path.to_owned();
debug_assert!(dir_path.starts_with(&root_path));
let file_creation_date = if let Ok(file) = File::open(&path) {
let metadata = file.metadata()?;
metadata.created().or_else(|_| metadata.modified()).ok()
} else {
None
};
let mut context = Context {
ct: tera::Context::new(),
path,
dir_path,
root_path,
doc_file_date: file_creation_date,
_marker: PhantomData,
};
context.sync_paths_to_map();
context.insert_config_vars();
context.insert_settings();
Ok(context)
}
}
impl Context<HasSettings> {
pub(crate) fn insert_front_matter(
mut self,
fm: &FrontMatter,
) -> Context<ReadyForFilenameTemplate> {
Context::insert_front_matter2(&mut self, fm);
Context {
ct: self.ct,
path: self.path,
dir_path: self.dir_path,
root_path: self.root_path,
doc_file_date: self.doc_file_date,
_marker: PhantomData,
}
}
pub(crate) fn insert_front_matter_and_raw_text_from_existing_content(
mut self,
clipboards: &Vec<&impl Content>,
) -> Result<Context<HasExistingContent>, NoteError> {
self.insert_front_matter_and_raw_text_from_existing_content2(clipboards)?;
Ok(Context {
ct: self.ct,
path: self.path,
dir_path: self.dir_path,
root_path: self.root_path,
doc_file_date: self.doc_file_date,
_marker: PhantomData,
})
}
#[cfg(feature = "viewer")]
pub(crate) fn insert_error_content(
mut self,
note_erroneous_content: &impl Content,
error_message: &str,
viewer_doc_js: &str,
) -> Context<ReadyForHtmlErrorTemplate> {
self.ct.insert(TMPL_HTML_VAR_VIEWER_DOC_JS, viewer_doc_js);
self.ct.insert(TMPL_HTML_VAR_DOC_ERROR, error_message);
self.ct
.insert(TMPL_HTML_VAR_DOC_TEXT, ¬e_erroneous_content.as_str());
Context {
ct: self.ct,
path: self.path,
dir_path: self.dir_path,
root_path: self.root_path,
doc_file_date: self.doc_file_date,
_marker: PhantomData,
}
}
}
impl Context<HasExistingContent> {
pub(crate) fn insert_front_matter_and_raw_text_from_existing_content(
mut self,
clipboards: &Vec<&impl Content>,
) -> Result<Context<HasExistingContent>, NoteError> {
self.insert_front_matter_and_raw_text_from_existing_content2(clipboards)?;
Ok(Context {
ct: self.ct,
path: self.path,
dir_path: self.dir_path,
root_path: self.root_path,
doc_file_date: self.doc_file_date,
_marker: PhantomData,
})
}
pub(crate) fn set_state_ready_for_content_template(self) -> Context<ReadyForContentTemplate> {
Context {
ct: self.ct,
path: self.path,
dir_path: self.dir_path,
root_path: self.root_path,
doc_file_date: self.doc_file_date,
_marker: PhantomData,
}
}
}
impl Context<ReadyForFilenameTemplate> {
#[inline]
pub(crate) fn assert_precoditions(&self) -> Result<(), NoteError> {
let path = &self.path;
let lib_cfg = &LIB_CFG.read_recursive();
let fm_all = self.get(TMPL_VAR_FM_ALL);
if fm_all.is_none() {
return Ok(());
}
let fm_all = fm_all.unwrap();
let fm_scheme = fm_all.get(TMPL_VAR_FM_SCHEME).and_then(|v| v.as_str());
let scheme_idx = fm_scheme.and_then(|scheme_name| {
lib_cfg
.scheme
.iter()
.enumerate()
.find_map(|(i, s)| (s.name == scheme_name).then_some(i))
});
let scheme_idx = scheme_idx.unwrap_or_else(|| SETTINGS.read_recursive().current_scheme);
let scheme = &lib_cfg.scheme[scheme_idx];
for (key, conditions) in scheme.tmpl.fm_var.assertions.iter() {
if let Some(value) = fm_all.get(key) {
for cond in conditions {
match cond {
Assertion::IsDefined => {}
Assertion::IsString => {
if !all_leaves(value, &|v| matches!(v, Value::String(..))) {
return Err(NoteError::FrontMatterFieldIsNotString {
field_name: name(scheme, key).to_string(),
});
}
}
Assertion::IsNotEmptyString => {
if !all_leaves(value, &|v| {
matches!(v, Value::String(..)) && v.as_str() != Some("")
}) {
return Err(NoteError::FrontMatterFieldIsEmptyString {
field_name: name(scheme, key).to_string(),
});
}
}
Assertion::IsNumber => {
if !all_leaves(value, &|v| matches!(v, Value::Number(..))) {
return Err(NoteError::FrontMatterFieldIsNotNumber {
field_name: name(scheme, key).to_string(),
});
}
}
Assertion::IsBool => {
if !all_leaves(value, &|v| matches!(v, Value::Bool(..))) {
return Err(NoteError::FrontMatterFieldIsNotBool {
field_name: name(scheme, key).to_string(),
});
}
}
Assertion::IsNotCompound => {
if matches!(value, Value::Array(..))
|| matches!(value, Value::Object(..))
{
return Err(NoteError::FrontMatterFieldIsCompound {
field_name: name(scheme, key).to_string(),
});
}
}
Assertion::IsValidSortTag => {
let fm_sort_tag = value.as_str().unwrap_or_default();
if !fm_sort_tag.is_empty() {
let (_, rest, is_sequential) = fm_sort_tag.split_sort_tag(true);
if !rest.is_empty() {
return Err(NoteError::FrontMatterFieldIsInvalidSortTag {
sort_tag: fm_sort_tag.to_owned(),
sort_tag_extra_chars: scheme
.filename
.sort_tag
.extra_chars
.escape_default()
.to_string(),
filename_sort_tag_letters_in_succession_max: scheme
.filename
.sort_tag
.letters_in_succession_max,
});
}
if !is_sequential {
return Ok(());
}
let docpath = path.to_str().unwrap_or_default();
let (dirpath, filename) =
docpath.rsplit_once(['/', '\\']).unwrap_or(("", docpath));
let sort_tag = filename.split_sort_tag(false).0;
if sort_tag.is_empty() || sort_tag == fm_sort_tag {
return Ok(());
}
let dirpath = Path::new(dirpath);
if let Some(other_file) =
dirpath.has_file_with_sort_tag(fm_sort_tag)
{
return Err(NoteError::FrontMatterFieldIsDuplicateSortTag {
sort_tag: fm_sort_tag.to_string(),
existing_file: other_file,
});
}
}
}
Assertion::IsTpnoteExtension => {
let file_ext = value.as_str().unwrap_or_default();
if !file_ext.is_empty() && !(*file_ext).is_tpnote_ext() {
return Err(NoteError::FrontMatterFieldIsNotTpnoteExtension {
extension: file_ext.to_string(),
extensions: {
use std::fmt::Write;
let mut errstr = scheme.filename.extensions.iter().fold(
String::new(),
|mut output, (k, _v1, _v2)| {
let _ = write!(output, "{k}, ");
output
},
);
errstr.truncate(errstr.len().saturating_sub(2));
errstr
},
});
}
}
Assertion::IsConfiguredScheme => {
let fm_scheme = value.as_str().unwrap_or_default();
match lib_cfg.scheme_idx(fm_scheme) {
Ok(_) => {}
Err(LibCfgError::SchemeNotFound {
scheme_name,
schemes,
}) => {
return Err(NoteError::SchemeNotFound {
scheme_val: scheme_name,
scheme_key: key.to_string(),
schemes,
});
}
Err(e) => return Err(e.into()),
};
}
Assertion::NoOperation => {}
} }
} else if conditions.contains(&Assertion::IsDefined) {
return Err(NoteError::FrontMatterFieldMissing {
field_name: name(scheme, key).to_string(),
});
}
}
Ok(())
}
#[cfg(test)]
pub(crate) fn set_state_ready_for_content_template(self) -> Context<ReadyForContentTemplate> {
Context {
ct: self.ct,
path: self.path,
dir_path: self.dir_path,
root_path: self.root_path,
doc_file_date: self.doc_file_date,
_marker: PhantomData,
}
}
pub(crate) fn insert_raw_content_and_css(
mut self,
content: &impl Content,
viewer_doc_js: &str,
) -> Context<ReadyForHtmlTemplate> {
self.ct.insert(TMPL_HTML_VAR_VIEWER_DOC_JS, viewer_doc_js);
self.insert_raw_text_from_existing_content(content);
{
let lib_cfg = &LIB_CFG.read_recursive();
self.ct.insert(
TMPL_HTML_VAR_EXPORTER_DOC_CSS,
&(lib_cfg.tmpl_html.exporter_doc_css),
);
#[cfg(feature = "renderer")]
self.ct.insert(
TMPL_HTML_VAR_EXPORTER_HIGHLIGHTING_CSS,
&(lib_cfg.tmpl_html.exporter_highlighting_css),
);
}
#[cfg(not(feature = "renderer"))]
self.ct.insert(TMPL_HTML_VAR_EXPORTER_HIGHLIGHTING_CSS, "");
self.ct.insert(
TMPL_HTML_VAR_VIEWER_DOC_CSS_PATH,
TMPL_HTML_VAR_VIEWER_DOC_CSS_PATH_VALUE,
);
self.ct.insert(
TMPL_HTML_VAR_VIEWER_HIGHLIGHTING_CSS_PATH,
TMPL_HTML_VAR_VIEWER_HIGHLIGHTING_CSS_PATH_VALUE,
);
Context {
ct: self.ct,
path: self.path,
dir_path: self.dir_path,
root_path: self.root_path,
doc_file_date: self.doc_file_date,
_marker: PhantomData,
}
}
}
impl<S: ContextState> Deref for Context<S> {
type Target = tera::Context;
fn deref(&self) -> &Self::Target {
&self.ct
}
}
#[cfg(test)]
mod tests {
use crate::{config::TMPL_VAR_FM_ALL, error::NoteError};
use std::path::Path;
#[test]
fn test_insert_front_matter() {
use crate::context::Context;
use crate::front_matter::FrontMatter;
use std::path::Path;
let context = Context::from(Path::new("/path/to/mynote.md")).unwrap();
let context = context
.insert_front_matter(&FrontMatter::try_from("title: My Stdin.\nsome: text").unwrap());
assert_eq!(
&context
.get(TMPL_VAR_FM_ALL)
.unwrap()
.get("fm_title")
.unwrap()
.to_string(),
r#""My Stdin.""#
);
assert_eq!(
&context
.get(TMPL_VAR_FM_ALL)
.unwrap()
.get("fm_some")
.unwrap()
.to_string(),
r#""text""#
);
assert_eq!(
&context
.get(TMPL_VAR_FM_ALL)
.unwrap()
.get("fm_title")
.unwrap()
.to_string(),
r#""My Stdin.""#
);
assert_eq!(
&context
.get(TMPL_VAR_FM_ALL)
.unwrap()
.get("fm_some")
.unwrap()
.to_string(),
r#""text""#
);
}
#[test]
fn test_insert_front_matter2() {
use crate::context::Context;
use crate::front_matter::FrontMatter;
use std::path::Path;
let context = Context::from(Path::new("/path/to/mynote.md")).unwrap();
let context = context
.insert_front_matter(&FrontMatter::try_from("title: My Stdin.\nsome: text").unwrap());
let context = context.set_state_ready_for_content_template();
assert_eq!(
&context
.get(TMPL_VAR_FM_ALL)
.unwrap()
.get("fm_title")
.unwrap()
.to_string(),
r#""My Stdin.""#
);
assert_eq!(
&context
.get(TMPL_VAR_FM_ALL)
.unwrap()
.get("fm_some")
.unwrap()
.to_string(),
r#""text""#
);
assert_eq!(
&context
.get(TMPL_VAR_FM_ALL)
.unwrap()
.get("fm_title")
.unwrap()
.to_string(),
r#""My Stdin.""#
);
assert_eq!(
&context
.get(TMPL_VAR_FM_ALL)
.unwrap()
.get("fm_some")
.unwrap()
.to_string(),
r#""text""#
);
}
#[test]
fn test_insert_front_matter_and_raw_text_from_existing_content() {
use crate::content::Content;
use crate::content::ContentString;
use crate::context::Context;
use crate::settings::set_test_default_settings;
use std::path::Path;
set_test_default_settings().unwrap();
let context = Context::from(Path::new("/path/to/mynote.md")).unwrap();
let c1 = ContentString::from_string(
String::from("Data from clipboard."),
"txt_clipboard".to_string(),
);
let c2 = ContentString::from_string(
"---\ntitle: My Stdin.\n---\nbody".to_string(),
"stdin".to_string(),
);
let c = vec![&c1, &c2];
let context = context
.insert_front_matter_and_raw_text_from_existing_content(&c)
.unwrap();
assert_eq!(
&context
.get("txt_clipboard")
.unwrap()
.get("body")
.unwrap()
.to_string(),
"\"Data from clipboard.\""
);
assert_eq!(
&context
.get("stdin")
.unwrap()
.get("body")
.unwrap()
.to_string(),
"\"body\""
);
assert_eq!(
&context
.get("stdin")
.unwrap()
.get("header")
.unwrap()
.to_string(),
"\"title: My Stdin.\""
);
assert_eq!(
&context
.get("fm")
.unwrap()
.get("fm_title")
.unwrap()
.to_string(),
"\"My Stdin.\""
);
}
#[test]
fn test_assert_preconditions() {
use crate::context::Context;
use crate::front_matter::FrontMatter;
use serde_json::json;
let input = "";
let fm = FrontMatter::try_from(input).unwrap();
let cx = Context::from(Path::new("does not matter")).unwrap();
let cx = cx.insert_front_matter(&fm);
assert!(matches!(
cx.assert_precoditions().unwrap_err(),
NoteError::FrontMatterFieldMissing { .. }
));
let input = "# document start
title: The book
sort_tag: 123b";
let fm = FrontMatter::try_from(input).unwrap();
let cx = Context::from(Path::new("./03b-test.md")).unwrap();
let cx = cx.insert_front_matter(&fm);
assert!(matches!(cx.assert_precoditions(), Ok(())));
let input = "# document start
title: The book
sort_tag:
- 1234
- 456";
let fm = FrontMatter::try_from(input).unwrap();
let cx = Context::from(Path::new("does not matter")).unwrap();
let cx = cx.insert_front_matter(&fm);
assert!(matches!(
cx.assert_precoditions().unwrap_err(),
NoteError::FrontMatterFieldIsCompound { .. }
));
let input = "# document start
title: The book
sort_tag:
first: 1234
second: 456";
let fm = FrontMatter::try_from(input).unwrap();
let cx = Context::from(Path::new("does not matter")).unwrap();
let cx = cx.insert_front_matter(&fm);
assert!(matches!(
cx.assert_precoditions().unwrap_err(),
NoteError::FrontMatterFieldIsCompound { .. }
));
let input = "# document start
title: The book
file_ext: xyz";
let fm = FrontMatter::try_from(input).unwrap();
let cx = Context::from(Path::new("does not matter")).unwrap();
let cx = cx.insert_front_matter(&fm);
assert!(matches!(
cx.assert_precoditions().unwrap_err(),
NoteError::FrontMatterFieldIsNotTpnoteExtension { .. }
));
let input = "# document start
title: The book
filename_sync: error, here should be a bool";
let fm = FrontMatter::try_from(input).unwrap();
let cx = Context::from(Path::new("does not matter")).unwrap();
let cx = cx.insert_front_matter(&fm);
assert!(matches!(
cx.assert_precoditions().unwrap_err(),
NoteError::FrontMatterFieldIsNotBool { .. }
));
let input = "# document start
title: my title
subtitle: my subtitle
";
let expected = json!({"fm_title": "my title", "fm_subtitle": "my subtitle"});
let fm = FrontMatter::try_from(input).unwrap();
let cx = Context::from(Path::new("does not matter")).unwrap();
let cx = cx.insert_front_matter(&fm);
assert_eq!(cx.get(TMPL_VAR_FM_ALL).unwrap(), &expected);
let input = "# document start
title: my title
file_ext: ''
";
let expected = json!({"fm_title": "my title", "fm_file_ext": ""});
let fm = FrontMatter::try_from(input).unwrap();
let cx = Context::from(Path::new("does not matter")).unwrap();
let cx = cx.insert_front_matter(&fm);
assert_eq!(cx.get(TMPL_VAR_FM_ALL).unwrap(), &expected);
let input = "# document start
title: ''
subtitle: my subtitle
";
let fm = FrontMatter::try_from(input).unwrap();
let cx = Context::from(Path::new("does not matter")).unwrap();
let cx = cx.insert_front_matter(&fm);
assert!(matches!(
cx.assert_precoditions().unwrap_err(),
NoteError::FrontMatterFieldIsEmptyString { .. }
));
let input = "# document start
title: My doc
author:
- First author
- Second author
";
let fm = FrontMatter::try_from(input).unwrap();
let cx = Context::from(Path::new("does not matter")).unwrap();
let cx = cx.insert_front_matter(&fm);
assert!(cx.assert_precoditions().is_ok());
let input = "# document start
title: My doc
subtitle: my subtitle
author:
- First title
- 1234
";
let fm = FrontMatter::try_from(input).unwrap();
let cx = Context::from(Path::new("does not matter")).unwrap();
let cx = cx.insert_front_matter(&fm);
assert!(matches!(
cx.assert_precoditions().unwrap_err(),
NoteError::FrontMatterFieldIsNotString { .. }
));
}
}