diff --git a/src/main.rs b/src/main.rs index 8af49ce..abd883c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,12 @@ use std::path::PathBuf; use eyre::{eyre, Result}; use gumdrop::Options; -use obsidian_export::postprocessors::{filter_by_tags, softbreaks_to_hardbreaks}; +use obsidian_export::postprocessors::{ + filter_by_tags, + remove_obsidian_comments, + softbreaks_to_hardbreaks, + CommentStrategy, +}; use obsidian_export::{ExportError, Exporter, FrontmatterStrategy, WalkOptions}; const VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -65,17 +70,20 @@ struct Opts { preserve_mtime: bool, #[options( + help = "Comment strategy (one of: keep-unchanged or remove)", no_short, help = "Convert soft line breaks to hard line breaks. This mimics Obsidian's 'Strict line breaks' setting", default = "false" )] hard_linebreaks: bool, + #[options( no_short, - help = "Remove Obsidian style comments from exported file", - default = "false" + long = "comments", + parse(try_from_str = "comment_strategy_from_str"), + default = "keep-unchanged" )] - remove_obsidian_comments: bool, + comment_strategy: CommentStrategy, } fn frontmatter_strategy_from_str(input: &str) -> Result { @@ -87,6 +95,14 @@ fn frontmatter_strategy_from_str(input: &str) -> Result { } } +fn comment_strategy_from_str(input: &str) -> Result { + match input { + "keep-unchanged" => Ok(CommentStrategy::KeepUnchanged), + "remove" => Ok(CommentStrategy::Remove), + _ => Err(eyre!("must be one of: keep-unchanged or remove")), + } +} + fn main() { // Due to the use of free arguments in Opts, we must bypass Gumdrop to determine whether the // version flag was specified. Without this, "missing required free argument" would get printed @@ -117,7 +133,7 @@ fn main() { exporter.add_postprocessor(&softbreaks_to_hardbreaks); } - if args.remove_obsidian_comments { + if matches!(args.comment_strategy, CommentStrategy::Remove) { exporter.add_postprocessor(&remove_obsidian_comments); } diff --git a/src/postprocessors.rs b/src/postprocessors.rs index 4bdd0c3..df392a9 100644 --- a/src/postprocessors.rs +++ b/src/postprocessors.rs @@ -1,9 +1,9 @@ //! A collection of officially maintained [postprocessors][crate::Postprocessor]. -use super::{Context, MarkdownEvents, PostprocessorResult}; +use std::cell::LazyCell; + use pulldown_cmark::{CowStr, Event, Tag}; use regex::Regex; - use serde_yaml::Value; use super::{Context, MarkdownEvents, PostprocessorResult}; @@ -55,15 +55,27 @@ fn filter_by_tags_( } } +//Available strategies for what to do with comments +#[derive(Debug)] +#[non_exhaustive] +pub enum CommentStrategy { + // Leave comments alone and export them as normal + KeepUnchanged, + // Remove any comments from the output + Remove, +} + +#[allow(clippy::else_if_without_else, clippy::arithmetic_side_effects)] /// This postprocessor removes all Obsidian comments from a file excluding codeblocks. Enabling this /// prohibits comments from being exported but leaves them untouched in the original files pub fn remove_obsidian_comments( _context: &mut Context, - events: &mut MarkdownEvents, + events: &mut MarkdownEvents<'_>, ) -> PostprocessorResult { let mut output = Vec::with_capacity(events.len()); let mut inside_comment = false; let mut inside_codeblock = false; + let re = LazyCell::new(|| Regex::new(r"%%.*?%%").unwrap()); for event in &mut *events { output.push(event.to_owned()); @@ -87,7 +99,6 @@ pub fn remove_obsidian_comments( } if !text.eq(&CowStr::from("%%")) { - let re = Regex::new(r"%%.*?%%").unwrap(); let result = re.replace_all(text, "").to_string(); output.push(Event::Text(CowStr::from(result))); continue; @@ -96,14 +107,25 @@ pub fn remove_obsidian_comments( inside_comment = true; } Event::Start(Tag::CodeBlock(_)) => { - inside_codeblock = true; + if inside_comment { + output.pop(); + } else { + inside_codeblock = true; + } } Event::End(Tag::CodeBlock(_)) => { - inside_codeblock = false; + if inside_comment { + output.pop(); + } else { + inside_codeblock = false; + } } Event::End(Tag::Paragraph) => { - if output[output.len() - 2] == Event::Start(Tag::Paragraph) { - // If the comment was the only item on the line remove the start and end paragraph events to remove the \n in the output file. + if output.len() >= 2 + && output.get(output.len() - 2) == Option::from(&Event::Start(Tag::Paragraph)) + { + // If the comment was the only item on the line remove the start and end + // paragraph events to remove the \n in the output file. output.pop(); output.pop(); } diff --git a/tests/postprocessors_test.rs b/tests/postprocessors_test.rs index 051e728..a2edbb0 100644 --- a/tests/postprocessors_test.rs +++ b/tests/postprocessors_test.rs @@ -1,11 +1,13 @@ -use obsidian_export::postprocessors::{ - filter_by_tags, remove_obsidian_comments, softbreaks_to_hardbreaks, -}; use std::collections::HashSet; use std::fs::{read_to_string, remove_file}; use std::path::PathBuf; use std::sync::Mutex; +use obsidian_export::postprocessors::{ + filter_by_tags, + remove_obsidian_comments, + softbreaks_to_hardbreaks, +}; use obsidian_export::{Context, Exporter, MarkdownEvents, PostprocessorResult}; use pretty_assertions::assert_eq; use pulldown_cmark::{CowStr, Event}; diff --git a/tests/testdata/input/remove-comments/test_comments.md b/tests/testdata/input/remove-comments/test_comments.md index 0eae644..9074976 100644 --- a/tests/testdata/input/remove-comments/test_comments.md +++ b/tests/testdata/input/remove-comments/test_comments.md @@ -9,3 +9,19 @@ This is a block comment that should be removed %% + +%% + +None of the below should be visible in output: + +## Header + +Code block: + +``` +foo +``` + +--- + +%%