feat: use enum_dispatch for command dispatch and add Open command

Replace manual dynamic dispatch using Box <dyn Runnable> 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)
This commit is contained in:
Edward Langley
2026-04-08 10:00:41 -07:00
parent a3a74d2787
commit cb24cce1f0
4 changed files with 30 additions and 22 deletions

View File

@ -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<Commands>,
}
#[enum_dispatch]
trait Runnable {
fn run(self, model_file: Option<PathBuf>) -> 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<PathBuf>,
}
trait Runnable {
fn run(self: Box<Self>, model_file: Option<PathBuf>) -> Result<()>;
}
impl From<Commands> for Box<dyn Runnable> {
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<Self>, model_file: Option<PathBuf>) -> Result<()> {
fn run(self, model_file: Option<PathBuf>) -> 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<dyn Runnable> = cli
.command
.map(|c| -> Box<dyn Runnable> { 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<Self>, model_file: Option<PathBuf>) -> Result<()> {
fn run(self, model_file: Option<PathBuf>) -> 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<Self>, _model_file: Option<PathBuf>) -> Result<()> {
fn run(self, _model_file: Option<PathBuf>) -> Result<()> {
run_headless_commands(&self.json, &self.file)
}
}
impl Runnable for ScriptArgs {
fn run(self: Box<Self>, _model_file: Option<PathBuf>) -> Result<()> {
fn run(self, _model_file: Option<PathBuf>) -> Result<()> {
run_headless_script(&self.path, &self.file)
}
}