diff options
Diffstat (limited to 'argparse')
34 files changed, 3274 insertions, 0 deletions
| diff --git a/argparse/.gitignore b/argparse/.gitignore new file mode 100644 index 0000000..6ae6389 --- /dev/null +++ b/argparse/.gitignore @@ -0,0 +1,13 @@ +/.vagga +*.o +*.so +*.rlib +/argparse_test +/target +# examples +/greeting +/structure +/subcommands + +# Cargo files +Cargo.lock diff --git a/argparse/.travis.yml b/argparse/.travis.yml new file mode 100644 index 0000000..7ccbf43 --- /dev/null +++ b/argparse/.travis.yml @@ -0,0 +1,34 @@ +sudo: false +dist: trusty +language: rust + +cache: +- cargo + +before_cache: +- rm -r $TRAVIS_BUILD_DIR/target/debug + +jobs: +  include: +  - os: linux +    rust: stable +  - os: linux +    rust: beta +  - os: linux +    rust: nightly + +  # deploy +  - stage: publish +    os: linux +    rust: stable +    env: +    # CARGO_TOKEN +    - secure: "tk6bJEv46YfZwAKYzxn9+afzEb6nGym9lo/YJgjYIolv2qsNyMLlmC8ptRSRTHwOQPd3c54Y9XYP+61miMmWjppQSjJ4yvkUqnyiYzzdxzVM5dNIbXcqO6GbTgE2rIx9BOH0c/qrmw1KW2iz8TChxgQu/vv8pmDL1kmyawVy3EE=" +    install: true +    script: true + +    deploy: +    - provider: script +      script: 'cargo publish --verbose --token=$CARGO_TOKEN' +      on: +        tags: true diff --git a/argparse/Cargo.toml b/argparse/Cargo.toml new file mode 100644 index 0000000..7552373 --- /dev/null +++ b/argparse/Cargo.toml @@ -0,0 +1,12 @@ +[package] + +name = "argparse" +description = "Powerful command-line argument parsing library" +license = "MIT" +readme = "README.rst" +keywords = ["command-line", "cli", "command", "argument"] +categories = ["command-line-interface"] +homepage = "http://github.com/tailhook/rust-argparse" +version = "0.2.2" +authors = ["Paul Colomiets <paul@colomiets.name>"] + diff --git a/argparse/LICENSE b/argparse/LICENSE new file mode 100644 index 0000000..3e00de4 --- /dev/null +++ b/argparse/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2014-2015 Paul Colomiets + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/argparse/README.rst b/argparse/README.rst new file mode 100644 index 0000000..8a3fca0 --- /dev/null +++ b/argparse/README.rst @@ -0,0 +1,341 @@ +======== +Argparse +======== + +The ``rust-argparse`` is command-line parsing module for rust. It's inspired +by python's ``argparse`` module. + +Features: + +* Supports standard (GNU) option conventions +* Properly typed values +* Automatically generated help and usage messages + +Importing +========= +Edit your Cargo.toml to add ``rust-argparse`` to your project. + +.. code-block:: rust + +    [dependencies] +    argparse = "0.2.2" + + +Example +======= + +The following code is a simple Rust program with command-line arguments: + +.. code-block:: rust + +    extern crate argparse; + +    use argparse::{ArgumentParser, StoreTrue, Store}; + +    fn main() { +        let mut verbose = false; +        let mut name = "World".to_string(); +        {  // this block limits scope of borrows by ap.refer() method +            let mut ap = ArgumentParser::new(); +            ap.set_description("Greet somebody."); +            ap.refer(&mut verbose) +                .add_option(&["-v", "--verbose"], StoreTrue, +                "Be verbose"); +            ap.refer(&mut name) +                .add_option(&["--name"], Store, +                "Name for the greeting"); +            ap.parse_args_or_exit(); +        } + +        if verbose { +            println!("name is {}", name); +        } +        println!("Hello {}!", name); +    } + +Assuming the Rust code above is saved into a file ``greeting.rs``, let's see +what we have now:: + +    $ rustc greeting.rs +    $ ./greeting -h +    Usage: +      ./greeting [OPTIONS] + +    Greet somebody. + +    Optional arguments: +      -h, --help  Show this help message and exit +      -v, --verbose +                 Be verbose +      --name NAME Name for the greeting +    $ ./greeting +    Hello World! +    $ ./greeting --name Bob +    Hello Bob! +    $ ./greeting -v --name Alice +    name is Alice +    Hello Alice! + + +Basic Workflow +============== + + +Create ArgumentParser +--------------------- + +The argument parser is created empty and is built incrementally. So we create +a mutable variable:: + +    extern crate argparse; +    use argparse::ArgumentParser; + +    let mut parser = ArgumentParser::new(); + + +Customize +--------- + +There are optional customization methods. The most important one is:: + +    parser.set_description("My command-line utility") + +The descripion is rewrapped to fit 80 column string nicely. Just like option +descriptions. + +Add Options +----------- + +The ``refer`` method creates a cell variable, which the result will be written +to:: + +    let mut verbose = false; +    parser.refer(&mut verbose); + +Next we add an options which control the variable: +For example:: + +    parser.refer(&mut verbose) +        .add_option(&["-v", "--verbose"], StoreTrue, +                    "Be verbose"); + +You may add multiple options for the same variable:: + +    parser.refer(&mut verbose) +        .add_option(&["-v", "--verbose"], StoreTrue, +                    "Be verbose") +        .add_option(&["-q", "--quiet"], StoreFalse, +                    "Be verbose"); + +Similarly positional arguments are added:: + +    let mut command = String; +    parser.refer(&mut command) +        .add_argument("command", Store, +                      "Command to run"); + + + +Organizing Options +------------------ + +It's often useful to organize options into some kind of structure. You can +easily borrow variables from the structure into option parser. For example:: + +    struct Options { +        verbose: bool, +    } +    ... +    let mut options = Options { verbose: false }; +    parser.refer(&mut options.verbose) +        .add_option(&["-v"], StoreTrue, +                    "Be verbose"); + + +Parsing Arguments +----------------- + +All the complex work is done in ``parser.parse_args()``. But there is +a simpler option:: + +    parser.parse_args_or_exit() + +In case you don't want argparse to exit itself, you might use the +``parse_args`` function directly:: + +    use std::process::exit; + +    match parser.parse_args() { +        Ok(()) =>  {} +        Err(x) => { +            std::process::exit(x); +        } +    } + + +ArgumentParser Methods +====================== + +``parser.refer<T>(var: &mut T) -> Ref`` +    Attach the variable to argument parser. The options are added to the +    returned ``Ref`` object and modify a variable passed to the method. + +``parser.add_option(names: &[&str], action: TypedAction, help: &str)`` +    Add a single option which has no parameters. Most options must be added +    by ``refer(..)`` and methods on ``Ref`` object (see below). + +    Example:: + +        ap.add_option(&["-V", "--version"], +            Print(env!("CARGO_PKG_VERSION").to_string()), "Show version"); + +``parser.set_description(descr: &str)`` +    Set description that is at the top of help message. + +``parser.stop_on_first_argument(val: bool)`` +    If called with ``true``, parser will stop searching for options when first +    non-option (the one doesn't start with ``-``) argument is encountered. This +    is useful if you want to parse following options with another argparser or +    external program. + +``parser.silence_double_dash(val: bool)`` +    If called with ``true`` (default), parser will not treat *first* double +    dash ``--`` as positional argument. Use ``false`` if you need to add some +    meaning to the ``--`` marker. + +``parser.print_usage(name: &str, writer: &mut Write)`` +    Prints usage string to stderr. + +``parser.print_help(name: &str, writer: &mut Write)`` +    Writes help to ``writer``, used by ``--help`` option internally. + +``parser.parse_args()`` +    Method that does all the dirty work. And returns ``Result`` + +``parser.parse_args_or_exit()`` +    Method that does all the dirty work. And in case of failure just ``exit()`` + + +Variable Reference Methods +========================== + +The ``argparse::Ref`` object is returned from ``parser.refer()``. +The following methods are used to add and customize arguments: + +``option.add_option(names: &[&str], action: TypedAction, help: &str)`` +    Add an option. All items in names should be either in format ``-X`` or +    ``--long-option`` (i.e. one dash and one char or two dashes and long name). +    How this option will be interpreted and whether it will have an argument +    dependes on the action. See below list of actions. + +``option.add_argument(name: &str, action: TypedAction, help: &str)`` +    Add a positional argument + +``option.metavar(var: &str)`` +    A name of the argument in usage messages (for options having argument). + +``option.envvar(var: &str)`` +    A name of the environment variable to get option value from. The value +    would be parsed with ``FromStr::from_str``, just like an option having +    ``Store`` action. + +``option.required()`` +    The option or argument is required (it's optional by default). If multiple +    options or multiple arguments are defined for this reference at least one +    of them is required. + + +Actions +======= + +The following actions are available out of the box. They may be used in either +``add_option`` or ``add_argument``: + +``Store`` +    An option has single argument. Stores a value from command-line in a +    variable. Any type that has the ``FromStr`` and ``Clone`` traits implemented +    may be used. + +``StoreOption`` +    As ``Store``, but wrap value with ``Some`` for use with ``Option``. For +    example: + +        let mut x: Option<i32> = None; +        ap.refer(&mut x).add_option(&["-x"], StoreOption, "Set var x"); + +``StoreConst(value)`` +    An option has no arguments. Store a hard-coded ``value`` into variable, +    when specified. Any type with the ``Clone`` trait implemented may be used. + +``PushConst(value)`` +    An option has no arguments. Push a hard-coded ``value`` into variable, +    when specified. Any type which has the ``Clone`` type implemented may be +    used. Option might used for a list of operations to perform, when ``required`` +    is set for this variable, at least one operation is required. + +``StoreTrue`` +    Stores boolean ``true`` value in a variable. +    (shortcut for ``StoreConst(true)``) + +``StoreFalse`` +    Stores boolean ``false`` value in a variable. +    (shortcut for ``StoreConst(false)``) + + +``IncrBy(num)`` +    An option has no arguments. Increments the value stored in a variable by a +    value ``num``. Any type which has the ``Add`` and ``Clone`` traits may be used. + +``DecrBy(nym)`` +    Decrements the value stored in a variable by a value ``num``. Any type +    which has the ``Add`` and ``Clone`` traits may be used. + +``Collect`` +    When used for an ``--option``, requires single argument. When used for a +    positional argument consumes all remaining arguments. Parsed options are +    added to the list. I.e. a ``Collect`` action requires a +    ``Vec<int>`` variable. Parses arguments using ``FromStr`` trait. + +``List`` +    When used for positional argument, works the same as ``List``. When used +    as an option, consumes all remaining arguments. + +    Note the usage of ``List`` is strongly discouraged, because of complex +    rules below. Use ``Collect`` and positional options if possible. But usage +    of ``List`` action may be useful if you need shell expansion of anything +    other than last positional argument. + +    Let's learn rules by example. For the next options:: + +        ap.refer(&mut lst1).add_option(&["-X", "--xx"], List, "List1"); +        ap.refer(&mut lst2).add_argument("yy", List, "List2"); + +    The following command line:: + +        ./run 1 2 3 -X 4 5 6 + +    Will return ``[1, 2, 3]`` in the ``lst1`` and the ``[4,5,6]`` in the +    ``lst2``. + +    Note that using when using ``=`` or equivalent short option mode, the +    'consume all' mode is not enabled. I.e. in the following command-line:: + +        ./run 1 2 -X3 4 --xx=5 6 + +    The ``lst1`` has ``[3, 5]`` and ``lst2`` has ``[1, 2, 4, 6]``. +    The argument consuming also stops on ``--`` or the next option:: + +        ./run: -X 1 2 3 -- 4 5 6 +        ./run: -X 1 2 --xx=3 4 5 6 + +    Both of the above parse ``[4, 5, 6]`` as ``lst1`` and +    the ``[1, 2, 3]`` as the ``lst2``. + +``Print(value)`` +    Print the text and exit (with status ``0``). Useful for ``--version`` +    option:: + +        ap.add_option(&["-V", "--version"], +            Print(env!("CARGO_PKG_VERSION").to_string()), "Show version"); + + diff --git a/argparse/bulk.yaml b/argparse/bulk.yaml new file mode 100644 index 0000000..73a7d02 --- /dev/null +++ b/argparse/bulk.yaml @@ -0,0 +1,11 @@ +minimum-bulk: v0.4.5 + +versions: + +- file: Cargo.toml +  block-start: ^\[package\] +  block-end: ^\[.*\] +  regex: ^version\s*=\s*"(\S+)" + +- file: README.rst +  regex: ^\s*argparse\s*=\s*"(\S+)" diff --git a/argparse/examples/greeting.rs b/argparse/examples/greeting.rs new file mode 100644 index 0000000..77e719c --- /dev/null +++ b/argparse/examples/greeting.rs @@ -0,0 +1,26 @@ +extern crate argparse; + +use argparse::{ArgumentParser, StoreTrue, Store, Print}; + +fn main() { +    let mut verbose = false; +    let mut name = "World".to_string(); +    { +        let mut ap = ArgumentParser::new(); +        ap.set_description("Greet somebody."); +        ap.add_option(&["-V", "--version"], +            Print(env!("CARGO_PKG_VERSION").to_string()), "Show version"); +        ap.refer(&mut verbose) +            .add_option(&["-v", "--verbose"], StoreTrue, +            "Be verbose"); +        ap.refer(&mut name) +            .add_option(&["--name"], Store, +            "Name for the greeting"); +        ap.parse_args_or_exit(); +    } + +    if verbose { +        println!("name is {}", name); +    } +    println!("Hello {}!", name); +} diff --git a/argparse/examples/structure.rs b/argparse/examples/structure.rs new file mode 100644 index 0000000..59b9345 --- /dev/null +++ b/argparse/examples/structure.rs @@ -0,0 +1,38 @@ +extern crate argparse; + +use std::process::exit; + +use argparse::{ArgumentParser, StoreTrue, Store}; + +struct Options { +    verbose: bool, +    name: String, +} + +fn main() { +    let mut options = Options { +        verbose: false, +        name: "World".to_string(), +    }; +    { +        let mut ap = ArgumentParser::new(); +        ap.set_description("Greet somebody."); +        ap.refer(&mut options.verbose) +            .add_option(&["-v", "--verbose"], StoreTrue, +            "Be verbose"); +        ap.refer(&mut options.name) +            .add_option(&["--name"], Store, +            "Name for the greeting"); +        match ap.parse_args() { +            Ok(()) => {} +            Err(x) => { +                exit(x); +            } +        } +    } + +    if options.verbose { +        println!("name is {}", options.name); +    } +    println!("Hello {}!", options.name); +} diff --git a/argparse/examples/subcommands.rs b/argparse/examples/subcommands.rs new file mode 100644 index 0000000..8fb061a --- /dev/null +++ b/argparse/examples/subcommands.rs @@ -0,0 +1,88 @@ +use std::str::FromStr; +use std::io::{stdout, stderr}; +extern crate argparse; + +use argparse::{ArgumentParser, StoreTrue, Store, List}; + +#[allow(non_camel_case_types)] +#[derive(Debug)] +enum Command { +    play, +    record, +} + +impl FromStr for Command { +    type Err = (); +    fn from_str(src: &str) -> Result<Command, ()> { +        return match src { +            "play" => Ok(Command::play), +            "record" => Ok(Command::record), +            _ => Err(()), +        }; +    } +} + + + +fn play_command(verbose: bool, args: Vec<String>) { +    let mut output = "".to_string(); +    { +        let mut ap = ArgumentParser::new(); +        ap.set_description("Plays a sound"); +        ap.refer(&mut output) +            .add_option(&["--output"], Store, +                r#"Output sink to play to"#); +        match ap.parse(args, &mut stdout(), &mut stderr()) { +            Ok(()) =>  {} +            Err(x) => { +                std::process::exit(x); +            } +        } +    } +    println!("Verbosity: {}, Output: {}", verbose, output); +} + +fn record_command(verbose: bool, args: Vec<String>) { +    let mut input = "".to_string(); +    { +        let mut ap = ArgumentParser::new(); +        ap.set_description("Records a sound"); +        ap.refer(&mut input) +            .add_option(&["--input"], Store, +                r#"Output source to record from"#); +        match ap.parse(args, &mut stdout(), &mut stderr()) { +            Ok(()) =>  {} +            Err(x) => { +                std::process::exit(x); +            } +        } +    } +    println!("Verbosity: {}, Input: {}", verbose, input); +} + +fn main() { +    let mut verbose = false; +    let mut subcommand = Command::play; +    let mut args = vec!(); +    { +        let mut ap = ArgumentParser::new(); +        ap.set_description("Plays or records sound"); +        ap.refer(&mut verbose) +            .add_option(&["-v", "--verbose"], StoreTrue, +            "Be verbose"); +        ap.refer(&mut subcommand).required() +            .add_argument("command", Store, +                r#"Command to run (either "play" or "record")"#); +        ap.refer(&mut args) +            .add_argument("arguments", List, +                r#"Arguments for command"#); +        ap.stop_on_first_argument(true); +        ap.parse_args_or_exit(); +    } + +    args.insert(0, format!("subcommand {:?}", subcommand)); +    match subcommand { +        Command::play => play_command(verbose, args), +        Command::record => record_command(verbose, args), +    } +} diff --git a/argparse/src/action.rs b/argparse/src/action.rs new file mode 100644 index 0000000..c3a0aff --- /dev/null +++ b/argparse/src/action.rs @@ -0,0 +1,33 @@ +use std::cell::RefCell; +use std::rc::Rc; + +pub enum ParseResult { +    Parsed, +    Help, +    Exit, +    Error(String), +} + + +pub enum Action<'a> { +    Flag(Box<IFlagAction + 'a>), +    Single(Box<IArgAction + 'a>), +    Push(Box<IArgsAction + 'a>), +    Many(Box<IArgsAction + 'a>), +} + +pub trait TypedAction<T> { +    fn bind<'x>(&self, Rc<RefCell<&'x mut T>>) -> Action<'x>; +} + +pub trait IFlagAction { +    fn parse_flag(&self) -> ParseResult; +} + +pub trait IArgAction { +    fn parse_arg(&self, arg: &str) -> ParseResult; +} + +pub trait IArgsAction { +    fn parse_args(&self, args: &[&str]) -> ParseResult; +} diff --git a/argparse/src/bool.rs b/argparse/src/bool.rs new file mode 100644 index 0000000..6ada458 --- /dev/null +++ b/argparse/src/bool.rs @@ -0,0 +1,22 @@ +use std::cell::RefCell; +use std::rc::Rc; + +use super::action::Action; +use super::action::TypedAction; +use super::action::Action::Flag; +use super::generic::StoreConstAction; +use super::{StoreTrue, StoreFalse}; + + +impl TypedAction<bool> for StoreTrue { +    fn bind<'x>(&self, cell: Rc<RefCell<&'x mut bool>>) -> Action<'x> { +        return Flag(Box::new(StoreConstAction { cell: cell, value: true })); +    } +} + +impl TypedAction<bool> for StoreFalse { +    fn bind<'x>(&self, cell: Rc<RefCell<&'x mut bool>>) -> Action<'x> { +        return Flag(Box::new(StoreConstAction { cell: cell, value: false })); +    } +} + diff --git a/argparse/src/custom.rs b/argparse/src/custom.rs new file mode 100644 index 0000000..d002ffe --- /dev/null +++ b/argparse/src/custom.rs @@ -0,0 +1,95 @@ +use std::cell::RefCell; +use std::rc::Rc; + +use super::{Parse, ParseOption, ParseList, ParseCollect, FromCommandLine}; +use super::action::Action; +use super::action::{TypedAction, IArgAction, IArgsAction}; +use super::action::ParseResult; +use super::action::ParseResult::{Parsed, Error}; +use super::action::Action::{Single, Push, Many}; + +pub struct ParseAction<'a, T: 'a> { +    pub cell: Rc<RefCell<&'a mut T>>, +} + +pub struct ParseOptionAction<'a, T: 'a> { +    cell: Rc<RefCell<&'a mut Option<T>>>, +} + +pub struct ParseListAction<'a, T: 'a> { +    cell: Rc<RefCell<&'a mut Vec<T>>>, +} + +impl<T: 'static + FromCommandLine> TypedAction<T> for Parse { +    fn bind<'x>(&self, cell: Rc<RefCell<&'x mut T>>) -> Action<'x> { +        return Single(Box::new(ParseAction { cell: cell })); +    } +} + +impl<T: 'static + FromCommandLine> TypedAction<Option<T>> for ParseOption { +    fn bind<'x>(&self, cell: Rc<RefCell<&'x mut Option<T>>>) -> Action<'x> { +        return Single(Box::new(ParseOptionAction { cell: cell })); +    } +} + +impl<T: 'static + FromCommandLine + Clone> TypedAction<Vec<T>> for ParseList { +    fn bind<'x>(&self, cell: Rc<RefCell<&'x mut Vec<T>>>) -> Action<'x> { +        return Many(Box::new(ParseListAction { cell: cell })); +    } +} + +impl<T> TypedAction<Vec<T>> for ParseCollect +    where T: 'static + FromCommandLine + Clone +{ +    fn bind<'x>(&self, cell: Rc<RefCell<&'x mut Vec<T>>>) -> Action<'x> { +        return Push(Box::new(ParseListAction { cell: cell })) +    } +} + +impl<'a, T: FromCommandLine> IArgAction for ParseAction<'a, T> { +    fn parse_arg(&self, arg: &str) -> ParseResult { +        match FromCommandLine::from_argument(arg) { +            Ok(x) => { +                **self.cell.borrow_mut() = x; +                return Parsed; +            } +            Err(error) => { +                return Error(format!("Bad value {:?}: {}", arg, error)); +            } +        } +    } +} + +impl<'a, T: FromCommandLine> IArgAction for ParseOptionAction<'a, T> { +    fn parse_arg(&self, arg: &str) -> ParseResult { +        match FromCommandLine::from_argument(arg) { +            Ok(x) => { +                **self.cell.borrow_mut() = Some(x); +                return Parsed; +            } +            Err(error) => { +                return Error(format!("Bad value {:?}: {}", arg, error)); +            } +        } +    } +} + +impl<'a, T: FromCommandLine + Clone> IArgsAction for ParseListAction<'a, T> { +    fn parse_args(&self, args: &[&str]) -> ParseResult { +        let mut result = vec!(); +        for arg in args.iter() { +            match FromCommandLine::from_argument(*arg) { +                Ok(x) => { +                    result.push(x); +                } +                Err(error) => { +                    return Error(format!("Bad value {:?}: {}", arg, error)); +                } +            } +        } +        **self.cell.borrow_mut() = result; +        return Parsed; +    } +} + + diff --git a/argparse/src/from_cli.rs b/argparse/src/from_cli.rs new file mode 100644 index 0000000..7ea6d58 --- /dev/null +++ b/argparse/src/from_cli.rs @@ -0,0 +1,100 @@ +use std::str::FromStr; +use std::path::PathBuf; +use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr}; + +use super::FromCommandLine; + + +impl FromCommandLine for PathBuf { +    fn from_argument(s: &str) -> Result<Self, String> { +        Ok(From::from(s)) +    } +} + +impl FromCommandLine for f32 { +    fn from_argument(s: &str) -> Result<Self, String> { +        FromStr::from_str(s).map_err(|e| format!("{:?}", e)) +    } +} +impl FromCommandLine for f64 { +    fn from_argument(s: &str) -> Result<Self, String> { +        FromStr::from_str(s).map_err(|e| format!("{:?}", e)) +    } +} + +// TODO(tailhook) implement various radices for integer values +impl FromCommandLine for isize { +    fn from_argument(s: &str) -> Result<Self, String> { +        FromStr::from_str(s).map_err(|e| format!("{:?}", e)) +    } +} +impl FromCommandLine for i8 { +    fn from_argument(s: &str) -> Result<Self, String> { +        FromStr::from_str(s).map_err(|e| format!("{:?}", e)) +    } +} +impl FromCommandLine for i16 { +    fn from_argument(s: &str) -> Result<Self, String> { +        FromStr::from_str(s).map_err(|e| format!("{:?}", e)) +    } +} +impl FromCommandLine for i32 { +    fn from_argument(s: &str) -> Result<Self, String> { +        FromStr::from_str(s).map_err(|e| format!("{:?}", e)) +    } +} +impl FromCommandLine for i64 { +    fn from_argument(s: &str) -> Result<Self, String> { +        FromStr::from_str(s).map_err(|e| format!("{:?}", e)) +    } +} +impl FromCommandLine for usize { +    fn from_argument(s: &str) -> Result<Self, String> { +        FromStr::from_str(s).map_err(|e| format!("{:?}", e)) +    } +} +impl FromCommandLine for u8 { +    fn from_argument(s: &str) -> Result<Self, String> { +        FromStr::from_str(s).map_err(|e| format!("{:?}", e)) +    } +} +impl FromCommandLine for u16 { +    fn from_argument(s: &str) -> Result<Self, String> { +        FromStr::from_str(s).map_err(|e| format!("{:?}", e)) +    } +} +impl FromCommandLine for u32 { +    fn from_argument(s: &str) -> Result<Self, String> { +        FromStr::from_str(s).map_err(|e| format!("{:?}", e)) +    } +} +impl FromCommandLine for u64 { +    fn from_argument(s: &str) -> Result<Self, String> { +        FromStr::from_str(s).map_err(|e| format!("{:?}", e)) +    } +} +impl FromCommandLine for bool { +    fn from_argument(s: &str) -> Result<Self, String> { +        FromStr::from_str(s).map_err(|e| format!("{:?}", e)) +    } +} +impl FromCommandLine for String { +    fn from_argument(s: &str) -> Result<Self, String> { +        FromStr::from_str(s).map_err(|_| unreachable!()) +    } +} +impl FromCommandLine for Ipv4Addr { +    fn from_argument(s: &str) -> Result<Self, String> { +        FromStr::from_str(s).map_err(|e| format!("{:?}", e)) +    } +} +impl FromCommandLine for Ipv6Addr { +    fn from_argument(s: &str) -> Result<Self, String> { +        FromStr::from_str(s).map_err(|e| format!("{:?}", e)) +    } +} +impl FromCommandLine for SocketAddr { +    fn from_argument(s: &str) -> Result<Self, String> { +        FromStr::from_str(s).map_err(|e| format!("{:?}", e)) +    } +} diff --git a/argparse/src/generic.rs b/argparse/src/generic.rs new file mode 100644 index 0000000..1b1b7dc --- /dev/null +++ b/argparse/src/generic.rs @@ -0,0 +1,133 @@ +use std::cell::RefCell; +use std::str::FromStr; +use std::rc::Rc; + +use super::{StoreConst, Store, StoreOption, List, Collect, PushConst}; +use super::action::Action; +use super::action::{TypedAction, IFlagAction, IArgAction, IArgsAction}; +use super::action::ParseResult; +use super::action::ParseResult::{Parsed, Error}; +use super::action::Action::{Flag, Single, Push, Many}; + +pub struct StoreConstAction<'a, T: 'a> { +    pub value: T, +    pub cell: Rc<RefCell<&'a mut T>>, +} + +pub struct PushConstAction<'a, T: 'a> { +    pub value: T, +    pub cell: Rc<RefCell<&'a mut Vec<T>>>, +} + +pub struct StoreAction<'a, T: 'a> { +    pub cell: Rc<RefCell<&'a mut T>>, +} + +pub struct StoreOptionAction<'a, T: 'a> { +    cell: Rc<RefCell<&'a mut Option<T>>>, +} + +pub struct ListAction<'a, T: 'a> { +    cell: Rc<RefCell<&'a mut Vec<T>>>, +} + +impl<T: 'static + Clone> TypedAction<T> for StoreConst<T> { +    fn bind<'x>(&self, cell: Rc<RefCell<&'x mut T>>) -> Action<'x> { +        let StoreConst(ref val) = *self; +        return Flag(Box::new(StoreConstAction { cell: cell, value: val.clone() })); +    } +} + +impl<T: 'static + Clone> TypedAction<Vec<T>> for PushConst<T> { +    fn bind<'x>(&self, cell: Rc<RefCell<&'x mut Vec<T>>>) -> Action<'x> { +        let PushConst(ref val) = *self; +        return Flag(Box::new(PushConstAction { cell: cell, value: val.clone() })); +    } +} + +impl<T: 'static + FromStr> TypedAction<T> for Store { +    fn bind<'x>(&self, cell: Rc<RefCell<&'x mut T>>) -> Action<'x> { +        return Single(Box::new(StoreAction { cell: cell })); +    } +} + +impl<T: 'static + FromStr> TypedAction<Option<T>> for StoreOption { +    fn bind<'x>(&self, cell: Rc<RefCell<&'x mut Option<T>>>) -> Action<'x> { +        return Single(Box::new(StoreOptionAction { cell: cell })); +    } +} + +impl<T: 'static + FromStr + Clone> TypedAction<Vec<T>> for List { +    fn bind<'x>(&self, cell: Rc<RefCell<&'x mut Vec<T>>>) -> Action<'x> { +        return Many(Box::new(ListAction { cell: cell })); +    } +} + +impl<T: 'static + FromStr + Clone> TypedAction<Vec<T>> for Collect { +    fn bind<'x>(&self, cell: Rc<RefCell<&'x mut Vec<T>>>) -> Action<'x> { +        return Push(Box::new(ListAction { cell: cell })); +    } +} + +impl<'a, T: Clone> IFlagAction for StoreConstAction<'a, T> { +    fn parse_flag(&self) -> ParseResult { +        let mut targ = self.cell.borrow_mut(); +        **targ = self.value.clone(); +        return Parsed; +    } +} + +impl<'a, T: Clone> IFlagAction for PushConstAction<'a, T> { +    fn parse_flag(&self) -> ParseResult { +        let mut targ = self.cell.borrow_mut(); +        targ.push(self.value.clone()); +        return Parsed; +    } +} + +impl<'a, T: FromStr> IArgAction for StoreAction<'a, T> { +    fn parse_arg(&self, arg: &str) -> ParseResult { +        match FromStr::from_str(arg) { +            Ok(x) => { +                **self.cell.borrow_mut() = x; +                return Parsed; +            } +            Err(_) => { +                return Error(format!("Bad value {}", arg)); +            } +        } +    } +} + +impl<'a, T: FromStr> IArgAction for StoreOptionAction<'a, T> { +    fn parse_arg(&self, arg: &str) -> ParseResult { +        match FromStr::from_str(arg) { +            Ok(x) => { +                **self.cell.borrow_mut() = Some(x); +                return Parsed; +            } +            Err(_) => { +                return Error(format!("Bad value {}", arg)); +            } +        } +    } +} + +impl<'a, T: FromStr + Clone> IArgsAction for ListAction<'a, T> { +    fn parse_args(&self, args: &[&str]) -> ParseResult { +        let mut result = vec!(); +        for arg in args.iter() { +            match FromStr::from_str(*arg) { +                Ok(x) => { +                    result.push(x); +                } +                Err(_) => { +                    return Error(format!("Bad value {}", arg)); +                } +            } +        } +        **self.cell.borrow_mut() = result; +        return Parsed; +    } +} + diff --git a/argparse/src/help.rs b/argparse/src/help.rs new file mode 100644 index 0000000..c7145be --- /dev/null +++ b/argparse/src/help.rs @@ -0,0 +1,93 @@ +use std::str::CharIndices; +use std::io::Result as IoResult; +use std::io::Write; + +use super::action::{IFlagAction, ParseResult}; +use super::action::ParseResult::Help; + +pub struct HelpAction; + +impl IFlagAction for HelpAction { +    fn parse_flag(&self) -> ParseResult { +        return Help; +    } +} + + +struct WordsIter<'a> { +    data: &'a str, +    iter: CharIndices<'a>, +} + +impl<'a> WordsIter<'a> { +    fn new(data: &'a str) -> WordsIter<'a> { +        return WordsIter { +            data: data, +            iter: data.char_indices(), +            }; +    } +} + +impl<'a> Iterator for WordsIter<'a> { +    type Item = &'a str; +    fn next(&mut self) -> Option<&'a str> { +        let word_start; +        loop { +            let (idx, ch) = match self.iter.next() { +                None => return None, +                Some((idx, ch)) => ((idx, ch)), +            }; +            match ch { +                ' ' | '\t' | '\r' | '\n' => continue, +                _ => { +                    word_start = idx; +                    break; +                } +            } +        } +        loop { +            let (idx, ch) = match self.iter.next() { +                None => break, +                Some((idx, ch)) => ((idx, ch)), +            }; +            match ch { +                ' ' | '\t' | '\r' | '\n' => { +                    return Some(&self.data[word_start..idx]); +                } +                _ => continue, +            } +        } +        return Some(&self.data[word_start..self.data.len()]); +    } +} + +pub fn wrap_text(buf: &mut Write, data: &str, width: usize, indent: usize) +    -> IoResult<()> +{ +    let mut witer = WordsIter::new(data); +    let mut off = indent; +    match witer.next() { +        None => { +            return Ok(()); +        } +        Some(word) => { +            try!(buf.write(word.as_bytes())); +            off += word.len(); +        } +    } +    for word in witer { +        if off + word.len() + 1 > width { +            try!(buf.write(b"\n")); +            for _ in 0..indent { +                try!(buf.write(b" ")); +            } +            off = indent; +        } else { +            try!(buf.write(b" ")); +            off += 1; +        } +        try!(buf.write(word.as_bytes())); +        off += word.len(); +    } +    return Ok(()); +} diff --git a/argparse/src/lib.rs b/argparse/src/lib.rs new file mode 100644 index 0000000..62b0cf9 --- /dev/null +++ b/argparse/src/lib.rs @@ -0,0 +1,64 @@ +#![crate_name = "argparse"] +#![crate_type = "lib"] + +pub use self::parser::{ArgumentParser, Ref}; + +pub mod action; +pub mod parser; +mod generic; +mod custom; +mod help; +mod print; + +mod bool; +mod num; +mod from_cli; + +pub trait FromCommandLine: Sized { +    fn from_argument(s: &str) -> Result<Self, String>; +} + +// TODO(tailhook) make consts +pub struct StoreTrue; +pub struct StoreFalse; + +pub struct StoreConst<T>(pub T); + +pub struct PushConst<T>(pub T); + +pub struct Store; +pub struct Parse; + +pub struct StoreOption; +pub struct ParseOption; + +pub struct List; +pub struct ParseList; + +pub struct Collect; +pub struct ParseCollect; + +/// Print string and exit with status 0 +/// +/// Particularly useful for `--version` option and similar +pub struct Print(pub String); + +pub struct IncrBy<T>(pub T); + +pub struct DecrBy<T>(pub T); + + +#[cfg(test)] mod test_parser; +#[cfg(test)] mod test_bool; +#[cfg(test)] mod test_int; +#[cfg(test)] mod test_float; +#[cfg(test)] mod test_str; +#[cfg(test)] mod test_enum; +#[cfg(test)] mod test_pos; +#[cfg(test)] mod test_many; +#[cfg(test)] mod test_optional; +#[cfg(test)] mod test_usage; +#[cfg(test)] mod test_help; +#[cfg(test)] mod test_env; +#[cfg(test)] mod test_const; +#[cfg(test)] mod test_path; diff --git a/argparse/src/num.rs b/argparse/src/num.rs new file mode 100644 index 0000000..9f34de7 --- /dev/null +++ b/argparse/src/num.rs @@ -0,0 +1,58 @@ +use std::cell::RefCell; +use std::rc::Rc; +use std::ops::{Add, Sub}; + +use super::{IncrBy, DecrBy}; +use super::action::{TypedAction, Action, ParseResult}; +use super::action::ParseResult::Parsed; +use super::action::IFlagAction; +use super::action::Action::Flag; + +pub struct IncrByAction<'a, T: 'a> { +    delta: T, +    cell: Rc<RefCell<&'a mut T>>, +} + +pub struct DecrByAction<'a, T: 'a> { +    delta: T, +    cell: Rc<RefCell<&'a mut T>>, +} + +impl<T: 'static + Add<Output = T> + Clone> TypedAction<T> for IncrBy<T> { +    fn bind<'x>(&self, cell: Rc<RefCell<&'x mut T>>) -> Action<'x> { +        let IncrBy(ref delta) = *self; +        return Flag(Box::new(IncrByAction { cell: cell, delta: delta.clone() })); +    } +} + +impl<T: 'static + Sub<Output = T> + Clone> TypedAction<T> for DecrBy<T> { +    fn bind<'x>(&self, cell: Rc<RefCell<&'x mut T>>) -> Action<'x> { +        let DecrBy(ref delta) = *self; +        return Flag(Box::new(DecrByAction { cell: cell, delta: delta.clone() })); +    } +} + +impl<'a, T: Add<Output = T> + Clone> IFlagAction for IncrByAction<'a, T> { +    fn parse_flag(&self) -> ParseResult { +        let oldval = { +            let targ = self.cell.borrow(); +            targ.clone() +        }; +        let mut targ = self.cell.borrow_mut(); +        **targ = oldval + self.delta.clone(); +        return Parsed; +    } +} + +impl<'a, T: Sub<Output = T> + Clone> IFlagAction for DecrByAction<'a, T> { +    fn parse_flag(&self) -> ParseResult { +        let oldval = { +            let targ = self.cell.borrow(); +            targ.clone() +        }; +        let mut targ = self.cell.borrow_mut(); +        **targ = oldval - self.delta.clone(); +        return Parsed; +    } +} + diff --git a/argparse/src/parser.rs b/argparse/src/parser.rs new file mode 100644 index 0000000..ac7e3da --- /dev/null +++ b/argparse/src/parser.rs @@ -0,0 +1,967 @@ +use std::env; +use std::io::{Write}; +use std::io::Result as IoResult; +use std::io::{stdout, stderr}; +use std::rc::Rc; +use std::cell::RefCell; +use std::iter::Peekable; +use std::slice::Iter; +use std::hash::Hash; +use std::hash::Hasher; +use std::str::FromStr; +use std::process::exit; + +#[allow(unused_imports)] #[allow(deprecated)] +use std::ascii::AsciiExt; + +use std::collections::HashMap; +use std::collections::hash_map::Entry; +use std::collections::HashSet; + +use super::action::{Action, ParseResult}; +use super::action::ParseResult::{Parsed, Help, Exit, Error}; +use super::action::TypedAction; +use super::action::Action::{Flag, Single, Push, Many}; +use super::action::IArgAction; +use super::generic::StoreAction; +use super::help::{HelpAction, wrap_text}; +use action::IFlagAction; + +use self::ArgumentKind::{Positional, ShortOption, LongOption, Delimiter}; + + +static OPTION_WIDTH: usize = 24; +static TOTAL_WIDTH: usize = 79; + + +enum ArgumentKind { +    Positional, +    ShortOption, +    LongOption, +    Delimiter, // Barely "--" +} + + +impl ArgumentKind { +    fn check(name: &str) -> ArgumentKind { +        let mut iter = name.chars(); +        let char1 = iter.next(); +        let char2 = iter.next(); +        let char3 = iter.next(); +        return match char1 { +            Some('-') => match char2 { +                Some('-') => match char3 { +                        Some(_) => LongOption, // --opt +                        None => Delimiter,  // just -- +                }, +                Some(_) => ShortOption,  // -opts +                None => Positional,  // single dash +            }, +            Some(_) | None => Positional, +        } +    } +} + +struct GenericArgument<'parser> { +    id: usize, +    varid: usize, +    name: &'parser str, +    help: &'parser str, +    action: Action<'parser>, +} + +struct GenericOption<'parser> { +    id: usize, +    varid: Option<usize>, +    names: Vec<&'parser str>, +    help: &'parser str, +    action: Action<'parser>, +} + +struct EnvVar<'parser> { +    varid: usize, +    name: &'parser str, +    action: Box<IArgAction + 'parser>, +} + +impl<'a> Hash for GenericOption<'a> { +    fn hash<H>(&self, state: &mut H) where H: Hasher { +        self.id.hash(state); +    } +} + +impl<'a> PartialEq for GenericOption<'a> { +    fn eq(&self, other: &GenericOption<'a>) -> bool { +        return self.id == other.id; +    } +} + +impl<'a> Eq for GenericOption<'a> {} + +impl<'a> Hash for GenericArgument<'a> { +    fn hash<H>(&self, state: &mut H) where H: Hasher { +        self.id.hash(state); +    } +} + +impl<'a> PartialEq for GenericArgument<'a> { +    fn eq(&self, other: &GenericArgument<'a>) -> bool { +        return self.id == other.id; +    } +} + +impl<'a> Eq for GenericArgument<'a> {} + +pub struct Var { +    id: usize, +    metavar: String, +    required: bool, +} + +impl Hash for Var { +    fn hash<H>(&self, state: &mut H) where H: Hasher { +        self.id.hash(state); +    } +} + +impl PartialEq for Var { +    fn eq(&self, other: &Var) -> bool { +        return self.id == other.id; +    } +} + +impl Eq for Var {} + +struct Context<'ctx, 'parser: 'ctx> { +    parser: &'ctx ArgumentParser<'parser>, +    set_vars: HashSet<usize>, +    list_options: HashMap<Rc<GenericOption<'parser>>, Vec<&'ctx str>>, +    list_arguments: HashMap<Rc<GenericArgument<'parser>>, Vec<&'ctx str>>, +    arguments: Vec<&'ctx str>, +    iter: Peekable<Iter<'ctx, String>>, +    stderr: &'ctx mut (Write + 'ctx), +} + +impl<'a, 'b> Context<'a, 'b> { + +    fn parse_option(&mut self, opt: Rc<GenericOption<'b>>, +        optarg: Option<&'a str>) +        -> ParseResult +    { +        let value = match optarg { +            Some(value) => value, +            None => match self.iter.next() { +                Some(value) => { +                    &value[..] +                } +                None => { +                    return match opt.action { +                        Many(_) => Parsed, +                        _ => Error(format!( +                            // TODO(tailhook) is {:?} ok? +                            "Option {:?} requires an argument", opt.names)), +                    }; +                } +            }, +        }; +        match opt.varid { +            Some(varid) => { self.set_vars.insert(varid); } +            None => {} +        } +        match opt.action { +            Single(ref action) => { +                return action.parse_arg(value); +            } +            Push(_) => { +                (match self.list_options.entry(opt.clone()) { +                    Entry::Occupied(occ) => occ.into_mut(), +                    Entry::Vacant(vac) => vac.insert(Vec::new()), +                }).push(value); +                return Parsed; +            } +            Many(_) => { +                let vec = match self.list_options.entry(opt.clone()) { +                    Entry::Occupied(occ) => occ.into_mut(), +                    Entry::Vacant(vac) => vac.insert(Vec::new()), +                }; +                vec.push(value); +                match optarg { +                    Some(_) => return Parsed, +                    _ => {} +                } +                loop { +                    match self.iter.peek() { +                        None => { break; } +                        Some(arg) if arg.starts_with("-") => { +                            break; +                        } +                        Some(value) => { +                            vec.push(&value[..]); +                        } +                    } +                    self.iter.next(); +                } +                return Parsed; +            } +            _ => panic!(), +        }; +    } + +    fn parse_long_option(&mut self, arg: &'a str) -> ParseResult { +        let mut equals_iter = arg.splitn(2, '='); +        let optname = equals_iter.next().unwrap(); +        let valueref = equals_iter.next(); +        let opt = self.parser.long_options.get(&optname.to_string()); +        match opt { +            Some(opt) => { +                match opt.action { +                    Flag(ref action) => { +                        match valueref { +                            Some(_) => { +                                return Error(format!( +                                    "Option {} does not accept an argument", +                                    optname)); +                            } +                            None => { +                                match opt.varid { +                                    Some(varid) => { +                                        self.set_vars.insert(varid); +                                    } +                                    None => {} +                                } +                                return action.parse_flag(); +                            } +                        } +                    } +                    Single(_) | Push(_) | Many(_) => { +                        return self.parse_option(opt.clone(), valueref); +                    } +                } +            } +            None => { +                return Error(format!("Unknown option {}", arg)); +            } +        } +    } + +    fn parse_short_options<'x>(&'x mut self, arg: &'a str) -> ParseResult { +        let mut iter = arg.char_indices(); +        iter.next(); +        for (idx, ch) in iter { +            let opt = match self.parser.short_options.get(&ch) { +                Some(opt) => { opt } +                None => { +                    return Error(format!("Unknown short option \"{}\"", ch)); +                } +            }; +            let res = match opt.action { +                Flag(ref action) => { +                    match opt.varid { +                        Some(varid) => { self.set_vars.insert(varid); } +                        None => {} +                    } +                    action.parse_flag() +                } +                Single(_) | Push(_) | Many(_) => { +                    let value; +                    if idx + 1 < arg.len() { +                        value = Some(&arg[idx+1..arg.len()]); +                    } else { +                        value = None; +                    } +                    return self.parse_option(opt.clone(), value); +                } +            }; +            match res { +                Parsed => { continue; } +                x => { return x; } +            } +        } +        return Parsed; +    } + +    fn postpone_argument(&mut self, arg: &'a str) { +        self.arguments.push(arg); +    } + +    fn parse_options(&mut self) -> ParseResult { +        self.iter.next();  // Command name +        loop { +            let next = self.iter.next(); +            let arg = match next { +                Some(arg) => { arg } +                None => { break; } +            }; +            let res = match ArgumentKind::check(&arg[..]) { +                Positional => { +                    self.postpone_argument(&arg[..]); +                    if self.parser.stop_on_first_argument { +                        break; +                    } +                    continue; +                } +                LongOption => self.parse_long_option(&arg[..]), +                ShortOption => self.parse_short_options(&arg[..]), +                Delimiter => { +                    if !self.parser.silence_double_dash { +                        self.postpone_argument("--"); +                    } +                    break; +                } +            }; +            match res { +                Parsed => continue, +                _ => return res, +            } +        } + +        loop { +            match self.iter.next() { +                None => break, +                Some(arg) => self.postpone_argument(&arg[..]), +            } +        } +        return Parsed; +    } + +    fn parse_arguments(&mut self) -> ParseResult { +        let mut pargs = self.parser.arguments.iter(); +        for arg in self.arguments.iter() { +            let opt; +            loop { +                match pargs.next() { +                    Some(option) => { +                        if self.set_vars.contains(&option.varid) { +                            continue; +                        } +                        opt = option; +                        break; +                    } +                    None => match self.parser.catchall_argument { +                        Some(ref option) => { +                            opt = option; +                            break; +                        } +                        None => return Error(format!( +                            "Unexpected argument {}", arg)), +                    } +                }; +            } +            let res = match opt.action { +                Single(ref act) => { +                    self.set_vars.insert(opt.varid); +                    act.parse_arg(*arg) +                }, +                Many(_) | Push(_) => { +                    (match self.list_arguments.entry(opt.clone()) { +                        Entry::Occupied(occ) => occ.into_mut(), +                        Entry::Vacant(vac) => vac.insert(Vec::new()), +                    }).push(*arg); +                    Parsed +                }, +                _ => unreachable!(), +            }; +            match res { +                Parsed => continue, +                _ => return res, +            } +        } +        return Parsed; +    } + +    fn parse_list_vars(&mut self) -> ParseResult { +        for (opt, lst) in self.list_options.iter() { +            match opt.action { +                Push(ref act) | Many(ref act) => { +                    let res = act.parse_args(&lst[..]); +                    match res { +                        Parsed => continue, +                        _ => return res, +                    } +                } +                _ => panic!(), +            } +        } +        for (opt, lst) in self.list_arguments.iter() { +            match opt.action { +                Push(ref act) | Many(ref act) => { +                    let res = act.parse_args(&lst[..]); +                    match res { +                        Parsed => continue, +                        _ => return res, +                    } +                } +                _ => panic!(), +            } +        } +        return Parsed; +    } + +    fn parse_env_vars(&mut self) -> ParseResult { +        for evar in self.parser.env_vars.iter() { +            match env::var(evar.name) { +                Ok(val) => { +                    match evar.action.parse_arg(&val[..]) { +                        Parsed => { +                            self.set_vars.insert(evar.varid); +                            continue; +                        } +                        Error(err) => { +                            write!(self.stderr, +                                "WARNING: Environment variable {}: {}\n", +                                evar.name, err).ok(); +                        } +                        _ => unreachable!(), +                    } +                } +                Err(_) => {} +            } +        } +        return Parsed; +    } + +    fn check_required(&mut self) -> ParseResult { +        // Check for required arguments +        for var in self.parser.vars.iter() { +            if var.required && !self.set_vars.contains(&var.id) { +                // First try positional arguments +                for opt in self.parser.arguments.iter() { +                    if opt.varid == var.id { +                        return Error(format!( +                            "Argument {} is required", opt.name)); +                    } +                } +                // Then options +                let mut all_options = vec!(); +                for opt in self.parser.options.iter() { +                    match opt.varid { +                        Some(varid) if varid == var.id => {} +                        _ => { continue } +                    } +                    all_options.extend(opt.names.clone().into_iter()); +                } +                if all_options.len() > 1 { +                    return Error(format!( +                        "One of the options {:?} is required", all_options)); +                } else if all_options.len() == 1 { +                    return Error(format!( +                        "Option {:?} is required", all_options)); +                } +                // Then envvars +                for envvar in self.parser.env_vars.iter() { +                    if envvar.varid == var.id { +                        return Error(format!( +                            "Environment var {} is required", envvar.name)); +                    } +                } +            } +        } +        return Parsed; +    } + +    fn parse(parser: &ArgumentParser, args: &Vec<String>, stderr: &mut Write) +        -> ParseResult +    { +        let mut ctx = Context { +            parser: parser, +            iter: args.iter().peekable(), +            set_vars: HashSet::new(), +            list_options: HashMap::new(), +            list_arguments: HashMap::new(), +            arguments: Vec::new(), +            stderr: stderr, +        }; + +        match ctx.parse_env_vars() { +            Parsed => {} +            x => { return x; } +        } + +        match ctx.parse_options() { +            Parsed => {} +            x => { return x; } +        } + +        match ctx.parse_arguments() { +            Parsed => {} +            x => { return x; } +        } + +        match ctx.parse_list_vars() { +            Parsed => {} +            x => { return x; } +        } + +        match ctx.check_required() { +            Parsed => {} +            x => { return x; } +        } + +        return Parsed; +    } +} + +pub struct Ref<'parser:'refer, 'refer, T: 'parser> { +    cell: Rc<RefCell<&'parser mut T>>, +    varid: usize, +    parser: &'refer mut ArgumentParser<'parser>, +} + +impl<'parser, 'refer, T> Ref<'parser, 'refer, T> { + +    pub fn add_option<'x, A: TypedAction<T>>(&'x mut self, +        names: &[&'parser str], action: A, help: &'parser str) +        -> &'x mut Ref<'parser, 'refer, T> +    { +        { +            let var = &mut self.parser.vars[self.varid]; +            if var.metavar.len() == 0 { +                let mut longest_name = names[0]; +                let mut llen = longest_name.len(); +                for name in names.iter() { +                    if name.len() > llen { +                        longest_name = *name; +                        llen = longest_name.len(); +                    } +                } +                if llen > 2 { +                    var.metavar = longest_name[2..llen] +                        .to_ascii_uppercase().replace("-", "_"); +                } +            } +        } +        self.parser.add_option_for(Some(self.varid), names, +            action.bind(self.cell.clone()), +            help); +        return self; +    } + +    pub fn add_argument<'x, A: TypedAction<T>>(&'x mut self, +        name: &'parser str, action: A, help: &'parser str) +        -> &'x mut Ref<'parser, 'refer, T> +    { +        let act = action.bind(self.cell.clone()); +        let opt = Rc::new(GenericArgument { +            id: self.parser.arguments.len(), +            varid: self.varid, +            name: name, +            help: help, +            action: act, +            }); +        match opt.action { +            Flag(_) => panic!("Flag arguments can't be positional"), +            Many(_) | Push(_) => { +                match self.parser.catchall_argument { +                    Some(ref y) => panic!(format!( +                        "Option {} conflicts with option {}", +                        name, y.name)), +                    None => {}, +                } +                self.parser.catchall_argument = Some(opt); +            } +            Single(_) => { +                self.parser.arguments.push(opt); +            } +        } +        { +            let var = &mut self.parser.vars[self.varid]; +            if var.metavar.len() == 0 { +                var.metavar = name.to_string(); +            } +        } +        return self; +    } + +    pub fn metavar<'x>(&'x mut self, name: &str) +        -> &'x mut Ref<'parser, 'refer, T> +    { +        { +            let var = &mut self.parser.vars[self.varid]; +            var.metavar = name.to_string(); +        } +        return self; +    } + +    pub fn required<'x>(&'x mut self) +        -> &'x mut Ref<'parser, 'refer, T> +    { +        { +            let var = &mut self.parser.vars[self.varid]; +            var.required = true; +        } +        return self; +    } +} + +impl<'parser, 'refer, T: 'static + FromStr> Ref<'parser, 'refer, T> { +    pub fn envvar<'x>(&'x mut self, varname: &'parser str) +        -> &'x mut Ref<'parser, 'refer, T> +    { +        self.parser.env_vars.push(Rc::new(EnvVar { +            varid: self.varid, +            name: varname, +            action: Box::new(StoreAction { cell: self.cell.clone() }), +            })); +        return self; +    } +} + +/// The main argument parser class +pub struct ArgumentParser<'parser> { +    description: &'parser str, +    vars: Vec<Box<Var>>, +    options: Vec<Rc<GenericOption<'parser>>>, +    arguments: Vec<Rc<GenericArgument<'parser>>>, +    env_vars: Vec<Rc<EnvVar<'parser>>>, +    catchall_argument: Option<Rc<GenericArgument<'parser>>>, +    short_options: HashMap<char, Rc<GenericOption<'parser>>>, +    long_options: HashMap<String, Rc<GenericOption<'parser>>>, +    stop_on_first_argument: bool, +    silence_double_dash: bool, +} + + + +impl<'parser> ArgumentParser<'parser> { + +    /// Create an empty argument parser +    pub fn new() -> ArgumentParser<'parser> { + +        let mut ap = ArgumentParser { +            description: "", +            vars: Vec::new(), +            env_vars: Vec::new(), +            arguments: Vec::new(), +            catchall_argument: None, +            options: Vec::new(), +            short_options: HashMap::new(), +            long_options: HashMap::new(), +            stop_on_first_argument: false, +            silence_double_dash: true, +            }; +        ap.add_option_for(None, &["-h", "--help"], Flag(Box::new(HelpAction)), +            "Show this help message and exit"); +        return ap; +    } + +    /// Borrow mutable variable for an argument +    /// +    /// This returns `Ref` object which should be used configure the option +    pub fn refer<'x, T>(&'x mut self, val: &'parser mut T) +        -> Box<Ref<'parser, 'x, T>> +    { +        let cell = Rc::new(RefCell::new(val)); +        let id = self.vars.len(); +        self.vars.push(Box::new(Var { +                id: id, +                required: false, +                metavar: "".to_string(), +                })); +        return Box::new(Ref { +            cell: cell.clone(), +            varid: id, +            parser: self, +        }); +    } + +    /// Add option to argument parser +    /// +    /// This is only useful for options that don't store value. For +    /// example `Print(...)` +    pub fn add_option<F:IFlagAction + 'parser>(&mut self, +        names: &[&'parser str], action: F, help: &'parser str) +    { +        self.add_option_for(None, names, Flag(Box::new(action)), help); +    } + +    /// Set description of the command +    pub fn set_description(&mut self, descr: &'parser str) { +        self.description = descr; +    } + +    fn add_option_for(&mut self, var: Option<usize>, +        names: &[&'parser str], +        action: Action<'parser>, help: &'parser str) +    { +        let opt = Rc::new(GenericOption { +            id: self.options.len(), +            varid: var, +            names: names.to_vec(), +            help: help, +            action: action, +            }); + +        if names.len() < 1 { +            panic!("At least one name for option must be specified"); +        } +        for nameptr in names.iter() { +            let name = *nameptr; +            match ArgumentKind::check(name) { +                Positional|Delimiter => { +                    panic!("Bad argument name {}", name); +                } +                LongOption => { +                    self.long_options.insert( +                        name.to_string(), opt.clone()); +                } +                ShortOption => { +                    if name.len() > 2 { +                        panic!("Bad short argument {}", name); +                    } +                    self.short_options.insert( +                        name.as_bytes()[1] as char, opt.clone()); +                } +            } +        } +        self.options.push(opt); +    } + +    /// Print help +    /// +    /// Usually command-line option is used for printing help, +    /// this is here for any awkward cases +    pub fn print_help(&self, name: &str, writer: &mut Write) -> IoResult<()> { +        return HelpFormatter::print_help(self, name, writer); +    } + +    /// Print usage +    /// +    /// Usually printed into stderr on error of command-line parsing +    pub fn print_usage(&self, name: &str, writer: &mut Write) -> IoResult<()> +    { +        return HelpFormatter::print_usage(self, name, writer); +    } + +    /// Parse arguments +    /// +    /// This is most powerful method. Usually you need `parse_args` +    /// or `parse_args_or_exit` instead +    pub fn parse(&self, args: Vec<String>, +        stdout: &mut Write, stderr: &mut Write) +        -> Result<(), i32> +    { +        let name = if args.len() > 0 { &args[0][..] } else { "unknown" }; +        match Context::parse(self, &args, stderr) { +            Parsed => return Ok(()), +            Exit => return Err(0), +            Help => { +                self.print_help(name, stdout).unwrap(); +                return Err(0); +            } +            Error(message) => { +                self.error(&name[..], &message[..], stderr); +                return Err(2); +            } +        } +    } + +    /// Write an error similar to one produced by the library itself +    /// +    /// Only needed if you like to do some argument validation that is out +    /// of scope of the argparse +    pub fn error(&self, command: &str, message: &str, writer: &mut Write) { +        self.print_usage(command, writer).unwrap(); +        write!(writer, "{}: {}\n", command, message).ok(); +    } + +    /// Configure parser to ignore options when first non-option argument is +    /// encountered. +    /// +    /// Useful for commands that want to pass following options to the +    /// subcommand or subprocess, but need some options to be set before +    /// command is specified. +    pub fn stop_on_first_argument(&mut self, want_stop: bool) { +        self.stop_on_first_argument = want_stop; +    } + +    /// Do not put double-dash (bare `--`) into argument +    /// +    /// The double-dash is used to stop parsing options and treat all the +    /// following tokens as the arguments regardless of whether they start +    /// with dash (minus) or not. +    /// +    /// The method only useful for `List` arguments. On by default. The method +    /// allows to set option to `false` so that `cmd xx -- yy` will get +    /// ``xx -- yy`` as arguments instead of ``xx yy`` by default. This is +    /// useful if your ``--`` argument is meaningful. Only first double-dash +    /// is ignored by default. +    pub fn silence_double_dash(&mut self, silence: bool) { +        self.silence_double_dash = silence; +    } + +    /// Convenience method to parse arguments +    /// +    /// On error returns error code that is supposed to be returned by +    /// an application. (i.e. zero on `--help` and `2` on argument error) +    pub fn parse_args(&self) -> Result<(), i32> { +        // TODO(tailhook) can we get rid of collect? +        return self.parse(env::args().collect(), +            &mut stdout(), &mut stderr()); +    } + +    /// The simplest conveninece method +    /// +    /// The method returns only in case of successful parsing or exits with +    /// appropriate code (including successful on `--help`) otherwise. +    pub fn parse_args_or_exit(&self) { +        // TODO(tailhook) can we get rid of collect? +        self.parse(env::args().collect(), &mut stdout(), &mut stderr()) +            .map_err(|c| exit(c)) +            .ok(); +    } +} + +struct HelpFormatter<'a, 'b: 'a> { +    name: &'a str, +    parser: &'a ArgumentParser<'b>, +    buf: &'a mut (Write + 'a), +} + +impl<'a, 'b> HelpFormatter<'a, 'b> { +    pub fn print_usage(parser: &ArgumentParser, name: &str, writer: &mut Write) +        -> IoResult<()> +    { +        return HelpFormatter { parser: parser, name: name, buf: writer } +            .write_usage(); +    } + +    pub fn print_help(parser: &ArgumentParser, name: &str, writer: &mut Write) +        -> IoResult<()> +    { +        return HelpFormatter { parser: parser, name: name, buf: writer } +            .write_help(); +    } + +    pub fn print_argument(&mut self, arg: &GenericArgument<'b>) +        -> IoResult<()> +    { +        let mut num = 2; +        try!(write!(self.buf, "  {}", arg.name)); +        num += arg.name.len(); +        if num >= OPTION_WIDTH { +            try!(write!(self.buf, "\n")); +            for _ in 0..OPTION_WIDTH { +                try!(write!(self.buf, " ")); +            } +        } else { +            for _ in num..OPTION_WIDTH { +                try!(write!(self.buf, " ")); +            } +        } +        try!(wrap_text(self.buf, arg.help, TOTAL_WIDTH, OPTION_WIDTH)); +        try!(write!(self.buf, "\n")); +        return Ok(()); +    } + +    pub fn print_option(&mut self, opt: &GenericOption<'b>) -> IoResult<()> { +        let mut num = 2; +        try!(write!(self.buf, "  ")); +        let mut niter = opt.names.iter(); +        let name = niter.next().unwrap(); +        try!(write!(self.buf, "{}", name)); +        num += name.len(); +        for name in niter { +            try!(write!(self.buf, ",")); +            try!(write!(self.buf, "{}", name)); +            num += name.len() + 1; +        } +        match opt.action { +            Flag(_) => {} +            Single(_) | Push(_) | Many(_) => { +                try!(write!(self.buf, " ")); +                let var = &self.parser.vars[opt.varid.unwrap()]; +                try!(write!(self.buf, "{}", &var.metavar[..])); +                num += var.metavar.len() + 1; +            } +        } +        if num >= OPTION_WIDTH { +            try!(write!(self.buf, "\n")); +            for _ in 0..OPTION_WIDTH { +                try!(write!(self.buf, " ")); +            } +        } else { +            for _ in num..OPTION_WIDTH { +                try!(write!(self.buf, " ")); +            } +        } +        try!(wrap_text(self.buf, opt.help, TOTAL_WIDTH, OPTION_WIDTH)); +        try!(write!(self.buf, "\n")); +        return Ok(()); +    } + +    fn write_help(&mut self) -> IoResult<()> { +        try!(self.write_usage()); +        try!(write!(self.buf, "\n")); +        if self.parser.description.len() > 0 { +            try!(wrap_text(self.buf, self.parser.description,TOTAL_WIDTH, 0)); +            try!(write!(self.buf, "\n")); +        } +        if self.parser.arguments.len() > 0 +            || self.parser.catchall_argument.is_some() +        { +            try!(write!(self.buf, "\nPositional arguments:\n")); +            for arg in self.parser.arguments.iter() { +                try!(self.print_argument(&**arg)); +            } +            match self.parser.catchall_argument { +                Some(ref opt) => { +                    try!(self.print_argument(&**opt)); +                } +                None => {} +            } +        } +        if self.parser.short_options.len() > 0 +            || self.parser.long_options.len() > 0 +        { +            try!(write!(self.buf, "\nOptional arguments:\n")); +            for opt in self.parser.options.iter() { +                try!(self.print_option(&**opt)); +            } +        } +        return Ok(()); +    } + +    fn write_usage(&mut self) -> IoResult<()> { +        try!(write!(self.buf, "Usage:\n  ")); +        try!(write!(self.buf, "{}", self.name)); +        if self.parser.options.len() != 0 { +            if self.parser.short_options.len() > 1 +                || self.parser.long_options.len() > 1 +            { +                try!(write!(self.buf, " [OPTIONS]")); +            } +            for opt in self.parser.arguments.iter() { +                let var = &self.parser.vars[opt.varid]; +                try!(write!(self.buf, " ")); +                if !var.required { +                    try!(write!(self.buf, "[")); +                } +                try!(write!(self.buf, "{}", +                    &opt.name.to_ascii_uppercase()[..])); +                if !var.required { +                    try!(write!(self.buf, "]")); +                } +            } +            match self.parser.catchall_argument { +                Some(ref opt) => { +                    let var = &self.parser.vars[opt.varid]; +                    try!(write!(self.buf, " ")); +                    if !var.required { +                        try!(write!(self.buf, "[")); +                    } +                    try!(write!(self.buf, "{}", +                        &opt.name.to_ascii_uppercase()[..])); +                    if !var.required { +                        try!(write!(self.buf, " ...]")); +                    } else { +                        try!(write!(self.buf, " [...]")); +                    } +                } +                None => {} +            } +        } +        try!(write!(self.buf, "\n")); +        return Ok(()); +    } + +} diff --git a/argparse/src/print.rs b/argparse/src/print.rs new file mode 100644 index 0000000..d0335c5 --- /dev/null +++ b/argparse/src/print.rs @@ -0,0 +1,13 @@ +use Print; +use action::{IFlagAction, ParseResult}; + +impl IFlagAction for Print { +    fn parse_flag(&self) -> ParseResult { +        if self.0.ends_with("\n") { +            print!("{}", self.0); +        } else { +            println!("{}", self.0); +        } +        return ParseResult::Exit; +    } +} diff --git a/argparse/src/test_bool.rs b/argparse/src/test_bool.rs new file mode 100644 index 0000000..a30cf3c --- /dev/null +++ b/argparse/src/test_bool.rs @@ -0,0 +1,98 @@ +use parser::ArgumentParser; +use super::Store; +use super::{StoreTrue, StoreFalse}; +use test_parser::{check_ok}; + +fn store_true(args: &[&str]) -> bool { +    let mut verbose = false; +    { +        let mut ap = ArgumentParser::new(); +        ap.refer(&mut verbose) +          .add_option(&["-t", "--true"], StoreTrue, +            "Store true action"); +        check_ok(&ap,  args); +    } +    return verbose; +} + +#[test] +fn test_store_true() { +    assert!(!store_true(&["./argparse_test"])); +    assert!(store_true(&["./argparse_test", "--true"])); +} + +fn store_false(args: &[&str]) -> bool { +    let mut verbose = true; +    { +        let mut ap = ArgumentParser::new(); +        ap.refer(&mut verbose) +          .add_option(&["-f", "--false"], StoreFalse, +            "Store false action"); +        check_ok(&ap,  args); +    } +    return verbose; +} +#[test] +fn test_store_false() { +    assert!(store_false(&["./argparse_test"])); +    assert!(!store_false(&["./argparse_test", "--false"])); +} + +fn store_bool(args: &[&str]) -> bool { +    let mut verbose = false; +    { +        let mut ap = ArgumentParser::new(); +        ap.refer(&mut verbose) +          .add_option(&["-f", "--false"], StoreFalse, +            "Store false action") +          .add_option(&["-t", "--true"], StoreTrue, +            "Store false action"); +        check_ok(&ap,  args); +    } +    return verbose; +} + +#[test] +fn test_bool() { +    assert!(!store_bool(&["./argparse_test"])); +    assert!(store_bool(&["./argparse_test", "-t"])); +    assert!(!store_bool(&["./argparse_test", "-f"])); +    assert!(store_bool(&["./argparse_test", "-fft"])); +    assert!(!store_bool(&["./argparse_test", "-fffft", "-f"])); +    assert!(store_bool(&["./argparse_test", "--false", "-fffft", "-f", +                   "--true"])); +} + +fn set_bool(args: &[&str]) -> bool { +    let mut verbose = false; +    { +        let mut ap = ArgumentParser::new(); +        ap.refer(&mut verbose) +          .add_option(&["-s", "--set"], Store, +            "Set boolean value"); +        check_ok(&ap,  args); +    } +    return verbose; +} + + +#[test] +fn test_set_bool() { +    assert!(!set_bool(&["./argparse_test"])); +    assert!(set_bool(&["./argparse_test", "-strue"])); +    assert!(!set_bool(&["./argparse_test", "-sfalse"])); + +    // Unfortunately other values do not work +} + +#[test] +#[should_panic(expected="Bad value yes")] +fn test_bad_bools1() { +    assert!(!set_bool(&["./argparse_test", "-syes"])); +} + +#[test] +#[should_panic(expected="Bad value no")] +fn test_bad_bools2() { +    assert!(!set_bool(&["./argparse_test", "-sno"])); +} diff --git a/argparse/src/test_const.rs b/argparse/src/test_const.rs new file mode 100644 index 0000000..b12e3d8 --- /dev/null +++ b/argparse/src/test_const.rs @@ -0,0 +1,28 @@ +use parser::ArgumentParser; +use super::{PushConst}; +use test_parser::{check_ok}; + + +fn push_const(args: &[&str]) -> Vec<u32> { +    let mut res = vec!(); +    { +        let mut ap = ArgumentParser::new(); +        ap.refer(&mut res) +          .add_option(&["-o", "--one"], PushConst(1), +            "Add one to the list") +          .add_option(&["-t", "--two"], PushConst(2), +            "Add two to the list") +          .add_option(&["-3", "--three"], PushConst(3), +            "Add three to the list"); +        check_ok(&ap,  args); +    } +    return res; +} + +#[test] +fn test_push() { +    assert_eq!(push_const(&["./argparse_test"]), vec!()); +    assert_eq!(push_const(&["./argparse_test", "--one"]), vec!(1)); +    assert_eq!(push_const(&["./argparse_test", "-3"]), vec!(3)); +    assert_eq!(push_const(&["./argparse_test", "-oo3tt"]), vec!(1, 1, 3, 2, 2)); +} diff --git a/argparse/src/test_enum.rs b/argparse/src/test_enum.rs new file mode 100644 index 0000000..5205738 --- /dev/null +++ b/argparse/src/test_enum.rs @@ -0,0 +1,51 @@ +use std::str::FromStr; + +use parser::ArgumentParser; +use super::Store; +use test_parser::{check_ok}; + +use self::Greeting::{Hello, Hi, NoGreeting}; + + +#[derive(PartialEq, Eq, Debug)] +enum Greeting { +    Hello, +    Hi, +    NoGreeting, +} + +impl FromStr for Greeting { +    type Err = (); +    fn from_str(src: &str) -> Result<Greeting, ()> { +        return match src { +            "hello" => Ok(Hello), +            "hi" => Ok(Hi), +            _ => Err(()), +        }; +    } +} + +fn parse_enum(args: &[&str]) -> Greeting { +    let mut val = NoGreeting; +    { +        let mut ap = ArgumentParser::new(); +        ap.refer(&mut val) +          .add_option(&["-g"], Store, +            "Greeting"); +        check_ok(&ap, args); +    } +    return val; +} + +#[test] +fn test_parse_enum() { +    assert_eq!(parse_enum(&["./argparse_test"]), NoGreeting); +    assert_eq!(parse_enum(&["./argparse_test", "-ghello"]), Hello); +    assert_eq!(parse_enum(&["./argparse_test", "-ghi"]), Hi); +} + +#[test] +#[should_panic] +fn test_parse_error() { +    parse_enum(&["./argparse_test", "-ghell"]); +} diff --git a/argparse/src/test_env.rs b/argparse/src/test_env.rs new file mode 100644 index 0000000..2e1b649 --- /dev/null +++ b/argparse/src/test_env.rs @@ -0,0 +1,43 @@ +use std::env; + +use parser::ArgumentParser; +use super::Store; +use test_parser::{check_ok}; + + +fn required(args: &[&str]) -> (isize, isize) { +    let mut val1 = 1isize; +    let mut val2 = 2isize; +    { +        let mut ap = ArgumentParser::new(); +        ap.refer(&mut val1) +            .add_option(&["--v1"], Store, "The value 1") +            .add_argument("v1", Store, "The value 1") +            .envvar("TEST_ENV_REQUIRED_V1") +            .required(); +        ap.refer(&mut val2) +            .add_argument("v2", Store, "The value 2"); +        check_ok(&ap, args); +    } +    return (val1, val2) +} + +#[test] +#[should_panic] +fn test_required() { +    env::set_var("TEST_ENV_REQUIRED_V1", "some_crap"); +    required(&["./argparse_test"]); +    env::remove_var("TEST_ENV_REQUIRED_V1"); +} + +#[test] +fn test_req() { +    env::set_var("TEST_ENV_REQUIRED_V1", "some_crap"); +    assert_eq!(required(&["./argparse_test", "10"]), (10, 2)); +    assert_eq!(required(&["./argparse_test", "11", "21"]), (11, 21)); +    assert_eq!(required(&["./argparse_test", "--v1=7"]), (7, 2)); +    env::set_var("TEST_ENV_REQUIRED_V1", "9"); +    assert_eq!(required(&["./argparse_test", "10"]), (9, 10)); +    assert_eq!(required(&["./argparse_test", "7", "--v1=15"]), (15, 7)); +    env::remove_var("TEST_ENV_REQUIRED_V1"); +} diff --git a/argparse/src/test_float.rs b/argparse/src/test_float.rs new file mode 100644 index 0000000..9ef0f56 --- /dev/null +++ b/argparse/src/test_float.rs @@ -0,0 +1,50 @@ +use parser::ArgumentParser; +use super::{IncrBy,DecrBy}; +use super::Store; +use test_parser::{check_ok}; + +fn incr_decr(args: &[&str]) -> f32 { +    let mut val = 0f32; +    { +        let mut ap = ArgumentParser::new(); +        ap.refer(&mut val) +          .add_option(&["-d", "--decr"], DecrBy(0.25f32), +            "Decrement value") +          .add_option(&["-i", "--incr"], IncrBy(0.5f32), +            "Increment value"); +        check_ok(&ap, args); +    } +    return val; +} + +#[test] +fn test_incr_decr() { +    assert_eq!(incr_decr(&["./argparse_test", +        "--incr", "-iii"]), 2.0); +    assert_eq!(incr_decr(&["./argparse_test", +        "-iiddd", "--incr", "-iii"]), 2.25); +} + +#[test] +fn test_float() { +    let mut val = 0.1; +    { +        let mut ap = ArgumentParser::new(); +        ap.refer(&mut val) +          .add_option(&["-s", "--set"], Store, +            "Set float value"); +        check_ok(&ap, &["./argparse_test", "-s", "15.125"]); +    } +    assert_eq!(val, 15.125); +} + +#[test] +#[should_panic] +fn test_fail() { +    let mut val = 0.1; +    let mut ap = ArgumentParser::new(); +    ap.refer(&mut val) +      .add_option(&["-s", "--set"], Store, +        "Set float value"); +    check_ok(&ap, &["./argparse_test", "-s", "test"]); +} diff --git a/argparse/src/test_help.rs b/argparse/src/test_help.rs new file mode 100644 index 0000000..f23b14b --- /dev/null +++ b/argparse/src/test_help.rs @@ -0,0 +1,154 @@ +use std::str::from_utf8; + +use parser::ArgumentParser; +use super::{Store, List}; + +#[test] +fn test_empty() { +    let mut ap = ArgumentParser::new(); +    let mut buf = Vec::<u8>::new(); +    ap.set_description("Test program"); +    assert!(ap.print_help("./argparse_test", &mut buf).is_ok()); +    assert_eq!("Usage:\n".to_string() +        + "  ./argparse_test\n" +        + "\n" +        + "Test program\n" +        + "\n" +        + "Optional arguments:\n" +        + "  -h,--help             Show this help message and exit\n" +        , from_utf8(&buf[..]).unwrap().to_string()); +} + +#[test] +fn test_options() { +    let mut val = 0; +    let mut val2 = 0; +    let mut ap = ArgumentParser::new(); +    ap.set_description("Test program. The description of the program is ought +        to be very long, because we want to test how word wrapping works for +        it. So some more text would be ok for the test"); +    ap.refer(&mut val) +      .add_option(&["--value"], Store, +        "Set integer value"); +    ap.refer(&mut val2) +      .add_option(&["-L", "--long-option"], Store, +        "Long option value"); +    let mut buf = Vec::<u8>::new(); +    assert!(ap.print_help("./argparse_test", &mut buf).is_ok()); +    assert_eq!("Usage:\n".to_string() +        + "  ./argparse_test [OPTIONS] + +Test program. The description of the program is ought to be very long, because +we want to test how word wrapping works for it. So some more text would be ok +for the test\n" +        + "\n" +        + "Optional arguments:\n" +        + "  -h,--help             Show this help message and exit\n" +        + "  --value VALUE         Set integer value\n" +        + "  -L,--long-option LONG_OPTION\n" +        + "                        Long option value\n" +        , from_utf8(&buf[..]).unwrap().to_string()); +} + +#[test] +fn test_argument() { +    let mut val = 0; +    let mut ap = ArgumentParser::new(); +    ap.set_description("Test program"); +    ap.refer(&mut val) +      .add_argument("value", Store, +        "Integer value"); +    let mut buf = Vec::<u8>::new(); +    assert!(ap.print_help("./argparse_test", &mut buf).is_ok()); +    assert_eq!("Usage:\n".to_string() +        + "  ./argparse_test [VALUE]\n" +        + "\n" +        + "Test program\n" +        + "\n" +        + "Positional arguments:\n" +        + "  value                 Integer value\n" +        + "\n" +        + "Optional arguments:\n" +        + "  -h,--help             Show this help message and exit\n" +        , from_utf8(&buf[..]).unwrap().to_string()); +} + +#[test] +fn test_arguments() { +    let mut v1 = 0; +    let mut v2 = Vec::<u32>::new(); +    let mut ap = ArgumentParser::new(); +    ap.set_description("Test program"); +    ap.refer(&mut v1) +      .add_argument("v1", Store, +        "Integer value 1"); +    ap.refer(&mut v2) +      .add_argument("v2", List, +        "More values"); +    let mut buf = Vec::<u8>::new(); +    assert!(ap.print_help("./argparse_test", &mut buf).is_ok()); +    assert_eq!("Usage:\n".to_string() +        + "  ./argparse_test [V1] [V2 ...]\n" +        + "\n" +        + "Test program\n" +        + "\n" +        + "Positional arguments:\n" +        + "  v1                    Integer value 1\n" +        + "  v2                    More values\n" +        + "\n" +        + "Optional arguments:\n" +        + "  -h,--help             Show this help message and exit\n" +        , from_utf8(&buf[..]).unwrap().to_string()); +} + +#[test] +fn test_req_arguments() { +    let mut v1 = 0; +    let mut v2 = Vec::<u32>::new(); +    let mut ap = ArgumentParser::new(); +    ap.set_description("Test program"); +    ap.refer(&mut v1) +      .add_argument("v1", Store, +        "Integer value 1") +      .required(); +    ap.refer(&mut v2) +      .add_argument("v2", List, +        "More values") +      .required(); +    let mut buf = Vec::<u8>::new(); +    assert!(ap.print_help("./argparse_test", &mut buf).is_ok()); +    assert_eq!("Usage:\n".to_string() +        + "  ./argparse_test V1 V2 [...]\n" +        + "\n" +        + "Test program\n" +        + "\n" +        + "Positional arguments:\n" +        + "  v1                    Integer value 1\n" +        + "  v2                    More values\n" +        + "\n" +        + "Optional arguments:\n" +        + "  -h,--help             Show this help message and exit\n" +        , from_utf8(&buf[..]).unwrap().to_string()); +} + +#[test] +fn test_metavar() { +    let mut val2 = 0; +    let mut ap = ArgumentParser::new(); +    ap.set_description("Test program."); +    ap.refer(&mut val2) +      .add_option(&["-L", "--long-option"], Store, +        "Long option value") +      .metavar("VAL"); +    let mut buf = Vec::<u8>::new(); +    assert!(ap.print_help("./argparse_test", &mut buf).is_ok()); +    assert_eq!("Usage:\n".to_string() +        + "  ./argparse_test [OPTIONS]\n" +        + "\n" +        + "Test program.\n" +        + "\n" +        + "Optional arguments:\n" +        + "  -h,--help             Show this help message and exit\n" +        + "  -L,--long-option VAL  Long option value\n" +        , from_utf8(&buf[..]).unwrap().to_string()); +} diff --git a/argparse/src/test_int.rs b/argparse/src/test_int.rs new file mode 100644 index 0000000..1826eaf --- /dev/null +++ b/argparse/src/test_int.rs @@ -0,0 +1,107 @@ +use parser::ArgumentParser; +use super::{IncrBy,DecrBy}; +use super::Store; +use test_parser::{check_ok}; + +fn incr_int(args: &[&str]) -> usize { +    let mut val = 0; +    { +        let mut ap = ArgumentParser::new(); +        ap.refer(&mut val) +          .add_option(&["-i", "--incr"], IncrBy(1usize), +            "Increment value"); +        check_ok(&ap, args); +    } +    return val; +} + +#[test] +fn test_incr_int() { +    assert_eq!(incr_int(&["./argparse_test"]), 0); +    assert_eq!(incr_int(&["./argparse_test", "--incr"]), 1); +    assert_eq!(incr_int(&["./argparse_test", "-iiiii"]), 5); +} + +fn decr_int(args: &[&str]) -> isize { +    let mut val = 5; +    { +        let mut ap = ArgumentParser::new(); +        ap.refer(&mut val) +          .add_option(&["-d", "--decr"], DecrBy(1isize), +            "Decrement value"); +        check_ok(&ap, args); +    } +    return val; +} + +#[test] +fn test_decr_int() { +    assert_eq!(decr_int(&["./argparse_test"]), 5); +    assert_eq!(decr_int(&["./argparse_test", "--decr"]), 4); +    assert_eq!(decr_int(&["./argparse_test", "-dddddd"]), -1); +} + +#[test] +fn test_incr_decr() { +    let mut val = 0; +    { +        let mut ap = ArgumentParser::new(); +        ap.refer(&mut val) +          .add_option(&["-d", "--decr"], DecrBy(1isize), +            "Decrement value") +          .add_option(&["-i", "--incr"], IncrBy(1isize), +            "Increment value"); +        check_ok(&ap, &["./argparse_test", +            "-iiddd", "--incr", "-iii"]); +    } +    assert_eq!(val, 3); +} + +fn set_int(args: &[&str]) -> isize { +    let mut val = 0; +    { +        let mut ap = ArgumentParser::new(); +        ap.refer(&mut val) +          .add_option(&["-s", "--set"], Store, +            "Set integer value"); +        check_ok(&ap, args); +    } +    return val; +} + +#[test] +fn test_set_int() { +    assert_eq!(set_int(&["./argparse_test", "-s", "10"]), 10); +    assert_eq!(set_int(&["./argparse_test", "--set", "99"]), 99); +    assert_eq!(set_int(&["./argparse_test", "-s", "7", "-s77"]), 77); +    assert_eq!(set_int(&["./argparse_test", "-s333", "--set=123"]), 123); +} + +#[test] +#[should_panic(expected="Bad value 1.5")] +fn test_set_int_bad() { +    set_int(&["./argparse_test", "-s1.5"]); +} + +fn set_i16(args: &[&str]) -> i16 { +    let mut val = 0; +    { +        let mut ap = ArgumentParser::new(); +        ap.refer(&mut val) +          .add_option(&["-s", "--set"], Store, +            "Set integer value"); +        check_ok(&ap, args); +    } +    return val; +} + +#[test] +fn test_i16() { +    assert_eq!(set_i16(&["./argparse_test", "-s", "124"]), 124); +} + +#[test] +#[should_panic(expected="Bad value 1000000")] +fn test_i16_big() { +    set_i16(&["./argparse_test", "-s", "1000000"]); +} diff --git a/argparse/src/test_many.rs b/argparse/src/test_many.rs new file mode 100644 index 0000000..68e2cec --- /dev/null +++ b/argparse/src/test_many.rs @@ -0,0 +1,95 @@ +use parser::ArgumentParser; +use super::{List, Store, Collect}; +use test_parser::{check_ok}; + +fn pos_list(args: &[&str]) -> (isize, Vec<isize>) { +    let mut val1 = 1; +    let mut val2 = Vec::new(); +    { +        let mut ap = ArgumentParser::new(); +        ap.refer(&mut val1).add_argument("v1", Store, "The value 1"); +        ap.refer(&mut val2).add_argument("v2", List, "The list of vals"); +        check_ok(&ap, args); +    } +    return (val1, val2); +} + +#[test] +fn test_pos_list() { +    assert_eq!(pos_list(&["./argparse_test", "10"]), (10, vec!())); +    assert_eq!(pos_list(&["./argparse_test", "11", "21"]), (11, vec!(21))); +    assert_eq!(pos_list(&["./argparse_test", "10", "20", "30"]), +        (10, vec!(20, 30))); +} + +fn pos_collect(args: &[&str]) -> Vec<isize> { +    let mut lst = Vec::new(); +    { +        let mut ap = ArgumentParser::new(); +        ap.refer(&mut lst) +            .add_argument("v", Collect, "The list of vals"); +        check_ok(&ap, args); +    } +    return lst; +} + +#[test] +fn test_pos_collect() { +    assert_eq!(pos_collect(&["./argparse_test", "10"]), vec!(10)); +    assert_eq!(pos_collect(&["./argparse_test", "11", "21"]), vec!(11, 21)); +    assert_eq!(pos_collect(&["./argparse_test", "10", "20", "30"]), +        vec!(10, 20, 30)); +} + +#[test] +#[should_panic] +fn wrong_type() { +    pos_collect(&["./argparse_test", "10", "20", "test"]); +} + +fn collect(args: &[&str]) -> Vec<isize> { +    let mut lst = Vec::new(); +    { +        let mut ap = ArgumentParser::new(); +        ap.refer(&mut lst).add_option(&["-a", "--add"], Collect, +            "The list of vals"); +        check_ok(&ap, args); +    } +    return lst; +} + +#[test] +fn test_collect() { +    assert_eq!(collect(&["./argparse_test", "-a10"]), vec!(10)); +    assert_eq!(collect(&["./argparse_test", "--add=11", "-a", "21"]), +        vec!(11, 21)); +    assert_eq!(collect(&["./argparse_test", +        "-a", "10", "--add=20", "--add", "30"]), vec!(10, 20, 30)); +} + +#[test] +#[should_panic] +fn test_extra() { +    collect(&["./argparse_test", "-a", "10", "20", "30"]); +} + +fn list(args: &[&str]) -> Vec<isize> { +    let mut vec = Vec::new(); +    { +        let mut ap = ArgumentParser::new(); +        ap.refer(&mut vec).add_option(&["-a", "--add"], List, +            "The list of vals"); +        check_ok(&ap, args); +    } +    return vec; +} + +#[test] +#[should_panic] +fn test_list() { +    assert_eq!(list(&["./argparse_test", "-a10"]), vec!(10)); +    assert_eq!(list(&["./argparse_test", "--add", "11", "21"]), vec!(11, 21)); +    assert_eq!(list(&["./argparse_test", "-a", "10", "20", "30"]), +        vec!(10, 20, 30)); +    assert_eq!(list(&["./argparse_test", "10", "20", "30"]), vec!(10, 20, 30)); +} diff --git a/argparse/src/test_optional.rs b/argparse/src/test_optional.rs new file mode 100644 index 0000000..5f4c35e --- /dev/null +++ b/argparse/src/test_optional.rs @@ -0,0 +1,54 @@ +use parser::ArgumentParser; +use super::StoreOption; +use test_parser::{check_ok}; + +fn opt(args: &[&str]) -> Option<isize> { +    let mut val = None; +    { +        let mut ap = ArgumentParser::new(); +        ap.refer(&mut val) +          .add_option(&["-s", "--set"], StoreOption, +            "Set int value"); +        check_ok(&ap, args); +    } +    return val; +} + +#[test] +fn test_opt() { +    assert_eq!(opt(&["./argparse_test"]), None); +    assert_eq!(opt(&["./argparse_test", "-s", "10"]), Some(10)); +    assert_eq!(opt(&["./argparse_test", "--set", "11"]), Some(11)); +} + +#[test] +#[should_panic] +fn test_opt_no_arg() { +    opt(&["./argparse_test", "--set"]); +} + +fn optstr(args: &[&str]) -> Option<String> { +    let mut val = None; +    { +        let mut ap = ArgumentParser::new(); +        ap.refer(&mut val) +          .add_option(&["-s", "--set"], StoreOption, +            "Set string value"); +        check_ok(&ap, args); +    } +    return val; +} + +#[test] +fn test_str() { +    assert_eq!(optstr(&["./argparse_test"]), None); +    assert_eq!(optstr(&["./argparse_test", "-s", "10"]), Some(10.to_string())); +    assert_eq!(optstr(&["./argparse_test", "--set", "11"]), +        Some(11.to_string())); +} + +#[test] +#[should_panic] +fn test_str_no_art() { +    optstr(&["./argparse_test", "--set"]); +} diff --git a/argparse/src/test_parser.rs b/argparse/src/test_parser.rs new file mode 100644 index 0000000..f60aa64 --- /dev/null +++ b/argparse/src/test_parser.rs @@ -0,0 +1,64 @@ + +use parser::ArgumentParser; + +pub fn check_ok(ap: &ArgumentParser, args: &[&str]) { +    let mut stdout = Vec::<u8>::new(); +    let mut stderr = Vec::<u8>::new(); +    let mut owned_args = Vec::new(); +    for x in args.iter() { +        owned_args.push(x.to_string()); +    } +    let res = ap.parse(owned_args, &mut stdout, &mut stderr); +    match res { +        Ok(()) => return, +        Err(x) => panic!( +            String::from_utf8(stderr).unwrap() + +            &format!("Expected ok, but found Exit({})", x)[..]), +    } +} + +pub fn check_exit(ap: &ArgumentParser, args: &[&str]) { +    let mut stdout = Vec::<u8>::new(); +    let mut stderr = Vec::<u8>::new(); +    let mut owned_args = Vec::new(); +    for x in args.iter() { +        owned_args.push(x.to_string()); +    } +    let res = ap.parse(owned_args, &mut stdout, &mut stderr); +    match res { +        Err(0) => return, +        Err(x) => panic!(format!("Expected code {} got {}", 0usize, x)), +        Ok(()) => panic!(format!("Expected failure, got success")), +    } +} + +pub fn check_err(ap: &ArgumentParser, args: &[&str]) { +    let mut stdout = Vec::<u8>::new(); +    let mut stderr = Vec::<u8>::new(); +    let mut owned_args = Vec::new(); +    for x in args.iter() { +        owned_args.push(x.to_string()); +    } +    let res = ap.parse(owned_args, &mut stdout, &mut stderr); +    match res { +        Err(2) => return, +        Err(x) => panic!(format!("Expected code {} got {}", 2usize, x)), +        Ok(()) => panic!(format!("Expected failure, got success")), +    } +} + +#[test] +fn test_no_arg() { +    let ap = ArgumentParser::new(); +    check_ok(&ap, &["./argparse_test"]); +    check_err(&ap, &["./argparse_test", "a"]); +    check_err(&ap, &["./argparse_test", "-a"]); +    check_err(&ap, &["./argparse_test", "--an-option"]); +} + +#[test] +fn test_help() { +    let ap = ArgumentParser::new(); +    check_ok(&ap, &["./argparse_test"]); +    check_exit(&ap, &["./argparse_test", "--help"]); +} diff --git a/argparse/src/test_path.rs b/argparse/src/test_path.rs new file mode 100644 index 0000000..868ae8c --- /dev/null +++ b/argparse/src/test_path.rs @@ -0,0 +1,30 @@ +use std::path::PathBuf; +use parser::ArgumentParser; +use super::Parse; +use test_parser::{check_ok}; + +fn parse_str(args: &[&str]) -> PathBuf { +    let mut val: PathBuf = From::from(""); +    { +        let mut ap = ArgumentParser::new(); +        ap.refer(&mut val) +          .add_option(&["-s", "--set"], Parse, +            "Set path value"); +        check_ok(&ap, args); +    } +    return val; +} + +#[test] +fn test_path() { +    assert_eq!(parse_str(&["./argparse_test", "-s", "/hello"]), +               PathBuf::from("/hello")); +    assert_eq!(parse_str(&["./argparse_test", "--set", "a///b/../c"]), +               PathBuf::from("a/b/../c")); +} + +#[test] +#[should_panic] +fn test_err() { +    parse_str(&["./argparse_test", "--set"]); +} diff --git a/argparse/src/test_pos.rs b/argparse/src/test_pos.rs new file mode 100644 index 0000000..2542050 --- /dev/null +++ b/argparse/src/test_pos.rs @@ -0,0 +1,196 @@ +use parser::ArgumentParser; +use super::{Store, List}; +use test_parser::{check_ok}; + +fn parse_pos(args: &[&str]) -> isize { +    let mut val = 0; +    { +        let mut ap = ArgumentParser::new(); +        ap.refer(&mut val) +            .add_argument("value", Store, "The value"); +        check_ok(&ap, args); +    } +    return val; +} + + +#[test] +fn test_argument() { +    assert_eq!(parse_pos(&["./argparse_test", "10"]), 10); +} + +#[test] +#[should_panic] +fn too_much_args() { +    parse_pos(&["./argparse_test", "10", "20"]); +} + +#[test] +#[should_panic] +fn wrong_value() { +    parse_pos(&["./argparse_test", "test", "20"]); +} + +#[test] +#[should_panic] +fn float_value() { +    parse_pos(&["./argparse_test", "1.5"]); +} + +fn parse_two(args: &[&str]) -> (isize, isize) { +    let mut val1 = 1; +    let mut val2 = 2; +    { +        let mut ap = ArgumentParser::new(); +        ap.refer(&mut val1) +            .add_argument("v1", Store, "The value 1"); +        ap.refer(&mut val2) +            .add_argument("v2", Store, "The value 2"); +        check_ok(&ap, args); +    } +    return (val1, val2); +} + +#[test] +fn test_two() { +    assert_eq!(parse_two(&["./argparse_test", "10"]), (10, 2)); +    assert_eq!(parse_two(&["./argparse_test", "11", "21"]), (11, 21)); +} + +#[test] +#[should_panic] +fn test_two_fail_many() { +    parse_two(&["./argparse_test", "10", "20", "30"]); +} + +#[test] +#[should_panic] +fn test_two_fail_value() { +    parse_two(&["./argparse_test", "test", "20"]); +} + +#[test] +#[should_panic] +fn test_two_fail_float() { +    parse_two(&["./argparse_test", "1.5"]); +} + +fn parse_pos_opt(args: &[&str]) -> (isize, isize) { +    let mut val1 = 1; +    let mut val2 = 2; +    { +        let mut ap = ArgumentParser::new(); +        ap.refer(&mut val1) +            .add_option(&["--v1"], Store, "The value 1") +            .add_argument("v1", Store, "The value 1"); +        ap.refer(&mut val2) +            .add_argument("v2", Store, "The value 2"); +        check_ok(&ap, args); +    } +    return (val1, val2); +} + +#[test] +fn test_positional_optional() { +    assert_eq!(parse_pos_opt(&["./argparse_test", "10"]), (10, 2)); +    assert_eq!(parse_pos_opt(&["./argparse_test", "11", "21"]), (11, 21)); +    assert_eq!(parse_pos_opt(&["./argparse_test", "--v1=7", "8"]), (7, 8)); +    assert_eq!(parse_pos_opt(&["./argparse_test", "10", "--v1=9"]), (9, 10)); +} + +#[test] +#[should_panic] +fn test_pos_opt_err() { +    parse_pos_opt(&["./argparse_test", "--v1=10", "20", "30"]); +} + +fn parse_pos_req(args: &[&str]) -> (isize, isize) { +    let mut val1 = 1; +    let mut val2 = 2; +    { +        let mut ap = ArgumentParser::new(); +        ap.refer(&mut val1) +            .add_option(&["--v1"], Store, "The value 1") +            .add_argument("v1", Store, "The value 1") +            .required(); +        ap.refer(&mut val2) +            .add_argument("v2", Store, "The value 2"); +        check_ok(&ap, args); +    } +    return (val1, val2); +} + +#[test] +fn test_positional_required() { +    assert_eq!(parse_pos_req(&["./argparse_test", "10"]), (10, 2)); +    assert_eq!(parse_pos_req(&["./argparse_test", "11", "21"]), (11, 21)); +    assert_eq!(parse_pos_req(&["./argparse_test", "--v1=7"]), (7, 2)); +    assert_eq!(parse_pos_req(&["./argparse_test", "10", "--v1=9"]), (9, 10)); +} + +#[test] +#[should_panic] +fn test_pos_extra() { +    parse_pos_req(&["./argparse_test", "--v1=10", "20", "30"]); +} + +#[test] +#[should_panic] +fn test_pos_no_req() { +    parse_pos_req(&["./argparse_test"]); +} + +fn pos_stop(args: &[&str]) -> (isize, Vec<String>) { +    let mut val1 = 1; +    let mut val2 = Vec::new(); +    { +        let mut ap = ArgumentParser::new(); +        ap.refer(&mut val1) +            .add_option(&["--v1"], Store, "The value 1") +            .add_argument("v1", Store, "The value 1") +            .required(); +        ap.refer(&mut val2) +            .add_argument("v2", List, "The value 2"); +        ap.stop_on_first_argument(true); +        check_ok(&ap, args); +    } +    return (val1, val2); +} + +#[test] +fn test_pos_stop() { +    assert_eq!(pos_stop(&["./argparse_test", "10"]), (10, vec!())); +    assert_eq!(pos_stop(&["./argparse_test", "11", "21"]), +        (11, vec!("21".to_string()))); +    assert_eq!(pos_stop(&["./argparse_test", "--v1=7"]), (7, vec!())); +    assert_eq!(pos_stop(&["./argparse_test", "10", "--v1=9", "--whatever"]), +        (10, vec!("--v1=9".to_string(), "--whatever".to_string()))); +} + +#[test] +#[should_panic] +fn test_test() { +    pos_stop(&["./argparse_test"]); +} + +fn pos_dash(args: &[&str], dash: bool) -> Vec<String> { +    let mut val = Vec::new(); +    { +        let mut ap = ArgumentParser::new(); +        ap.refer(&mut val) +            .add_argument("v1", List, "The value"); +        ap.silence_double_dash(dash); +        check_ok(&ap, args); +    } +    return val; +} + +#[test] +fn test_pos_dash() { +    assert_eq!(pos_dash(&["./argparse_test", "1"], true), +        vec!("1".to_string())); +    assert_eq!(pos_dash(&["./argparse_test", "--", "1"], true), +        vec!("1".to_string())); +    assert_eq!(pos_dash(&["./argparse_test", "--", "1"], false), +        vec!("--".to_string(), "1".to_string())); +} diff --git a/argparse/src/test_str.rs b/argparse/src/test_str.rs new file mode 100644 index 0000000..42db286 --- /dev/null +++ b/argparse/src/test_str.rs @@ -0,0 +1,28 @@ +use parser::ArgumentParser; +use super::Store; +use test_parser::{check_ok}; + +fn parse_str(args: &[&str]) -> String { +    let mut val: String = "".to_string(); +    { +        let mut ap = ArgumentParser::new(); +        ap.refer(&mut val) +          .add_option(&["-s", "--set"], Store, +            "Set string value"); +        check_ok(&ap, args); +    } +    return val; +} + +#[test] +fn test_str() { +    assert_eq!(parse_str(&["./argparse_test", "-s", "10"]), "10".to_string()); +    assert_eq!(parse_str(&["./argparse_test", "--set", "value"]), +               "value".to_string()); +} + +#[test] +#[should_panic] +fn test_err() { +    parse_str(&["./argparse_test", "--set"]); +} diff --git a/argparse/src/test_usage.rs b/argparse/src/test_usage.rs new file mode 100644 index 0000000..efe89fe --- /dev/null +++ b/argparse/src/test_usage.rs @@ -0,0 +1,57 @@ +use std::str::from_utf8; + +use parser::ArgumentParser; +use super::{Store, List}; + +#[test] +fn test_empty() { +    let ap = ArgumentParser::new(); +    let mut buf = Vec::<u8>::new(); +    assert!(ap.print_usage("./argparse_test", &mut buf).is_ok()); +    assert_eq!("Usage:\n  ./argparse_test\n", from_utf8(&buf[..]).unwrap()); +} + +#[test] +fn test_options() { +    let mut val = 0; +    let mut buf = Vec::<u8>::new(); +    { +        let mut ap = ArgumentParser::new(); +        ap.refer(&mut val) +          .add_option(&["--value"], Store, +            "Set integer value"); +        assert!(ap.print_usage("./argparse_test", &mut buf).is_ok()); +    } +    assert_eq!("Usage:\n  ./argparse_test [OPTIONS]\n", +        from_utf8(&buf[..]).unwrap()); +} + +#[test] +fn test_argument() { +    let mut val = 0; +    let mut ap = ArgumentParser::new(); +    ap.refer(&mut val) +      .add_argument("value", Store, +        "Integer value"); +    let mut buf = Vec::<u8>::new(); +    assert!(ap.print_usage("./argparse_test", &mut buf).is_ok()); +    assert_eq!("Usage:\n  ./argparse_test [VALUE]\n", +        from_utf8(&buf[..]).unwrap()); +} + +#[test] +fn test_arguments() { +    let mut v1 = 0; +    let mut v2 = Vec::<u32>::new(); +    let mut ap = ArgumentParser::new(); +    ap.refer(&mut v1) +      .add_argument("v1", Store, +        "Integer value 1"); +    ap.refer(&mut v2) +      .add_argument("v2", List, +        "More values"); +    let mut buf = Vec::<u8>::new(); +    assert!(ap.print_usage("./argparse_test", &mut buf).is_ok()); +    assert_eq!("Usage:\n  ./argparse_test [V1] [V2 ...]\n", +        from_utf8(&buf[..]).unwrap()); +} diff --git a/argparse/vagga.yaml b/argparse/vagga.yaml new file mode 100644 index 0000000..c6e0c52 --- /dev/null +++ b/argparse/vagga.yaml @@ -0,0 +1,59 @@ +containers: + +  build: +    setup: +    - !Ubuntu bionic +    - !Install [build-essential, ca-certificates, vim] +    - !TarInstall +      url: https://static.rust-lang.org/dist/rust-1.28.0-x86_64-unknown-linux-gnu.tar.gz +      # We install rustc and cargo, but skip rust-docs +      script: "./install.sh --prefix=/usr \ +        --components=rustc,rust-std-x86_64-unknown-linux-gnu,cargo" +    - &bulk !Tar +      url: "https://github.com/tailhook/bulk/releases/download/v0.4.12/bulk-v0.4.12.tar.gz" +      sha256: 7deeb4895b3909afea46194ef01bafdeb30ff89fc4a7b6497172ba117734040e +      path: / +    environ: +      HOME: /work/run +    volumes: +      /tmp: !Tmpfs { size: 100Mi } + + +commands: + +  make: !Command +    description: Build the library +    container: build +    run: [cargo, build] + +  test: !Command +    description: Run the tests +    container: build +    run: [cargo, test] + +  cargo: !Command +    container: build +    run: [cargo] + +  example-greeting: !Command +    description: Build and run "greeting" example +    container: build +    accepts-arguments: true +    run: [cargo, run, --example, greeting, "--"] + +  example-structure: !Command +    description: Build and run "structure" example +    container: build +    accepts-arguments: true +    run: [cargo, run, --example, structure, "--"] + +  example-subcommands: !Command +    description: Build and run "subcommands" example +    container: build +    accepts-arguments: true +    run: [cargo, run, --example, subcommands, "--"] + +  _bulk: !Command +    description: Run `bulk` command (for version bookkeeping) +    container: build +    run: [bulk] | 
