feat: multiple-CSV import

This commit is contained in:
Edward Langley
2026-04-02 16:21:45 -07:00
parent be277f43c2
commit dd728ccac8
3 changed files with 108 additions and 27 deletions

View File

@ -29,7 +29,7 @@ trait Runnable {
struct CmdLineArgs {
file_path: Option<PathBuf>,
import_path: Option<PathBuf>,
import_paths: Vec<PathBuf>,
}
impl Runnable for CmdLineArgs {
@ -38,36 +38,55 @@ impl Runnable for CmdLineArgs {
let model = get_initial_model(&self.file_path)?;
// Pre-TUI import: parse JSON or CSV and open wizard
let import_value = self.import_path.and_then(get_import_data);
let import_value = if self.import_paths.is_empty() {
None
} else {
get_import_data(&self.import_paths)
};
run_tui(model, self.file_path, import_value)
}
}
fn get_import_data(path: PathBuf) -> Option<Value> {
match std::fs::read_to_string(&path) {
Err(e) => {
eprintln!("Cannot read '{}': {e}", path.display());
None
fn get_import_data(paths: &[PathBuf]) -> Option<Value> {
let all_csv = paths.iter().all(|p| csv_path_p(p));
if paths.len() > 1 {
if !all_csv {
eprintln!("Multi-file import only supports CSV files");
return None;
}
Ok(content) => {
if csv_path_p(&path) {
// Parse CSV and wrap as JSON array
match crate::import::csv_parser::parse_csv(&path) {
Ok(records) => Some(serde_json::Value::Array(records)),
Err(e) => {
eprintln!("CSV parse error: {e}");
None
match crate::import::csv_parser::merge_csvs(paths) {
Ok(records) => Some(Value::Array(records)),
Err(e) => {
eprintln!("CSV merge error: {e}");
None
}
}
} else {
let path = &paths[0];
match std::fs::read_to_string(path) {
Err(e) => {
eprintln!("Cannot read '{}': {e}", path.display());
None
}
Ok(content) => {
if csv_path_p(path) {
match crate::import::csv_parser::parse_csv(path) {
Ok(records) => Some(Value::Array(records)),
Err(e) => {
eprintln!("CSV parse error: {e}");
None
}
}
}
} else {
// Parse JSON
match serde_json::from_str::<serde_json::Value>(&content) {
Err(e) => {
eprintln!("JSON parse error: {e}");
None
} else {
match serde_json::from_str::<Value>(&content) {
Err(e) => {
eprintln!("JSON parse error: {e}");
None
}
Ok(json) => Some(json),
}
Ok(json) => Some(json),
}
}
}
@ -127,7 +146,8 @@ impl Runnable for HelpArgs {
println!("improvise — multi-dimensional data modeling TUI\n");
println!("USAGE:");
println!(" improvise [file.improv] Open or create a model");
println!(" improvise --import data.json Import JSON (or CSV) then open TUI");
println!(" improvise --import data.json Import JSON or CSV then open TUI");
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!("\nTUI KEYS (vim-style):");
@ -154,7 +174,7 @@ fn parse_args(args: Vec<String>) -> Box<dyn Runnable> {
let mut file_path: Option<PathBuf> = None;
let mut headless_cmds: Vec<String> = Vec::new();
let mut headless_script: Option<PathBuf> = None;
let mut import_path: Option<PathBuf> = None;
let mut import_paths: Vec<PathBuf> = Vec::new();
let mut i = 1;
while i < args.len() {
@ -171,7 +191,11 @@ fn parse_args(args: Vec<String>) -> Box<dyn Runnable> {
}
"--import" => {
i += 1;
import_path = args.get(i).map(PathBuf::from);
while i < args.len() && !args[i].starts_with('-') {
import_paths.push(PathBuf::from(&args[i]));
i += 1;
}
continue; // skip the i += 1 at the bottom
}
"--help" | "-h" => {
return Box::new(HelpArgs);
@ -193,7 +217,7 @@ fn parse_args(args: Vec<String>) -> Box<dyn Runnable> {
} else {
Box::new(CmdLineArgs {
file_path,
import_path,
import_paths,
})
}
}