1use crate::ast::{Alignment, 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: true,
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::Document(children) => self.write_document(children),
109 Node::Heading { level, content } => self.write_heading(*level, content),
110 Node::Paragraph(content) => self.write_paragraph(content),
111 Node::BlockQuote(content) => self.write_blockquote(content),
112 Node::CodeBlock { language, content } => self.write_code_block(language, content),
113 Node::UnorderedList(items) => self.write_unordered_list(items),
114 Node::OrderedList { start, items } => self.write_ordered_list(*start, items),
115 Node::ThematicBreak => self.write_thematic_break(),
116 Node::Table {
117 headers,
118 rows,
119 alignments,
120 } => self.write_table(headers, rows, alignments),
121 Node::Link {
122 url,
123 title,
124 content,
125 } => self.write_link(url, title, content),
126 Node::Image { url, title, alt } => self.write_image(url, title, alt),
127 Node::Emphasis(content) => self.write_emphasis(content),
128 Node::Strong(content) => self.write_strong(content),
129 Node::InlineCode(content) => self.write_inline_code(content),
130 Node::Text(content) => self.write_text(content),
131 Node::Html(content) => self.write_html(content),
132 Node::SoftBreak => self.write_soft_break(),
133 Node::HardBreak => self.write_hard_break(),
134 }
135 }
136
137 fn write_document(&mut self, children: &[Node]) -> fmt::Result {
139 for (i, child) in children.iter().enumerate() {
140 self.write(child)?;
141 if i < children.len() - 1 {
142 self.write_str("\n\n")?;
143 }
144 }
145 Ok(())
146 }
147
148 fn write_heading(&mut self, level: u8, content: &[Node]) -> fmt::Result {
150 if !(1..=6).contains(&level) {
151 return Err(fmt::Error);
152 }
153
154 for _ in 0..level {
155 self.write_char('#')?;
156 }
157 self.write_char(' ')?;
158
159 for (i, node) in content.iter().enumerate() {
160 self.write(node)?;
161 if i < content.len() - 1 && !matches!(node, Node::SoftBreak | Node::HardBreak) {
162 self.write_char(' ')?;
163 }
164 }
165
166 Ok(())
167 }
168 fn write_paragraph(&mut self, content: &[Node]) -> fmt::Result {
170 let mut prev_is_inline = false;
171
172 for (i, node) in content.iter().enumerate() {
173 let is_inline = self.is_inline_element(node);
175
176 if prev_is_inline
179 && is_inline
180 && i > 0
181 && !matches!(node, Node::SoftBreak | Node::HardBreak)
182 {
183 } else if i > 0 {
185 self.write_char('\n')?;
187 for _ in 0..(self.indent_level * self.options.indent_spaces) {
189 self.write_char(' ')?;
190 }
191 }
192
193 self.write(node)?;
194 prev_is_inline = is_inline;
195 }
196 Ok(())
197 }
198
199 fn write_blockquote(&mut self, content: &[Node]) -> fmt::Result {
201 self.indent_level += 1;
202
203 for (i, node) in content.iter().enumerate() {
204 self.write_str("> ")?;
205 self.write(node)?;
206 if i < content.len() - 1 {
207 self.write_str("\n> \n")?;
208 }
209 }
210
211 self.indent_level -= 1;
212 Ok(())
213 }
214
215 fn write_code_block(&mut self, language: &Option<String>, content: &str) -> fmt::Result {
217 let mut max_backticks = 0;
218 let mut current = 0;
219 for c in content.chars() {
220 if c == '`' {
221 current += 1;
222 if current > max_backticks {
223 max_backticks = current;
224 }
225 } else {
226 current = 0;
227 }
228 }
229 let fence_len = max(max_backticks + 1, 3);
230 let fence = "`".repeat(fence_len);
231
232 self.write_str(&fence)?;
233 if let Some(lang) = language {
234 self.write_str(lang)?;
235 }
236 self.write_char('\n')?;
237 self.write_str(content)?;
238
239 if !content.ends_with('\n') {
241 self.write_char('\n')?;
242 }
243
244 self.write_str(&fence)?;
245 Ok(())
246 }
247
248 fn write_unordered_list(&mut self, items: &[ListItem]) -> fmt::Result {
250 for (i, item) in items.iter().enumerate() {
251 self.write_list_item(item, "- ")?;
252 if i < items.len() - 1 {
253 self.write_char('\n')?;
254 }
255 }
256 Ok(())
257 }
258
259 fn write_ordered_list(&mut self, start: u32, items: &[ListItem]) -> fmt::Result {
261 for (i, item) in items.iter().enumerate() {
262 let num = start as usize + i;
263 let prefix = format!("{}. ", num);
264 self.write_list_item(item, &prefix)?;
265 if i < items.len() - 1 {
266 self.write_char('\n')?;
267 }
268 }
269 Ok(())
270 }
271
272 fn write_list_item(&mut self, item: &ListItem, prefix: &str) -> fmt::Result {
274 for _ in 0..(self.indent_level * self.options.indent_spaces) {
276 self.write_char(' ')?;
277 }
278 self.write_str(prefix)?;
279
280 if item.is_task {
281 if item.task_completed {
282 self.write_str("[x] ")?;
283 } else {
284 self.write_str("[ ] ")?;
285 }
286 }
287
288 self.indent_level += 1;
289
290 let mut prev_is_inline = false;
292
293 for (i, node) in item.content.iter().enumerate() {
294 let is_inline = self.is_inline_element(node);
296 let is_list = matches!(node, Node::OrderedList { .. } | Node::UnorderedList(..));
297
298 if is_list {
300 if i > 0 {
301 self.write_char('\n')?;
302 }
303 self.write(node)?;
304 prev_is_inline = false;
305 continue;
306 }
307
308 if prev_is_inline && is_inline {
310 } else if i > 0 {
312 self.write_char('\n')?;
314 let prefix_length = prefix.len() + if item.is_task { 4 } else { 0 };
316 for _ in 0..(self.indent_level * self.options.indent_spaces) + prefix_length {
317 self.write_char(' ')?;
318 }
319 }
320
321 self.write(node)?;
322 prev_is_inline = is_inline;
323 }
324
325 self.indent_level -= 1;
326 Ok(())
327 }
328
329 fn write_thematic_break(&mut self) -> fmt::Result {
331 self.write_str("---")
332 }
333
334 fn check_no_newline(&self, node: &Node) -> fmt::Result {
336 if Self::node_contains_newline(node) {
337 return Err(fmt::Error);
338 }
339 Ok(())
340 }
341
342 fn node_contains_newline(node: &Node) -> bool {
344 match node {
345 Node::Text(s) | Node::InlineCode(s) | Node::Html(s) => s.contains('\n'),
346 Node::Emphasis(children) | Node::Strong(children) => {
347 children.iter().any(Self::node_contains_newline)
348 }
349 Node::Link { content, .. } => content.iter().any(Self::node_contains_newline),
350 Node::Image { alt, .. } => alt.contains('\n'),
351 _ => false,
352 }
353 }
354
355 fn write_table(
357 &mut self,
358 headers: &[Node],
359 rows: &[Vec<Node>],
360 alignments: &[Alignment],
361 ) -> fmt::Result {
362 self.write_char('|')?;
364 for header in headers {
365 self.check_no_newline(header)?;
366 self.write_char(' ')?;
367 self.write(header)?;
368 self.write_str(" |")?;
369 }
370 self.write_char('\n')?;
371
372 self.write_char('|')?;
374 for alignment in alignments {
375 match alignment {
376 Alignment::None => self.write_str(" --- |")?,
377 Alignment::Left => self.write_str(" :--- |")?,
378 Alignment::Center => self.write_str(" :---: |")?,
379 Alignment::Right => self.write_str(" ---: |")?,
380 }
381 }
382 self.write_char('\n')?;
383
384 for row in rows {
386 self.write_char('|')?;
387 for cell in row {
388 self.check_no_newline(cell)?;
389 self.write_char(' ')?;
390 self.write(cell)?;
391 self.write_str(" |")?;
392 }
393 self.write_char('\n')?;
394 }
395
396 Ok(())
397 }
398
399 fn write_link(&mut self, url: &str, title: &Option<String>, content: &[Node]) -> fmt::Result {
401 for node in content {
402 self.check_no_newline(node)?;
403 }
404 self.write_char('[')?;
405
406 for node in content {
407 self.write(node)?;
408 }
409
410 self.write_str("](")?;
411 self.write_str(url)?;
412
413 if let Some(title_text) = title {
414 self.write_str(" \"")?;
415 self.write_str(title_text)?;
416 self.write_char('"')?;
417 }
418
419 self.write_char(')')
420 }
421
422 fn write_image(&mut self, url: &str, title: &Option<String>, alt: &str) -> fmt::Result {
424 self.check_no_newline(&Node::Text(alt.to_string()))?;
425 self.write_str("?;
428 self.write_str(url)?;
429
430 if let Some(title_text) = title {
431 self.write_str(" \"")?;
432 self.write_str(title_text)?;
433 self.write_char('"')?;
434 }
435
436 self.write_char(')')
437 }
438
439 fn write_emphasis(&mut self, content: &[Node]) -> fmt::Result {
441 for node in content {
442 self.check_no_newline(node)?;
443 }
444 self.write_char('*')?;
445
446 for node in content {
447 self.write(node)?;
448 }
449
450 self.write_char('*')
451 }
452
453 fn write_strong(&mut self, content: &[Node]) -> fmt::Result {
455 for node in content {
456 self.check_no_newline(node)?;
457 }
458 self.write_str("**")?;
459
460 for node in content {
461 self.write(node)?;
462 }
463
464 self.write_str("**")
465 }
466
467 fn write_inline_code(&mut self, content: &str) -> fmt::Result {
469 self.check_no_newline(&Node::InlineCode(content.to_string()))?;
470 self.write_char('`')?;
471 self.write_str(content)?;
472 self.write_char('`')
473 }
474
475 fn write_text(&mut self, content: &str) -> fmt::Result {
477 self.check_no_newline(&Node::Text(content.to_string()))?;
478 let escaped = content
480 .replace('\\', "\\\\")
481 .replace('*', "\\*")
482 .replace('_', "\\_")
483 .replace('[', "\\[")
484 .replace(']', "\\]")
485 .replace('<', "\\<")
486 .replace('>', "\\>")
487 .replace('`', "\\`");
488
489 self.write_str(&escaped)
490 }
491
492 fn write_html(&mut self, content: &str) -> fmt::Result {
494 self.write_str(content)
495 }
496
497 fn write_soft_break(&mut self) -> fmt::Result {
499 self.write_char('\n')
500 }
501
502 fn write_hard_break(&mut self) -> fmt::Result {
504 if self.options.hard_break_spaces {
505 self.write_str(" \n")
506 } else {
507 self.write_str("\\\n")
508 }
509 }
510
511 fn is_inline_element(&self, node: &Node) -> bool {
513 matches!(
514 node,
515 Node::Text(_)
516 | Node::Emphasis(_)
517 | Node::Strong(_)
518 | Node::InlineCode(_)
519 | Node::Link { .. }
520 | Node::Image { .. }
521 )
522 }
523
524 pub fn into_string(self) -> String {
540 self.buffer
541 }
542
543 fn write_char(&mut self, c: char) -> fmt::Result {
545 self.buffer.push(c);
546 Ok(())
547 }
548
549 fn write_str(&mut self, s: &str) -> fmt::Result {
551 self.buffer.push_str(s);
552 Ok(())
553 }
554}
555
556impl Default for CommonMarkWriter {
557 fn default() -> Self {
558 Self::new()
559 }
560}
561
562impl fmt::Display for Node {
564 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
565 let mut writer = CommonMarkWriter::new();
566 writer.write(self)?;
567 write!(f, "{}", writer.into_string())
568 }
569}