diff --git a/src/main.rs b/src/main.rs index 3203217..8ad6f4b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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,42 +94,69 @@ fn get_import_data(paths: &[PathBuf]) -> Option { } } +enum HeadlessMode { + Commands(Vec), + Script(Option), +} struct HeadlessArgs { file_path: Option, - commands: Vec, - script: Option, + mode: HeadlessMode, } impl Runnable for HeadlessArgs { fn run(self: Box) -> Result<()> { let mut model = get_initial_model(&self.file_path)?; - let mut cmds: Vec = self.commands; - if let Some(script_path) = self.script { - let content = std::fs::read_to_string(&script_path)?; - for line in content.lines() { - let trimmed = line.trim(); - 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}")); - println!("{}", serde_json::to_string(&r)?); - exit_code = 1; - continue; - } - }; - let result = command::dispatch(&mut model, &parsed); - if !result.ok { - exit_code = 1; + 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 = Vec::new(); + for line in content.lines() { + let trimmed = line.trim(); + if !trimmed.is_empty() + && !trimmed.starts_with("//") + && !trimmed.starts_with('#') + { + cmds.push(trimmed.to_string()); + } + } + 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)?); + } + }; + } + 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)?); + } } - println!("{}", serde_json::to_string(&result)?); } if let Some(path) = self.file_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) -> Box { 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 {