From db3e312a77c94878a2e4796ef914a09d2fd57977 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johan=20F=C3=B6rberg?= Date: Tue, 24 Jun 2025 14:33:56 +0200 Subject: [PATCH] Parse Obsidian-style comments In addition to normal HTML comment style, Obsidian supports comments enclosed by "%%". --- src/main.rs | 6 ++- src/postprocessors.rs | 56 ++++++++++++++++++++++++ tests/export_test.rs | 18 ++++++++ tests/testdata/expected/comments/note.md | 8 ++++ tests/testdata/input/comments/note.md | 8 ++++ 5 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 tests/testdata/expected/comments/note.md create mode 100644 tests/testdata/input/comments/note.md diff --git a/src/main.rs b/src/main.rs index bdab2d2..4ff0238 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,9 @@ 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, parse_obsidian_comments, softbreaks_to_hardbreaks, +}; use obsidian_export::{ExportError, Exporter, FrontmatterStrategy, WalkOptions}; const VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -107,6 +109,8 @@ fn main() { exporter.preserve_mtime(args.preserve_mtime); exporter.walk_options(walk_options); + exporter.add_postprocessor(&parse_obsidian_comments); + if args.hard_linebreaks { exporter.add_postprocessor(&softbreaks_to_hardbreaks); } diff --git a/src/postprocessors.rs b/src/postprocessors.rs index c4077d9..aca5a20 100644 --- a/src/postprocessors.rs +++ b/src/postprocessors.rs @@ -19,6 +19,62 @@ pub fn softbreaks_to_hardbreaks( PostprocessorResult::Continue } +/// In addition to HTML comments "", Obsidian syntax includes a second comment +/// syntax "%% my comment %%". This function converts Obsidian-style comments to the standard HTML +/// style. +/// +/// Because this step is done when the document is already parsed from markdown, any embedded +/// markup inside comments will be elided from the output. +pub fn parse_obsidian_comments( + context: &mut Context, + events: &mut MarkdownEvents<'_>, +) -> PostprocessorResult { + let mut in_comment = false; + let mut comment_acc = String::new(); + let input = std::mem::take(events); + let mut output = MarkdownEvents::new(); + + for event in input { + match event { + Event::Text(s) => { + for (idx, text) in s.split("%%").enumerate() { + if idx > 0 { + if in_comment && !comment_acc.is_empty() { + output.push(Event::InlineHtml(format!("").into())); + comment_acc.clear(); + } + + in_comment = !in_comment; + } + + if !text.is_empty() { + if in_comment { + comment_acc.push_str(text); + } else { + output.push(Event::Text(text.to_owned().into())); + } + } + } + } + _ => { + if !in_comment { + output.push(event); + } + } + } + } + + assert!( + !in_comment, + "Unmatched comment delimiter in {}", + context.destination.display() + ); + + std::mem::swap(events, &mut output); + + PostprocessorResult::Continue +} + pub fn filter_by_tags( skip_tags: Vec, only_tags: Vec, diff --git a/tests/export_test.rs b/tests/export_test.rs index 8b81721..6ea4508 100644 --- a/tests/export_test.rs +++ b/tests/export_test.rs @@ -6,6 +6,7 @@ use std::io::prelude::*; use std::os::unix::fs::PermissionsExt; use std::path::PathBuf; +use obsidian_export::postprocessors::parse_obsidian_comments; use obsidian_export::{ExportError, Exporter, FrontmatterStrategy}; use pretty_assertions::assert_eq; use tempfile::TempDir; @@ -459,3 +460,20 @@ fn test_same_filename_different_directories() { let actual = read_to_string(tmp_dir.path().join(PathBuf::from("Note.md"))).unwrap(); assert_eq!(expected, actual); } + +#[test] +fn test_parse_obsidian_comments() { + let tmp_dir = TempDir::new().expect("failed to make tempdir"); + + let mut exporter = Exporter::new( + PathBuf::from("tests/testdata/input/comments/"), + tmp_dir.path().to_path_buf(), + ); + exporter.add_postprocessor(&parse_obsidian_comments); + exporter.run().expect("exporter returned error"); + + assert_eq!( + read_to_string("tests/testdata/expected/comments/note.md").unwrap(), + read_to_string(tmp_dir.path().join(PathBuf::from("note.md"))).unwrap(), + ); +} diff --git a/tests/testdata/expected/comments/note.md b/tests/testdata/expected/comments/note.md new file mode 100644 index 0000000..393eefd --- /dev/null +++ b/tests/testdata/expected/comments/note.md @@ -0,0 +1,8 @@ +Test + + + +Test2 + + +This sentence is true. diff --git a/tests/testdata/input/comments/note.md b/tests/testdata/input/comments/note.md new file mode 100644 index 0000000..591be42 --- /dev/null +++ b/tests/testdata/input/comments/note.md @@ -0,0 +1,8 @@ +Test + +%% A comment %% + +Test2 +%% A comment containing [[embedded]] **markup** %% + +This sentence is %% not %% true.