use comrak::plugins::syntect::SyntectAdapterBuilder; use comrak::{markdown_to_html_with_plugins, Options, Plugins}; use std::fs; use std::path::Path; pub fn render_markdown(content: String) -> String { let adapter = SyntectAdapterBuilder::new() .theme("base16-ocean.dark") .build(); let options = Options::default(); let mut plugins = Plugins::default(); plugins.render.codefence_syntax_highlighter = Some(&adapter); markdown_to_html_with_plugins(&content, &options, &plugins) } pub fn convert_file(path: String) -> Result { let content = fs::read_to_string(&path).map_err(|e| e.to_string())?; let adapter = SyntectAdapterBuilder::new() .theme("InspiredGitHub") .build(); let mut options = Options::default(); options.extension.table = true; options.extension.strikethrough = true; options.extension.autolink = true; options.extension.tasklist = true; let mut plugins = Plugins::default(); plugins.render.codefence_syntax_highlighter = Some(&adapter); Ok(markdown_to_html_with_plugins(&content, &options, &plugins)) } pub(crate) fn collect_md_files(dir: &Path, result: &mut Vec) -> std::io::Result<()> { for entry in fs::read_dir(dir)? { let entry = entry?; let name = entry.file_name(); if name.to_string_lossy().starts_with('.') { continue; } let path = entry.path(); if path.is_dir() { collect_md_files(&path, result)?; } else if path.extension().is_some_and(|e| e == "md") { if let Some(s) = path.to_str() { result.push(s.to_string()); } } } Ok(()) } pub fn list_md_files(dir: String) -> Result, String> { let path = Path::new(&dir); if !path.is_dir() { return Err(format!("{dir} n'est pas un dossier")); } let mut files = Vec::new(); collect_md_files(path, &mut files).map_err(|e| e.to_string())?; files.sort(); Ok(files) } #[cfg(test)] mod tests { use super::*; use std::path::PathBuf; fn tmpdir(name: &str) -> PathBuf { std::env::temp_dir().join(name) } #[test] fn render_markdown_heading() { let html = render_markdown("# Hello\n".to_string()); assert!(html.contains("

")); assert!(html.contains("Hello")); } #[test] fn render_markdown_code_block() { let html = render_markdown("```rust\nfn main() {}\n```\n".to_string()); assert!(html.contains("")); } #[test] fn render_markdown_empty() { let html = render_markdown(String::new()); assert!(!html.contains("

")); } #[test] fn convert_file_success() { let dir = tmpdir("pena_convert_success"); fs::create_dir_all(&dir).unwrap(); let file = dir.join("test.md"); fs::write(&file, "# Title\n\nParagraph.").unwrap(); let result = convert_file(file.to_string_lossy().to_string()); fs::remove_dir_all(&dir).unwrap(); assert!(result.is_ok()); assert!(result.unwrap().contains("

")); } #[test] fn convert_file_with_table() { let dir = tmpdir("pena_convert_table"); fs::create_dir_all(&dir).unwrap(); let file = dir.join("table.md"); fs::write(&file, "| A | B |\n|---|---|\n| 1 | 2 |").unwrap(); let result = convert_file(file.to_string_lossy().to_string()); fs::remove_dir_all(&dir).unwrap(); assert!(result.is_ok()); assert!(result.unwrap().contains("")); } #[test] fn convert_file_not_found() { let result = convert_file("/nonexistent/path/does/not/exist.md".to_string()); assert!(result.is_err()); } #[test] fn collect_md_flat() { let dir = tmpdir("pena_collect_flat"); fs::create_dir_all(&dir).unwrap(); fs::write(dir.join("a.md"), "").unwrap(); fs::write(dir.join("b.md"), "").unwrap(); fs::write(dir.join("c.txt"), "").unwrap(); let mut result = Vec::new(); collect_md_files(&dir, &mut result).unwrap(); result.sort(); fs::remove_dir_all(&dir).unwrap(); assert_eq!(result.len(), 2); assert!(result[0].ends_with("a.md")); assert!(result[1].ends_with("b.md")); } #[test] fn collect_md_recursive() { let dir = tmpdir("pena_collect_recursive"); let sub = dir.join("docs"); fs::create_dir_all(&sub).unwrap(); fs::write(dir.join("Home.md"), "").unwrap(); fs::write(sub.join("Page.md"), "").unwrap(); let mut result = Vec::new(); collect_md_files(&dir, &mut result).unwrap(); fs::remove_dir_all(&dir).unwrap(); assert_eq!(result.len(), 2); } #[test] fn collect_md_ignores_hidden_dir() { let dir = tmpdir("pena_collect_hidden_dir"); let hidden = dir.join(".hidden"); fs::create_dir_all(&hidden).unwrap(); fs::write(dir.join("visible.md"), "").unwrap(); fs::write(hidden.join("secret.md"), "").unwrap(); let mut result = Vec::new(); collect_md_files(&dir, &mut result).unwrap(); fs::remove_dir_all(&dir).unwrap(); assert_eq!(result.len(), 1); assert!(result[0].ends_with("visible.md")); } #[test] fn collect_md_ignores_hidden_file() { let dir = tmpdir("pena_collect_hidden_file"); fs::create_dir_all(&dir).unwrap(); fs::write(dir.join("visible.md"), "").unwrap(); fs::write(dir.join(".hidden.md"), "").unwrap(); let mut result = Vec::new(); collect_md_files(&dir, &mut result).unwrap(); fs::remove_dir_all(&dir).unwrap(); assert_eq!(result.len(), 1); assert!(result[0].ends_with("visible.md")); } #[test] fn collect_md_nonexistent_dir() { let mut result = Vec::new(); let err = collect_md_files(Path::new("/nonexistent/path/for/pena"), &mut result); assert!(err.is_err()); } #[test] fn list_md_files_sorted() { let dir = tmpdir("pena_list_sorted"); fs::create_dir_all(&dir).unwrap(); fs::write(dir.join("z.md"), "").unwrap(); fs::write(dir.join("a.md"), "").unwrap(); fs::write(dir.join("m.md"), "").unwrap(); let result = list_md_files(dir.to_string_lossy().to_string()).unwrap(); fs::remove_dir_all(&dir).unwrap(); assert_eq!(result.len(), 3); assert!(result[0] < result[1] && result[1] < result[2]); } #[test] fn list_md_files_not_a_dir() { let result = list_md_files("/nonexistent/does/not/exist".to_string()); assert!(result.is_err()); } }