markdown_code_runner/
lib.rs1pub mod executor;
31pub mod markers;
32pub mod parser;
33pub mod standardize;
34
35use anyhow::{Context, Result};
36use std::fs;
37use std::path::Path;
38
39pub use executor::Language;
40pub use markers::WARNING;
41pub use parser::{process_markdown, BacktickOptions, ProcessingState, Section};
42pub use standardize::standardize_code_fences;
43
44pub fn update_markdown_file(
59 input_filepath: &Path,
60 output_filepath: Option<&Path>,
61 verbose: bool,
62 backtick_standardize: bool,
63 execute: bool,
64 standardize: bool,
65) -> Result<()> {
66 let content = fs::read_to_string(input_filepath)
68 .with_context(|| format!("Failed to read input file: {:?}", input_filepath))?;
69
70 if verbose {
71 eprintln!("Processing input file: {:?}", input_filepath);
72 }
73
74 let lines: Vec<String> = content.lines().map(|s| s.to_string()).collect();
76
77 let new_lines = process_markdown(&lines, verbose, backtick_standardize, execute)?;
79
80 let mut updated_content = new_lines.join("\n");
82 updated_content = updated_content.trim_end().to_string();
84 updated_content.push('\n');
85
86 if standardize {
88 if verbose {
89 eprintln!("Standardizing all code fences...");
90 }
91 updated_content = standardize_code_fences(&updated_content);
92 }
93
94 let output_path = output_filepath.unwrap_or(input_filepath);
96
97 if verbose {
98 eprintln!("Writing output to: {:?}", output_path);
99 }
100
101 fs::write(output_path, &updated_content)
103 .with_context(|| format!("Failed to write output file: {:?}", output_path))?;
104
105 if verbose {
106 eprintln!("Done!");
107 }
108
109 Ok(())
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115 use std::fs;
116 use tempfile::tempdir;
117
118 #[test]
119 fn test_update_markdown_file() {
120 let dir = tempdir().unwrap();
121 let input_path = dir.path().join("test.md");
122 let output_path = dir.path().join("output.md");
123
124 let content = r#"# Test
125```python markdown-code-runner
126print('Hello')
127```
128<!-- OUTPUT:START -->
129old
130<!-- OUTPUT:END -->
131"#;
132
133 fs::write(&input_path, content).unwrap();
134
135 update_markdown_file(&input_path, Some(&output_path), false, false, true, false).unwrap();
136
137 let result = fs::read_to_string(&output_path).unwrap();
138 assert!(result.contains("Hello"));
139 assert!(!result.contains("old"));
140 }
141
142 #[test]
143 fn test_update_markdown_file_no_execute() {
144 let dir = tempdir().unwrap();
145 let input_path = dir.path().join("test.md");
146
147 let content = r#"# Test
148```python markdown-code-runner
149print('Hello')
150```
151<!-- OUTPUT:START -->
152old
153<!-- OUTPUT:END -->
154"#;
155
156 fs::write(&input_path, content).unwrap();
157
158 update_markdown_file(&input_path, None, false, false, false, false).unwrap();
159
160 let result = fs::read_to_string(&input_path).unwrap();
161 assert!(result.contains("old"));
162 }
163
164 #[test]
165 fn test_update_markdown_file_standardize() {
166 let dir = tempdir().unwrap();
167 let input_path = dir.path().join("test.md");
168 let output_path = dir.path().join("output.md");
169
170 let content = r#"# Test
171```python markdown-code-runner
172print('Hello')
173```
174<!-- OUTPUT:START -->
175old
176<!-- OUTPUT:END -->
177"#;
178
179 fs::write(&input_path, content).unwrap();
180
181 update_markdown_file(&input_path, Some(&output_path), false, false, true, true).unwrap();
182
183 let result = fs::read_to_string(&output_path).unwrap();
184 assert!(result.contains("```python\n"));
185 assert!(!result.contains("```python markdown-code-runner"));
187 }
188}