Add CSV import functionality
- Use csv crate for robust CSV parsing (handles quoted fields, empty values, \r\n) - Extend --import command to auto-detect format by file extension (.csv or .json) - Reuse existing ImportPipeline and analyzer for field type detection - Categories detected automatically (string fields), measures for numeric fields - Updated help text and welcome screen to mention CSV support All 201 tests pass.
This commit is contained in:
40
src/main.rs
40
src/main.rs
@ -54,26 +54,40 @@ impl Runnable for CmdLineArgs {
|
||||
// Load or create model
|
||||
let model = get_initial_model(&self.file_path)?;
|
||||
|
||||
// Pre-TUI import: parse JSON and open wizard
|
||||
let import_json = if let Some(ref path) = self.import_path {
|
||||
// Pre-TUI import: parse JSON or CSV and open wizard
|
||||
let import_value = if let Some(ref path) = self.import_path {
|
||||
match std::fs::read_to_string(path) {
|
||||
Err(e) => {
|
||||
eprintln!("Cannot read '{}': {e}", path.display());
|
||||
return Ok(());
|
||||
}
|
||||
Ok(content) => match serde_json::from_str::<serde_json::Value>(&content) {
|
||||
Err(e) => {
|
||||
eprintln!("JSON parse error: {e}");
|
||||
return Ok(());
|
||||
Ok(content) => {
|
||||
if path.to_string_lossy().ends_with(".csv") {
|
||||
// Parse CSV and wrap as JSON array
|
||||
match crate::import::csv_parser::parse_csv(&path.to_string_lossy()) {
|
||||
Ok(records) => Some(serde_json::Value::Array(records)),
|
||||
Err(e) => {
|
||||
eprintln!("CSV parse error: {e}");
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Parse JSON
|
||||
match serde_json::from_str::<serde_json::Value>(&content) {
|
||||
Err(e) => {
|
||||
eprintln!("JSON parse error: {e}");
|
||||
return Ok(());
|
||||
}
|
||||
Ok(json) => Some(json),
|
||||
}
|
||||
}
|
||||
Ok(json) => Some(json),
|
||||
},
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
run_tui(model, self.file_path, import_json)
|
||||
run_tui(model, self.file_path, import_value)
|
||||
}
|
||||
}
|
||||
|
||||
@ -130,7 +144,7 @@ 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 then open TUI");
|
||||
println!(" improvise --import data.json Import JSON (or CSV) then open TUI");
|
||||
println!(" improvise --cmd '{{...}}' Run a JSON command (headless, repeatable)");
|
||||
println!(" improvise --script cmds.jsonl Run commands from file (headless)");
|
||||
println!("\nTUI KEYS (vim-style):");
|
||||
@ -246,13 +260,13 @@ impl<'a> Drop for TuiContext<'a> {
|
||||
fn run_tui(
|
||||
model: Model,
|
||||
file_path: Option<PathBuf>,
|
||||
import_json: Option<serde_json::Value>,
|
||||
import_value: Option<serde_json::Value>,
|
||||
) -> Result<()> {
|
||||
let mut stdout = io::stdout();
|
||||
let mut tui_context = TuiContext::enter(&mut stdout)?;
|
||||
let mut app = App::new(model, file_path);
|
||||
|
||||
if let Some(json) = import_json {
|
||||
if let Some(json) = import_value {
|
||||
app.start_import_wizard(json);
|
||||
}
|
||||
|
||||
@ -518,7 +532,7 @@ fn draw_welcome(f: &mut Frame, area: Rect) {
|
||||
),
|
||||
("", Style::default()),
|
||||
(
|
||||
":import <file.json> Import a JSON file",
|
||||
":import <file> Import JSON or CSV file",
|
||||
Style::default().fg(Color::Cyan),
|
||||
),
|
||||
(
|
||||
|
||||
Reference in New Issue
Block a user