context_creator/formatters/
markdown.rs1use super::{DigestData, DigestFormatter};
4use crate::core::context_builder::{
5 format_import_names, format_imported_by_names, format_path_with_metadata, generate_file_tree,
6 generate_statistics, get_language_hint, path_to_anchor,
7};
8use crate::core::walker::FileInfo;
9use anyhow::Result;
10
11pub struct MarkdownFormatter {
13 buffer: String,
14}
15
16impl MarkdownFormatter {
17 pub fn new() -> Self {
19 Self {
20 buffer: String::with_capacity(1024 * 1024), }
22 }
23}
24
25impl Default for MarkdownFormatter {
26 fn default() -> Self {
27 Self::new()
28 }
29}
30
31impl DigestFormatter for MarkdownFormatter {
32 fn render_header(&mut self, data: &DigestData) -> Result<()> {
33 if !data.options.doc_header_template.is_empty() {
34 let header = data
35 .options
36 .doc_header_template
37 .replace("{directory}", data.base_directory);
38 self.buffer.push_str(&header);
39 self.buffer.push_str("\n\n");
40 }
41 Ok(())
42 }
43
44 fn render_statistics(&mut self, data: &DigestData) -> Result<()> {
45 if data.options.include_stats {
46 let stats = generate_statistics(data.files);
47 self.buffer.push_str(&stats);
48 self.buffer.push_str("\n\n");
49 }
50 Ok(())
51 }
52
53 fn render_file_tree(&mut self, data: &DigestData) -> Result<()> {
54 if data.options.include_tree {
55 let tree = generate_file_tree(data.files, data.options);
56 self.buffer.push_str("## File Structure\n\n");
57 self.buffer.push_str("```\n");
58 self.buffer.push_str(&tree);
59 self.buffer.push_str("```\n\n");
60 }
61 Ok(())
62 }
63
64 fn render_toc(&mut self, data: &DigestData) -> Result<()> {
65 if data.options.include_toc {
66 self.buffer.push_str("## Table of Contents\n\n");
67 for file in data.files {
68 let anchor = path_to_anchor(&file.relative_path);
69 self.buffer.push_str(&format!(
70 "- [{path}](#{anchor})\n",
71 path = file.relative_path.display(),
72 anchor = anchor
73 ));
74 }
75 self.buffer.push('\n');
76 }
77 Ok(())
78 }
79
80 fn render_file_details(&mut self, file: &FileInfo, data: &DigestData) -> Result<()> {
81 let path_with_metadata = format_path_with_metadata(file, data.options);
83 let header = data
84 .options
85 .file_header_template
86 .replace("{path}", &path_with_metadata);
87 self.buffer.push_str(&header);
88 self.buffer.push_str("\n\n");
89
90 add_markdown_semantic_info(&mut self.buffer, file);
92
93 if let Ok(content) = data.cache.get_or_load(&file.path) {
95 let language = get_language_hint(&file.file_type);
96 self.buffer.push_str(&format!("```{language}\n"));
97 self.buffer.push_str(&content);
98 if !content.ends_with('\n') {
99 self.buffer.push('\n');
100 }
101 self.buffer.push_str("```\n\n");
102 }
103
104 Ok(())
105 }
106
107 fn finalize(self: Box<Self>) -> String {
108 self.buffer
109 }
110
111 fn format_name(&self) -> &'static str {
112 "markdown"
113 }
114}
115
116fn add_markdown_semantic_info(output: &mut String, file: &FileInfo) {
117 if !file.imports.is_empty() {
118 output.push_str("Imports: ");
119 let names = format_import_names(&file.imports);
120 output.push_str(&format!("{}\n\n", names.join(", ")));
121 }
122
123 if !file.imported_by.is_empty() {
124 output.push_str("Imported by: ");
125 let names = format_imported_by_names(&file.imported_by);
126 output.push_str(&format!("{}\n\n", names.join(", ")));
127 }
128
129 if !file.function_calls.is_empty() {
130 output.push_str("Function calls: ");
131 let names = format_function_call_names(&file.function_calls);
132 output.push_str(&format!("{}\n\n", names.join(", ")));
133 }
134
135 if !file.type_references.is_empty() {
136 output.push_str("Type references: ");
137 let names = format_type_reference_names(&file.type_references);
138 output.push_str(&format!("{}\n\n", names.join(", ")));
139 }
140}
141
142fn format_function_call_names(
143 calls: &[crate::core::semantic::analyzer::FunctionCall],
144) -> Vec<String> {
145 calls
146 .iter()
147 .map(|fc| {
148 if let Some(module) = &fc.module {
149 format!("{}.{}", module, fc.name)
150 } else {
151 fc.name.clone()
152 }
153 })
154 .collect()
155}
156
157fn format_type_reference_names(
158 refs: &[crate::core::semantic::analyzer::TypeReference],
159) -> Vec<String> {
160 refs.iter()
161 .map(|tr| {
162 if let Some(module) = &tr.module {
163 if module.ends_with(&format!("::{}", tr.name)) {
164 module.clone()
165 } else {
166 format!("{}.{}", module, tr.name)
167 }
168 } else {
169 tr.name.clone()
170 }
171 })
172 .collect()
173}