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