From d137415a69007a90569ebbf38a92424fba60b997 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Tue, 18 Dec 2018 00:39:15 +0100 Subject: Add argparse 0.2.2 as a dependency This patch adds the crate rust-argparse [0] in version 0.2.2 as a dependency, as discussed in issue #4. [0] https://github.com/tailhook/rust-argparse Import subrepo argparse/:argparse at 0de60a5e6d9ee1a3570d6089afd3ccd6ed7480c5 --- argparse/.gitignore | 13 + argparse/.travis.yml | 34 ++ argparse/Cargo.toml | 12 + argparse/LICENSE | 19 + argparse/README.rst | 341 ++++++++++++++ argparse/bulk.yaml | 11 + argparse/examples/greeting.rs | 26 ++ argparse/examples/structure.rs | 38 ++ argparse/examples/subcommands.rs | 88 ++++ argparse/src/action.rs | 33 ++ argparse/src/bool.rs | 22 + argparse/src/custom.rs | 95 ++++ argparse/src/from_cli.rs | 100 ++++ argparse/src/generic.rs | 133 ++++++ argparse/src/help.rs | 93 ++++ argparse/src/lib.rs | 64 +++ argparse/src/num.rs | 58 +++ argparse/src/parser.rs | 967 +++++++++++++++++++++++++++++++++++++++ argparse/src/print.rs | 13 + argparse/src/test_bool.rs | 98 ++++ argparse/src/test_const.rs | 28 ++ argparse/src/test_enum.rs | 51 +++ argparse/src/test_env.rs | 43 ++ argparse/src/test_float.rs | 50 ++ argparse/src/test_help.rs | 154 +++++++ argparse/src/test_int.rs | 107 +++++ argparse/src/test_many.rs | 95 ++++ argparse/src/test_optional.rs | 54 +++ argparse/src/test_parser.rs | 64 +++ argparse/src/test_path.rs | 30 ++ argparse/src/test_pos.rs | 196 ++++++++ argparse/src/test_str.rs | 28 ++ argparse/src/test_usage.rs | 57 +++ argparse/vagga.yaml | 59 +++ 34 files changed, 3274 insertions(+) create mode 100644 argparse/.gitignore create mode 100644 argparse/.travis.yml create mode 100644 argparse/Cargo.toml create mode 100644 argparse/LICENSE create mode 100644 argparse/README.rst create mode 100644 argparse/bulk.yaml create mode 100644 argparse/examples/greeting.rs create mode 100644 argparse/examples/structure.rs create mode 100644 argparse/examples/subcommands.rs create mode 100644 argparse/src/action.rs create mode 100644 argparse/src/bool.rs create mode 100644 argparse/src/custom.rs create mode 100644 argparse/src/from_cli.rs create mode 100644 argparse/src/generic.rs create mode 100644 argparse/src/help.rs create mode 100644 argparse/src/lib.rs create mode 100644 argparse/src/num.rs create mode 100644 argparse/src/parser.rs create mode 100644 argparse/src/print.rs create mode 100644 argparse/src/test_bool.rs create mode 100644 argparse/src/test_const.rs create mode 100644 argparse/src/test_enum.rs create mode 100644 argparse/src/test_env.rs create mode 100644 argparse/src/test_float.rs create mode 100644 argparse/src/test_help.rs create mode 100644 argparse/src/test_int.rs create mode 100644 argparse/src/test_many.rs create mode 100644 argparse/src/test_optional.rs create mode 100644 argparse/src/test_parser.rs create mode 100644 argparse/src/test_path.rs create mode 100644 argparse/src/test_pos.rs create mode 100644 argparse/src/test_str.rs create mode 100644 argparse/src/test_usage.rs create mode 100644 argparse/vagga.yaml (limited to 'argparse') 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 "] + 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(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 = 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`` 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 { + return match src { + "play" => Ok(Command::play), + "record" => Ok(Command::record), + _ => Err(()), + }; + } +} + + + +fn play_command(verbose: bool, args: Vec) { + 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) { + 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), + Single(Box), + Push(Box), + Many(Box), +} + +pub trait TypedAction { + fn bind<'x>(&self, Rc>) -> 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 for StoreTrue { + fn bind<'x>(&self, cell: Rc>) -> Action<'x> { + return Flag(Box::new(StoreConstAction { cell: cell, value: true })); + } +} + +impl TypedAction for StoreFalse { + fn bind<'x>(&self, cell: Rc>) -> 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>, +} + +pub struct ParseOptionAction<'a, T: 'a> { + cell: Rc>>, +} + +pub struct ParseListAction<'a, T: 'a> { + cell: Rc>>, +} + +impl TypedAction for Parse { + fn bind<'x>(&self, cell: Rc>) -> Action<'x> { + return Single(Box::new(ParseAction { cell: cell })); + } +} + +impl TypedAction> for ParseOption { + fn bind<'x>(&self, cell: Rc>>) -> Action<'x> { + return Single(Box::new(ParseOptionAction { cell: cell })); + } +} + +impl TypedAction> for ParseList { + fn bind<'x>(&self, cell: Rc>>) -> Action<'x> { + return Many(Box::new(ParseListAction { cell: cell })); + } +} + +impl TypedAction> for ParseCollect + where T: 'static + FromCommandLine + Clone +{ + fn bind<'x>(&self, cell: Rc>>) -> 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 { + Ok(From::from(s)) + } +} + +impl FromCommandLine for f32 { + fn from_argument(s: &str) -> Result { + FromStr::from_str(s).map_err(|e| format!("{:?}", e)) + } +} +impl FromCommandLine for f64 { + fn from_argument(s: &str) -> Result { + 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 { + FromStr::from_str(s).map_err(|e| format!("{:?}", e)) + } +} +impl FromCommandLine for i8 { + fn from_argument(s: &str) -> Result { + FromStr::from_str(s).map_err(|e| format!("{:?}", e)) + } +} +impl FromCommandLine for i16 { + fn from_argument(s: &str) -> Result { + FromStr::from_str(s).map_err(|e| format!("{:?}", e)) + } +} +impl FromCommandLine for i32 { + fn from_argument(s: &str) -> Result { + FromStr::from_str(s).map_err(|e| format!("{:?}", e)) + } +} +impl FromCommandLine for i64 { + fn from_argument(s: &str) -> Result { + FromStr::from_str(s).map_err(|e| format!("{:?}", e)) + } +} +impl FromCommandLine for usize { + fn from_argument(s: &str) -> Result { + FromStr::from_str(s).map_err(|e| format!("{:?}", e)) + } +} +impl FromCommandLine for u8 { + fn from_argument(s: &str) -> Result { + FromStr::from_str(s).map_err(|e| format!("{:?}", e)) + } +} +impl FromCommandLine for u16 { + fn from_argument(s: &str) -> Result { + FromStr::from_str(s).map_err(|e| format!("{:?}", e)) + } +} +impl FromCommandLine for u32 { + fn from_argument(s: &str) -> Result { + FromStr::from_str(s).map_err(|e| format!("{:?}", e)) + } +} +impl FromCommandLine for u64 { + fn from_argument(s: &str) -> Result { + FromStr::from_str(s).map_err(|e| format!("{:?}", e)) + } +} +impl FromCommandLine for bool { + fn from_argument(s: &str) -> Result { + FromStr::from_str(s).map_err(|e| format!("{:?}", e)) + } +} +impl FromCommandLine for String { + fn from_argument(s: &str) -> Result { + FromStr::from_str(s).map_err(|_| unreachable!()) + } +} +impl FromCommandLine for Ipv4Addr { + fn from_argument(s: &str) -> Result { + FromStr::from_str(s).map_err(|e| format!("{:?}", e)) + } +} +impl FromCommandLine for Ipv6Addr { + fn from_argument(s: &str) -> Result { + FromStr::from_str(s).map_err(|e| format!("{:?}", e)) + } +} +impl FromCommandLine for SocketAddr { + fn from_argument(s: &str) -> Result { + 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>, +} + +pub struct PushConstAction<'a, T: 'a> { + pub value: T, + pub cell: Rc>>, +} + +pub struct StoreAction<'a, T: 'a> { + pub cell: Rc>, +} + +pub struct StoreOptionAction<'a, T: 'a> { + cell: Rc>>, +} + +pub struct ListAction<'a, T: 'a> { + cell: Rc>>, +} + +impl TypedAction for StoreConst { + fn bind<'x>(&self, cell: Rc>) -> Action<'x> { + let StoreConst(ref val) = *self; + return Flag(Box::new(StoreConstAction { cell: cell, value: val.clone() })); + } +} + +impl TypedAction> for PushConst { + fn bind<'x>(&self, cell: Rc>>) -> Action<'x> { + let PushConst(ref val) = *self; + return Flag(Box::new(PushConstAction { cell: cell, value: val.clone() })); + } +} + +impl TypedAction for Store { + fn bind<'x>(&self, cell: Rc>) -> Action<'x> { + return Single(Box::new(StoreAction { cell: cell })); + } +} + +impl TypedAction> for StoreOption { + fn bind<'x>(&self, cell: Rc>>) -> Action<'x> { + return Single(Box::new(StoreOptionAction { cell: cell })); + } +} + +impl TypedAction> for List { + fn bind<'x>(&self, cell: Rc>>) -> Action<'x> { + return Many(Box::new(ListAction { cell: cell })); + } +} + +impl TypedAction> for Collect { + fn bind<'x>(&self, cell: Rc>>) -> 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; +} + +// TODO(tailhook) make consts +pub struct StoreTrue; +pub struct StoreFalse; + +pub struct StoreConst(pub T); + +pub struct PushConst(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(pub T); + +pub struct DecrBy(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>, +} + +pub struct DecrByAction<'a, T: 'a> { + delta: T, + cell: Rc>, +} + +impl + Clone> TypedAction for IncrBy { + fn bind<'x>(&self, cell: Rc>) -> Action<'x> { + let IncrBy(ref delta) = *self; + return Flag(Box::new(IncrByAction { cell: cell, delta: delta.clone() })); + } +} + +impl + Clone> TypedAction for DecrBy { + fn bind<'x>(&self, cell: Rc>) -> Action<'x> { + let DecrBy(ref delta) = *self; + return Flag(Box::new(DecrByAction { cell: cell, delta: delta.clone() })); + } +} + +impl<'a, T: Add + 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 + 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, + names: Vec<&'parser str>, + help: &'parser str, + action: Action<'parser>, +} + +struct EnvVar<'parser> { + varid: usize, + name: &'parser str, + action: Box, +} + +impl<'a> Hash for GenericOption<'a> { + fn hash(&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(&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(&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, + list_options: HashMap>, Vec<&'ctx str>>, + list_arguments: HashMap>, Vec<&'ctx str>>, + arguments: Vec<&'ctx str>, + iter: Peekable>, + stderr: &'ctx mut (Write + 'ctx), +} + +impl<'a, 'b> Context<'a, 'b> { + + fn parse_option(&mut self, opt: Rc>, + 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, 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>, + varid: usize, + parser: &'refer mut ArgumentParser<'parser>, +} + +impl<'parser, 'refer, T> Ref<'parser, 'refer, T> { + + pub fn add_option<'x, A: TypedAction>(&'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>(&'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>, + options: Vec>>, + arguments: Vec>>, + env_vars: Vec>>, + catchall_argument: Option>>, + short_options: HashMap>>, + long_options: HashMap>>, + 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> + { + 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(&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, + 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, + 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 { + 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 { + 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::::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::::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::::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::::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::::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::::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::::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::::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) { + 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 { + 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 { + 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 { + 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 { + 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 { + 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::::new(); + let mut stderr = Vec::::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::::new(); + let mut stderr = Vec::::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::::new(); + let mut stderr = Vec::::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) { + 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 { + 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::::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::::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::::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::::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::::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] -- cgit v1.2.3