Adding a Command
The main entry point to the Command Line Interface (CLI) is through the main.rs file. This file is located in the Oxen/src/cli/src directory.
Each command is defined in it's own submodule and implements the RunCmd
trait.
#![allow(unused)] fn main() { #[async_trait] pub trait RunCmd { fn name(&self) -> &str; fn args(&self) -> clap::Command; async fn run(&self, args: &clap::ArgMatches) -> Result<(), OxenError>; } }
These submodules can be found in cmd
subdirectory. They are named after the command they implement. For example if you are curious how oxen add
is implemented, you would look at add.rs.
Moo' World
To show this pattern in action, let's add a new command to Oxen. This new command will be a simple "Hello, World!" command. The new command will be named "moo" and will be implemented in the moo.rs file.
The command simply prints "moo!" when you run oxen moo
. It also takes a --loud
flag which makes it print "MOO!" instead of "moo!" if you pass the flag as well as a -n
flag which adds extra o's to the end of the string.
$ oxen moo
moo
oxen moo --loud
MOO!
oxen moo -n 10
moooooooooo
Name The Command
The first method to implement in the trait is simply the name of the command. This is used to identify the command in the CLI and in the help menu.
#![allow(unused)] fn main() { impl RunCmd for MooCmd { fn name(&self) -> &str { "moo" } } }
Setup Args
The next step is setting up the command line arguments. We use the clap crate to handle the command line arguments. The arguments are defined in the args
method.
#![allow(unused)] fn main() { impl RunCmd for MooCmd { fn args(&self) -> Command { // Setups the CLI args for the command Command::new(NAME) .about("Hello, world! 🐂") .arg( Arg::new("number") .long("number") .short('n') .help("How long is the moo?") .default_value("2") .action(clap::ArgAction::Set), ) .arg( Arg::new("loud") .long("loud") .short('l') .help("Make the MOO louder.") .action(clap::ArgAction::SetTrue), ) } } }
Parse Args and Run Command
Finally we need to implement the run
method which is called when the command is run. The run
method is called with the parsed command line arguments.
#![allow(unused)] fn main() { impl RunCmd for MooCmd { async fn run(&self, args: &clap::ArgMatches) -> Result<(), OxenError> { // Parse Args let n = args .get_one::<String>("number") .expect("Must supply number") .parse::<usize>() .expect("number must be a valid integer."); let loud = args.get_flag("loud"); if loud { // Print the moo loudly with -n number of o's println!("M{}!", "O".repeat(n)); } else { // Print the moo with -n number of o's println!("m{}", "o".repeat(n)); } Ok(()) } } }
If a command returns an OxenError
it will be handled and printed in the main.rs
file and return a non zero exit code.
Add to CLI
Now that our command is implemented, we need to add it to the CLI. This is done in the main.rs file. All you need to do is add a new instance of your command to the cmds
vector. The rest of the file is just adding the arguments, parsing them, then calling your run
method.
#![allow(unused)] fn main() { let cmds: Vec<Box<dyn cmd::RunCmd>> = vec![ Box::new(cmd::AddCmd), Box::new(cmd::MooCmd), // Your new command ]; // ... run commands }
This should be all you need to get Oxen to go "MOO!". Let's build and run.
cargo build
./target/debug/oxen moo --help
You will see the help menu for your new command.
Hello, world! 🐂
Usage: oxen moo [OPTIONS]
Options:
-n, --number <number> How long is the moo? [default: 2]
-l, --loud Make the MOO louder.
-h, --help Print help
Then you can simply run your command.
./target/debug/oxen moo
You should see the output "moo"
moo
You can also make the moo louder with the --loud
flag and add more o's with the -n
flag.
$ ./target/debug/oxen moo --loud
MOO!
$ ./target/debug/oxen moo -n 10
moooooooooo
🎉 And there you have it!
Congrats on adding your first command to Oxen! The moo command is already implemented in the main Oxen codebase as an easter egg and an example you can follow along with.