use std::collections::BTreeMap;
use std::io;
use std::path::{Path, PathBuf};
use super::bundle::{is_tracked_rel_path, Bundle, BUNDLE_VERSION};
pub(crate) fn run_export(out: Option<PathBuf>) -> i32 {
let root = crate::paths::config_dir();
let bundle = match collect_bundle(&root) {
Ok(bundle) => bundle,
Err(err) => {
eprintln!("export failed: cannot read {}: {err}", root.display());
return 1;
}
};
let count = bundle.files.len();
let json = to_json(&bundle);
let Some(path) = out else {
println!("{json}");
return 0;
};
match crate::utils::atomic::atomic_write(&path, json.as_bytes()) {
Ok(()) => {
println!("exported {count} tracked file(s) to {}", path.display());
0
}
Err(err) => {
eprintln!("export failed: cannot write {}: {err}", path.display());
1
}
}
}
fn to_json(bundle: &Bundle) -> String {
serde_json::to_string_pretty(bundle).unwrap_or_default()
}
fn collect_bundle(root: &Path) -> io::Result<Bundle> {
let mut files = BTreeMap::new();
collect_dir(root, Path::new(""), &mut files)?;
Ok(Bundle {
version: BUNDLE_VERSION,
files,
})
}
fn collect_dir(dir: &Path, rel: &Path, files: &mut BTreeMap<String, String>) -> io::Result<()> {
for entry in std::fs::read_dir(dir)? {
let entry = entry?;
let child_rel = rel.join(entry.file_name());
let path = entry.path();
if path.is_dir() {
collect_dir(&path, &child_rel, files)?;
} else if is_tracked_rel_path(&child_rel) {
insert_file(&path, &child_rel, files);
}
}
Ok(())
}
fn insert_file(path: &Path, rel: &Path, files: &mut BTreeMap<String, String>) {
match std::fs::read_to_string(path) {
Ok(content) => {
files.insert(rel_key(rel), content);
}
Err(err) => eprintln!("skipping {}: {err}", path.display()),
}
}
fn rel_key(rel: &Path) -> String {
let parts: Vec<&str> = rel
.components()
.filter_map(|component| component.as_os_str().to_str())
.collect();
parts.join("/")
}
#[cfg(test)]
#[path = "export_tests.rs"]
mod export_tests;