cargo_docs_md/generator/capture.rs
1//! In-memory markdown capture for testing.
2//!
3//! This module provides [`MarkdownCapture`] for capturing generated markdown
4//! in memory instead of writing to disk, enabling snapshot testing.
5
6use std::collections::HashMap;
7
8/// Captures generated markdown in memory for testing.
9///
10/// Instead of writing files to disk, this struct stores all generated
11/// markdown content in a `HashMap`, keyed by relative file path. This
12/// enables snapshot testing and verification of output without filesystem
13/// side effects.
14#[derive(Debug, Default)]
15pub struct MarkdownCapture {
16 /// Maps file paths (relative to output directory) to their generated content.
17 files: HashMap<String, String>,
18}
19
20impl MarkdownCapture {
21 /// Create a new empty capture.
22 #[must_use]
23 pub fn new() -> Self {
24 Self {
25 files: HashMap::new(),
26 }
27 }
28
29 /// Add a file to the capture.
30 ///
31 /// # Arguments
32 /// * `path` - Relative path of the file (e.g., "index.md" or "span/index.md")
33 /// * `content` - The markdown content for this file
34 pub fn insert(&mut self, path: String, content: String) {
35 self.files.insert(path, content);
36 }
37
38 /// Get the content of a specific file.
39 #[must_use]
40 pub fn get(&self, path: &str) -> Option<&String> {
41 self.files.get(path)
42 }
43
44 /// Get all file paths in sorted order.
45 #[must_use]
46 pub fn paths(&self) -> Vec<&String> {
47 let mut paths: Vec<_> = self.files.keys().collect();
48 paths.sort();
49 paths
50 }
51
52 /// Get the number of captured files.
53 #[must_use]
54 pub fn len(&self) -> usize {
55 self.files.len()
56 }
57
58 /// Check if the capture is empty.
59 #[must_use]
60 pub fn is_empty(&self) -> bool {
61 self.files.is_empty()
62 }
63
64 /// Convert all captured files to a single string for snapshot testing.
65 ///
66 /// Files are sorted by path and separated with clear headers.
67 #[must_use]
68 pub fn to_snapshot_string(&self) -> String {
69 use std::fmt::Write;
70
71 let mut result = String::new();
72 let mut paths: Vec<_> = self.files.keys().collect();
73 paths.sort();
74
75 for path in paths {
76 _ = writeln!(result, "=== {path} ===");
77 _ = write!(result, "{}", &self.files[path]);
78
79 if !self.files[path].ends_with('\n') {
80 result.push('\n');
81 }
82
83 result.push('\n');
84 }
85
86 result
87 }
88
89 /// Consume self and return the underlying `HashMap`.
90 #[must_use]
91 pub fn into_inner(self) -> HashMap<String, String> {
92 self.files
93 }
94}