diff --git a/src/lib.rs b/src/lib.rs index ee90e63..bb3f7e5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -133,8 +133,8 @@ pub type MarkdownEvents<'a> = Vec>; /// # exporter.run().unwrap(); /// ``` -pub type Postprocessor = - dyn Fn(&mut Context, &mut MarkdownEvents) -> PostprocessorResult + Send + Sync; +pub type Postprocessor<'f> = + dyn Fn(&mut Context, &mut MarkdownEvents) -> PostprocessorResult + Send + Sync + 'f; type Result = std::result::Result; const PERCENTENCODE_CHARS: &AsciiSet = &CONTROLS.add(b' ').add(b'(').add(b')').add(b'%').add(b'?'); @@ -231,8 +231,8 @@ pub struct Exporter<'a> { vault_contents: Option>, walk_options: WalkOptions<'a>, process_embeds_recursively: bool, - postprocessors: Vec<&'a Postprocessor>, - embed_postprocessors: Vec<&'a Postprocessor>, + postprocessors: Vec<&'a Postprocessor<'a>>, + embed_postprocessors: Vec<&'a Postprocessor<'a>>, } impl<'a> fmt::Debug for Exporter<'a> { diff --git a/tests/postprocessors_test.rs b/tests/postprocessors_test.rs index ef701b6..5311896 100644 --- a/tests/postprocessors_test.rs +++ b/tests/postprocessors_test.rs @@ -3,8 +3,10 @@ use obsidian_export::{Context, Exporter, MarkdownEvents, PostprocessorResult}; use pretty_assertions::assert_eq; use pulldown_cmark::{CowStr, Event}; use serde_yaml::Value; +use std::collections::HashSet; use std::fs::{read_to_string, remove_file}; use std::path::PathBuf; +use std::sync::Mutex; use tempfile::TempDir; /// This postprocessor replaces any instance of "foo" with "bar" in the note body. @@ -105,6 +107,38 @@ fn test_postprocessor_change_destination() { assert!(new_note_path.exists()); } +// Ensure postprocessor type definition has proper lifetimes to allow state (here: `parents`) +// to be passed in. Otherwise, this fails with an error like: +// error[E0597]: `parents` does not live long enough +// cast requires that `parents` is borrowed for `'static` +#[test] +fn test_postprocessor_stateful_callback() { + let tmp_dir = TempDir::new().expect("failed to make tempdir"); + let mut exporter = Exporter::new( + PathBuf::from("tests/testdata/input/postprocessors"), + tmp_dir.path().to_path_buf(), + ); + + let parents: Mutex> = Default::default(); + let callback = |ctx: &mut Context, _mdevents: &mut MarkdownEvents| -> PostprocessorResult { + parents + .lock() + .unwrap() + .insert(ctx.destination.parent().unwrap().to_path_buf()); + PostprocessorResult::Continue + }; + exporter.add_postprocessor(&callback); + + exporter.run().unwrap(); + + let expected = tmp_dir.path().clone(); + + let parents = parents.lock().unwrap(); + println!("{:?}", parents); + assert_eq!(1, parents.len()); + assert!(parents.contains(expected)); +} + // The purpose of this test to verify the `append_frontmatter` postprocessor is called to extend // the frontmatter, and the `foo_to_bar` postprocessor is called to replace instances of "foo" with // "bar" (only in the note body).