first draft

This commit is contained in:
Eric Woolsey 2023-08-07 23:23:07 -06:00
parent 80130260e9
commit a012c3f25a
2 changed files with 37 additions and 10 deletions

View File

@ -5,6 +5,7 @@ pub extern crate serde_yaml;
extern crate lazy_static; extern crate lazy_static;
mod context; mod context;
mod preprocessors;
mod frontmatter; mod frontmatter;
pub mod postprocessors; pub mod postprocessors;
mod references; mod references;
@ -132,7 +133,8 @@ pub type MarkdownEvents<'a> = Vec<Event<'a>>;
/// exporter.add_postprocessor(&foo_to_bar); /// exporter.add_postprocessor(&foo_to_bar);
/// # exporter.run().unwrap(); /// # exporter.run().unwrap();
/// ``` /// ```
pub type Preprocessor =
dyn Fn(&mut Context, &mut String) -> PostprocessorResult + Send + Sync;
pub type Postprocessor = pub type Postprocessor =
dyn Fn(&mut Context, &mut MarkdownEvents) -> PostprocessorResult + Send + Sync; dyn Fn(&mut Context, &mut MarkdownEvents) -> PostprocessorResult + Send + Sync;
type Result<T, E = ExportError> = std::result::Result<T, E>; type Result<T, E = ExportError> = std::result::Result<T, E>;
@ -231,6 +233,7 @@ pub struct Exporter<'a> {
vault_contents: Option<Vec<PathBuf>>, vault_contents: Option<Vec<PathBuf>>,
walk_options: WalkOptions<'a>, walk_options: WalkOptions<'a>,
process_embeds_recursively: bool, process_embeds_recursively: bool,
preprocessors: Vec<&'a Preprocessor>,
postprocessors: Vec<&'a Postprocessor>, postprocessors: Vec<&'a Postprocessor>,
embed_postprocessors: Vec<&'a Postprocessor>, embed_postprocessors: Vec<&'a Postprocessor>,
} }
@ -274,6 +277,7 @@ impl<'a> Exporter<'a> {
walk_options: WalkOptions::default(), walk_options: WalkOptions::default(),
process_embeds_recursively: true, process_embeds_recursively: true,
vault_contents: None, vault_contents: None,
preprocessors: vec![&preprocessors::remove_ignore_blocks],
postprocessors: vec![], postprocessors: vec![],
embed_postprocessors: vec![], embed_postprocessors: vec![],
} }
@ -381,7 +385,7 @@ impl<'a> Exporter<'a> {
.strip_prefix(&self.start_at.clone()) .strip_prefix(&self.start_at.clone())
.expect("file should always be nested under root") .expect("file should always be nested under root")
.to_path_buf(); .to_path_buf();
let destination = &self.destination.join(&relative_path); let destination = &self.destination.join(relative_path);
self.export_note(&file, destination) self.export_note(&file, destination)
})?; })?;
Ok(()) Ok(())
@ -398,7 +402,7 @@ impl<'a> Exporter<'a> {
fn parse_and_export_obsidian_note(&self, src: &Path, dest: &Path) -> Result<()> { fn parse_and_export_obsidian_note(&self, src: &Path, dest: &Path) -> Result<()> {
let mut context = Context::new(src.to_path_buf(), dest.to_path_buf()); let mut context = Context::new(src.to_path_buf(), dest.to_path_buf());
let (frontmatter, mut markdown_events) = self.parse_obsidian_note(src, &context)?; let (frontmatter, mut markdown_events) = self.parse_obsidian_note(src, &mut context)?;
context.frontmatter = frontmatter; context.frontmatter = frontmatter;
for func in &self.postprocessors { for func in &self.postprocessors {
match func(&mut context, &mut markdown_events) { match func(&mut context, &mut markdown_events) {
@ -432,14 +436,17 @@ impl<'a> Exporter<'a> {
fn parse_obsidian_note<'b>( fn parse_obsidian_note<'b>(
&self, &self,
path: &Path, path: &Path,
context: &Context, context: &mut Context,
) -> Result<(Frontmatter, MarkdownEvents<'b>)> { ) -> Result<(Frontmatter, MarkdownEvents<'b>)> {
if context.note_depth() > NOTE_RECURSION_LIMIT { if context.note_depth() > NOTE_RECURSION_LIMIT {
return Err(ExportError::RecursionLimitExceeded { return Err(ExportError::RecursionLimitExceeded {
file_tree: context.file_tree(), file_tree: context.file_tree(),
}); });
} }
let content = fs::read_to_string(path).context(ReadSnafu { path })?; let mut content = fs::read_to_string(path).context(ReadSnafu { path })?;
for preprocessor in &self.preprocessors {
preprocessor(context, &mut content);
}
let (frontmatter, content) = let (frontmatter, content) =
matter::matter(&content).unwrap_or(("".to_string(), content.to_string())); matter::matter(&content).unwrap_or(("".to_string(), content.to_string()));
let frontmatter = let frontmatter =
@ -598,7 +605,7 @@ impl<'a> Exporter<'a> {
let events = match path.extension().unwrap_or(&no_ext).to_str() { let events = match path.extension().unwrap_or(&no_ext).to_str() {
Some("md") => { Some("md") => {
let (frontmatter, mut events) = self.parse_obsidian_note(path, &child_context)?; let (frontmatter, mut events) = self.parse_obsidian_note(path, &mut child_context)?;
child_context.frontmatter = frontmatter; child_context.frontmatter = frontmatter;
if let Some(section) = note_ref.section { if let Some(section) = note_ref.section {
events = reduce_to_section(events, section); events = reduce_to_section(events, section);
@ -647,9 +654,9 @@ impl<'a> Exporter<'a> {
Ok(events) Ok(events)
} }
fn make_link_to_file<'b, 'c>( fn make_link_to_file<'c>(
&self, &self,
reference: ObsidianNoteReference<'b>, reference: ObsidianNoteReference<'_>,
context: &Context, context: &Context,
) -> MarkdownEvents<'c> { ) -> MarkdownEvents<'c> {
let target_file = reference let target_file = reference
@ -732,7 +739,7 @@ fn lookup_filename_in_vault<'a>(
path_normalized.ends_with(&filename_normalized) path_normalized.ends_with(&filename_normalized)
|| path_normalized.ends_with(filename_normalized.clone() + ".md") || path_normalized.ends_with(filename_normalized.clone() + ".md")
|| path_normalized_lowered.ends_with(&filename_normalized.to_lowercase()) || path_normalized_lowered.ends_with(filename_normalized.to_lowercase())
|| path_normalized_lowered.ends_with(filename_normalized.to_lowercase() + ".md") || path_normalized_lowered.ends_with(filename_normalized.to_lowercase() + ".md")
}) })
} }
@ -783,7 +790,7 @@ fn is_markdown_file(file: &Path) -> bool {
/// Reduce a given `MarkdownEvents` to just those elements which are children of the given section /// Reduce a given `MarkdownEvents` to just those elements which are children of the given section
/// (heading name). /// (heading name).
fn reduce_to_section<'a, 'b>(events: MarkdownEvents<'a>, section: &'b str) -> MarkdownEvents<'a> { fn reduce_to_section<'a>(events: MarkdownEvents<'a>, section: &'_ str) -> MarkdownEvents<'a> {
let mut filtered_events = Vec::with_capacity(events.len()); let mut filtered_events = Vec::with_capacity(events.len());
let mut target_section_encountered = false; let mut target_section_encountered = false;
let mut currently_in_target_section = false; let mut currently_in_target_section = false;

20
src/preprocessors.rs Normal file
View File

@ -0,0 +1,20 @@
//! A collection of officially maintained [postprocessors][crate::Postprocessor].
use super::{Context, PostprocessorResult};
use regex::Regex;
const IGNORE_REGEX_0: &str = r"%% EXPORT_IGNORE_BEGIN %%(.|\n)*?%% EXPORT_IGNORE_END %%";
const IGNORE_REGEX_1: &str = r"# EXPORT_IGNORE_BEGIN(.|\n)*?# EXPORT_IGNORE_END";
/// This postprocessor removes all
pub fn remove_ignore_blocks(
_context: &mut Context,
string: &mut String,
) -> PostprocessorResult {
let re = Regex::new(IGNORE_REGEX_0).unwrap();
*string = re.replace_all(string, "").to_string();
let re = Regex::new(IGNORE_REGEX_1).unwrap();
*string = re.replace_all(string, "").to_string();
PostprocessorResult::Continue
}