feat: adjust arg processing so script+command modes are exclusive

This commit is contained in:
Edward Langley
2026-04-03 20:29:08 -07:00
parent 9f5b7f602a
commit ac0c538c98

View File

@ -13,6 +13,7 @@ use std::path::PathBuf;
use anyhow::{Context, Result};
use command::CommandResult;
use draw::run_tui;
use model::Model;
use serde_json::Value;
@ -93,32 +94,38 @@ fn get_import_data(paths: &[PathBuf]) -> Option<Value> {
}
}
enum HeadlessMode {
Commands(Vec<String>),
Script(Option<PathBuf>),
}
struct HeadlessArgs {
file_path: Option<PathBuf>,
commands: Vec<String>,
script: Option<PathBuf>,
mode: HeadlessMode,
}
impl Runnable for HeadlessArgs {
fn run(self: Box<Self>) -> Result<()> {
let mut model = get_initial_model(&self.file_path)?;
let mut cmds: Vec<String> = self.commands;
if let Some(script_path) = self.script {
let mut exit_code = 0;
match self.mode {
HeadlessMode::Script(script) => {
if let Some(script_path) = script {
let content = std::fs::read_to_string(&script_path)?;
let mut cmds: Vec<String> = Vec::new();
for line in content.lines() {
let trimmed = line.trim();
if !trimmed.is_empty() && !trimmed.starts_with("//") && !trimmed.starts_with('#') {
if !trimmed.is_empty()
&& !trimmed.starts_with("//")
&& !trimmed.starts_with('#')
{
cmds.push(trimmed.to_string());
}
}
}
let mut exit_code = 0;
for raw_cmd in &cmds {
let parsed: command::Command = match serde_json::from_str(raw_cmd) {
Ok(c) => c,
Err(e) => {
let r = command::CommandResult::err(format!("JSON parse error: {e}"));
let r = CommandResult::err(format!("JSON parse error: {e}"));
println!("{}", serde_json::to_string(&r)?);
exit_code = 1;
continue;
@ -130,6 +137,27 @@ impl Runnable for HeadlessArgs {
}
println!("{}", serde_json::to_string(&result)?);
}
};
}
HeadlessMode::Commands(cmds) => {
for raw_cmd in &cmds {
let parsed: command::Command = match serde_json::from_str(raw_cmd) {
Ok(c) => c,
Err(e) => {
let r = CommandResult::err(format!("JSON parse error: {e}"));
println!("{}", serde_json::to_string(&r)?);
exit_code = 1;
continue;
}
};
let result = command::dispatch(&mut model, &parsed);
if !result.ok {
exit_code = 1;
}
println!("{}", serde_json::to_string(&result)?);
}
}
}
if let Some(path) = self.file_path {
persistence::save(&model, &path)?;
@ -150,8 +178,8 @@ impl Runnable for HelpArgs {
println!(
" improvise --import a.csv b.csv Import multiple CSVs (filenames become a category)"
);
println!(" improvise --cmd '{{...}}' Run a JSON command (headless, repeatable)");
println!(" improvise --script cmds.jsonl Run commands from file (headless)");
println!(" improvise --cmd '{{...}}' Run JSON command(s) headless (repeatable)");
println!(" improvise --script cmds.jsonl Run commands from file headless (exclusive with --cmd)");
println!("\nTUI KEYS (vim-style):");
println!(" : Command mode (:q :w :import :add-cat :formula …)");
println!(" hjkl / ↑↓←→ Navigate grid");
@ -210,11 +238,17 @@ fn parse_args(args: Vec<String>) -> Box<dyn Runnable> {
i += 1;
}
if !headless_cmds.is_empty() || headless_script.is_some() {
if !headless_cmds.is_empty() && headless_script.is_some() {
eprintln!("Error: --cmd and --script cannot be used together");
std::process::exit(1);
} else if !headless_cmds.is_empty() || headless_script.is_some() {
Box::new(HeadlessArgs {
file_path,
commands: headless_cmds,
script: headless_script,
mode: if headless_script.is_some() {
HeadlessMode::Script(headless_script)
} else {
HeadlessMode::Commands(headless_cmds)
},
})
} else {
Box::new(CmdLineArgs {