1use crate::ast::{Alignment, BlockNode, HtmlElement, InlineNode, ListItem, Node};
7use std::{
8 cmp::max,
9 fmt::{self},
10};
11
12#[derive(Debug, Clone)]
14pub struct WriterOptions {
15 pub strict: bool,
17 pub hard_break_spaces: bool,
19 pub indent_spaces: usize,
21}
22
23impl Default for WriterOptions {
24 fn default() -> Self {
25 Self {
26 strict: true,
27 hard_break_spaces: false,
28 indent_spaces: 4,
29 }
30 }
31}
32
33#[derive(Debug)]
37pub struct CommonMarkWriter {
38 options: WriterOptions,
39 buffer: String,
40 indent_level: usize,
42}
43
44impl CommonMarkWriter {
45 pub fn new() -> Self {
58 Self::with_options(WriterOptions::default())
59 }
60
61 pub fn with_options(options: WriterOptions) -> Self {
80 Self {
81 options,
82 buffer: String::new(),
83 indent_level: 0,
84 }
85 }
86
87 pub fn write(&mut self, node: &Node) -> fmt::Result {
107 match node {
108 Node::Block(block_node) => self.write_block(block_node),
109 Node::Inline(inline_node) => self.write_inline(inline_node),
110 }
111 }
112
113 fn write_block(&mut self, node: &BlockNode) -> fmt::Result {
115 match node {
116 BlockNode::Document(children) => self.write_document(children),
117 BlockNode::Heading { level, content } => self.write_heading(*level, content),
118 BlockNode::Paragraph(content) => self.write_paragraph(content),
119 BlockNode::BlockQuote(content) => self.write_blockquote(content),
120 BlockNode::CodeBlock { language, content } => self.write_code_block(language, content),
121 BlockNode::UnorderedList(items) => self.write_unordered_list(items),
122 BlockNode::OrderedList { start, items } => self.write_ordered_list(*start, items),
123 BlockNode::ThematicBreak => self.write_thematic_break(),
124 BlockNode::Table {
125 headers,
126 rows,
127 alignments,
128 } => self.write_table(headers, rows, alignments),
129 BlockNode::HtmlBlock(content) => self.write_html_block(content),
130 }
131 }
132
133 fn write_inline(&mut self, node: &InlineNode) -> fmt::Result {
135 match node {
136 InlineNode::Text(content) => self.write_text(content),
137 InlineNode::Emphasis(content) => self.write_emphasis(content),
138 InlineNode::Strong(content) => self.write_strong(content),
139 InlineNode::Strike(content) => self.write_strike(content),
140 InlineNode::InlineCode(content) => self.write_inline_code(content),
141 InlineNode::Link {
142 url,
143 title,
144 content,
145 } => self.write_link(url, title, content),
146 InlineNode::Image { url, title, alt } => self.write_image(url, title, alt),
147 InlineNode::HtmlElement(element) => self.write_html_element(element),
148 InlineNode::InlineContainer(content) => self.write_inline_container(content),
149 InlineNode::SoftBreak => self.write_soft_break(),
150 InlineNode::HardBreak => self.write_hard_break(),
151 }
152 }
153
154 fn write_document(&mut self, children: &[BlockNode]) -> fmt::Result {
156 for (i, child) in children.iter().enumerate() {
157 self.write_block(child)?;
158 if i < children.len() - 1 {
159 self.write_str("\n\n")?;
160 }
161 }
162 Ok(())
163 }
164
165 fn write_heading(&mut self, level: u8, content: &[InlineNode]) -> fmt::Result {
167 if !(1..=6).contains(&level) {
168 return Err(fmt::Error);
169 }
170
171 for _ in 0..level {
172 self.write_char('#')?;
173 }
174 self.write_char(' ')?;
175
176 for (i, node) in content.iter().enumerate() {
177 self.write_inline(node)?;
178 if i < content.len() - 1
179 && !matches!(node, InlineNode::SoftBreak | InlineNode::HardBreak)
180 {
181 self.write_char(' ')?;
182 }
183 }
184
185 Ok(())
186 }
187
188 fn write_paragraph(&mut self, content: &[InlineNode]) -> fmt::Result {
190 let mut prev_is_inline = false;
191
192 for (i, node) in content.iter().enumerate() {
193 let is_inline = !matches!(node, InlineNode::SoftBreak | InlineNode::HardBreak);
195
196 if prev_is_inline && is_inline && i > 0 {
198 } else if i > 0 {
200 self.write_char('\n')?;
202 for _ in 0..(self.indent_level * self.options.indent_spaces) {
204 self.write_char(' ')?;
205 }
206 }
207
208 self.write_inline(node)?;
209 prev_is_inline = is_inline;
210 }
211 Ok(())
212 }
213
214 fn write_blockquote(&mut self, content: &[BlockNode]) -> fmt::Result {
216 self.indent_level += 1;
217
218 for (i, node) in content.iter().enumerate() {
219 self.write_str("> ")?;
220 self.write_block(node)?;
221 if i < content.len() - 1 {
222 self.write_str("\n> \n")?;
223 }
224 }
225
226 self.indent_level -= 1;
227 Ok(())
228 }
229
230 fn write_code_block(&mut self, language: &Option<String>, content: &str) -> fmt::Result {
232 let mut max_backticks = 0;
233 let mut current = 0;
234 for c in content.chars() {
235 if c == '`' {
236 current += 1;
237 if current > max_backticks {
238 max_backticks = current;
239 }
240 } else {
241 current = 0;
242 }
243 }
244 let fence_len = max(max_backticks + 1, 3);
245 let fence = "`".repeat(fence_len);
246
247 self.write_str(&fence)?;
248 if let Some(lang) = language {
249 self.write_str(lang)?;
250 }
251 self.write_char('\n')?;
252 self.write_str(content)?;
253
254 if !content.ends_with('\n') {
256 self.write_char('\n')?;
257 }
258
259 self.write_str(&fence)?;
260 Ok(())
261 }
262
263 fn write_unordered_list(&mut self, items: &[ListItem]) -> fmt::Result {
265 for (i, item) in items.iter().enumerate() {
266 self.write_list_item(item, "- ")?;
267 if i < items.len() - 1 {
268 self.write_char('\n')?;
269 }
270 }
271 Ok(())
272 }
273
274 fn write_ordered_list(&mut self, start: u32, items: &[ListItem]) -> fmt::Result {
276 for (i, item) in items.iter().enumerate() {
277 let num = start as usize + i;
278 let prefix = format!("{}. ", num);
279 self.write_list_item(item, &prefix)?;
280 if i < items.len() - 1 {
281 self.write_char('\n')?;
282 }
283 }
284 Ok(())
285 }
286
287 fn write_list_item(&mut self, item: &ListItem, prefix: &str) -> fmt::Result {
289 for _ in 0..(self.indent_level * self.options.indent_spaces) {
291 self.write_char(' ')?;
292 }
293 self.write_str(prefix)?;
294
295 match item {
296 ListItem::Regular { content } => {
297 self.write_list_item_content(content, prefix, false)?;
298 }
299 ListItem::Task { completed, content } => {
300 if *completed {
302 self.write_str("[x] ")?;
303 } else {
304 self.write_str("[ ] ")?;
305 }
306 self.write_list_item_content(content, prefix, true)?;
307 }
308 }
309
310 Ok(())
311 }
312
313 fn write_list_item_content(
315 &mut self,
316 content: &[BlockNode],
317 prefix: &str,
318 is_task: bool,
319 ) -> fmt::Result {
320 self.indent_level += 1;
321
322 for (i, node) in content.iter().enumerate() {
323 let is_list = matches!(
324 node,
325 BlockNode::OrderedList { .. } | BlockNode::UnorderedList(..)
326 );
327
328 if is_list {
330 if i > 0 {
331 self.write_char('\n')?;
332 }
333 self.write_block(node)?;
334 continue;
335 }
336
337 if i > 0 {
338 self.write_char('\n')?;
339 let prefix_length = prefix.len() + if is_task { 4 } else { 0 };
341 for _ in 0..(self.indent_level * self.options.indent_spaces) + prefix_length {
342 self.write_char(' ')?;
343 }
344 }
345
346 self.write_block(node)?;
347 }
348
349 self.indent_level -= 1;
350 Ok(())
351 }
352
353 fn write_thematic_break(&mut self) -> fmt::Result {
355 self.write_str("---")
356 }
357
358 fn check_no_newline(&self, node: &InlineNode) -> fmt::Result {
360 if Self::inline_node_contains_newline(node) {
361 return Err(fmt::Error);
362 }
363 Ok(())
364 }
365
366 fn inline_node_contains_newline(node: &InlineNode) -> bool {
368 match node {
369 InlineNode::Text(s) | InlineNode::InlineCode(s) => s.contains('\n'),
370 InlineNode::Emphasis(children)
371 | InlineNode::Strong(children)
372 | InlineNode::Strike(children)
373 | InlineNode::InlineContainer(children) => {
374 children.iter().any(Self::inline_node_contains_newline)
375 }
376 InlineNode::HtmlElement(element) => element
377 .children
378 .iter()
379 .any(Self::inline_node_contains_newline),
380 InlineNode::Link { content, .. } => {
381 content.iter().any(Self::inline_node_contains_newline)
382 }
383 InlineNode::Image { alt, .. } => alt.contains('\n'),
384 InlineNode::SoftBreak | InlineNode::HardBreak => true,
385 }
386 }
387
388 fn write_table(
390 &mut self,
391 headers: &[InlineNode],
392 rows: &[Vec<InlineNode>],
393 alignments: &[Alignment],
394 ) -> fmt::Result {
395 self.write_char('|')?;
397 for header in headers {
398 self.check_no_newline(header)?;
399 self.write_char(' ')?;
400 self.write_inline(header)?;
401 self.write_str(" |")?;
402 }
403 self.write_char('\n')?;
404
405 self.write_char('|')?;
407 for alignment in alignments {
408 match alignment {
409 Alignment::None => self.write_str(" --- |")?,
410 Alignment::Left => self.write_str(" :--- |")?,
411 Alignment::Center => self.write_str(" :---: |")?,
412 Alignment::Right => self.write_str(" ---: |")?,
413 }
414 }
415 self.write_char('\n')?;
416
417 for row in rows {
419 self.write_char('|')?;
420 for cell in row {
421 self.check_no_newline(cell)?;
422 self.write_char(' ')?;
423 self.write_inline(cell)?;
424 self.write_str(" |")?;
425 }
426 self.write_char('\n')?;
427 }
428
429 Ok(())
430 }
431
432 fn write_link(
434 &mut self,
435 url: &str,
436 title: &Option<String>,
437 content: &[InlineNode],
438 ) -> fmt::Result {
439 for node in content {
440 self.check_no_newline(node)?;
441 }
442 self.write_char('[')?;
443
444 for node in content {
445 self.write_inline(node)?;
446 }
447
448 self.write_str("](")?;
449 self.write_str(url)?;
450
451 if let Some(title_text) = title {
452 self.write_str(" \"")?;
453 self.write_str(title_text)?;
454 self.write_char('"')?;
455 }
456
457 self.write_char(')')
458 }
459
460 fn write_image(&mut self, url: &str, title: &Option<String>, alt: &str) -> fmt::Result {
462 if alt.contains('\n') {
463 return Err(fmt::Error);
464 }
465
466 self.write_str("?;
469 self.write_str(url)?;
470
471 if let Some(title_text) = title {
472 self.write_str(" \"")?;
473 self.write_str(title_text)?;
474 self.write_char('"')?;
475 }
476
477 self.write_char(')')
478 }
479
480 fn write_emphasis(&mut self, content: &[InlineNode]) -> fmt::Result {
482 for node in content {
483 self.check_no_newline(node)?;
484 }
485 self.write_char('*')?;
486
487 for node in content {
488 self.write_inline(node)?;
489 }
490
491 self.write_char('*')
492 }
493
494 fn write_strong(&mut self, content: &[InlineNode]) -> fmt::Result {
496 for node in content {
497 self.check_no_newline(node)?;
498 }
499 self.write_str("**")?;
500
501 for node in content {
502 self.write_inline(node)?;
503 }
504
505 self.write_str("**")
506 }
507
508 fn write_strike(&mut self, content: &[InlineNode]) -> fmt::Result {
510 for node in content {
511 self.check_no_newline(node)?;
512 }
513 self.write_str("~~")?;
514
515 for node in content {
516 self.write_inline(node)?;
517 }
518
519 self.write_str("~~")
520 }
521
522 fn write_inline_code(&mut self, content: &str) -> fmt::Result {
524 if content.contains('\n') {
525 return Err(fmt::Error);
526 }
527
528 self.write_char('`')?;
529 self.write_str(content)?;
530 self.write_char('`')
531 }
532
533 fn write_text(&mut self, content: &str) -> fmt::Result {
535 if content.contains('\n') {
536 return Err(fmt::Error);
537 }
538
539 let escaped = content
541 .replace('\\', "\\\\")
542 .replace('*', "\\*")
543 .replace('_', "\\_")
544 .replace('[', "\\[")
545 .replace(']', "\\]")
546 .replace('<', "\\<")
547 .replace('>', "\\>")
548 .replace('`', "\\`");
549
550 self.write_str(&escaped)
551 }
552
553 fn write_html_element(&mut self, element: &HtmlElement) -> fmt::Result {
555 self.write_char('<')?;
556 self.write_str(&element.tag)?;
557
558 for attr in &element.attributes {
560 self.write_char(' ')?;
561 self.write_str(&attr.name)?;
562 self.write_str("=\"")?;
563 let escaped_value = attr.value.replace('"', """);
565 self.write_str(&escaped_value)?;
566 self.write_char('"')?;
567 }
568
569 if element.self_closing {
570 self.write_str(" />")?;
572 return Ok(());
573 }
574
575 self.write_char('>')?;
576
577 for child in &element.children {
579 self.check_no_newline(child)?;
580 self.write_inline(child)?;
581 }
582
583 self.write_str("</")?;
585 self.write_str(&element.tag)?;
586 self.write_char('>')?;
587
588 Ok(())
589 }
590
591 fn write_html_block(&mut self, content: &str) -> fmt::Result {
593 self.write_str(content)
594 }
595
596 fn write_inline_container(&mut self, children: &[InlineNode]) -> fmt::Result {
598 for (i, child) in children.iter().enumerate() {
599 self.check_no_newline(child)?;
600 self.write_inline(child)?;
601 if i < children.len() - 1 {
602 self.write_str(" ")?;
603 }
604 }
605 Ok(())
606 }
607
608 fn write_soft_break(&mut self) -> fmt::Result {
610 self.write_char('\n')
611 }
612
613 fn write_hard_break(&mut self) -> fmt::Result {
615 if self.options.hard_break_spaces {
616 self.write_str(" \n")
617 } else {
618 self.write_str("\\\n")
619 }
620 }
621
622 pub fn into_string(self) -> String {
638 self.buffer
639 }
640
641 fn write_char(&mut self, c: char) -> fmt::Result {
643 self.buffer.push(c);
644 Ok(())
645 }
646
647 fn write_str(&mut self, s: &str) -> fmt::Result {
649 self.buffer.push_str(s);
650 Ok(())
651 }
652}
653
654impl Default for CommonMarkWriter {
655 fn default() -> Self {
656 Self::new()
657 }
658}
659
660impl fmt::Display for Node {
662 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
663 let mut writer = CommonMarkWriter::new();
664 writer.write(self)?;
665 write!(f, "{}", writer.into_string())
666 }
667}
668
669impl fmt::Display for BlockNode {
671 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
672 let mut writer = CommonMarkWriter::new();
673 writer.write_block(self)?;
674 write!(f, "{}", writer.into_string())
675 }
676}
677
678impl fmt::Display for InlineNode {
680 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
681 let mut writer = CommonMarkWriter::new();
682 writer.write_inline(self)?;
683 write!(f, "{}", writer.into_string())
684 }
685}