Initial implementation of Improvise TUI
Multi-dimensional data modeling terminal application with:
- Core data model: categories, items, groups, sparse cell store
- Formula system: recursive-descent parser, named formulas, WHERE clauses
- View system: Row/Column/Page axes, tile-based pivot, page slicing
- JSON import wizard (interactive TUI + headless auto-mode)
- Command layer: all mutations via typed Command enum for headless replay
- TUI: Ratatui grid, tile bar, formula/category/view panels, help overlay
- Persistence: .improv (JSON), .improv.gz (gzip), CSV export, autosave
- Static binary via x86_64-unknown-linux-musl + nix flake devShell
- Headless mode: --cmd '{"op":"..."}' and --script file.jsonl
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
99
src/command/types.rs
Normal file
99
src/command/types.rs
Normal file
@ -0,0 +1,99 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// All commands that can mutate a Model.
|
||||
///
|
||||
/// Serialized as `{"op": "<variant>", ...rest}` where `rest` contains
|
||||
/// the variant's fields flattened into the same JSON object.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(tag = "op")]
|
||||
pub enum Command {
|
||||
/// Add a category (dimension).
|
||||
AddCategory { name: String },
|
||||
|
||||
/// Add an item to a category.
|
||||
AddItem { category: String, item: String },
|
||||
|
||||
/// Add an item inside a named group.
|
||||
AddItemInGroup { category: String, item: String, group: String },
|
||||
|
||||
/// Set a cell value. `coords` is a list of `[category, item]` pairs.
|
||||
SetCell {
|
||||
coords: Vec<[String; 2]>,
|
||||
#[serde(flatten)]
|
||||
value: CellValueArg,
|
||||
},
|
||||
|
||||
/// Clear a cell.
|
||||
ClearCell { coords: Vec<[String; 2]> },
|
||||
|
||||
/// Add or replace a formula.
|
||||
/// `raw` is the full formula string, e.g. "Profit = Revenue - Cost".
|
||||
/// `target_category` names the category that owns the formula target.
|
||||
AddFormula { raw: String, target_category: String },
|
||||
|
||||
/// Remove a formula by its target name.
|
||||
RemoveFormula { target: String },
|
||||
|
||||
/// Create a new view.
|
||||
CreateView { name: String },
|
||||
|
||||
/// Delete a view.
|
||||
DeleteView { name: String },
|
||||
|
||||
/// Switch the active view.
|
||||
SwitchView { name: String },
|
||||
|
||||
/// Set the axis of a category in the active view.
|
||||
/// axis: "row" | "column" | "page"
|
||||
SetAxis { category: String, axis: String },
|
||||
|
||||
/// Set the page-axis selection for a category.
|
||||
SetPageSelection { category: String, item: String },
|
||||
|
||||
/// Toggle collapse of a group in the active view.
|
||||
ToggleGroup { category: String, group: String },
|
||||
|
||||
/// Save the model to a file path.
|
||||
Save { path: String },
|
||||
|
||||
/// Load a model from a file path (replaces current model).
|
||||
Load { path: String },
|
||||
|
||||
/// Export the active view to CSV.
|
||||
ExportCsv { path: String },
|
||||
|
||||
/// Import a JSON file via the analyzer (non-interactive, uses auto-detected proposals).
|
||||
ImportJson {
|
||||
path: String,
|
||||
model_name: Option<String>,
|
||||
/// Dot-path to the records array (empty = root)
|
||||
array_path: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
/// Inline value for SetCell
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum CellValueArg {
|
||||
Number { number: f64 },
|
||||
Text { text: String },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CommandResult {
|
||||
pub ok: bool,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub message: Option<String>,
|
||||
}
|
||||
|
||||
impl CommandResult {
|
||||
pub fn ok() -> Self {
|
||||
Self { ok: true, message: None }
|
||||
}
|
||||
pub fn ok_msg(msg: impl Into<String>) -> Self {
|
||||
Self { ok: true, message: Some(msg.into()) }
|
||||
}
|
||||
pub fn err(msg: impl Into<String>) -> Self {
|
||||
Self { ok: false, message: Some(msg.into()) }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user