From cb24cce1f0a040d030a987d2f636fed34fceaa55 Mon Sep 17 00:00:00 2001 From: Edward Langley Date: Wed, 8 Apr 2026 10:00:41 -0700 Subject: [PATCH] feat: use enum_dispatch for command dispatch and add Open command Replace manual dynamic dispatch using Box with enum_dispatch for improved performance and cleaner code. Add a new Open command to allow opening the TUI as the default behavior when no subcommand is provided. Co-Authored-By: fiddlerwoaroof/git-smart-commit (unsloth/gemma-4-26B-A4B-it-GGUF:UD-Q5_K_XL) --- Cargo.lock | 13 +++++++++++++ Cargo.toml | 1 + flake.nix | 1 + src/main.rs | 37 +++++++++++++++---------------------- 4 files changed, 30 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 829f256..98a8744 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -339,6 +339,18 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "enum_dispatch" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" +dependencies = [ + "once_cell", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -494,6 +506,7 @@ dependencies = [ "crossterm", "csv", "dirs", + "enum_dispatch", "flate2", "indexmap", "proptest", diff --git a/Cargo.toml b/Cargo.toml index 9f2b0fc..6993f1c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ unicode-width = "0.2" dirs = "5" csv = "1" clap = { version = "4.6.0", features = ["derive"] } +enum_dispatch = "0.3.13" [dev-dependencies] proptest = "1" diff --git a/flake.nix b/flake.nix index 947a741..10d434b 100644 --- a/flake.nix +++ b/flake.nix @@ -42,6 +42,7 @@ pkgs.pkg-config pkgs.rust-analyzer crate2nix.packages.${system}.default + pkgs.cargo-expand ]; RUST_BACKTRACE = "1"; }; diff --git a/src/main.rs b/src/main.rs index 3b4d75e..5ee75c9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,6 +14,7 @@ use std::path::PathBuf; use anyhow::{Context, Result}; use clap::{Parser, Subcommand}; +use enum_dispatch::enum_dispatch; use draw::run_tui; use model::Model; @@ -29,7 +30,13 @@ struct Cli { command: Option, } +#[enum_dispatch] +trait Runnable { + fn run(self, model_file: Option) -> Result<()>; +} + #[derive(Subcommand)] +#[enum_dispatch(Runnable)] enum Commands { /// Import JSON or CSV data, then open TUI (or save with --output) Import(ImportArgs), @@ -37,6 +44,8 @@ enum Commands { Cmd(CmdArgs), /// Run commands from a script file headless Script(ScriptArgs), + /// Open the TUI (default when no subcommand given) + Open(OpenTui), } #[derive(clap::Args)] @@ -105,23 +114,10 @@ struct ScriptArgs { file: Option, } -trait Runnable { - fn run(self: Box, model_file: Option) -> Result<()>; -} - -impl From for Box { - fn from(cmd: Commands) -> Self { - match cmd { - Commands::Import(args) => Box::new(args), - Commands::Cmd(args) => Box::new(args), - Commands::Script(args) => Box::new(args), - } - } -} - +#[derive(clap::Args)] struct OpenTui; impl Runnable for OpenTui { - fn run(self: Box, model_file: Option) -> Result<()> { + fn run(self, model_file: Option) -> Result<()> { let model = get_initial_model(&model_file)?; run_tui(model, model_file, None) } @@ -129,15 +125,12 @@ impl Runnable for OpenTui { fn main() -> Result<()> { let cli = Cli::parse(); - let cmd: Box = cli - .command - .map(|c| -> Box { c.into() }) - .unwrap_or_else(|| Box::new(OpenTui)); + let cmd = cli.command.unwrap_or(Commands::Open(OpenTui)); cmd.run(cli.file) } impl Runnable for ImportArgs { - fn run(self: Box, model_file: Option) -> Result<()> { + fn run(self, model_file: Option) -> Result<()> { if self.files.is_empty() { anyhow::bail!("No files specified for import"); } @@ -164,13 +157,13 @@ impl Runnable for ImportArgs { } impl Runnable for CmdArgs { - fn run(self: Box, _model_file: Option) -> Result<()> { + fn run(self, _model_file: Option) -> Result<()> { run_headless_commands(&self.json, &self.file) } } impl Runnable for ScriptArgs { - fn run(self: Box, _model_file: Option) -> Result<()> { + fn run(self, _model_file: Option) -> Result<()> { run_headless_script(&self.path, &self.file) } }