1
+ use std:: borrow:: Cow ;
2
+
1
3
/// Escapes Markdown reserved characters in the given text.
2
4
fn escape_markdown ( text : & str ) -> String {
3
5
// Characters that should be escaped in markdown
@@ -25,8 +27,12 @@ pub enum Markdown {
25
27
level : u8 ,
26
28
text : String ,
27
29
} ,
28
- Paragraph ( String ) ,
29
- InlineCode ( String ) ,
30
+ Paragraph {
31
+ text : String ,
32
+ bold : bool ,
33
+ italic : bool ,
34
+ code : bool ,
35
+ } ,
30
36
CodeBlock {
31
37
language : Option < String > ,
32
38
code : String ,
@@ -52,6 +58,54 @@ pub enum Markdown {
52
58
} ,
53
59
}
54
60
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
+
55
109
impl IntoMarkdown for Markdown {
56
110
fn to_markdown ( & self , builder : & mut MarkdownBuilder ) {
57
111
match self {
@@ -62,28 +116,54 @@ impl IntoMarkdown for Markdown {
62
116
// Escape the text for Markdown
63
117
builder. append ( & format ! ( "{} {}" , hashes, escape_markdown( text) ) ) ;
64
118
}
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
+ }
67
152
}
68
153
Markdown :: CodeBlock { language, code } => {
69
154
// Do not escape code blocks
70
155
let lang = language. as_deref ( ) . unwrap_or ( "" ) ;
71
156
builder. append ( & format ! ( "```{}\n {}\n ```" , lang, code) ) ;
72
157
}
73
- Markdown :: InlineCode ( code) => {
74
- // Do not escape inline code
75
- builder. append ( & format ! ( "`{}`" , code) ) ;
76
- }
77
158
Markdown :: List { ordered, items } => {
78
159
let list_output = items
79
160
. iter ( )
80
161
. enumerate ( )
81
162
. map ( |( i, item) | {
82
- let escaped_item = escape_markdown ( item) ;
83
163
if * ordered {
84
- format ! ( "{}. {}" , i + 1 , escaped_item )
164
+ format ! ( "{}. {}" , i + 1 , item )
85
165
} else {
86
- format ! ( "- {}" , escaped_item )
166
+ format ! ( "- {}" , item )
87
167
}
88
168
} )
89
169
. collect :: < Vec < String > > ( )
@@ -149,6 +229,56 @@ impl IntoMarkdown for Markdown {
149
229
}
150
230
}
151
231
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
+
152
282
/// Builder pattern for generating comprehensive Markdown documentation.
153
283
/// Now also doubles as the accumulator for the generated markdown.
154
284
pub struct MarkdownBuilder {
@@ -194,8 +324,35 @@ impl MarkdownBuilder {
194
324
}
195
325
196
326
/// 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
+ } ) ;
199
356
self
200
357
}
201
358
@@ -214,13 +371,27 @@ impl MarkdownBuilder {
214
371
215
372
/// Adds an inline code element.
216
373
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
+ } ) ;
218
380
self
219
381
}
220
382
221
383
/// 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
+
224
395
self . elements . push ( Markdown :: List {
225
396
ordered,
226
397
items : converted_items,
@@ -316,8 +487,16 @@ impl TableBuilder {
316
487
}
317
488
318
489
/// 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 ( ) ;
321
500
self . rows . push ( converted) ;
322
501
self
323
502
}
@@ -340,17 +519,26 @@ mod tests {
340
519
let mut builder = MarkdownBuilder :: new ( ) ;
341
520
let markdown = builder
342
521
. 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`." )
344
523
. codeblock ( Some ( "rust" ) , "fn main() { println!(\" Hello, world!\" ); }" )
345
524
. list (
346
525
false ,
347
- vec ! [
526
+ markdown_vec ! [
348
527
"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( ) ,
351
531
] ,
352
532
)
353
533
. 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
+ )
354
542
. image (
355
543
"Rust Logo" ,
356
544
"https://www.rust-lang.org/logos/rust-logo-512x512.png" ,
@@ -361,7 +549,10 @@ mod tests {
361
549
table
362
550
. headers ( vec ! [ "Header 1" , "Header 2" ] )
363
551
. 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
+ ] ) ;
365
556
} )
366
557
. build ( ) ;
367
558
let expected = r#"
@@ -374,12 +565,13 @@ mod tests {
374
565
```
375
566
376
567
- First bullet with \#hash
377
- - Second bullet with \[brackets\]
378
- - Third bullet with \(parentheses\)
568
+ - `Second bullet with [brackets]`
379
569
380
570
> This is a quote\!
381
571
> It spans multiple lines\.
382
572
573
+ 1. _italic_ **bold** `code`
574
+
383
575

384
576
385
577
[Rust Homepage](https://www.rust-lang.org)
@@ -389,7 +581,7 @@ mod tests {
389
581
| Header 1 | Header 2 |
390
582
| --- | --- |
391
583
| Row 1 Col 1 | Row 1 Col 2 |
392
- | Row 2 Col 1 | Row 2 Col 2 |
584
+ | Row 2 Col 1 | `some_code` |
393
585
"# ;
394
586
395
587
let trimmed_indentation_expected = expected
0 commit comments