1use crate::ast::{
27 BlockQuote, Bold, ItalicsStar, ItalicsUnderscore, MarkdownDocument, MarkdownElement,
28 MarkdownElementCollection, MultiLineCode, OneLineCode, Plain, Spoiler, Strikethrough,
29 Underline,
30};
31
32#[derive(Default)]
51#[non_exhaustive]
52pub struct ToMarkdownStringOption {
53 pub omit_format: bool,
55
56 pub omit_spoiler: bool,
58
59 pub omit_one_line_code: bool,
61
62 pub omit_multi_line_code: bool,
64}
65
66impl ToMarkdownStringOption {
67 pub fn new() -> Self {
68 Default::default()
69 }
70
71 pub fn omit_format(mut self, value: bool) -> Self {
72 self.omit_format = value;
73 self
74 }
75
76 pub fn omit_spoiler(mut self, value: bool) -> Self {
77 self.omit_spoiler = value;
78 self
79 }
80
81 pub fn omit_one_line_code(mut self, value: bool) -> Self {
82 self.omit_one_line_code = value;
83 self
84 }
85
86 pub fn omit_multi_line_code(mut self, value: bool) -> Self {
87 self.omit_multi_line_code = value;
88 self
89 }
90}
91
92pub trait ToMarkdownString {
94 fn to_markdown_string(&self, option: &ToMarkdownStringOption) -> String;
96}
97
98impl ToMarkdownString for MarkdownDocument {
99 fn to_markdown_string(&self, option: &ToMarkdownStringOption) -> String {
101 self.content().to_markdown_string(option)
102 }
103}
104
105impl ToMarkdownString for MarkdownElementCollection {
106 fn to_markdown_string(&self, option: &ToMarkdownStringOption) -> String {
108 self.get()
109 .iter()
110 .map(|c| c.to_markdown_string(option))
111 .collect::<String>()
112 }
113}
114
115impl ToMarkdownString for MarkdownElement {
116 fn to_markdown_string(&self, option: &ToMarkdownStringOption) -> String {
118 match self {
119 MarkdownElement::Plain(x) => x.to_markdown_string(option),
120 MarkdownElement::ItalicsStar(x) => x.to_markdown_string(option),
121 MarkdownElement::ItalicsUnderscore(x) => x.to_markdown_string(option),
122 MarkdownElement::Bold(x) => x.to_markdown_string(option),
123 MarkdownElement::Underline(x) => x.to_markdown_string(option),
124 MarkdownElement::Strikethrough(x) => x.to_markdown_string(option),
125 MarkdownElement::Spoiler(x) => x.to_markdown_string(option),
126 MarkdownElement::OneLineCode(x) => x.to_markdown_string(option),
127 MarkdownElement::MultiLineCode(x) => x.to_markdown_string(option),
128 MarkdownElement::BlockQuote(x) => x.to_markdown_string(option),
129 }
130 }
131}
132
133impl ToMarkdownString for Plain {
134 fn to_markdown_string(&self, _option: &ToMarkdownStringOption) -> String {
136 self.content().to_string()
137 }
138}
139
140impl ToMarkdownString for ItalicsStar {
141 fn to_markdown_string(&self, option: &ToMarkdownStringOption) -> String {
143 let content = self.content().to_markdown_string(option);
144
145 if option.omit_format {
146 content
147 } else {
148 format!("*{}*", content)
149 }
150 }
151}
152
153impl ToMarkdownString for ItalicsUnderscore {
154 fn to_markdown_string(&self, option: &ToMarkdownStringOption) -> String {
156 let content = self.content().to_markdown_string(option);
157
158 if option.omit_format {
159 content
160 } else {
161 format!("_{}_", content)
162 }
163 }
164}
165
166impl ToMarkdownString for Bold {
167 fn to_markdown_string(&self, option: &ToMarkdownStringOption) -> String {
169 let content = self.content().to_markdown_string(option);
170
171 if option.omit_format {
172 content
173 } else {
174 format!("**{}**", content)
175 }
176 }
177}
178
179impl ToMarkdownString for Underline {
180 fn to_markdown_string(&self, option: &ToMarkdownStringOption) -> String {
182 let content = self.content().to_markdown_string(option);
183
184 if option.omit_format {
185 content
186 } else {
187 format!("__{}__", content)
188 }
189 }
190}
191
192impl ToMarkdownString for Strikethrough {
193 fn to_markdown_string(&self, option: &ToMarkdownStringOption) -> String {
195 let content = self.content().to_markdown_string(option);
196
197 if option.omit_format {
198 content
199 } else {
200 format!("~~{}~~", content)
201 }
202 }
203}
204
205impl ToMarkdownString for Spoiler {
206 fn to_markdown_string(&self, option: &ToMarkdownStringOption) -> String {
208 let content = self.content().to_markdown_string(option);
209
210 if option.omit_spoiler {
211 "".to_string()
212 } else if option.omit_format {
213 content
214 } else {
215 format!("||{}||", content)
216 }
217 }
218}
219
220impl ToMarkdownString for OneLineCode {
221 fn to_markdown_string(&self, option: &ToMarkdownStringOption) -> String {
223 let content = self.content().to_string();
224
225 if option.omit_one_line_code {
226 "".to_string()
227 } else if option.omit_format {
228 content
229 } else {
230 format!("`{}`", content)
231 }
232 }
233}
234
235impl ToMarkdownString for MultiLineCode {
236 fn to_markdown_string(&self, option: &ToMarkdownStringOption) -> String {
238 let content = self.content().to_string();
239
240 if option.omit_multi_line_code {
241 "".to_string()
242 } else if option.omit_format {
243 content
244 } else {
245 format!("```{}{}```", self.language().unwrap_or(""), content)
246 }
247 }
248}
249
250impl ToMarkdownString for BlockQuote {
251 fn to_markdown_string(&self, option: &ToMarkdownStringOption) -> String {
253 let content = self.content().to_markdown_string(option);
254
255 if option.omit_format {
256 content
257 } else {
258 content
259 .split('\n')
260 .map(|line| format!("> {}", line))
261 .collect::<Vec<_>>()
262 .join("\n")
263 }
264 }
265}
266
267#[cfg(test)]
268mod tests {
269 use super::*;
270
271 fn example_text() -> MarkdownElementCollection {
272 MarkdownElementCollection::new(vec![MarkdownElement::Plain(Box::new(Plain::new("text")))])
273 }
274
275 fn option_default() -> ToMarkdownStringOption {
276 ToMarkdownStringOption::new()
277 }
278
279 fn option_omit_format() -> ToMarkdownStringOption {
280 ToMarkdownStringOption::new().omit_format(true)
281 }
282
283 #[test]
284 fn test_document_to_string() {
285 let ast = MarkdownDocument::new(MarkdownElementCollection::new(vec![
286 MarkdownElement::Spoiler(Box::new(Spoiler::new(MarkdownElementCollection::new(
287 vec![MarkdownElement::Plain(Box::new(Plain::new("spoiler")))],
288 )))),
289 MarkdownElement::Plain(Box::new(Plain::new(" plain "))),
290 MarkdownElement::OneLineCode(Box::new(OneLineCode::new("code"))),
291 ]));
292
293 assert_eq!(
294 ast.to_markdown_string(&option_default()),
295 "||spoiler|| plain `code`"
296 );
297 assert_eq!(
298 ast.to_markdown_string(&option_omit_format()),
299 "spoiler plain code"
300 );
301 assert_eq!(
302 ast.to_markdown_string(&option_default().omit_spoiler(true)),
303 " plain `code`"
304 );
305 assert_eq!(
306 ast.to_markdown_string(&option_omit_format().omit_one_line_code(true)),
307 "spoiler plain "
308 );
309 assert_eq!(
310 ast.to_markdown_string(&option_default().omit_spoiler(true).omit_one_line_code(true)),
311 " plain "
312 );
313 }
314
315 #[test]
316 fn test_element_collection_to_string() {
317 let ast = MarkdownElementCollection::new(vec![
318 MarkdownElement::OneLineCode(Box::new(OneLineCode::new("code"))),
319 MarkdownElement::Plain(Box::new(Plain::new(" plain "))),
320 MarkdownElement::Underline(Box::new(Underline::new(MarkdownElementCollection::new(
321 vec![MarkdownElement::Bold(Box::new(Bold::new(
322 MarkdownElementCollection::new(vec![MarkdownElement::Plain(Box::new(
323 Plain::new("underline bold"),
324 ))]),
325 )))],
326 )))),
327 ]);
328
329 assert_eq!(
330 ast.to_markdown_string(&option_default()),
331 "`code` plain __**underline bold**__"
332 );
333 assert_eq!(
334 ast.to_markdown_string(&option_omit_format()),
335 "code plain underline bold"
336 );
337 assert_eq!(
338 ast.to_markdown_string(&option_default().omit_one_line_code(true)),
339 " plain __**underline bold**__"
340 );
341 assert_eq!(
342 ast.to_markdown_string(&option_omit_format().omit_one_line_code(true)),
343 " plain underline bold"
344 );
345 }
346
347 #[test]
348 fn test_plain_to_string() {
349 let ast = Plain::new("plain text");
350
351 assert_eq!(ast.to_markdown_string(&option_default()), "plain text");
352 assert_eq!(ast.to_markdown_string(&option_omit_format()), "plain text");
353 }
354
355 #[test]
356 fn test_italics_star_to_string() {
357 assert_eq!(
358 ItalicsStar::new(example_text()).to_markdown_string(&option_default()),
359 "*text*"
360 );
361 assert_eq!(
362 ItalicsStar::new(example_text()).to_markdown_string(&option_omit_format()),
363 "text"
364 );
365 }
366
367 #[test]
368 fn test_italics_underscore_to_string() {
369 assert_eq!(
370 ItalicsUnderscore::new(example_text()).to_markdown_string(&option_default()),
371 "_text_"
372 );
373 assert_eq!(
374 ItalicsUnderscore::new(example_text()).to_markdown_string(&option_omit_format()),
375 "text"
376 );
377 }
378
379 #[test]
380 fn test_bold_to_string() {
381 assert_eq!(
382 Bold::new(example_text()).to_markdown_string(&option_default()),
383 "**text**"
384 );
385 assert_eq!(
386 Bold::new(example_text()).to_markdown_string(&option_omit_format()),
387 "text"
388 );
389 }
390
391 #[test]
392 fn test_underline_to_string() {
393 assert_eq!(
394 Underline::new(example_text()).to_markdown_string(&option_default()),
395 "__text__"
396 );
397 assert_eq!(
398 Underline::new(example_text()).to_markdown_string(&option_omit_format()),
399 "text"
400 );
401 }
402
403 #[test]
404 fn test_strikethrough_to_string() {
405 assert_eq!(
406 Strikethrough::new(example_text()).to_markdown_string(&option_default()),
407 "~~text~~"
408 );
409 assert_eq!(
410 Strikethrough::new(example_text()).to_markdown_string(&option_omit_format()),
411 "text"
412 );
413 }
414
415 #[test]
416 fn test_spoiler_to_string() {
417 assert_eq!(
418 Spoiler::new(example_text()).to_markdown_string(&option_default()),
419 "||text||"
420 );
421 assert_eq!(
422 Spoiler::new(example_text()).to_markdown_string(&option_omit_format()),
423 "text"
424 );
425 assert_eq!(
426 Spoiler::new(example_text()).to_markdown_string(&option_default().omit_spoiler(true)),
427 ""
428 );
429 assert_eq!(
430 Spoiler::new(example_text())
431 .to_markdown_string(&option_omit_format().omit_spoiler(true)),
432 ""
433 );
434 }
435
436 #[test]
437 fn test_one_line_code_to_string() {
438 assert_eq!(
439 OneLineCode::new("one line code").to_markdown_string(&option_default()),
440 "`one line code`"
441 );
442 assert_eq!(
443 OneLineCode::new("one line code").to_markdown_string(&option_omit_format()),
444 "one line code"
445 );
446 assert_eq!(
447 OneLineCode::new("one line code")
448 .to_markdown_string(&option_default().omit_one_line_code(true)),
449 ""
450 );
451 assert_eq!(
452 OneLineCode::new("one line code")
453 .to_markdown_string(&option_omit_format().omit_one_line_code(true)),
454 ""
455 );
456 }
457
458 #[test]
459 fn test_multi_line_code_to_string() {
460 assert_eq!(
461 MultiLineCode::new("\nmulti\nline\ncode\n", None).to_markdown_string(&option_default()),
462 "```\nmulti\nline\ncode\n```"
463 );
464 assert_eq!(
465 MultiLineCode::new("\nmulti\nline\ncode\n", None)
466 .to_markdown_string(&option_omit_format()),
467 "\nmulti\nline\ncode\n"
468 );
469
470 assert_eq!(
471 MultiLineCode::new(" multi\nline\ncode\n", None).to_markdown_string(&option_default()),
472 "``` multi\nline\ncode\n```"
473 );
474 assert_eq!(
475 MultiLineCode::new(" multi\nline\ncode\n", None)
476 .to_markdown_string(&option_omit_format()),
477 " multi\nline\ncode\n"
478 );
479
480 assert_eq!(
481 MultiLineCode::new("multi line code", None).to_markdown_string(&option_default()),
482 "```multi line code```"
483 );
484 assert_eq!(
485 MultiLineCode::new("multi line code", None).to_markdown_string(&option_omit_format()),
486 "multi line code"
487 );
488
489 assert_eq!(
490 MultiLineCode::new("\nmulti\nline\ncode\n", Some("js".to_string()))
491 .to_markdown_string(&option_default()),
492 "```js\nmulti\nline\ncode\n```"
493 );
494 assert_eq!(
495 MultiLineCode::new("\nmulti\nline\ncode\n", Some("js".to_string()))
496 .to_markdown_string(&option_omit_format()),
497 "\nmulti\nline\ncode\n"
498 );
499
500 assert_eq!(
501 MultiLineCode::new("\nmulti\nline\ncode\n", None)
502 .to_markdown_string(&option_default().omit_multi_line_code(true)),
503 ""
504 );
505 assert_eq!(
506 MultiLineCode::new("\nmulti\nline\ncode\n", None)
507 .to_markdown_string(&option_omit_format().omit_multi_line_code(true)),
508 ""
509 );
510 }
511
512 #[test]
513 fn test_block_quote_to_string() {
514 let test_case = || {
515 MarkdownElementCollection::new(vec![MarkdownElement::Plain(Box::new(Plain::new(
516 "block quote\ntext",
517 )))])
518 };
519
520 assert_eq!(
521 BlockQuote::new(test_case()).to_markdown_string(&option_default()),
522 "> block quote\n> text"
523 );
524 assert_eq!(
525 BlockQuote::new(test_case()).to_markdown_string(&option_omit_format()),
526 "block quote\ntext"
527 );
528 }
529}