1use core::fmt::Display;
2use std::borrow::ToOwned;
3use std::path::Path;
4use std::string::String;
5
6use pulldown_cmark::CowStr;
7use thiserror::Error;
8
9use crate::{CMarkDocs, CMarkReadme};
10
11pub fn assert_sync<M1, M2>(readme: &CMarkReadme<&Path, M1>, docs: &CMarkDocs<&Path, M2>) {
13 match check_sync(readme, docs) {
14 Ok(()) => {}
15 Err(CheckSyncError::MatchFailed(err)) => {
16 err.emit_to_stderr_colored();
17 panic!();
18 }
19 }
20}
21
22pub fn check_sync<P1, P2, M1, M2>(
24 readme: &CMarkReadme<P1, M1>,
25 docs: &CMarkDocs<P2, M2>,
26) -> Result<(), CheckSyncError> {
27 use std::vec::Vec;
28
29 let mut readme_iter = readme.iter();
30 let mut docs_iter = docs.iter();
31 let mut matched_events = Vec::new();
32
33 loop {
34 let NextItem {
35 node: readme_node,
36 event: readme_event,
37 removed: readme_removed_nodes,
38 } = next_node(&mut readme_iter);
39
40 let NextItem {
41 node: docs_node,
42 event: docs_event,
43 removed: docs_removed_nodes,
44 } = next_node(&mut docs_iter);
45
46 if readme_node.is_none() && docs_node.is_none() {
47 break;
48 }
49
50 if readme_event == docs_event {
51 matched_events.push(readme_event.unwrap());
52 } else {
53 use crate::CodemapFiles;
54 use std::sync::Arc;
55
56 let mut codemap_files = CodemapFiles::new();
57 let mut diags = std::vec![node_not_mached_diagnostic(
58 &mut codemap_files,
59 &readme_node,
60 &docs_node,
61 )];
62
63 diags.extend(removed_nodes_note(
64 &mut codemap_files,
65 &readme_removed_nodes,
66 "readme",
67 ));
68
69 diags.extend(removed_nodes_note(
70 &mut codemap_files,
71 &docs_removed_nodes,
72 "docs",
73 ));
74
75 if let (Some(readme_event), Some(docs_event)) = (readme_event, docs_event) {
76 diags.append(&mut event_diff_notes(&readme_event, &docs_event));
77 }
78
79 diags.push(previous_events_notes(&matched_events));
80
81 let codemap_files = Arc::new(codemap_files);
82 return Err(CheckSyncError::MatchFailed(MatchFailed {
83 diags,
84 codemap_files,
85 }));
86 }
87 }
88 Ok(())
89}
90
91#[derive(Clone, Debug, Error)]
93pub enum CheckSyncError {
94 #[error(
96 "CMarkReadme and CMarkDocs nodes are not the same. \
97 Use `MatchFailed::emit_to_stderr` for details."
98 )]
99 MatchFailed(MatchFailed),
100}
101
102#[derive(Clone, Debug)]
104pub struct MatchFailed {
105 diags: std::vec::Vec<codemap_diagnostic::Diagnostic>,
106 codemap_files: std::sync::Arc<crate::CodemapFiles>,
107}
108
109impl MatchFailed {
110 pub fn emit_to_stderr_colored(&self) {
112 use codemap_diagnostic::{ColorConfig, Emitter};
113
114 let mut emitter = Emitter::stderr(ColorConfig::Always, Some(self.codemap_files.codemap()));
115 emitter.emit(&self.diags);
116 }
117}
118
119impl Display for MatchFailed {
120 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
121 use codemap_diagnostic::Emitter;
122 use std::vec::Vec;
123
124 let mut raw = Vec::new();
125 {
126 let mut emitter = Emitter::vec(&mut raw, Some(self.codemap_files.codemap()));
127 emitter.emit(&self.diags);
128 }
129 let msg = String::from_utf8_lossy(&raw);
130 write!(f, "{}", msg)
131 }
132}
133
134struct NextItem<'a> {
135 node: Option<std::sync::Arc<crate::CMarkItem>>,
136 event: Option<pulldown_cmark::Event<'a>>,
137 removed: std::vec::Vec<std::sync::Arc<crate::CMarkItem>>,
138}
139
140fn next_node<'a>(iter: &mut crate::CMarkDataIter<'a>) -> NextItem<'a> {
141 use std::sync::Arc;
142 use std::vec::Vec;
143
144 let mut removed = Vec::new();
145 loop {
146 if let Some(node) = iter.next() {
147 if let Some(event) = node.event() {
148 return NextItem {
149 node: Some(Arc::clone(node)),
150 event: Some(event.clone()),
151 removed,
152 };
153 } else {
154 removed.push(Arc::clone(node));
155 }
156 } else {
157 return NextItem {
158 node: None,
159 event: None,
160 removed,
161 };
162 }
163 }
164}
165
166fn node_not_mached_diagnostic(
167 codemap_files: &mut crate::CodemapFiles,
168 readme_node: &Option<std::sync::Arc<crate::CMarkItem>>,
169 docs_node: &Option<std::sync::Arc<crate::CMarkItem>>,
170) -> codemap_diagnostic::Diagnostic {
171 use crate::CodemapSpans;
172 use codemap_diagnostic::{Diagnostic, Level};
173 use std::format;
174
175 let nodes = [readme_node, docs_node];
176 let spans = nodes
177 .iter()
178 .filter_map(|node| node.as_ref())
179 .flat_map(|node| node.spans());
180 let span_labels = CodemapSpans::span_labels_from(codemap_files, spans);
181 let readme_event = readme_node.as_ref().and_then(|node| node.event());
182 let docs_event = docs_node.as_ref().and_then(|node| node.event());
183
184 let message = match (readme_event, docs_event) {
185 (Some(readme_event), Some(docs_event)) => format!(
186 "readme node\n`{}`\n does not match docs node\n`{}`",
187 FmtPrint(readme_event),
188 FmtPrint(docs_event)
189 ),
190 (Some(readme_event), None) => format!(
191 "readme node\n`{}`\n does not match any docs node",
192 FmtPrint(readme_event)
193 ),
194 (None, Some(docs_event)) => format!(
195 "docs node\n`{}`\n does not match any readme node",
196 FmtPrint(docs_event)
197 ),
198 (None, None) => unreachable!(),
199 };
200
201 Diagnostic {
202 level: Level::Error,
203 message,
204 code: None,
205 spans: span_labels,
206 }
207}
208
209fn removed_nodes_note(
210 codemap_files: &mut crate::CodemapFiles,
211 nodes: &[std::sync::Arc<crate::CMarkItem>],
212 node_type: &str,
213) -> Option<codemap_diagnostic::Diagnostic> {
214 use crate::CodemapSpans;
215 use codemap_diagnostic::{Diagnostic, Level};
216 use std::format;
217
218 if nodes.is_empty() {
219 None
220 } else {
221 let spans = nodes.iter().flat_map(|node| node.spans());
222 let span_labels = CodemapSpans::span_labels_from(codemap_files, spans);
223 Some(Diagnostic {
224 level: Level::Note,
225 message: format!("some {} nodes were removed before these", node_type),
226 code: None,
227 spans: span_labels,
228 })
229 }
230}
231
232fn event_diff_notes(
233 readme_event: &pulldown_cmark::Event<'_>,
234 docs_event: &pulldown_cmark::Event<'_>,
235) -> std::vec::Vec<codemap_diagnostic::Diagnostic> {
236 use std::iter::repeat;
237 use std::string::ToString;
238 use std::{format, vec};
239
240 use pulldown_cmark::{CodeBlockKind, Event, Tag};
241
242 let readme_event_name = get_event_name(readme_event);
243 let docs_event_name = get_event_name(docs_event);
244 if readme_event_name != docs_event_name {
245 return vec![
246 text_note(std::format!(
247 "readme node event name is \"{}\"",
248 readme_event_name
249 )),
250 text_note(std::format!(
251 "docs node event name is \"{}\"",
252 docs_event_name
253 )),
254 ];
255 }
256
257 let readme_tag = get_event_start_tag(readme_event);
258 let docs_tag = get_event_start_tag(docs_event);
259 if let (Some(readme_tag), Some(docs_tag)) = (readme_tag, docs_tag) {
260 let readme_tag_name = get_start_tag_name(readme_tag);
261 let docs_tag_name = get_start_tag_name(docs_tag);
262 if readme_tag_name != docs_tag_name {
263 let mut notes = vec![
264 text_note(std::format!(
265 "readme node event start tag name is \"{}\"",
266 readme_tag_name
267 )),
268 text_note(std::format!(
269 "docs node event start tag name is \"{}\"",
270 docs_tag_name
271 )),
272 ];
273 if let Event::Start(Tag::CodeBlock(CodeBlockKind::Indented)) = docs_event {
274 notes.push(text_note(
275 concat!(
276 "Possible issue: ",
277 "Rustdoc ignore indents in the consecutive ",
278 "doc-comments and doc-attributes. ",
279 "However, the four-space indents should be ",
280 "interpreted as Indented code blocks in CMark. ",
281 "Issue: https://github.com/rust-lang/rust/issues/70732",
282 )
283 .to_string(),
284 ));
285 }
286 return notes;
287 }
288 }
289
290 let readme_tag = get_event_end_tag(readme_event);
291 let docs_tag = get_event_end_tag(docs_event);
292 if let (Some(readme_tag), Some(docs_tag)) = (readme_tag, docs_tag) {
293 let readme_tag_name = get_end_tag_name(readme_tag);
294 let docs_tag_name = get_end_tag_name(docs_tag);
295 if readme_tag_name != docs_tag_name {
296 let notes = vec![
297 text_note(std::format!(
298 "readme node event end tag name is \"{}\"",
299 readme_tag_name
300 )),
301 text_note(std::format!(
302 "docs node event end tag name is \"{}\"",
303 docs_tag_name
304 )),
305 ];
306 return notes;
307 }
308 }
309
310 let readme_text = get_event_text(readme_event);
311 let docs_text = get_event_text(docs_event);
312 if let (Some(readme_text), Some(docs_text)) = (readme_text, docs_text) {
313 if readme_text != docs_text {
314 const OFFSET: usize = 32;
315 const LEN: usize = 32;
316
317 let readme_chars = readme_text.char_indices().map(Some).chain(repeat(None));
318 let docs_chars = docs_text.char_indices().map(Some).chain(repeat(None));
319 let mut chars = readme_chars.zip(docs_chars);
320 let pos = chars
321 .find_map(|pair| match pair {
322 (Some(lhs), Some(rhs)) => {
323 if lhs.1 != rhs.1 {
324 assert_eq!(lhs.0, rhs.0);
325 Some(lhs.0)
326 } else {
327 None
328 }
329 }
330 (Some(lhs), _) => Some(lhs.0),
331 (_, Some(rhs)) => Some(rhs.0),
332 (None, None) => unreachable!(),
333 })
334 .unwrap();
335 let start = pos.saturating_sub(OFFSET);
336 let end = pos + LEN;
337
338 return vec![
339 text_note(std::format!(
340 "readme node text part: \"{}\"",
341 formatted_subslice(readme_text, start, end)
342 )),
343 text_note(std::format!(
344 "docs node text part: \"{}\"",
345 formatted_subslice(docs_text, start, end)
346 )),
347 ];
348 }
349 }
350
351 vec![
352 text_note(format!("readme node: {}", FmtPrint(readme_event))),
353 text_note(format!("docs node: {}", FmtPrint(docs_event))),
354 ]
355}
356
357fn previous_events_notes(events: &[pulldown_cmark::Event<'_>]) -> codemap_diagnostic::Diagnostic {
358 use std::format;
359 use std::string::ToString;
360
361 const MAX_EVENTS_SHOWN: usize = 16;
362
363 if events.is_empty() {
364 text_note("match failed on first events".to_string())
365 } else {
366 let from = events.len().saturating_sub(MAX_EVENTS_SHOWN);
367 let mut note = "previous events: [\n".to_owned();
368 if from != 0 {
369 note += " ...\n";
370 }
371 for event in &events[from..] {
372 note += &format!(" {}\n", FmtPrint(event));
373 }
374 note += "]";
375 text_note(note)
376 }
377}
378
379fn text_note(message: String) -> codemap_diagnostic::Diagnostic {
380 use codemap_diagnostic::{Diagnostic, Level};
381 use std::vec::Vec;
382
383 Diagnostic {
384 level: Level::Note,
385 message,
386 code: None,
387 spans: Vec::new(),
388 }
389}
390
391fn formatted_subslice(text: &str, start: usize, end: usize) -> String {
392 use std::format;
393
394 let skip_before = start > 3;
395 let start = if skip_before { start } else { 0 };
396 let skip_after = text.len().saturating_sub(end) > 3;
397 let end = if skip_after { end } else { text.len() };
398
399 format!(
400 "{}{}{}",
401 if skip_before { "..." } else { "" },
402 &text[start..end],
403 if skip_after { "..." } else { "" }
404 )
405}
406
407fn get_event_start_tag<'a>(
408 event: &'a pulldown_cmark::Event<'_>,
409) -> Option<&'a pulldown_cmark::Tag<'a>> {
410 use pulldown_cmark::Event;
411 match event {
412 Event::Start(tag) => Some(tag),
413 _ => None,
414 }
415}
416
417fn get_event_end_tag<'a>(
418 event: &'a pulldown_cmark::Event<'_>,
419) -> Option<&'a pulldown_cmark::TagEnd> {
420 use pulldown_cmark::Event;
421 match event {
422 Event::End(tag) => Some(tag),
423 _ => None,
424 }
425}
426
427fn get_event_text<'a>(event: &'a pulldown_cmark::Event<'_>) -> Option<&'a str> {
428 use pulldown_cmark::Event;
429 match event {
430 Event::Text(text) => Some(text),
431 Event::Code(text) => Some(text),
432 Event::Html(text) => Some(text),
433 Event::FootnoteReference(text) => Some(text),
434 _ => None,
435 }
436}
437
438fn get_event_name<'a>(event: &pulldown_cmark::Event<'_>) -> &'a str {
439 use pulldown_cmark::Event;
440 match event {
441 Event::Start(..) => "Start",
442 Event::End(..) => "End",
443 Event::Text(..) => "Text",
444 Event::Code(..) => "Code",
445 Event::InlineMath(..) => "InlineMath",
446 Event::DisplayMath(..) => "DisplayMath",
447 Event::Html(..) => "Html",
448 Event::InlineHtml(..) => "InlineHtml",
449 Event::FootnoteReference(..) => "FootnoteReference",
450 Event::SoftBreak => "SoftBreak",
451 Event::HardBreak => "HardBreak",
452 Event::Rule => "Rule",
453 Event::TaskListMarker(..) => "TaskListMarker",
454 }
455}
456
457fn get_start_tag_name<'a>(tag: &'a pulldown_cmark::Tag<'_>) -> &'a str {
458 use pulldown_cmark::Tag;
459 match tag {
460 Tag::Paragraph => "Paragraph",
461 Tag::Heading { .. } => "Heading",
462 Tag::BlockQuote(..) => "BlockQuote",
463 Tag::CodeBlock(..) => "CodeBlock",
464 Tag::HtmlBlock { .. } => "HtmlBlock",
465 Tag::List(..) => "List",
466 Tag::Item => "Item",
467 Tag::FootnoteDefinition(..) => "FootnoteDefinition",
468 Tag::DefinitionList => "DefinitionList",
469 Tag::DefinitionListTitle => "DefinitionListTitle",
470 Tag::DefinitionListDefinition => "DefinitionListDefinition",
471 Tag::Table(..) => "Table",
472 Tag::TableHead => "TableHead",
473 Tag::TableRow => "TableRow",
474 Tag::TableCell => "TableCell",
475 Tag::Emphasis => "Emphasis",
476 Tag::Strong => "Strong",
477 Tag::Strikethrough => "Strikethrough",
478 Tag::Link { .. } => "Link",
479 Tag::Image { .. } => "Image",
480 Tag::MetadataBlock(..) => "MetadataBlock",
481 }
482}
483
484fn get_end_tag_name(tag: &pulldown_cmark::TagEnd) -> &str {
485 use pulldown_cmark::TagEnd;
486 match tag {
487 TagEnd::Paragraph => "Paragraph",
488 TagEnd::Heading { .. } => "Heading",
489 TagEnd::BlockQuote(..) => "BlockQuote",
490 TagEnd::CodeBlock => "CodeBlock",
491 TagEnd::HtmlBlock { .. } => "HtmlBlock",
492 TagEnd::List(..) => "List",
493 TagEnd::Item => "Item",
494 TagEnd::FootnoteDefinition => "FootnoteDefinition",
495 TagEnd::DefinitionList => "DefinitionList",
496 TagEnd::DefinitionListTitle => "DefinitionListTitle",
497 TagEnd::DefinitionListDefinition => "DefinitionListDefinition",
498 TagEnd::Table => "Table",
499 TagEnd::TableHead => "TableHead",
500 TagEnd::TableRow => "TableRow",
501 TagEnd::TableCell => "TableCell",
502 TagEnd::Emphasis => "Emphasis",
503 TagEnd::Strong => "Strong",
504 TagEnd::Strikethrough => "Strikethrough",
505 TagEnd::Link { .. } => "Link",
506 TagEnd::Image { .. } => "Image",
507 TagEnd::MetadataBlock(..) => "MetadataBlock",
508 }
509}
510
511pub trait Print {
512 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result;
513}
514
515#[derive(Clone, Debug)]
516pub struct FmtPrint<T>(T);
517
518impl<T> Display for FmtPrint<T>
519where
520 T: Print,
521{
522 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
523 T::fmt(&self.0, f)
524 }
525}
526
527impl<T> Print for &T
528where
529 T: ?Sized + Print,
530{
531 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
532 T::fmt(self, f)
533 }
534}
535
536impl<T> Print for Option<T>
537where
538 T: Print,
539{
540 fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
541 match self {
542 Some(value) => write!(fmt, "Some({})", FmtPrint(value)),
543 None => write!(fmt, "None"),
544 }
545 }
546}
547
548impl<T> Print for [T]
549where
550 T: Print,
551{
552 fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
553 let mut iter = self.iter();
554 writeln!(fmt, "[")?;
555 if let Some(first) = iter.next() {
556 writeln!(fmt, "{}", FmtPrint(first))?;
557 for item in iter {
558 writeln!(fmt, ", {}", FmtPrint(item))?;
559 }
560 }
561 writeln!(fmt, "]")?;
562 Ok(())
563 }
564}
565
566impl Print for str {
567 fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
568 write!(fmt, "{}", self)
569 }
570}
571
572impl Print for CowStr<'_> {
573 fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
574 write!(fmt, "{}", self)
575 }
576}
577
578impl<T1, T2> Print for (T1, T2)
579where
580 T1: Print,
581 T2: Print,
582{
583 fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
584 write!(fmt, "({}, {})", FmtPrint(&self.0), FmtPrint(&self.1))
585 }
586}
587
588impl Print for pulldown_cmark::Event<'_> {
589 fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
590 use pulldown_cmark::Event;
591
592 let event_name = get_event_name(self);
593 write!(fmt, "{}", event_name)?;
594
595 match self {
596 Event::Start(tag) => write!(fmt, "({})", FmtPrint(tag)),
597 Event::End(tag) => write!(fmt, "({})", FmtPrint(tag)),
598 Event::Text(text)
599 | Event::Code(text)
600 | Event::InlineMath(text)
601 | Event::DisplayMath(text)
602 | Event::Html(text)
603 | Event::InlineHtml(text)
604 | Event::FootnoteReference(text) => write!(fmt, "(\"{}\")", &text),
605 Event::SoftBreak => Ok(()),
606 Event::HardBreak => Ok(()),
607 Event::Rule => Ok(()),
608 Event::TaskListMarker(ch) => write!(fmt, "({})", ch),
609 }
610 }
611}
612
613impl Print for pulldown_cmark::Tag<'_> {
614 fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
615 use pulldown_cmark::{MetadataBlockKind, Tag};
616
617 let tag_name = get_start_tag_name(self);
618 write!(fmt, "{}", tag_name)?;
619
620 match self {
621 Tag::Paragraph => Ok(()),
622 Tag::Heading {
623 level,
624 id,
625 classes,
626 attrs,
627 } => write!(
628 fmt,
629 "({}, \"{}\", {}, {})",
630 level,
631 FmtPrint(id.as_deref()),
632 FmtPrint(classes.as_slice()),
633 FmtPrint(attrs.as_slice())
634 ),
635 Tag::BlockQuote(kind) => write!(fmt, "({})", FmtPrint(kind)),
636 Tag::CodeBlock(kind) => write!(fmt, "({})", FmtPrint(kind)),
637 Tag::HtmlBlock => Ok(()),
638 Tag::List(Some(first)) => write!(fmt, "(Some({}))", first),
639 Tag::List(None) => write!(fmt, "(None)"),
640 Tag::Item => Ok(()),
641 Tag::FootnoteDefinition(label) => write!(fmt, "(\"{}\")", &label),
642 Tag::DefinitionList => Ok(()),
643 Tag::DefinitionListTitle => Ok(()),
644 Tag::DefinitionListDefinition => Ok(()),
645 Tag::Table(alignment) => write!(fmt, "({})", FmtPrint(&alignment[..])),
646 Tag::TableHead => Ok(()),
647 Tag::TableRow => Ok(()),
648 Tag::TableCell => Ok(()),
649 Tag::Emphasis => Ok(()),
650 Tag::Strong => Ok(()),
651 Tag::Strikethrough => Ok(()),
652 Tag::Link {
653 link_type,
654 dest_url,
655 title,
656 id,
657 } => {
658 write!(
659 fmt,
660 "({}, \"{}\", \"{}\", \"{}\")",
661 FmtPrint(link_type),
662 dest_url,
663 title,
664 id
665 )
666 }
667 Tag::Image {
668 link_type,
669 dest_url,
670 title,
671 id,
672 } => {
673 write!(
674 fmt,
675 "({}, \"{}\", \"{}\", \"{}\")",
676 FmtPrint(link_type),
677 dest_url,
678 title,
679 id
680 )
681 }
682 Tag::MetadataBlock(MetadataBlockKind::YamlStyle) => {
683 write!(fmt, "(\"yaml style\")")
684 }
685 Tag::MetadataBlock(MetadataBlockKind::PlusesStyle) => {
686 write!(fmt, "(\"pluses style\")")
687 }
688 }
689 }
690}
691
692impl Print for pulldown_cmark::TagEnd {
693 fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
694 use pulldown_cmark::{MetadataBlockKind, TagEnd};
695
696 let tag_name = get_end_tag_name(self);
697 write!(fmt, "/{}", tag_name)?;
698
699 match self {
700 TagEnd::Paragraph => Ok(()),
701 TagEnd::Heading(level) => write!(fmt, "({})", level),
702 TagEnd::BlockQuote(kind) => write!(fmt, "({})", FmtPrint(kind)),
703 TagEnd::CodeBlock => Ok(()),
704 TagEnd::HtmlBlock => Ok(()),
705 TagEnd::List(is_ordered) => write!(fmt, "({})", &is_ordered),
706 TagEnd::Item => Ok(()),
707 TagEnd::FootnoteDefinition => Ok(()),
708 TagEnd::DefinitionList => Ok(()),
709 TagEnd::DefinitionListTitle => Ok(()),
710 TagEnd::DefinitionListDefinition => Ok(()),
711 TagEnd::Table => Ok(()),
712 TagEnd::TableHead => Ok(()),
713 TagEnd::TableRow => Ok(()),
714 TagEnd::TableCell => Ok(()),
715 TagEnd::Emphasis => Ok(()),
716 TagEnd::Strong => Ok(()),
717 TagEnd::Strikethrough => Ok(()),
718 TagEnd::Link => Ok(()),
719 TagEnd::Image => Ok(()),
720 TagEnd::MetadataBlock(MetadataBlockKind::YamlStyle) => {
721 write!(fmt, "(\"yaml style\")")
722 }
723 TagEnd::MetadataBlock(MetadataBlockKind::PlusesStyle) => {
724 write!(fmt, "(\"pluses style\")")
725 }
726 }
727 }
728}
729
730impl Print for pulldown_cmark::CodeBlockKind<'_> {
731 fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
732 use pulldown_cmark::CodeBlockKind;
733
734 match self {
735 CodeBlockKind::Indented => write!(fmt, "Indented"),
736 CodeBlockKind::Fenced(tag) => write!(fmt, "Fenced({})", tag),
737 }
738 }
739}
740
741impl Print for pulldown_cmark::Alignment {
742 fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
743 use pulldown_cmark::Alignment;
744
745 match self {
746 Alignment::None => write!(fmt, "None"),
747 Alignment::Left => write!(fmt, "Left"),
748 Alignment::Center => write!(fmt, "Center"),
749 Alignment::Right => write!(fmt, "Right"),
750 }
751 }
752}
753
754impl Print for pulldown_cmark::LinkType {
755 fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
756 use pulldown_cmark::LinkType;
757
758 match self {
759 LinkType::Inline => write!(fmt, "Inline"),
760 LinkType::Reference => write!(fmt, "Reference"),
761 LinkType::ReferenceUnknown => write!(fmt, "ReferenceUnknown"),
762 LinkType::Collapsed => write!(fmt, "Collapsed"),
763 LinkType::CollapsedUnknown => write!(fmt, "CollapsedUnknown"),
764 LinkType::Shortcut => write!(fmt, "Shortcut"),
765 LinkType::ShortcutUnknown => write!(fmt, "ShortcutUnknown"),
766 LinkType::Autolink => write!(fmt, "Autolink"),
767 LinkType::Email => write!(fmt, "Email"),
768 }
769 }
770}
771
772impl Print for pulldown_cmark::BlockQuoteKind {
773 fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
774 use pulldown_cmark::BlockQuoteKind;
775
776 match self {
777 BlockQuoteKind::Note => write!(fmt, "Note"),
778 BlockQuoteKind::Tip => write!(fmt, "Tip"),
779 BlockQuoteKind::Important => write!(fmt, "Important"),
780 BlockQuoteKind::Warning => write!(fmt, "Warning"),
781 BlockQuoteKind::Caution => write!(fmt, "Caution"),
782 }
783 }
784}