use std::path::{Path, PathBuf};
use dbmd_core::parser::{extract_sections, Section};
use crate::cli::OutlineArgs;
use crate::context::Context;
use crate::error::{CliError, CliResult, ExitCode};
struct Outline {
file: PathBuf,
sections: Vec<Section>,
}
pub fn run(ctx: &Context, args: &OutlineArgs) -> CliResult {
let dir = Path::new(&args.dir);
let given = Path::new(&args.file);
let abs = if given.is_absolute() {
given.to_path_buf()
} else {
dir.join(given)
};
let display = abs.strip_prefix(dir).unwrap_or(given).to_path_buf();
let text = std::fs::read_to_string(&abs).map_err(|e| {
CliError::new(ExitCode::Runtime, "IO_ERROR", e.to_string())
.with_hint(format!("could not read `{}`", args.file))
})?;
let body = strip_frontmatter(&text);
let sections = extract_sections(body);
let outline = Outline {
file: display,
sections,
};
if ctx.json {
emit_json(&outline);
} else {
emit_text(&outline);
}
Ok(())
}
fn strip_frontmatter(text: &str) -> &str {
let after_open = match text.strip_prefix("---\n") {
Some(rest) => rest,
None => match text.strip_prefix("---\r\n") {
Some(rest) => rest,
None => return text,
},
};
let mut search_from = 0usize;
while let Some(rel_idx) = after_open[search_from..].find("---") {
let idx = search_from + rel_idx;
let at_line_start = idx == 0 || after_open.as_bytes()[idx - 1] == b'\n';
let after = &after_open[idx + 3..];
let line_ends = after.is_empty()
|| after.starts_with('\n')
|| after.starts_with("\r\n")
|| after.starts_with('\r');
if at_line_start && line_ends {
if let Some(stripped) = after.strip_prefix("\r\n") {
return stripped;
}
if let Some(stripped) = after.strip_prefix('\n') {
return stripped;
}
if let Some(stripped) = after.strip_prefix('\r') {
return stripped;
}
return after; }
search_from = idx + 3;
}
text
}
fn emit_text(outline: &Outline) {
for section in &outline.sections {
let indent = " ".repeat(section.level.saturating_sub(2) as usize);
println!("{indent}{}", section.heading);
}
}
fn emit_json(outline: &Outline) {
let sections: Vec<serde_json::Value> = outline
.sections
.iter()
.map(|s| {
serde_json::json!({
"heading": s.heading,
"level": s.level,
"line": s.line,
})
})
.collect();
let out = serde_json::json!({
"file": path_str(&outline.file),
"sections": sections,
});
println!(
"{}",
serde_json::to_string(&out).expect("serialize outline")
);
}
fn path_str(p: &Path) -> String {
p.to_string_lossy().replace('\\', "/")
}