tailhook / rust-argparse

The command-line argument parser library for rust
MIT License
240 stars 39 forks source link

Argument grouping support #20

Closed andresv closed 9 years ago

andresv commented 9 years ago

It would be nice if argparse supports commands. For example lets say I have a application that has 2 operating modes: const and tracking. Each mode has its own args and some of them are required args. Here is an example where I tried to migrate my application from docopt to argparese. Argument shift should be available only for const mode. My approach was to use shift as type Option, however it seems to be quite difficult to implement it that way. Therefore currently I will use shift as u32 and init with 0xFFFFFFFF which means NA.

NB: maybe it is not that difficult and I just do not know the right way. Anyway here is a snippet what I tried to do:

use std::str::FromStr;

use argparse::{ArgumentParser, StoreTrue, Store};
use std::process::exit;
use self::OperatingMode::{TrackMode, ConstMode, UninitializedMode};

#[derive(PartialEq, Eq, Debug)]
pub enum OperatingMode {
    TrackMode,
    ConstMode,
    UninitializedMode,
}

impl FromStr for OperatingMode {
    type Err = ();
    fn from_str(src: &str) -> Result<OperatingMode, ()> {
        return match src {
            "track" => Ok(TrackMode),
            "const" => Ok(ConstMode),
            _ => Err(()),
        };
    }
}

pub struct Options {
    pub mode: OperatingMode,
    pub samplerate: u32,
    pub inputtype: String,

    pub shift: Option,
}

/*
static USAGE: &'static str = "
doppler <andres.vahter@gmail.com>

Usage:
    doppler (const (--samplerate <sps> | -s <sps>) --intype <type> --shift <Hz>)
    doppler (track (--samplerate <sps> | -s <sps>) --intype <type> --tlefile <file> --tlename <name> --location <lat,lon,alt> --freq <Hz>) [--time <Y-m-dTH:M:S>] [--shift <Hz>]
    doppler (-h | --help | --version)

Options:
    -s --samplerate <sps>       IQ data samplerate.
    --intype <type>             IQ data type <i16, f32>.

    -h --help                   Show this screen.
    --version                   Show version.

Const mode options:
    --shift <Hz>                Constant frequency shift in Hz [default: 0].

Track mode options:
    --tlefile <file>            TLE database file eg. \"http://www.celestrak.com/NORAD/elements/cubesat.txt\".
    --tlename <name>            TLE name eg. 'ESTCUBE 1'.
    --location <lat,lon,alt>    Observer location on earth.
    --time <Y-m-dTH:M:S>        Observation start time. It should be specified if input is IQ data recording. Real time is used otherwise.
    --freq <Hz>                 Satellite transmitter frequency in Hz.
    --shift <Hz>                Constant frequency shift in Hz [default: 0].
";

*/
pub fn args() -> Options {
    let mut options = Options {
        mode: UninitializedMode,
        samplerate: 0,
        inputtype: String::new(),

        shift: None,
    };

    {
        let mut ap = ArgumentParser::new();
        ap.set_description("commands");

        ap.refer(&mut options.mode)
            // positional
            .add_argument("mode", Store, "<const, track> operating mode")
            .required();

        ap.refer(&mut options.samplerate)
            .add_option(&["--samplerate"], Store, "IQ data samplerate")
            .metavar("<sps>")
            .required();

        ap.refer(&mut options.inputtype)
            .add_option(&["--intype"], Store, "IQ data type")
            .metavar("<i16, f32>")
            .required();

        ap.refer(&mut options.shift)
            .add_option(&["--shift"], Store, "Constant frequency shift in Hz")
            .metavar("<Hz>");

        match ap.parse_args() {
            Ok(()) => {}
            Err(x) => {exit(x);}
        }
    }

    match options.mode {
        TrackMode => {

        }
        ConstMode => {
            //match options.shift {
            //    Some(shift) => {}
            //    None => {println!("--shift", );}
            //}
        }
        _ => {exit(1)}
    }
    options
}
tailhook commented 9 years ago

Ok, I've added an example of how I usually accomplish the task: https://github.com/tailhook/rust-argparse/blob/master/examples/subcommands.rs

While I'd like to add some functionality that would simplify adding groups of options and subcommands, but I can't find out good API. The complex point is that it should allow keep different subcommands in different options, which is slightly hard to achieve with rust borrow rules.

Let me know if this solution is ok for you, or if you have another ideas.

andresv commented 9 years ago

Very clever, I will try that.

andresv commented 9 years ago

But what about --help? In this way subcommand arguments are not automatically added.

Tried subcommand.rs:

Usage:
    ./target/debug/doppler [OPTIONS] COMMAND [ARGUMENTS ...]

Plays or records sound

positional arguments:
  command               Command to run (either "play" or "record")
  arguments             Arguments for command

optional arguments:
  -h,--help             show this help message and exit
  -v,--verbose          Be verbose
tailhook commented 9 years ago

But what about --help? In this way subcommand arguments are not automatically added.

Right, the --help part is the complex one. You get help for subcommand by:

command subcommand --help

Which is reasonable enough, for most things.

The problematic part of making help for all subcommands on one page is incapsulation, you can't borrow variables from several functions at once. And I don't like lifting arguments out of the module which implements the functionality itself.

andresv commented 9 years ago

I switched to clap which has subcommand support built in: https://github.com/kbknapp/clap-rs. It has also other very nice features. Clap seems to be the most polished rust argparser so far.