Warning
This post was published 43 days ago. The information described in this article may have changed.
I'm trying to finally flesh out something I've used in many projects in the past, a way to build hierarchies of CLI commands. The thought is to provide a CLI interface like this:
# operations on tasks
my-util task create
my-util task list
my-util task delete
# operations on task comments
my-util task comment create
I'm using clap
with derive, but in my crate that I'm working on, that's not going to be a requirement. Usage with clap makes things really easy, from your main function you simply call Args::parse().run()
and everything should work as you might expect.
The basic form of the Command
trait looks like this:
pub trait Command {
fn pre_exec(&self) -> sysexits::Result<()> {
Ok(())
}
fn exec(&self) -> sysexits::Result<()>;
fn post_exec(&self, exec_outcome: sysexits::Result<()>) -> sysexits::Result<()> {
exec_outcome
}
fn run(&self) -> sysexits::Result<()> {
self.pre_exec()?;
let outcome = self.exec();
self.post_exec(outcome)
}
}
Building hierarchies of commands looks like this:
use clap::Parser;
#[derive(Debug, Clone, Parser)]
pub struct RootCommand {
#[clap(subcommand)]
pub cmd: SubCommands,
}
#[derive(Debug, Clone, Parser)]
pub enum SubCommands {
Tasks(TasksCommand),
}
impl Command for RootCommand {
fn exec(&self) -> sysexits::Result<()> {
match &self.cmd {
SubCommands::Tasks(c) => c.run(),
}
}
}
#[derive(Debug, Clone, Parser)]
pub struct TasksCommand;
impl Command for TasksCommand {
fn exec(&self) -> sysexits::Result<()> {
eprintln!("tasks command!");
Ok(())
}
}
This works fine, but since I'm now making this into a crate, I'd like the error type to be user-supplied so people can use whatever error type they'd like:
anyhow
makes things super easy if you don't care much about strict error typessysexits
makes sense if you want explicit exit codes for certain conditionsthiserror
allows you to be very specific about your errorsstd::error::Error
for masochistsNow that I'm adding a type
to Command
, I'm not sure how to specify the constraint so that it plays nicely with whatever you want to be using.
I've tried:
pub trait Command {
type Err: AsRef<dyn std::error::Error>;
}
This works for anyhow
but does not work for sysexits
.
Should I just abandon adding a constraint on the Err
type? Or is there a better way to constrain the type that will play nicely with most error crates?
Users will pretty much never need to have a dyn Command
as everything will be done statically and I'll be writing a proc-macro to automate away the match block stuff for parent commands.
3 posts - 2 participants
🏷️ rust_feed