refactor: split cmd.rs
This commit is contained in:
359
src/command/cmd/effect_cmds.rs
Normal file
359
src/command/cmd/effect_cmds.rs
Normal file
@ -0,0 +1,359 @@
|
||||
use crate::model::cell::CellValue;
|
||||
use crate::ui::effect::{self, Effect};
|
||||
use crate::view::Axis;
|
||||
|
||||
use super::core::{require_args, Cmd, CmdContext};
|
||||
|
||||
// ── Parseable model-mutation commands ────────────────────────────────────────
|
||||
// These are thin Cmd wrappers around effects, constructible from string args.
|
||||
// They share the same execution path as keymap-dispatched commands.
|
||||
|
||||
macro_rules! effect_cmd {
|
||||
($name:ident, $cmd_name:expr, $parse:expr, $exec:expr) => {
|
||||
#[derive(Debug)]
|
||||
pub struct $name(pub Vec<String>);
|
||||
impl Cmd for $name {
|
||||
fn name(&self) -> &'static str {
|
||||
$cmd_name
|
||||
}
|
||||
fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
|
||||
let args = &self.0;
|
||||
#[allow(clippy::redundant_closure_call)]
|
||||
($exec)(args, ctx)
|
||||
}
|
||||
}
|
||||
impl $name {
|
||||
pub fn parse(args: &[String]) -> Result<Box<dyn Cmd>, String> {
|
||||
#[allow(clippy::redundant_closure_call)]
|
||||
($parse)(args)?;
|
||||
Ok(Box::new($name(args.to_vec())))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
effect_cmd!(
|
||||
AddCategoryCmd,
|
||||
"add-category",
|
||||
|args: &[String]| require_args("add-category", args, 1),
|
||||
|args: &Vec<String>, _ctx: &CmdContext| -> Vec<Box<dyn Effect>> {
|
||||
vec![Box::new(effect::AddCategory(args[0].clone()))]
|
||||
}
|
||||
);
|
||||
|
||||
effect_cmd!(
|
||||
AddItemCmd,
|
||||
"add-item",
|
||||
|args: &[String]| require_args("add-item", args, 2),
|
||||
|args: &Vec<String>, _ctx: &CmdContext| -> Vec<Box<dyn Effect>> {
|
||||
vec![Box::new(effect::AddItem {
|
||||
category: args[0].clone(),
|
||||
item: args[1].clone(),
|
||||
})]
|
||||
}
|
||||
);
|
||||
|
||||
effect_cmd!(
|
||||
AddItemsCmd,
|
||||
"add-items",
|
||||
|args: &[String]| {
|
||||
if args.len() < 2 {
|
||||
Err("add-items requires a category and at least one item".to_string())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
|args: &Vec<String>, _ctx: &CmdContext| -> Vec<Box<dyn Effect>> {
|
||||
let category = &args[0];
|
||||
args[1..]
|
||||
.iter()
|
||||
.map(|item| -> Box<dyn Effect> {
|
||||
Box::new(effect::AddItem {
|
||||
category: category.clone(),
|
||||
item: item.clone(),
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
);
|
||||
|
||||
effect_cmd!(
|
||||
AddItemInGroupCmd,
|
||||
"add-item-in-group",
|
||||
|args: &[String]| require_args("add-item-in-group", args, 3),
|
||||
|args: &Vec<String>, _ctx: &CmdContext| -> Vec<Box<dyn Effect>> {
|
||||
vec![Box::new(effect::AddItemInGroup {
|
||||
category: args[0].clone(),
|
||||
item: args[1].clone(),
|
||||
group: args[2].clone(),
|
||||
})]
|
||||
}
|
||||
);
|
||||
|
||||
effect_cmd!(
|
||||
SetCellCmd,
|
||||
"set-cell",
|
||||
|args: &[String]| {
|
||||
if args.len() < 2 {
|
||||
Err("set-cell requires a value and at least one Cat/Item coordinate".to_string())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
|args: &Vec<String>, _ctx: &CmdContext| -> Vec<Box<dyn Effect>> {
|
||||
let value = if let Ok(n) = args[0].parse::<f64>() {
|
||||
CellValue::Number(n)
|
||||
} else {
|
||||
CellValue::Text(args[0].clone())
|
||||
};
|
||||
let coords: Vec<(String, String)> = args[1..]
|
||||
.iter()
|
||||
.filter_map(|s| {
|
||||
let (cat, item) = s.split_once('/')?;
|
||||
Some((cat.to_string(), item.to_string()))
|
||||
})
|
||||
.collect();
|
||||
let key = crate::model::cell::CellKey::new(coords);
|
||||
vec![Box::new(effect::SetCell(key, value))]
|
||||
}
|
||||
);
|
||||
|
||||
effect_cmd!(
|
||||
AddFormulaCmd,
|
||||
"add-formula",
|
||||
|args: &[String]| require_args("add-formula", args, 2),
|
||||
|args: &Vec<String>, _ctx: &CmdContext| -> Vec<Box<dyn Effect>> {
|
||||
vec![Box::new(effect::AddFormula {
|
||||
target_category: args[0].clone(),
|
||||
raw: args[1].clone(),
|
||||
})]
|
||||
}
|
||||
);
|
||||
|
||||
effect_cmd!(
|
||||
RemoveFormulaCmd,
|
||||
"remove-formula",
|
||||
|args: &[String]| require_args("remove-formula", args, 2),
|
||||
|args: &Vec<String>, _ctx: &CmdContext| -> Vec<Box<dyn Effect>> {
|
||||
vec![Box::new(effect::RemoveFormula {
|
||||
target_category: args[0].clone(),
|
||||
target: args[1].clone(),
|
||||
})]
|
||||
}
|
||||
);
|
||||
|
||||
effect_cmd!(
|
||||
CreateViewCmd,
|
||||
"create-view",
|
||||
|args: &[String]| require_args("create-view", args, 1),
|
||||
|args: &Vec<String>, _ctx: &CmdContext| -> Vec<Box<dyn Effect>> {
|
||||
vec![Box::new(effect::CreateView(args[0].clone()))]
|
||||
}
|
||||
);
|
||||
|
||||
effect_cmd!(
|
||||
DeleteViewCmd,
|
||||
"delete-view",
|
||||
|args: &[String]| require_args("delete-view", args, 1),
|
||||
|args: &Vec<String>, _ctx: &CmdContext| -> Vec<Box<dyn Effect>> {
|
||||
vec![Box::new(effect::DeleteView(args[0].clone()))]
|
||||
}
|
||||
);
|
||||
|
||||
effect_cmd!(
|
||||
SwitchViewCmd,
|
||||
"switch-view",
|
||||
|args: &[String]| require_args("switch-view", args, 1),
|
||||
|args: &Vec<String>, _ctx: &CmdContext| -> Vec<Box<dyn Effect>> {
|
||||
vec![Box::new(effect::SwitchView(args[0].clone()))]
|
||||
}
|
||||
);
|
||||
|
||||
effect_cmd!(
|
||||
SetAxisCmd,
|
||||
"set-axis",
|
||||
|args: &[String]| require_args("set-axis", args, 2),
|
||||
|args: &Vec<String>, _ctx: &CmdContext| -> Vec<Box<dyn Effect>> {
|
||||
let axis = match args[1].to_lowercase().as_str() {
|
||||
"row" => Axis::Row,
|
||||
"column" | "col" => Axis::Column,
|
||||
"page" => Axis::Page,
|
||||
"none" => Axis::None,
|
||||
_ => return vec![], // parse step already validated
|
||||
};
|
||||
vec![Box::new(effect::SetAxis {
|
||||
category: args[0].clone(),
|
||||
axis,
|
||||
})]
|
||||
}
|
||||
);
|
||||
|
||||
effect_cmd!(
|
||||
SetPageCmd,
|
||||
"set-page",
|
||||
|args: &[String]| require_args("set-page", args, 2),
|
||||
|args: &Vec<String>, _ctx: &CmdContext| -> Vec<Box<dyn Effect>> {
|
||||
vec![Box::new(effect::SetPageSelection {
|
||||
category: args[0].clone(),
|
||||
item: args[1].clone(),
|
||||
})]
|
||||
}
|
||||
);
|
||||
|
||||
effect_cmd!(
|
||||
ToggleGroupCmd,
|
||||
"toggle-group",
|
||||
|args: &[String]| require_args("toggle-group", args, 2),
|
||||
|args: &Vec<String>, _ctx: &CmdContext| -> Vec<Box<dyn Effect>> {
|
||||
vec![Box::new(effect::ToggleGroup {
|
||||
category: args[0].clone(),
|
||||
group: args[1].clone(),
|
||||
})]
|
||||
}
|
||||
);
|
||||
|
||||
effect_cmd!(
|
||||
HideItemCmd,
|
||||
"hide-item",
|
||||
|args: &[String]| require_args("hide-item", args, 2),
|
||||
|args: &Vec<String>, _ctx: &CmdContext| -> Vec<Box<dyn Effect>> {
|
||||
vec![Box::new(effect::HideItem {
|
||||
category: args[0].clone(),
|
||||
item: args[1].clone(),
|
||||
})]
|
||||
}
|
||||
);
|
||||
|
||||
effect_cmd!(
|
||||
ShowItemCmd,
|
||||
"show-item",
|
||||
|args: &[String]| require_args("show-item", args, 2),
|
||||
|args: &Vec<String>, _ctx: &CmdContext| -> Vec<Box<dyn Effect>> {
|
||||
vec![Box::new(effect::ShowItem {
|
||||
category: args[0].clone(),
|
||||
item: args[1].clone(),
|
||||
})]
|
||||
}
|
||||
);
|
||||
|
||||
effect_cmd!(
|
||||
SaveAsCmd,
|
||||
"save-as",
|
||||
|args: &[String]| require_args("save-as", args, 1),
|
||||
|args: &Vec<String>, _ctx: &CmdContext| -> Vec<Box<dyn Effect>> {
|
||||
vec![Box::new(effect::SaveAs(std::path::PathBuf::from(&args[0])))]
|
||||
}
|
||||
);
|
||||
|
||||
effect_cmd!(
|
||||
SetFormatCmd,
|
||||
"set-format",
|
||||
|args: &[String]| require_args("set-format", args, 1),
|
||||
|args: &Vec<String>, _ctx: &CmdContext| -> Vec<Box<dyn Effect>> {
|
||||
vec![
|
||||
Box::new(effect::SetNumberFormat(args.join(" "))),
|
||||
effect::mark_dirty(),
|
||||
]
|
||||
}
|
||||
);
|
||||
|
||||
effect_cmd!(
|
||||
ImportCmd,
|
||||
"import",
|
||||
|args: &[String]| require_args("import", args, 1),
|
||||
|args: &Vec<String>, _ctx: &CmdContext| -> Vec<Box<dyn Effect>> {
|
||||
vec![Box::new(effect::StartImportWizard(args[0].clone()))]
|
||||
}
|
||||
);
|
||||
|
||||
effect_cmd!(
|
||||
ExportCmd,
|
||||
"export",
|
||||
|_args: &[String]| -> Result<(), String> { Ok(()) },
|
||||
|args: &Vec<String>, _ctx: &CmdContext| -> Vec<Box<dyn Effect>> {
|
||||
let path = args.first().map(|s| s.as_str()).unwrap_or("export.csv");
|
||||
vec![Box::new(effect::ExportCsv(std::path::PathBuf::from(path)))]
|
||||
}
|
||||
);
|
||||
|
||||
effect_cmd!(
|
||||
WriteCmd,
|
||||
"w",
|
||||
|_args: &[String]| -> Result<(), String> { Ok(()) },
|
||||
|args: &Vec<String>, _ctx: &CmdContext| -> Vec<Box<dyn Effect>> {
|
||||
if args.is_empty() {
|
||||
vec![Box::new(effect::Save)]
|
||||
} else {
|
||||
vec![Box::new(effect::SaveAs(std::path::PathBuf::from(&args[0])))]
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
effect_cmd!(
|
||||
HelpCmd,
|
||||
"help",
|
||||
|_args: &[String]| -> Result<(), String> { Ok(()) },
|
||||
|_args: &Vec<String>, _ctx: &CmdContext| -> Vec<Box<dyn Effect>> {
|
||||
vec![effect::help_page_set(0), effect::change_mode(crate::ui::app::AppMode::Help)]
|
||||
}
|
||||
);
|
||||
|
||||
effect_cmd!(
|
||||
HelpPageNextCmd,
|
||||
"help-page-next",
|
||||
|_args: &[String]| -> Result<(), String> { Ok(()) },
|
||||
|_args: &Vec<String>, _ctx: &CmdContext| -> Vec<Box<dyn Effect>> {
|
||||
vec![effect::help_page_next()]
|
||||
}
|
||||
);
|
||||
|
||||
effect_cmd!(
|
||||
HelpPagePrevCmd,
|
||||
"help-page-prev",
|
||||
|_args: &[String]| -> Result<(), String> { Ok(()) },
|
||||
|_args: &Vec<String>, _ctx: &CmdContext| -> Vec<Box<dyn Effect>> {
|
||||
vec![effect::help_page_prev()]
|
||||
}
|
||||
);
|
||||
|
||||
effect_cmd!(
|
||||
LoadModelCmd,
|
||||
"load",
|
||||
|args: &[String]| require_args("load", args, 1),
|
||||
|args: &Vec<String>, _ctx: &CmdContext| -> Vec<Box<dyn Effect>> {
|
||||
vec![Box::new(effect::LoadModel(std::path::PathBuf::from(
|
||||
&args[0],
|
||||
)))]
|
||||
}
|
||||
);
|
||||
|
||||
effect_cmd!(
|
||||
ExportCsvCmd,
|
||||
"export-csv",
|
||||
|args: &[String]| require_args("export-csv", args, 1),
|
||||
|args: &Vec<String>, _ctx: &CmdContext| -> Vec<Box<dyn Effect>> {
|
||||
vec![Box::new(effect::ExportCsv(std::path::PathBuf::from(
|
||||
&args[0],
|
||||
)))]
|
||||
}
|
||||
);
|
||||
|
||||
effect_cmd!(
|
||||
ImportJsonCmd,
|
||||
"import-json",
|
||||
|args: &[String]| {
|
||||
if args.is_empty() {
|
||||
Err("import-json requires a path".to_string())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
|args: &Vec<String>, _ctx: &CmdContext| -> Vec<Box<dyn Effect>> {
|
||||
vec![Box::new(effect::ImportJsonHeadless {
|
||||
path: std::path::PathBuf::from(&args[0]),
|
||||
model_name: args.get(1).cloned(),
|
||||
array_path: args.get(2).cloned(),
|
||||
})]
|
||||
}
|
||||
);
|
||||
Reference in New Issue
Block a user