1+ use std:: borrow:: Cow ;
2+
13/// Escapes Markdown reserved characters in the given text.
24fn escape_markdown ( text : & str ) -> String {
35 // Characters that should be escaped in markdown
@@ -25,8 +27,12 @@ pub enum Markdown {
2527 level : u8 ,
2628 text : String ,
2729 } ,
28- Paragraph ( String ) ,
29- InlineCode ( String ) ,
30+ Paragraph {
31+ text : String ,
32+ bold : bool ,
33+ italic : bool ,
34+ code : bool ,
35+ } ,
3036 CodeBlock {
3137 language : Option < String > ,
3238 code : String ,
@@ -52,6 +58,54 @@ pub enum Markdown {
5258 } ,
5359}
5460
61+ #[ allow( dead_code) ]
62+ impl Markdown {
63+ pub fn new_paragraph ( text : impl Into < String > ) -> Self {
64+ Markdown :: Paragraph {
65+ text : text. into ( ) ,
66+ bold : false ,
67+ italic : false ,
68+ code : false ,
69+ }
70+ }
71+
72+ pub fn bold ( self ) -> Self {
73+ match self {
74+ Markdown :: Paragraph { text, .. } => Markdown :: Paragraph {
75+ text,
76+ bold : true ,
77+ italic : false ,
78+ code : false ,
79+ } ,
80+ _ => self ,
81+ }
82+ }
83+
84+ pub fn italic ( self ) -> Self {
85+ match self {
86+ Markdown :: Paragraph { text, .. } => Markdown :: Paragraph {
87+ text,
88+ bold : false ,
89+ italic : true ,
90+ code : false ,
91+ } ,
92+ _ => self ,
93+ }
94+ }
95+
96+ pub fn code ( self ) -> Self {
97+ match self {
98+ Markdown :: Paragraph { text, .. } => Markdown :: Paragraph {
99+ text,
100+ bold : false ,
101+ italic : false ,
102+ code : true ,
103+ } ,
104+ _ => self ,
105+ }
106+ }
107+ }
108+
55109impl IntoMarkdown for Markdown {
56110 fn to_markdown ( & self , builder : & mut MarkdownBuilder ) {
57111 match self {
@@ -62,28 +116,54 @@ impl IntoMarkdown for Markdown {
62116 // Escape the text for Markdown
63117 builder. append ( & format ! ( "{} {}" , hashes, escape_markdown( text) ) ) ;
64118 }
65- Markdown :: Paragraph ( text) => {
66- builder. append ( & escape_markdown ( text) ) ;
119+ Markdown :: Paragraph {
120+ text,
121+ bold,
122+ italic,
123+ code,
124+ } => {
125+ if * bold {
126+ builder. append ( "**" ) ;
127+ }
128+ if * italic {
129+ builder. append ( "_" ) ;
130+ }
131+ if * code {
132+ builder. append ( "`" ) ;
133+ }
134+
135+ let escaped = if * code {
136+ text. clone ( )
137+ } else {
138+ escape_markdown ( text)
139+ } ;
140+
141+ builder. append ( & escaped) ;
142+
143+ if * code {
144+ builder. append ( "`" ) ;
145+ }
146+ if * italic {
147+ builder. append ( "_" ) ;
148+ }
149+ if * bold {
150+ builder. append ( "**" ) ;
151+ }
67152 }
68153 Markdown :: CodeBlock { language, code } => {
69154 // Do not escape code blocks
70155 let lang = language. as_deref ( ) . unwrap_or ( "" ) ;
71156 builder. append ( & format ! ( "```{}\n {}\n ```" , lang, code) ) ;
72157 }
73- Markdown :: InlineCode ( code) => {
74- // Do not escape inline code
75- builder. append ( & format ! ( "`{}`" , code) ) ;
76- }
77158 Markdown :: List { ordered, items } => {
78159 let list_output = items
79160 . iter ( )
80161 . enumerate ( )
81162 . map ( |( i, item) | {
82- let escaped_item = escape_markdown ( item) ;
83163 if * ordered {
84- format ! ( "{}. {}" , i + 1 , escaped_item )
164+ format ! ( "{}. {}" , i + 1 , item )
85165 } else {
86- format ! ( "- {}" , escaped_item )
166+ format ! ( "- {}" , item )
87167 }
88168 } )
89169 . collect :: < Vec < String > > ( )
@@ -149,6 +229,56 @@ impl IntoMarkdown for Markdown {
149229 }
150230}
151231
232+ impl IntoMarkdown for & str {
233+ fn to_markdown ( & self , builder : & mut MarkdownBuilder ) {
234+ builder. append ( & escape_markdown ( self ) )
235+ }
236+ }
237+
238+ impl IntoMarkdown for String {
239+ fn to_markdown ( & self , builder : & mut MarkdownBuilder ) {
240+ builder. append ( & escape_markdown ( self . as_ref ( ) ) )
241+ }
242+ }
243+
244+ impl IntoMarkdown for Cow < ' _ , str > {
245+ fn to_markdown ( & self , builder : & mut MarkdownBuilder ) {
246+ builder. append ( & escape_markdown ( self . as_ref ( ) ) )
247+ }
248+ }
249+
250+ impl IntoMarkdown for Box < dyn IntoMarkdown > {
251+ fn to_markdown ( & self , builder : & mut MarkdownBuilder ) {
252+ self . as_ref ( ) . to_markdown ( builder)
253+ }
254+ }
255+
256+ /// Usage: markdown_vec![item1, item2, item3]
257+ /// Creates Vec<dyn IntoMarkdown> from a list of items.
258+ #[ macro_export]
259+ macro_rules! markdown_vec {
260+ ( $( $x: expr) ,* $( , ) ?) => {
261+ vec![ $(
262+ Box :: new( $x) as Box <dyn IntoMarkdown >
263+ ) ,* ]
264+ } ;
265+ }
266+
267+ impl < T : IntoMarkdown > IntoMarkdown for Vec < T > {
268+ fn to_markdown ( & self , builder : & mut MarkdownBuilder ) {
269+ for ( i, item) in self . iter ( ) . enumerate ( ) {
270+ item. to_markdown ( builder) ;
271+ if i < self . len ( ) - 1 {
272+ if builder. inline {
273+ builder. append ( " " ) ;
274+ } else {
275+ builder. append ( "\n \n " ) ;
276+ }
277+ }
278+ }
279+ }
280+ }
281+
152282/// Builder pattern for generating comprehensive Markdown documentation.
153283/// Now also doubles as the accumulator for the generated markdown.
154284pub struct MarkdownBuilder {
@@ -194,8 +324,35 @@ impl MarkdownBuilder {
194324 }
195325
196326 /// Adds a paragraph element.
197- pub fn paragraph ( & mut self , text : impl Into < String > ) -> & mut Self {
198- self . elements . push ( Markdown :: Paragraph ( text. into ( ) ) ) ;
327+ pub fn text ( & mut self , text : impl Into < String > ) -> & mut Self {
328+ self . elements . push ( Markdown :: Paragraph {
329+ text : text. into ( ) ,
330+ bold : false ,
331+ italic : false ,
332+ code : false ,
333+ } ) ;
334+ self
335+ }
336+
337+ /// Adds a bold element.
338+ pub fn bold ( & mut self , text : impl Into < String > ) -> & mut Self {
339+ self . elements . push ( Markdown :: Paragraph {
340+ text : text. into ( ) ,
341+ bold : true ,
342+ italic : false ,
343+ code : false ,
344+ } ) ;
345+ self
346+ }
347+
348+ /// Adds an italic element.
349+ pub fn italic ( & mut self , text : impl Into < String > ) -> & mut Self {
350+ self . elements . push ( Markdown :: Paragraph {
351+ text : text. into ( ) ,
352+ bold : false ,
353+ italic : true ,
354+ code : false ,
355+ } ) ;
199356 self
200357 }
201358
@@ -214,13 +371,27 @@ impl MarkdownBuilder {
214371
215372 /// Adds an inline code element.
216373 pub fn inline_code ( & mut self , code : impl Into < String > ) -> & mut Self {
217- self . elements . push ( Markdown :: InlineCode ( code. into ( ) ) ) ;
374+ self . elements . push ( Markdown :: Paragraph {
375+ text : code. into ( ) ,
376+ bold : false ,
377+ italic : false ,
378+ code : true ,
379+ } ) ;
218380 self
219381 }
220382
221383 /// Adds a list element.
222- pub fn list ( & mut self , ordered : bool , items : Vec < impl Into < String > > ) -> & mut Self {
223- let converted_items: Vec < String > = items. into_iter ( ) . map ( |s| s. into ( ) ) . collect ( ) ;
384+ pub fn list ( & mut self , ordered : bool , items : Vec < impl IntoMarkdown > ) -> & mut Self {
385+ let converted_items: Vec < String > = items
386+ . into_iter ( )
387+ . map ( |s| {
388+ let mut builder = MarkdownBuilder :: new ( ) ;
389+ builder. inline ( ) ;
390+ s. to_markdown ( & mut builder) ;
391+ builder. build ( )
392+ } )
393+ . collect ( ) ;
394+
224395 self . elements . push ( Markdown :: List {
225396 ordered,
226397 items : converted_items,
@@ -316,8 +487,16 @@ impl TableBuilder {
316487 }
317488
318489 /// Adds a row to the table.
319- pub fn row ( & mut self , row : Vec < impl Into < String > > ) -> & mut Self {
320- let converted: Vec < String > = row. into_iter ( ) . map ( |cell| cell. into ( ) ) . collect ( ) ;
490+ pub fn row ( & mut self , row : Vec < impl IntoMarkdown > ) -> & mut Self {
491+ let converted = row
492+ . into_iter ( )
493+ . map ( |r| {
494+ let mut builder = MarkdownBuilder :: new ( ) ;
495+ builder. inline ( ) ;
496+ r. to_markdown ( & mut builder) ;
497+ builder. build ( )
498+ } )
499+ . collect ( ) ;
321500 self . rows . push ( converted) ;
322501 self
323502 }
@@ -340,17 +519,26 @@ mod tests {
340519 let mut builder = MarkdownBuilder :: new ( ) ;
341520 let markdown = builder
342521 . heading ( 1 , "Documentation Title *with special chars*" )
343- . paragraph ( "This is the introduction with some _underscores_ and `backticks`." )
522+ . text ( "This is the introduction with some _underscores_ and `backticks`." )
344523 . codeblock ( Some ( "rust" ) , "fn main() { println!(\" Hello, world!\" ); }" )
345524 . list (
346525 false ,
347- vec ! [
526+ markdown_vec ! [
348527 "First bullet with #hash" ,
349- "Second bullet with [brackets]" ,
350- "Third bullet with (parentheses)" ,
528+ Markdown :: new_paragraph( "Second bullet with [brackets]" )
529+ . bold( )
530+ . code( ) ,
351531 ] ,
352532 )
353533 . quote ( "This is a quote!\n It spans multiple lines." )
534+ . list (
535+ true ,
536+ Vec :: from_iter ( vec ! [ markdown_vec![
537+ Markdown :: new_paragraph( "italic" ) . italic( ) ,
538+ Markdown :: new_paragraph( "bold" ) . bold( ) ,
539+ Markdown :: new_paragraph( "code" ) . code( ) ,
540+ ] ] ) ,
541+ )
354542 . image (
355543 "Rust Logo" ,
356544 "https://www.rust-lang.org/logos/rust-logo-512x512.png" ,
@@ -361,7 +549,10 @@ mod tests {
361549 table
362550 . headers ( vec ! [ "Header 1" , "Header 2" ] )
363551 . row ( vec ! [ "Row 1 Col 1" , "Row 1 Col 2" ] )
364- . row ( vec ! [ "Row 2 Col 1" , "Row 2 Col 2" ] ) ;
552+ . row ( markdown_vec ! [
553+ "Row 2 Col 1" ,
554+ Markdown :: new_paragraph( "some_code" ) . code( )
555+ ] ) ;
365556 } )
366557 . build ( ) ;
367558 let expected = r#"
@@ -374,12 +565,13 @@ mod tests {
374565 ```
375566
376567 - First bullet with \#hash
377- - Second bullet with \[brackets\]
378- - Third bullet with \(parentheses\)
568+ - `Second bullet with [brackets]`
379569
380570 > This is a quote\!
381571 > It spans multiple lines\.
382572
573+ 1. _italic_ **bold** `code`
574+
383575 
384576
385577 [Rust Homepage](https://www.rust-lang.org)
@@ -389,7 +581,7 @@ mod tests {
389581 | Header 1 | Header 2 |
390582 | --- | --- |
391583 | Row 1 Col 1 | Row 1 Col 2 |
392- | Row 2 Col 1 | Row 2 Col 2 |
584+ | Row 2 Col 1 | `some_code` |
393585 "# ;
394586
395587 let trimmed_indentation_expected = expected
0 commit comments