3 releases

0.0.2 Feb 25, 2020
0.0.1 Feb 25, 2020
0.0.0 Feb 7, 2020

#170 in #subcommand

MIT/Apache

3KB
60 lines

Argh Demo

Real knowledge comes from practice.

Recently, I noticed a command line parameter parsing tool called "argh", which is lightweight, easy to use, and really human friendly.

I like it for two reasons:

  • Small amount of code, simple and intuitive
  • Ingenious use of "Rust" syntax

Here I will talk about the use of "argh" from the perspective of the user, as a small "prompt board" for myself.

Command Line Args

First, let's create a new project. Naturally, cargo new <argh-demo>.

Then add argh to our dependencies like this:

[dependencies]
argh = "0.1"

Let's do something together, such as implementing a simple command line adder? It's like "a + b". Obviously, our function takes two arguments, num1 and num2.

fn add(num1: u16, num2: u16) {
    println!(arg1 + arg2);
}

See how argh does it:

#[derive(FromArgs)]
/// Add two numbers
struct DemoCli {
    /// the first number.
    #[argh(option)]
    num1: u16,

    /// the second number
    #[argh(option)]
    num2: u16,
}

We then connected them together in the main function.

fn main() {
    let cli: DemoCli = argh::from_env();
    add(cli.num1, cli.num2);
}

Now, let's try to run cargo run and see what happens:

Required options not provided:
    --num1
    --num2

Try the --help option,target / debug / argh-demo --help:

Usage: target/debug/argh-demo --num1 <num1> --num2 <num2>

Add two numbers

Options:
  --num1            the first number.
  --num2            the second number
  --help            display usage information

Well, it seems we have to add all the options target/debug/argh-demo --num1 1 --num2 2

1 + 2 = 3

Subcommands

It looks good, but if our goal is a complete calculator, select functions by subcommands, such as argh-demo add --num1 1-num2 2, This will make some changes in our design.

Let's see how to change the code:

  1. First declare a set of subcommands named subcommand in DemoCli.

    #[derive(FromArgs)]
    /// A simple calculation tool
    struct DemoCli {
        #[argh(subcommand)]
        subcommand: SubCommands,
    }
    
  2. Define the structure SubCommands containing the Add option:

    #[derive(FromArgs, PartialEq, Debug)]
    #[argh(subcommand)]
    enum SubCommands {
        Add(AddOptions),
    }
    
  3. Add content for AddOptions:

    #[derive(FromArgs, PartialEq, Debug)]
    /// Add two numbers
    #[argh(subcommand, name = "add")]
    pub struct AddOptions {
        /// the first number.
        #[argh(option)]
        num1: u16,
    
        /// the second number
        #[argh(option)]
        num2: u16,
    }
    

Obviously, you just need to rewrite the calling part in the main function:

match cli.subcommand {
    SubCommands::Add(options) => {
        add(options.num1, options.num2);
    }
};

Run cargo build && ./target/debug/argh-demo --help to see how we can use this improved version.

Usage: ./target/debug/argh-demo <command> [<args>]

A simple calculation tool

Options:
  --help            display usage information

Commands:
  add               Add two numbers

Continue to use 1 + 2 = 3 to try, target / debug / argh-demo --num1 1 --num2 2:

1 + 2 = 3

Better Structure

If our program has a lot of options / subcommands to organize, it seems messy to put in a single file. Imagine how programs like cargo do this. They have separate files for each subcommand in the commands folder.

If we were to introduce a sub subcommand, the program structure would look like this.

├── Cargo.lock
├── Cargo.toml
├── README.md
├── src
│   ├── commands
│   │   ├── add.rs
│   │   ├── mod.rs
│   │   └── sub.rs
│   └── main.rs

Since both the subcommands and the corresponding execute functions should be moved to the corresponding files in commands, main.rs will be much streamlined:

//! Just a demo for argh.

use argh::FromArgs;

mod commands;

#[derive(FromArgs)]
/// A simple calculation tool
struct DemoCli {
    #[argh(subcommand)]
    subcommand: SubCommands,
}

#[derive(FromArgs, PartialEq, Debug)]
#[argh(subcommand)]
enum SubCommands {
    Add(commands::add::AddOptions),
    Sub(commands::sub::SubOptions),
}

fn main() {
    let cli: DemoCli = argh::from_env();
    match cli.subcommand {
        SubCommands::Add(options) => {
            commands::add::execute(options);
        }
        SubCommands::Sub(options) => {
            commands::sub::execute(options);
        }
    };
}

So obviously, the add and sub modules should be exposed in commands mod.rs.

pub mod add;
pub mod sub;

Since we pass the options as a whole to the execute function, the add module needs to be modified accordingly:

use argh::FromArgs;

#[derive(FromArgs, PartialEq, Debug)]
/// Add two numbers
#[argh(subcommand, name = "add")]
pub struct AddOptions {
    /// the first number.
    #[argh(option)]
    num1: u16,

    /// the second number
    #[argh(option)]
    num2: u16,
}

pub fn execute(options: AddOptions) {
    println!(
        "{} + {} = {}",
        options.num1,
        options.num2,
        options.num1 + options.num2
    );
}

The next step is nothing more than writing a simple sub module:

use argh::FromArgs;

#[derive(FromArgs, PartialEq, Debug)]
/// Sub two numbers
#[argh(subcommand, name = "sub")]
pub struct SubOptions {
    /// the first number.
    #[argh(option)]
    num1: i16,

    /// the second number
    #[argh(option)]
    num2: i16,
}

pub fn execute(options: SubOptions) {
    println!(
        "{} - {} = {}",
        options.num1,
        options.num2,
        options.num1 - options.num2
    );
}

As usual, use --help option to see usage:

Usage: target/debug/argh-demo <command> [<args>]

A simple calculation tool

Options:
  --help            display usage information

Commands:
  add               Add two numbers
  sub               Sub two numbers

Finally test the functions again:

  1. target/debug/argh-demo add --num1 1 --num2 2

    1 + 2 = 3
    
  2. target/debug/argh-demo sub --num1 1 --num2 2

    1 - 2 = -1
    

Dependencies

~0.5–1MB
~24K SLoC