diff --git a/CHANGELOG.md b/CHANGELOG.md index 64912d62..6c7af8d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file. What's changed +* Add `escape_square_brackets` into `comment_formats` markdown configuration. ([#XXX](...) by lquerel). * Add `enforce_trailing_dots` into the `comment_formats` configuration. ([#XXX](...) by lquerel). * Add support for `indent_type` in both the comment filter and the `comment_formats` configuration. ([#XXX](...) by lquerel). * Add `regex_replace` filter to support replacing text using regex. ([#XXX](...) by lquerel). diff --git a/crates/weaver_forge/README.md b/crates/weaver_forge/README.md index 88d75acb..09a16305 100644 --- a/crates/weaver_forge/README.md +++ b/crates/weaver_forge/README.md @@ -584,6 +584,7 @@ comment_formats: # optional # Fields specific to 'markdown' format escape_backslashes: # Whether to escape backslashes in markdown (default: false). + escape_square_brackets: # Whether to escape square brackets in markdown (default: false). shortcut_reference_links: # Convert inlined links into shortcut reference links (default: false). indent_first_level_list_items: # Indent the first level of list items in markdown (default: false). default_block_code_language: # Default language for block code snippets (default: ""). diff --git a/crates/weaver_forge/expected_output/comment_format/example.rs b/crates/weaver_forge/expected_output/comment_format/example.rs index a2b38245..82304fbb 100644 --- a/crates/weaver_forge/expected_output/comment_format/example.rs +++ b/crates/weaver_forge/expected_output/comment_format/example.rs @@ -96,7 +96,7 @@ /// > Lorem ipsum dolor sit amet, consectetur adipiscing /// > elit sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. /// - /// > [!NOTE] Something very important here + /// > \[!NOTE\] Something very important here const ATTR: &str = ""; diff --git a/crates/weaver_forge/src/formats/markdown.rs b/crates/weaver_forge/src/formats/markdown.rs index ca770fe7..6e3fce88 100644 --- a/crates/weaver_forge/src/formats/markdown.rs +++ b/crates/weaver_forge/src/formats/markdown.rs @@ -12,10 +12,14 @@ use std::collections::HashMap; #[derive(Deserialize, Serialize, Debug, Clone, Default)] #[serde(rename_all = "snake_case")] pub struct MarkdownRenderOptions { - /// Whether to escape backslashes in the markdown. + /// Whether to escape backslashes in the Markdown. /// Default is false. #[serde(default)] pub(crate) escape_backslashes: bool, + /// Whether to escape square brackets in the Markdown text. Valid links are not affected. + /// Default is true. + #[serde(default = "default_bool::")] + pub(crate) escape_square_brackets: bool, /// Whether to indent the first level of list items in the markdown. /// Default is false. #[serde(default)] @@ -30,6 +34,12 @@ pub struct MarkdownRenderOptions { pub(crate) default_block_code_language: Option, } +/// Used to set a default value for a boolean field in a struct. +#[must_use] +pub const fn default_bool() -> bool { + V +} + pub(crate) struct ShortcutReferenceLink { pub(crate) label: String, pub(crate) url: String, @@ -196,11 +206,36 @@ impl MarkdownRenderer { } } Node::Text(text) => { + fn escape_unescaped_chars(s: &str, chars_to_escape: &[char]) -> String { + let mut result = String::with_capacity(s.len()); + let mut backslash_count = 0; + + for c in s.chars() { + if c == '\\' { + backslash_count += 1; + result.push(c); + } else { + if chars_to_escape.contains(&c) && backslash_count % 2 == 0 { + // Even number of backslashes means the character is unescaped + result.push('\\'); + } + result.push(c); + // Reset the backslash count after a non-backslash character + backslash_count = 0; + } + } + + result + } + + let mut text = text.value.clone(); if options.escape_backslashes { - ctx.add_text(&text.value.replace('\\', "\\\\")); - } else { - ctx.add_text(&text.value); + text = text.replace('\\', "\\\\"); + } + if options.escape_square_brackets { + text = escape_unescaped_chars(&text, &['[', ']']); } + ctx.add_text(&text); } Node::Paragraph(p) => { ctx.add_cond_blank_line(); @@ -375,6 +410,7 @@ mod tests { indent_type: IndentType::Space, format: RenderFormat::Markdown(MarkdownRenderOptions { escape_backslashes: false, + escape_square_brackets: false, indent_first_level_list_items: true, shortcut_reference_link: true, default_block_code_language: None, @@ -467,6 +503,87 @@ it's RECOMMENDED to: - Use a domain-specific attribute - Set `error.type` to capture all errors, regardless of whether they are defined within the domain-specific set or not."## ); + + let config = WeaverConfig { + comment_formats: Some( + vec![( + "go".to_owned(), + CommentFormat { + header: None, + prefix: Some("// ".to_owned()), + footer: None, + format: RenderFormat::Markdown(MarkdownRenderOptions { + escape_backslashes: false, + escape_square_brackets: false, + indent_first_level_list_items: true, + shortcut_reference_link: true, + default_block_code_language: None, + }), + trim: true, + remove_trailing_dots: true, + indent_type: Default::default(), + enforce_trailing_dots: false, + }, + )] + .into_iter() + .collect(), + ), + default_comment_format: Some("go".to_owned()), + ..WeaverConfig::default() + }; + + let renderer = MarkdownRenderer::try_new(&config)?; + let markdown = r##"In some cases a [URL] may refer to an [IP](http://ip.com) and/or port directly, + The file \\[extension\\] extracted \\[from] the `url.full`, excluding the leading dot."##; + let html = renderer.render(markdown, "go")?; + assert_eq!( + html, + r##"In some cases a [URL] may refer to an [IP] and/or port directly, +The file \[extension\] extracted \[from] the `url.full`, excluding the leading dot. + +[IP]: http://ip.com"## + ); + + let config = WeaverConfig { + comment_formats: Some( + vec![( + "go".to_owned(), + CommentFormat { + header: None, + prefix: Some("// ".to_owned()), + footer: None, + format: RenderFormat::Markdown(MarkdownRenderOptions { + escape_backslashes: false, + escape_square_brackets: true, + indent_first_level_list_items: true, + shortcut_reference_link: true, + default_block_code_language: None, + }), + trim: true, + remove_trailing_dots: true, + indent_type: Default::default(), + enforce_trailing_dots: false, + }, + )] + .into_iter() + .collect(), + ), + default_comment_format: Some("go".to_owned()), + ..WeaverConfig::default() + }; + + let renderer = MarkdownRenderer::try_new(&config)?; + let markdown = r##"In some cases a [URL] may refer to an [IP](http://ip.com) and/or port directly, + The file \[extension\] extracted \[from] the `url.full`, excluding the leading dot."##; + let html = renderer.render(markdown, "go")?; + assert_eq!( + html, + r##"In some cases a \[URL\] may refer to an [IP] and/or port directly, +The file \[extension\] extracted \[from\] the `url.full`, excluding the leading dot. + +[IP]: http://ip.com"## + ); + Ok(()) } } diff --git a/crates/weaver_forge/templates/comment_format/weaver.yaml b/crates/weaver_forge/templates/comment_format/weaver.yaml index 375ea7e6..ce265626 100644 --- a/crates/weaver_forge/templates/comment_format/weaver.yaml +++ b/crates/weaver_forge/templates/comment_format/weaver.yaml @@ -37,6 +37,7 @@ comment_formats: shortcut_reference_link: true trim: true remove_trailing_dots: true + escape_square_brackets: false python: format: markdown header: '"""' @@ -44,6 +45,7 @@ comment_formats: escape_backslashes: true trim: true remove_trailing_dots: true + escape_square_brackets: false templates: - template: "example.java.j2" diff --git a/docs/weaver-config.md b/docs/weaver-config.md index 731711f6..1dfc4b5a 100644 --- a/docs/weaver-config.md +++ b/docs/weaver-config.md @@ -54,6 +54,7 @@ comment_formats: # optional # The following fields are enabled only when format is set to 'markdown' escape_backslashes: # Whether to escape backslashes in the markdown (default: false). + escape_square_brackets: # Whether to escape square brackets in markdown (default: false). shortcut_reference_links: # Use this to convert inlined links into shortcut reference links, similar to those in Go documentation (default: false). indent_first_level_list_items: # Whether to indent the first level of list items in the markdown (default: false). default_block_code_language: # The default language for block code snippets (default: "").