mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-25 02:29:31 -06:00
Add a shared CLI/MCP batch runner with bounded discovery, retained relative paths and per-file processing in isolated engine sessions. Publish complete outputs without overwriting existing names, and return structured errors with an optional stop-on-error policy. Cover recursive jobs, overlapping inputs, concurrent output collisions, root confinement and partial-failure reporting in automation and CLI tests. Co-authored-by: LabDennis <341377186+LabDennis@users.noreply.github.com>
62 lines
2.8 KiB
Rust
62 lines
2.8 KiB
Rust
//! Friendly flags for the same folder-job tool exposed by `run` and MCP.
|
|
|
|
use serde_json::json;
|
|
|
|
use crate::{CliError, OptionSpec, automation, flag, print_output, validate_options};
|
|
|
|
const OPTIONS: &[OptionSpec] = &[
|
|
OptionSpec { name: "--root", takes_value: true },
|
|
OptionSpec { name: "--out-dir", takes_value: true },
|
|
OptionSpec { name: "--steps", takes_value: true },
|
|
OptionSpec { name: "--recursive", takes_value: false },
|
|
OptionSpec { name: "--stop-on-error", takes_value: false },
|
|
OptionSpec { name: "--ppi", takes_value: true },
|
|
OptionSpec { name: "--quality", takes_value: true },
|
|
OptionSpec { name: "--dpi", takes_value: true },
|
|
OptionSpec { name: "--language", takes_value: true },
|
|
OptionSpec { name: "--ocr-all-pages", takes_value: false },
|
|
];
|
|
|
|
pub(super) fn run(args: &[String]) -> Result<(), CliError> {
|
|
validate_options("batch", args, OPTIONS)?;
|
|
let mut paths = Vec::new();
|
|
let mut seen = std::collections::HashSet::new();
|
|
let mut i = 0;
|
|
while let Some(arg) = args.get(i) {
|
|
if let Some(spec) = OPTIONS.iter().find(|s| s.name == arg) {
|
|
if !seen.insert(spec.name) {
|
|
return Err(format!("batch: {} may only be supplied once", spec.name).into());
|
|
}
|
|
if spec.takes_value {
|
|
if args.get(i + 1).is_none_or(|v| v.is_empty() || v.starts_with("--")) {
|
|
return Err(format!("batch: {} needs a value", spec.name).into());
|
|
}
|
|
i += 1;
|
|
}
|
|
} else {
|
|
paths.push(arg);
|
|
}
|
|
i += 1;
|
|
}
|
|
let steps: Vec<_> = flag(args, "--steps").ok_or("batch: missing --steps (convert, ocr, optimize)")?.split(',').collect();
|
|
let out = flag(args, "--out-dir").ok_or("batch: missing --out-dir")?;
|
|
let mut params =
|
|
json!({"paths":paths,"steps":steps,"out_dir":out,"recursive":seen.contains("--recursive"),"stop_on_error":seen.contains("--stop-on-error")});
|
|
for (option, key) in [("--ppi", "ppi"), ("--quality", "quality"), ("--dpi", "dpi")] {
|
|
if let Some(v) = flag(args, option) {
|
|
params[key] = serde_json::from_str(v).map_err(|_| format!("batch: {option} needs a number"))?;
|
|
}
|
|
}
|
|
if let Some(lang) = flag(args, "--language") {
|
|
params["language"] = json!(lang);
|
|
}
|
|
if seen.contains("--ocr-all-pages") {
|
|
params["skip_text_pages"] = json!(false);
|
|
}
|
|
let mut auto = automation(args)?;
|
|
let content = auto.call("doc_batch", ¶ms).map_err(|e| e.to_string())?;
|
|
let failed = content.iter().any(|c| matches!(c, pdfcraft_automation::Content::Json(v) if v["failed"].as_u64().is_some_and(|n| n > 0)));
|
|
print_output(&auto, content, None)?;
|
|
if failed { Err("batch: one or more files failed; see the JSON report".into()) } else { Ok(()) }
|
|
}
|