use std::path::Path;
use dbmd_core::extract::{self, ExtractError};
use crate::cli::ExtractArgs;
use crate::context::Context;
use crate::error::{CliError, CliResult, ExitCode};
pub fn run(ctx: &Context, args: &ExtractArgs) -> CliResult {
let path = Path::new(&args.file);
let extracted = extract::extract(path).map_err(map_extract_error)?;
if ctx.json {
let json = serde_json::to_string_pretty(&extracted)
.map_err(|e| CliError::runtime(format!("failed to encode JSON: {e}")))?;
emit(&args.out, &json, true)
} else {
emit(&args.out, &extracted.text, false)
}
}
fn emit(out: &Option<String>, content: &str, add_trailing_newline: bool) -> CliResult {
match out {
Some(path) => {
let mut body = content.to_string();
if add_trailing_newline && !body.ends_with('\n') {
body.push('\n');
}
std::fs::write(path, body).map_err(|e| {
CliError::new(
ExitCode::Runtime,
"IO_ERROR",
format!("failed to write {path}: {e}"),
)
})?;
Ok(())
}
None => {
use std::io::Write;
let stdout = std::io::stdout();
let mut lock = stdout.lock();
let res = if add_trailing_newline {
writeln!(lock, "{content}")
} else {
write!(lock, "{content}")
};
res.map_err(|e| {
CliError::new(ExitCode::Runtime, "IO_ERROR", format!("write failed: {e}"))
})
}
}
}
fn map_extract_error(err: ExtractError) -> CliError {
match &err {
ExtractError::UnsupportedFormat(_) => CliError::new(
ExitCode::Runtime,
err.code(),
err.to_string(),
)
.with_hint(
"supported document types: .pdf, .docx, .xlsx, .epub, .html (detected by extension)",
),
ExtractError::Encrypted(_) => CliError::new(ExitCode::Runtime, err.code(), err.to_string())
.with_hint("the document is password-protected; dbmd extract cannot open it"),
ExtractError::Parse { .. } => CliError::new(ExitCode::Runtime, err.code(), err.to_string()),
ExtractError::Io(_) => CliError::new(ExitCode::Runtime, "IO_ERROR", err.to_string()),
}
}