use regex::Regex;
use std::fs;
use std::path::Path;
pub fn process_files(source_file: &Path, source_root: &Path, dest_root: &Path) -> Result<(), Box<dyn std::error::Error>> {
let content = fs::read_to_string(source_file)?;
println!(" 📖 Read {} bytes from {:?}", content.len(), source_file);
let converted = convert_content(&content);
println!(" 🔄 Converted content: {} bytes", converted.len());
let relative_path = source_file.strip_prefix(source_root)?;
println!(" 📍 Relative path: {:?}", relative_path);
let mut dest_path = dest_root.join(relative_path);
dest_path.set_extension("qmd");
println!(" 📝 Destination path: {:?}", dest_path);
if let Some(parent) = dest_path.parent() {
fs::create_dir_all(parent)?;
println!(" 📁 Created parent directory: {:?}", parent);
}
fs::write(&dest_path, converted)?;
println!(" ✅ Written to: {:?}", dest_path);
copy_img_folder(source_file, &dest_path)?;
Ok(())
}
pub fn convert_content(content: &str) -> String {
let mut result = String::new();
let mut in_frontmatter = false;
let mut frontmatter_lines = Vec::new();
for line in content.lines() {
if line == "---" {
if !in_frontmatter {
in_frontmatter = true;
continue;
} else {
result.push_str("---\n");
result.push_str(&convert_frontmatter(&frontmatter_lines));
frontmatter_lines.clear();
continue;
}
}
if in_frontmatter {
frontmatter_lines.push(line);
} else {
let converted_line = convert_admonitions(line);
result.push_str(&converted_line);
result.push('\n');
}
}
result
}
pub fn convert_frontmatter(lines: &[&str]) -> String {
let mut result = String::new();
for line in lines {
if line.trim().starts_with("sidebar_position") {
let value = line.split(':').nth(1).unwrap_or("").trim();
result.push_str(&format!("order: {}\n", value));
} else {
result.push_str(line);
result.push('\n');
}
}
result
}
pub fn convert_admonitions(line: &str) -> String {
let admonition_start = Regex::new(r"^:::(\w)+(.*)$").unwrap();
let admonition_end = Regex::new(r"^:::$").unwrap();
if let Some(caps) = admonition_start.captures(line) {
let admonition_type = &caps[1];
let title = caps.get(2).map(|m| m.as_str().trim()).unwrap_or("");
let quarto_type = match admonition_type.to_lowercase().as_str() {
"note" => "note",
"tip" => "tip",
"info" => "note",
"caution" => "caution",
"warning" => "warning",
"danger" => "important",
_ => admonition_type,
};
if title.is_empty() {
format!(":::: {{{}}}", quarto_type)
} else {
format!(":::: {{.callout-{}}}\n## {}", quarto_type, title)
}
}
else if admonition_end.is_match(line) {
"::::".to_string()
}
else {
line.to_string()
}
}
pub fn copy_img_folder(source_file: &Path, dest_file: &Path) -> Result<(), std::io::Error> {
if let Some(source_parent) = source_file.parent() {
let img_folder = source_parent.join("img");
if img_folder.exists() && img_folder.is_dir() {
if let Some(dest_parent) = dest_file.parent() {
let dest_img = dest_parent.join("img");
fs::create_dir_all(&dest_img)?;
for entry in fs::read_dir(&img_folder)? {
let entry = entry?;
let file_name = entry.file_name();
let dest_file_path = dest_img.join(&file_name);
fs::copy(entry.path(), dest_file_path)?;
}
}
}
}
Ok(())
}