obsidian_export/
postprocessors.rs

1//! A collection of officially maintained [postprocessors][crate::Postprocessor].
2
3use pulldown_cmark::Event;
4use serde_yaml::Value;
5
6use super::{Context, MarkdownEvents, PostprocessorResult};
7
8/// This postprocessor converts all soft line breaks to hard line breaks. Enabling this mimics
9/// Obsidian's _'Strict line breaks'_ setting.
10pub fn softbreaks_to_hardbreaks(
11    _context: &mut Context,
12    events: &mut MarkdownEvents<'_>,
13) -> PostprocessorResult {
14    for event in events.iter_mut() {
15        if event == &Event::SoftBreak {
16            *event = Event::HardBreak;
17        }
18    }
19    PostprocessorResult::Continue
20}
21
22pub fn filter_by_tags(
23    skip_tags: Vec<String>,
24    only_tags: Vec<String>,
25) -> impl Fn(&mut Context, &mut MarkdownEvents<'_>) -> PostprocessorResult {
26    move |context: &mut Context, _events: &mut MarkdownEvents<'_>| -> PostprocessorResult {
27        match context.frontmatter.get("tags") {
28            None => filter_by_tags_(&[], &skip_tags, &only_tags),
29            Some(Value::Sequence(tags)) => filter_by_tags_(tags, &skip_tags, &only_tags),
30            _ => PostprocessorResult::Continue,
31        }
32    }
33}
34
35fn filter_by_tags_(
36    tags: &[Value],
37    skip_tags: &[String],
38    only_tags: &[String],
39) -> PostprocessorResult {
40    let skip = skip_tags
41        .iter()
42        .any(|tag| tags.contains(&Value::String(tag.to_string())));
43    let include = only_tags.is_empty()
44        || only_tags
45            .iter()
46            .any(|tag| tags.contains(&Value::String(tag.to_string())));
47
48    if skip || !include {
49        PostprocessorResult::StopAndSkipNote
50    } else {
51        PostprocessorResult::Continue
52    }
53}
54
55#[test]
56fn test_filter_tags() {
57    let tags = vec![
58        Value::String("skip".into()),
59        Value::String("publish".into()),
60    ];
61    let empty_tags = vec![];
62    assert_eq!(
63        filter_by_tags_(&empty_tags, &[], &[]),
64        PostprocessorResult::Continue,
65        "When no exclusion & inclusion are specified, files without tags are included"
66    );
67    assert_eq!(
68        filter_by_tags_(&tags, &[], &[]),
69        PostprocessorResult::Continue,
70        "When no exclusion & inclusion are specified, files with tags are included"
71    );
72    assert_eq!(
73        filter_by_tags_(&tags, &["exclude".into()], &[]),
74        PostprocessorResult::Continue,
75        "When exclusion tags don't match files with tags are included"
76    );
77    assert_eq!(
78        filter_by_tags_(&empty_tags, &["exclude".into()], &[]),
79        PostprocessorResult::Continue,
80        "When exclusion tags don't match files without tags are included"
81    );
82    assert_eq!(
83        filter_by_tags_(&tags, &[], &["publish".into()]),
84        PostprocessorResult::Continue,
85        "When exclusion tags don't match files with tags are included"
86    );
87    assert_eq!(
88        filter_by_tags_(&empty_tags, &[], &["include".into()]),
89        PostprocessorResult::StopAndSkipNote,
90        "When inclusion tags are specified files without tags are excluded"
91    );
92    assert_eq!(
93        filter_by_tags_(&tags, &[], &["include".into()]),
94        PostprocessorResult::StopAndSkipNote,
95        "When exclusion tags don't match files with tags are exluded"
96    );
97    assert_eq!(
98        filter_by_tags_(&tags, &["skip".into()], &["skip".into()]),
99        PostprocessorResult::StopAndSkipNote,
100        "When both inclusion and exclusion tags are the same exclusion wins"
101    );
102    assert_eq!(
103        filter_by_tags_(&tags, &["skip".into()], &["publish".into()]),
104        PostprocessorResult::StopAndSkipNote,
105        "When both inclusion and exclusion tags match exclusion wins"
106    );
107}