gumdrop-0.8.1/.cargo_vcs_info.json0000644000000001360000000000100125170ustar { "git": { "sha1": "2e12c79a793066028ac2261274041f4a12ba6c61" }, "path_in_vcs": "" }gumdrop-0.8.1/.github/workflows/ci.yml000064400000000000000000000012760072674642500160600ustar 00000000000000name: CI on: pull_request: paths: - '**.rs' - '**.toml' - '.github/workflows/ci.yml' push: branches: [master] paths: - '**.rs' - '**.toml' - '.github/workflows/ci.yml' jobs: Test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: hecrj/setup-rust-action@v1 - name: Build run: cargo build --verbose - name: Test run: cargo test --verbose - name: Build with features run: cargo build --verbose --all-features - name: Test with features run: cargo test --verbose --all-features env: RUST_BACKTRACE: 1 RUST_INCREMENTAL: 0 RUSTFLAGS: "-C debuginfo=0" gumdrop-0.8.1/.gitignore000064400000000000000000000000360072674642500133260ustar 00000000000000target/ **/*.rs.bk Cargo.lock gumdrop-0.8.1/Cargo.lock0000644000000030240000000000100104710ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "assert_matches" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7deb0a829ca7bcfaf5da70b073a8d128619259a7be8216a355e23f00763059e5" [[package]] name = "gumdrop" version = "0.8.1" dependencies = [ "assert_matches", "gumdrop_derive", ] [[package]] name = "gumdrop_derive" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "729f9bd3449d77e7831a18abfb7ba2f99ee813dfd15b8c2167c9a54ba20aa99d" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "proc-macro2" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df246d292ff63439fea9bc8c0a270bed0e390d5ebd4db4ba15aba81111b5abe3" dependencies = [ "unicode-xid", ] [[package]] name = "quote" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f" dependencies = [ "proc-macro2", ] [[package]] name = "syn" version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0df0eb663f387145cab623dea85b09c2c5b4b0aef44e945d928e682fce71bb03" dependencies = [ "proc-macro2", "quote", "unicode-xid", ] [[package]] name = "unicode-xid" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" gumdrop-0.8.1/Cargo.toml0000644000000020340000000000100105140ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "gumdrop" version = "0.8.1" authors = ["Murarth "] description = "Option parser with custom derive support" homepage = "https://github.com/murarth/gumdrop" documentation = "https://docs.rs/gumdrop/" readme = "README.md" keywords = ["args", "command-line", "flag", "getopts", "option"] license = "MIT/Apache-2.0" repository = "https://github.com/murarth/gumdrop" [dependencies.gumdrop_derive] version = "0.8.1" [dev-dependencies.assert_matches] version = "1.1" [features] default = [] default_expr = ["gumdrop_derive/default_expr"] gumdrop-0.8.1/Cargo.toml.orig000064400000000000000000000011620072674642500142260ustar 00000000000000[package] name = "gumdrop" version = "0.8.1" authors = ["Murarth "] edition = "2018" description = "Option parser with custom derive support" documentation = "https://docs.rs/gumdrop/" homepage = "https://github.com/murarth/gumdrop" repository = "https://github.com/murarth/gumdrop" keywords = ["args", "command-line", "flag", "getopts", "option"] license = "MIT/Apache-2.0" readme = "README.md" [features] default = [] default_expr = ["gumdrop_derive/default_expr"] [dependencies] gumdrop_derive = { version = "0.8.1", path = "gumdrop_derive" } [dev-dependencies] assert_matches = "1.1" [workspace] gumdrop-0.8.1/LICENSE-APACHE000064400000000000000000000251370072674642500132730ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. gumdrop-0.8.1/LICENSE-MIT000064400000000000000000000020330072674642500127710ustar 00000000000000Copyright (c) 2017 Murarth 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. gumdrop-0.8.1/README.md000064400000000000000000000006160072674642500126210ustar 00000000000000# `gumdrop` Option parser with custom derive support [Documentation](https://docs.rs/gumdrop/) ## Building To include `gumdrop` in your project, add the following to your `Cargo.toml`: ```toml [dependencies] gumdrop = "0.8" ``` ## License `gumdrop` is distributed under the terms of both the MIT license and the Apache License (Version 2.0). See LICENSE-APACHE and LICENSE-MIT for details. gumdrop-0.8.1/examples/commands.rs000064400000000000000000000036640072674642500153350ustar 00000000000000use gumdrop::Options; // Define options for the program. #[derive(Debug, Options)] struct MyOptions { // Options here can be accepted with any command (or none at all), // but they must come before the command name. #[options(help = "print help message")] help: bool, #[options(help = "be verbose")] verbose: bool, // The `command` option will delegate option parsing to the command type, // starting at the first free argument. #[options(command)] command: Option, } // The set of commands and the options each one accepts. // // Each variant of a command enum should be a unary tuple variant with only // one field. This field must implement `Options` and is used to parse arguments // that are given after the command name. #[derive(Debug, Options)] enum Command { // Command names are generated from variant names. // By default, a CamelCase name will be converted into a lowercase, // hyphen-separated name; e.g. `FooBar` becomes `foo-bar`. // // Names can be explicitly specified using `#[options(name = "...")]` #[options(help = "make stuff")] Make(MakeOpts), #[options(help = "install stuff")] Install(InstallOpts), } // Options accepted for the `make` command #[derive(Debug, Options)] struct MakeOpts { #[options(help = "print help message")] help: bool, #[options(free)] free: Vec, #[options(help = "number of jobs", meta = "N")] jobs: Option, } // Options accepted for the `install` command #[derive(Debug, Options)] struct InstallOpts { #[options(help = "print help message")] help: bool, #[options(help = "target directory")] dir: Option, } fn main() { // Parse options from the environment. // If there's an error or the user requests help, // the process will exit after giving the appropriate response. let opts = MyOptions::parse_args_default_or_exit(); println!("{:#?}", opts); } gumdrop-0.8.1/examples/options.rs000064400000000000000000000041340072674642500152200ustar 00000000000000use gumdrop::Options; // Defines options that can be parsed from the command line. // // `derive(Options)` will generate an implementation of the trait `Options`. // Each field must either have a `Default` implementation or an inline // default value provided. // // (`Debug` is only derived here for demonstration purposes.) #[derive(Debug, Options)] struct MyOptions { // Contains "free" arguments -- those that are not options. // If no `free` field is declared, free arguments will result in an error. #[options(free)] free: Vec, // Boolean options are treated as flags, taking no additional values. // The optional `help` attribute is displayed in `usage` text. #[options(help = "print help message")] help: bool, // Non-boolean fields will take a value from the command line. // Wrapping the type in an `Option` is not necessary, but provides clarity. #[options(help = "give a string argument")] string: Option, // A field can be any type that implements `FromStr`. // The optional `meta` attribute is displayed in `usage` text. #[options(help = "give a number as an argument", meta = "N")] number: Option, // A `Vec` field will accumulate all values received from the command line. #[options(help = "give a list of string items")] item: Vec, // The `count` flag will treat the option as a counter. // Each time the option is encountered, the field is incremented. #[options(count, help = "increase a counting value")] count: u32, // Option names are automatically generated from field names, but these // can be overriden. The attributes `short = "?"`, `long = "..."`, // `no_short`, and `no_long` are used to control option names. #[options(no_short, help = "this option has no short form")] long_option_only: bool, } fn main() { // Parse options from the environment. // If there's an error or the user requests help, // the process will exit after giving the appropriate response. let opts = MyOptions::parse_args_default_or_exit(); println!("{:#?}", opts); } gumdrop-0.8.1/src/lib.rs000064400000000000000000000653170072674642500132560ustar 00000000000000//! Option parser with custom derive support //! //! For full documentation on customization of `derive(Options)`, please see the crate //! documentation for [`gumdrop_derive`](https://docs.rs/crate/gumdrop_derive/). //! //! # Examples //! //! ``` //! use gumdrop::Options; //! //! // Defines options that can be parsed from the command line. //! // //! // `derive(Options)` will generate an implementation of the trait `Options`. //! // Each field must either have a `Default` implementation or an inline //! // default value provided. //! // //! // (`Debug` is derived here only for demonstration purposes.) //! #[derive(Debug, Options)] //! struct MyOptions { //! // Contains "free" arguments -- those that are not options. //! // If no `free` field is declared, free arguments will result in an error. //! #[options(free)] //! free: Vec, //! //! // Boolean options are treated as flags, taking no additional values. //! // The optional `help` attribute is displayed in `usage` text. //! // //! // A boolean field named `help` is automatically given the `help_flag` attribute. //! // The `parse_args_or_exit` and `parse_args_default_or_exit` functions use help flags //! // to automatically display usage to the user. //! #[options(help = "print help message")] //! help: bool, //! //! // Non-boolean fields will take a value from the command line. //! // Wrapping the type in an `Option` is not necessary, but provides clarity. //! #[options(help = "give a string argument")] //! string: Option, //! //! // A field can be any type that implements `FromStr`. //! // The optional `meta` attribute is displayed in `usage` text. //! #[options(help = "give a number as an argument", meta = "N")] //! number: Option, //! //! // A `Vec` field will accumulate all values received from the command line. //! #[options(help = "give a list of string items")] //! item: Vec, //! //! // The `count` flag will treat the option as a counter. //! // Each time the option is encountered, the field is incremented. //! #[options(count, help = "increase a counting value")] //! count: u32, //! //! // Option names are automatically generated from field names, but these //! // can be overriden. The attributes `short = "?"`, `long = "..."`, //! // `no_short`, and `no_long` are used to control option names. //! #[options(no_short, help = "this option has no short form")] //! long_option_only: bool, //! } //! //! fn main() { //! let opts = MyOptions::parse_args_default_or_exit(); //! //! println!("{:#?}", opts); //! } //! ``` //! //! `derive(Options)` can also be used on `enum`s to produce a subcommand //! option parser. //! //! ``` //! use gumdrop::Options; //! //! // Define options for the program. //! #[derive(Debug, Options)] //! struct MyOptions { //! // Options here can be accepted with any command (or none at all), //! // but they must come before the command name. //! #[options(help = "print help message")] //! help: bool, //! #[options(help = "be verbose")] //! verbose: bool, //! //! // The `command` option will delegate option parsing to the command type, //! // starting at the first free argument. //! #[options(command)] //! command: Option, //! } //! //! // The set of commands and the options each one accepts. //! // //! // Each variant of a command enum should be a unary tuple variant with only //! // one field. This field must implement `Options` and is used to parse arguments //! // that are given after the command name. //! #[derive(Debug, Options)] //! enum Command { //! // Command names are generated from variant names. //! // By default, a CamelCase name will be converted into a lowercase, //! // hyphen-separated name; e.g. `FooBar` becomes `foo-bar`. //! // //! // Names can be explicitly specified using `#[options(name = "...")]` //! #[options(help = "show help for a command")] //! Help(HelpOpts), //! #[options(help = "make stuff")] //! Make(MakeOpts), //! #[options(help = "install stuff")] //! Install(InstallOpts), //! } //! //! // Options accepted for the `help` command //! #[derive(Debug, Options)] //! struct HelpOpts { //! #[options(free)] //! free: Vec, //! } //! //! // Options accepted for the `make` command //! #[derive(Debug, Options)] //! struct MakeOpts { //! #[options(free)] //! free: Vec, //! #[options(help = "number of jobs", meta = "N")] //! jobs: Option, //! } //! //! // Options accepted for the `install` command //! #[derive(Debug, Options)] //! struct InstallOpts { //! #[options(help = "target directory")] //! dir: Option, //! } //! //! fn main() { //! let opts = MyOptions::parse_args_default_or_exit(); //! //! println!("{:#?}", opts); //! } //! ``` //! //! A custom parsing function can be supplied for each option field. //! //! ``` //! use gumdrop::Options; //! //! #[derive(Debug, Options)] //! struct MyOptions { //! // `try_from_str = "..."` supplies a conversion function that may fail //! #[options(help = "a hexadecimal value", parse(try_from_str = "parse_hex"))] //! hex: u32, //! // `from_str = "..."` supplies a conversion function that always succeeds //! #[options(help = "a string that becomes uppercase", parse(from_str = "to_upper"))] //! upper: String, //! } //! //! fn parse_hex(s: &str) -> Result { //! u32::from_str_radix(s, 16) //! } //! //! fn to_upper(s: &str) -> String { //! s.to_uppercase() //! } //! //! fn main() { //! let opts = MyOptions::parse_args_default_or_exit(); //! //! println!("{:#?}", opts); //! } //! ``` #![deny(missing_docs)] pub use gumdrop_derive::*; use std::error::Error as StdError; use std::fmt; use std::slice::Iter; use std::str::Chars; /// Represents an error encountered during argument parsing #[derive(Debug)] pub struct Error { kind: ErrorKind, } #[derive(Debug)] enum ErrorKind { FailedParse(String, String), FailedParseDefault{ option: &'static str, value: &'static str, err: String, }, InsufficientArguments{ option: String, expected: usize, found: usize, }, MissingArgument(String), MissingCommand, MissingRequired(String), MissingRequiredCommand, MissingRequiredFree, UnexpectedArgument(String), UnexpectedSingleArgument(String, usize), UnexpectedFree(String), UnrecognizedCommand(String), UnrecognizedLongOption(String), UnrecognizedShortOption(char), } /// Parses options from a series of `&str`-like values. pub struct Parser<'a, S: 'a> { args: Iter<'a, S>, cur: Option>, style: ParsingStyle, terminated: bool, } /// Represents an option parsed from a `Parser` #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum Opt<'a> { /// Short option, e.g. `-o` Short(char), /// Long option, e.g. `--option` Long(&'a str), /// Long option with argument, e.g. `--option=value` LongWithArg(&'a str, &'a str), /// Free argument Free(&'a str), } /// Implements a set of options parsed from command line arguments. /// /// An implementation of this trait can be generated with `#[derive(Options)]`. pub trait Options { /// Parses arguments until the given parser is exhausted or until /// an error is encountered. fn parse>(parser: &mut Parser) -> Result where Self: Sized; /// Returns the subcommand instance, if present. /// /// This method **must never** return `self` or otherwise return a `&dyn Options` instance /// which would create a cycle. Doing so may cause other methods or `gumdrop` functions /// to loop infinitely or overflow the runtime stack. fn command(&self) -> Option<&dyn Options>; /// Returns the name of a parsed command, if present. /// /// This is implemented by `derive(Options)` in one of two ways: /// /// * For `struct` types, if the type contains a field marked /// `#[options(command)]`, this method is called on that value. /// Otherwise, `None` is returned. /// * For `enum` types, the name corresponding to the variant is returned. fn command_name(&self) -> Option<&'static str> { None } /// Returns whether the user supplied a "help" option to request /// usage information about the program or any contained subcommands. /// /// The default implementation returns `false`. fn help_requested(&self) -> bool { false } /// Parses arguments received from the command line. /// /// The first argument (the program name) should be omitted. fn parse_args>(args: &[S], style: ParsingStyle) -> Result where Self: Sized { Self::parse(&mut Parser::new(args, style)) } /// Parses arguments from the environment. /// /// If an error is encountered, the error is printed to `stderr` and the /// process will exit with status code `2`. /// /// If the user supplies a help option, option usage will be printed to /// `stderr` and the process will exit with status code `0`. /// /// Otherwise, the parsed options are returned. fn parse_args_or_exit(style: ParsingStyle) -> Self where Self: Sized { use std::env::args; use std::process::exit; let args = args().collect::>(); let opts = Self::parse_args(&args[1..], style).unwrap_or_else(|e| { eprintln!("{}: {}", args[0], e); exit(2); }); if opts.help_requested() { let mut command = &opts as &dyn Options; let mut command_str = String::new(); loop { if let Some(new_command) = command.command() { command = new_command; if let Some(name) = new_command.command_name() { command_str.push(' '); command_str.push_str(name); } } else { break; } } eprintln!("Usage: {}{} [OPTIONS]", args[0], command_str); eprintln!(); eprintln!("{}", command.self_usage()); if let Some(cmds) = command.self_command_list() { eprintln!(); eprintln!("Available commands:"); eprintln!("{}", cmds); } exit(0); } opts } /// Parses arguments from the environment, using the default /// [parsing style](enum.ParsingStyle.html). /// /// If an error is encountered, the error is printed to `stderr` and the /// process will exit with status code `2`. /// /// If the user supplies a help option, option usage will be printed to /// `stderr` and the process will exit with status code `0`. /// /// Otherwise, the parsed options are returned. fn parse_args_default_or_exit() -> Self where Self: Sized { Self::parse_args_or_exit(ParsingStyle::default()) } /// Parses arguments received from the command line, /// using the default [parsing style](enum.ParsingStyle.html). /// /// The first argument (the program name) should be omitted. fn parse_args_default>(args: &[S]) -> Result where Self: Sized { Self::parse(&mut Parser::new(args, ParsingStyle::default())) } /// Parses options for the named command. fn parse_command>(name: &str, parser: &mut Parser) -> Result where Self: Sized; /// Returns a string showing usage and help for each supported option. /// /// Option descriptions are separated by newlines. The returned string /// should **not** end with a newline. fn usage() -> &'static str where Self: Sized; /// Returns a string showing usage and help for this options instance. /// /// In contrast to `usage`, this method will return usage for a subcommand, /// if one is selected. /// /// Option descriptions are separated by newlines. The returned string /// should **not** end with a newline. fn self_usage(&self) -> &'static str; /// Returns a usage string for the named command. /// /// If the named command does not exist, `None` is returned. /// /// Command descriptions are separated by newlines. The returned string /// should **not** end with a newline. fn command_usage(command: &str) -> Option<&'static str> where Self: Sized; /// Returns a string listing available commands and help text. /// /// Commands are separated by newlines. The string should **not** end with /// a newline. /// /// For `enum` types with `derive(Options)`, this is the same as `usage`. /// /// For `struct` types containing a field marked `#[options(command)]`, /// `usage` is called on the command type. fn command_list() -> Option<&'static str> where Self: Sized; /// Returns a listing of available commands and help text. /// /// In contrast to `usage`, this method will return command list for a subcommand, /// if one is selected. /// /// Commands are separated by newlines. The string should **not** end with /// a newline. fn self_command_list(&self) -> Option<&'static str>; } /// Controls behavior of free arguments in `Parser` /// /// The [`parse_args_default`] and [`parse_args_default_or_exit`] functions will use the /// default parsing style, `AllOptions`. /// /// # Examples /// /// ``` /// use gumdrop::{Options, ParsingStyle}; /// /// #[derive(Options)] /// struct MyOptions { /// // If the "-o" is parsed as an option, this will be `true`. /// option: bool, /// // All free (non-option) arguments will be collected into this Vec. /// #[options(free)] /// free: Vec, /// } /// /// // Command line arguments. /// let args = &["foo", "-o", "bar"]; /// /// // Using the `AllOptions` parsing style, the "-o" argument in the middle of args /// // will be parsed as an option. /// let opts = MyOptions::parse_args(args, ParsingStyle::AllOptions).unwrap(); /// /// assert_eq!(opts.option, true); /// assert_eq!(opts.free, vec!["foo", "bar"]); /// /// // Using the `StopAtFirstFree` option, the first non-option argument will terminate /// // option parsing. That means "-o" is treated as a free argument. /// let opts = MyOptions::parse_args(args, ParsingStyle::StopAtFirstFree).unwrap(); /// /// assert_eq!(opts.option, false); /// assert_eq!(opts.free, vec!["foo", "-o", "bar"]); /// ``` /// /// [`parse_args_default`]: fn.parse_args_default.html /// [`parse_args_default_or_exit`]: fn.parse_args_default_or_exit.html #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum ParsingStyle { /// Process all option arguments that appear AllOptions, /// After the first "free" argument is encountered, /// all remaining arguments will be considered "free" arguments. StopAtFirstFree, } impl Error { /// Returns an error for a failed attempt at parsing an option value. pub fn failed_parse(opt: Opt, err: String) -> Error { Error{kind: ErrorKind::FailedParse(opt.to_string(), err)} } /// Returns an error for a failed attempt at parsing an option's default value. pub fn failed_parse_default(option: &'static str, value: &'static str, err: String) -> Error { Error{kind: ErrorKind::FailedParseDefault{option, value, err}} } /// Returns an error for a failed attempt at parsing an option value. pub fn failed_parse_with_name(name: String, err: String) -> Error { Error{kind: ErrorKind::FailedParse(name, err)} } /// Returns an error for an option expecting two or more arguments not /// receiving the expected number of arguments. pub fn insufficient_arguments(opt: Opt, expected: usize, found: usize) -> Error { Error{kind: ErrorKind::InsufficientArguments{ option: opt.to_string(), expected: expected, found: found, }} } /// Returns an error for an option receiving an unexpected argument value, /// e.g. `--option=value`. pub fn unexpected_argument(opt: Opt) -> Error { Error{kind: ErrorKind::UnexpectedArgument(opt.to_string())} } /// Returns an error for an option expecting two or more argument values /// receiving only one in the long form, e.g. `--option=value`. /// /// These options must be passed as, e.g. `--option value second-value [...]`. pub fn unexpected_single_argument(opt: Opt, n: usize) -> Error { Error{kind: ErrorKind::UnexpectedSingleArgument(opt.to_string(), n)} } /// Returns an error for a missing required argument. pub fn missing_argument(opt: Opt) -> Error { Error{kind: ErrorKind::MissingArgument(opt.to_string())} } /// Returns an error for a missing command name. pub fn missing_command() -> Error { Error{kind: ErrorKind::MissingCommand} } /// Returns an error for a missing required option. pub fn missing_required(opt: &str) -> Error { Error{kind: ErrorKind::MissingRequired(opt.to_owned())} } /// Returns an error for a missing required command. pub fn missing_required_command() -> Error { Error{kind: ErrorKind::MissingRequiredCommand} } /// Returns an error for a missing required free argument. pub fn missing_required_free() -> Error { Error{kind: ErrorKind::MissingRequiredFree} } /// Returns an error when a free argument was encountered, but the options /// type does not support free arguments. pub fn unexpected_free(arg: &str) -> Error { Error{kind: ErrorKind::UnexpectedFree(arg.to_owned())} } /// Returns an error for an unrecognized command. pub fn unrecognized_command(name: &str) -> Error { Error{kind: ErrorKind::UnrecognizedCommand(name.to_owned())} } /// Returns an error for an unrecognized option. pub fn unrecognized_option(opt: Opt) -> Error { match opt { Opt::Short(short) => Error::unrecognized_short(short), Opt::Long(long) | Opt::LongWithArg(long, _) => Error::unrecognized_long(long), Opt::Free(_) => panic!("`Error::unrecognized_option` called with `Opt::Free` value") } } /// Returns an error for an unrecognized long option, e.g. `--option`. pub fn unrecognized_long(opt: &str) -> Error { Error{kind: ErrorKind::UnrecognizedLongOption(opt.to_owned())} } /// Returns an error for an unrecognized short option, e.g. `-o`. pub fn unrecognized_short(opt: char) -> Error { Error{kind: ErrorKind::UnrecognizedShortOption(opt)} } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use self::ErrorKind::*; match &self.kind { FailedParse(opt, arg) => write!(f, "invalid argument to option `{}`: {}", opt, arg), FailedParseDefault{option, value, err} => write!(f, "invalid default value for `{}` ({:?}): {}", option, value, err), InsufficientArguments{option, expected, found} => write!(f, "insufficient arguments to option `{}`: expected {}; found {}", option, expected, found), MissingArgument(opt) => write!(f, "missing argument to option `{}`", opt), MissingCommand => f.write_str("missing command name"), MissingRequired(opt) => write!(f, "missing required option `{}`", opt), MissingRequiredCommand => f.write_str("missing required command"), MissingRequiredFree => f.write_str("missing required free argument"), UnexpectedArgument(opt) => write!(f, "option `{}` does not accept an argument", opt), UnexpectedSingleArgument(opt, n) => write!(f, "option `{}` expects {} arguments; found 1", opt, n), UnexpectedFree(arg) => write!(f, "unexpected free argument `{}`", arg), UnrecognizedCommand(cmd) => write!(f, "unrecognized command `{}`", cmd), UnrecognizedLongOption(opt) => write!(f, "unrecognized option `--{}`", opt), UnrecognizedShortOption(opt) => write!(f, "unrecognized option `-{}`", opt), } } } impl StdError for Error { fn description(&self) -> &str { "failed to parse arguments" } } impl<'a, S: 'a + AsRef> Parser<'a, S> { /// Returns a new parser for the given series of arguments. /// /// The given slice should **not** contain the program name as its first /// element. pub fn new(args: &'a [S], style: ParsingStyle) -> Parser<'a, S> { Parser{ args: args.iter(), cur: None, style: style, terminated: false, } } /// Returns the next option or `None` if no options remain. pub fn next_opt(&mut self) -> Option> { if let Some(mut cur) = self.cur.take() { if let Some(opt) = cur.next() { self.cur = Some(cur); return Some(Opt::Short(opt)); } } if self.terminated { return self.args.next().map(|s| Opt::Free(s.as_ref())); } match self.args.next().map(|s| s.as_ref()) { Some(arg @ "-") => { if self.style == ParsingStyle::StopAtFirstFree { self.terminated = true; } Some(Opt::Free(arg)) } Some("--") => { self.terminated = true; self.args.next().map(|s| Opt::Free(s.as_ref())) } Some(long) if long.starts_with("--") => { match long.find('=') { Some(pos) => Some(Opt::LongWithArg( &long[2..pos], &long[pos + 1..])), None => Some(Opt::Long(&long[2..])) } } Some(short) if short.starts_with('-') => { let mut chars = short[1..].chars(); let res = chars.next().map(Opt::Short); self.cur = Some(chars); res } Some(free) => { if self.style == ParsingStyle::StopAtFirstFree { self.terminated = true; } Some(Opt::Free(free)) } None => None } } /// Returns the next argument to an option or `None` if none remain. pub fn next_arg(&mut self) -> Option<&'a str> { if let Some(cur) = self.cur.take() { let arg = cur.as_str(); if !arg.is_empty() { return Some(arg); } } self.args.next().map(|s| s.as_ref()) } } impl<'a, S: 'a> Clone for Parser<'a, S> { fn clone(&self) -> Parser<'a, S> { Parser{ args: self.args.clone(), cur: self.cur.clone(), style: self.style, terminated: self.terminated, } } } impl<'a> Opt<'a> { #[doc(hidden)] pub fn to_string(&self) -> String { match *self { Opt::Short(ch) => format!("-{}", ch), Opt::Long(s) => format!("--{}", s), Opt::LongWithArg(opt, _) => format!("--{}", opt), Opt::Free(_) => "free".to_owned() } } } impl Default for ParsingStyle { /// Returns the default parsing style, `AllOptions`. fn default() -> ParsingStyle { ParsingStyle::AllOptions } } /// Parses arguments from the command line. /// /// The first argument (the program name) should be omitted. pub fn parse_args(args: &[String], style: ParsingStyle) -> Result { T::parse_args(args, style) } /// Parses arguments from the command line using the default /// [parsing style](enum.ParsingStyle.html). /// /// The first argument (the program name) should be omitted. pub fn parse_args_default(args: &[String]) -> Result { T::parse_args_default(args) } /// Parses arguments from the environment. /// /// If an error is encountered, the error is printed to `stderr` and the /// process will exit with status code `2`. /// /// If the user supplies a help option, option usage will be printed to /// `stderr` and the process will exit with status code `0`. /// /// Otherwise, the parsed options are returned. /// /// # Panics /// /// If any argument to the process is not valid unicode. pub fn parse_args_or_exit(style: ParsingStyle) -> T { T::parse_args_or_exit(style) } /// Parses arguments from the environment, using the default /// [parsing style](enum.ParsingStyle.html). /// /// If an error is encountered, the error is printed to `stderr` and the /// process will exit with status code `2`. /// /// If the user supplies a help option, option usage will be printed to /// `stderr` and the process will exit with status code `0`. /// /// Otherwise, the parsed options are returned. /// /// # Panics /// /// If any argument to the process is not valid unicode. pub fn parse_args_default_or_exit() -> T { T::parse_args_default_or_exit() } #[cfg(test)] mod test { use super::{Opt, Parser, ParsingStyle}; use assert_matches::assert_matches; #[test] fn test_parser() { let args = &["-a", "b", "-cde", "arg", "-xfoo", "--long", "--opt=val", "--", "y", "-z"]; let mut p = Parser::new(args, ParsingStyle::AllOptions); assert_matches!(p.next_opt(), Some(Opt::Short('a'))); assert_matches!(p.next_opt(), Some(Opt::Free("b"))); assert_matches!(p.next_opt(), Some(Opt::Short('c'))); assert_matches!(p.next_opt(), Some(Opt::Short('d'))); assert_matches!(p.next_opt(), Some(Opt::Short('e'))); assert_matches!(p.next_arg(), Some("arg")); assert_matches!(p.next_opt(), Some(Opt::Short('x'))); assert_matches!(p.next_arg(), Some("foo")); assert_matches!(p.next_opt(), Some(Opt::Long("long"))); assert_matches!(p.next_opt(), Some(Opt::LongWithArg("opt", "val"))); assert_matches!(p.next_opt(), Some(Opt::Free("y"))); assert_matches!(p.next_opt(), Some(Opt::Free("-z"))); assert_matches!(p.next_opt(), None); } #[test] fn test_parsing_style() { let args = &["-a", "b", "-c", "--d"]; let mut p = Parser::new(args, ParsingStyle::AllOptions); assert_matches!(p.next_opt(), Some(Opt::Short('a'))); assert_matches!(p.next_opt(), Some(Opt::Free("b"))); assert_matches!(p.next_opt(), Some(Opt::Short('c'))); assert_matches!(p.next_opt(), Some(Opt::Long("d"))); assert_matches!(p.next_opt(), None); let mut p = Parser::new(args, ParsingStyle::StopAtFirstFree); assert_matches!(p.next_opt(), Some(Opt::Short('a'))); assert_matches!(p.next_opt(), Some(Opt::Free("b"))); assert_matches!(p.next_opt(), Some(Opt::Free("-c"))); assert_matches!(p.next_opt(), Some(Opt::Free("--d"))); assert_matches!(p.next_opt(), None); } } gumdrop-0.8.1/tests/options.rs000064400000000000000000000765750072674642500145660ustar 00000000000000use std::str::FromStr; use assert_matches::assert_matches; use gumdrop::Options; const EMPTY: &'static [&'static str] = &[]; #[derive(Debug, Options)] struct NoOpts { } macro_rules! is_err { ( $e:expr , |$ident:ident| $expr:expr ) => { let $ident = $e.map(|_| ()).unwrap_err().to_string(); assert!($expr, "error {:?} does not match `{}`", $ident, stringify!($expr)); }; ( $e:expr , $str:expr ) => { assert_eq!($e.map(|_| ()).unwrap_err().to_string(), $str) }; } #[test] fn test_hygiene() { // Define these aliases in local scope to ensure that generated code // is using absolute paths, i.e. `::std::result::Result` #[allow(dead_code)] struct AsRef; #[allow(dead_code)] struct Default; #[allow(dead_code)] struct FromStr; #[allow(dead_code)] struct Option; #[allow(dead_code)] struct Some; #[allow(dead_code)] struct None; #[allow(dead_code)] struct Options; #[allow(dead_code)] struct Result; #[allow(dead_code)] struct Ok; #[allow(dead_code)] struct Err; #[allow(dead_code)] struct String; #[allow(dead_code)] struct ToString; #[allow(dead_code)] struct Vec; #[derive(Options)] struct Opts { a: i32, b: ::std::string::String, c: ::std::option::Option<::std::string::String>, d: ::std::option::Option, e: ::std::vec::Vec, f: ::std::vec::Vec<::std::string::String>, g: ::std::option::Option<(i32, i32)>, #[options(command)] cmd: ::std::option::Option, } #[derive(Options)] enum Cmd { Foo(FooOpts), Bar(BarOpts), } #[derive(Options)] struct FooOpts { #[options(free)] free: ::std::vec::Vec<::std::string::String>, a: i32, } #[derive(Options)] struct BarOpts { #[options(free)] first: ::std::option::Option<::std::string::String>, #[options(free)] rest: ::std::vec::Vec<::std::string::String>, a: i32, } // This is basically just a compile-pass test, so whatever. } #[test] fn test_command() { #[derive(Options)] struct Opts { help: bool, #[options(command)] command: Option, } #[derive(Debug, Options)] enum Command { Foo(FooOpts), Bar(BarOpts), #[options(name = "bzzz")] Baz(NoOpts), FooBar(NoOpts), FooXYZ(NoOpts), } #[derive(Debug, Options)] struct FooOpts { foo: Option, } #[derive(Debug, Options)] struct BarOpts { #[options(free)] free: Vec, } let opts = Opts::parse_args_default(EMPTY).unwrap(); assert_eq!(opts.command.is_none(), true); let opts = Opts::parse_args_default(&["-h"]).unwrap(); assert_eq!(opts.help, true); assert_eq!(opts.command.is_none(), true); let opts = Opts::parse_args_default(&["-h", "foo", "--foo", "x"]).unwrap(); assert_eq!(opts.help, true); let cmd = opts.command.unwrap(); assert_matches!(cmd, Command::Foo(FooOpts{foo: Some(ref foo)}) if foo == "x"); let opts = Opts::parse_args_default(&["--", "foo"]).unwrap(); assert_eq!(opts.help, false); let cmd = opts.command.unwrap(); assert_matches!(cmd, Command::Foo(_)); let opts = Opts::parse_args_default(&["bar", "free"]).unwrap(); let cmd = opts.command.unwrap(); assert_matches!(cmd, Command::Bar(ref bar) if bar.free == ["free"]); let opts = Opts::parse_args_default(&["bzzz"]).unwrap(); let cmd = opts.command.unwrap(); assert_matches!(cmd, Command::Baz(_)); let opts = Opts::parse_args_default(&["foo-bar"]).unwrap(); let cmd = opts.command.unwrap(); assert_matches!(cmd, Command::FooBar(_)); let opts = Opts::parse_args_default(&["foo-x-y-z"]).unwrap(); let cmd = opts.command.unwrap(); assert_matches!(cmd, Command::FooXYZ(_)); is_err!(Opts::parse_args_default(&["foo", "-h"]), "unrecognized option `-h`"); is_err!(Opts::parse_args_default(&["baz"]), "unrecognized command `baz`"); } #[test] fn test_nested_command() { #[derive(Debug, Options)] struct Main { #[options(help = "main help")] help: bool, #[options(command)] command: Option, } #[derive(Debug, Options)] enum Command { #[options(help = "alpha help")] Alpha(Alpha), #[options(help = "bravo help")] Bravo(Bravo), } #[derive(Debug, Options)] struct Alpha { #[options(help = "alpha command help")] help: bool, #[options(command)] command: Option, } #[derive(Debug, Options)] struct Bravo { #[options(help = "bravo command help")] help: bool, #[options(help = "bravo option help")] option: u32, } #[derive(Debug, Options)] enum AlphaCommand { #[options(help = "alpha foo help")] Foo(Foo), #[options(help = "alpha bar help")] Bar(Bar), } #[derive(Debug, Options)] struct Foo { #[options(help = "alpha foo command help")] help: bool, #[options(help = "alpha foo beep help")] beep: u32, } #[derive(Debug, Options)] struct Bar { #[options(help = "alpha bar command help")] help: bool, #[options(help = "alpha bar boop help")] boop: u32, } let opts = Main::parse_args_default(&["-h"]).unwrap(); assert_eq!(opts.self_usage(), Main::usage()); let opts = Main::parse_args_default(&["-h", "alpha"]).unwrap(); assert_eq!(opts.self_usage(), Alpha::usage()); let opts = Main::parse_args_default(&["-h", "bravo"]).unwrap(); assert_eq!(opts.self_usage(), Bravo::usage()); let opts = Main::parse_args_default(&["-h", "alpha", "foo"]).unwrap(); assert_eq!(opts.self_usage(), Foo::usage()); let opts = Main::parse_args_default(&["-h", "alpha", "bar"]).unwrap(); assert_eq!(opts.self_usage(), Bar::usage()); } #[test] fn test_command_name() { #[derive(Options)] struct Opts { help: bool, #[options(command)] command: Option, } #[derive(Debug, Options)] enum Command { Foo(NoOpts), Bar(NoOpts), #[options(name = "bzzz")] Baz(NoOpts), BoopyDoop(NoOpts), } let opts = Opts::parse_args_default(&["foo"]).unwrap(); assert_matches!(opts.command_name(), Some("foo")); let opts = Opts::parse_args_default(&["bar"]).unwrap(); assert_matches!(opts.command_name(), Some("bar")); let opts = Opts::parse_args_default(&["bzzz"]).unwrap(); assert_matches!(opts.command_name(), Some("bzzz")); let opts = Opts::parse_args_default(&["boopy-doop"]).unwrap(); assert_matches!(opts.command_name(), Some("boopy-doop")); } #[test] fn test_command_usage() { #[derive(Options)] struct Opts { #[options(help = "help me!")] help: bool, #[options(command)] command: Option, } #[derive(Options)] enum Command { #[options(help = "foo help")] Foo(NoOpts), #[options(help = "bar help")] Bar(NoOpts), #[options(help = "baz help")] #[options(name = "bzzz")] Baz(NoOpts), } assert_eq!(Command::usage(), &" foo foo help bar bar help bzzz baz help" // Skip leading newline [1..]); assert_eq!(Command::command_list(), Some(Command::usage())); assert_eq!(Opts::command_list(), Some(Command::usage())); } #[test] fn test_opt_bool() { #[derive(Options)] struct Opts { switch: bool, } let opts = Opts::parse_args_default(&["--switch"]).unwrap(); assert_eq!(opts.switch, true); let opts = Opts::parse_args_default(&["-s"]).unwrap(); assert_eq!(opts.switch, true); is_err!(Opts::parse_args_default(&["--switch=x"]), "option `--switch` does not accept an argument"); } #[test] fn test_opt_string() { #[derive(Options)] struct Opts { foo: String, } let opts = Opts::parse_args_default(&["--foo", "value"]).unwrap(); assert_eq!(opts.foo, "value"); let opts = Opts::parse_args_default(&["-f", "value"]).unwrap(); assert_eq!(opts.foo, "value"); let opts = Opts::parse_args_default(&["-fvalue"]).unwrap(); assert_eq!(opts.foo, "value"); } #[test] fn test_opt_int() { #[derive(Options)] struct Opts { number: i32, } let opts = Opts::parse_args_default(&["--number", "123"]).unwrap(); assert_eq!(opts.number, 123); let opts = Opts::parse_args_default(&["-n", "123"]).unwrap(); assert_eq!(opts.number, 123); let opts = Opts::parse_args_default(&["-n123"]).unwrap(); assert_eq!(opts.number, 123); is_err!(Opts::parse_args_default(&["-nfail"]), |e| e.starts_with("invalid argument to option `-n`: ")); is_err!(Opts::parse_args_default(&["--number", "fail"]), |e| e.starts_with("invalid argument to option `--number`: ")); is_err!(Opts::parse_args_default(&["--number=fail"]), |e| e.starts_with("invalid argument to option `--number`: ")); } #[test] fn test_opt_tuple() { #[derive(Options)] struct Opts { alpha: (i32, i32), bravo: Option<(i32, i32, i32)>, charlie: Vec<(i32, i32, i32, i32)>, #[options(free)] free: Vec, } let opts = Opts::parse_args_default(&[ "--alpha", "1", "2", "--bravo", "11", "12", "13", "--charlie", "21", "22", "23", "24", "--charlie", "31", "32", "33", "34", "free", ]).unwrap(); assert_eq!(opts.alpha, (1, 2)); assert_eq!(opts.bravo, Some((11, 12, 13))); assert_eq!(opts.charlie, vec![ (21, 22, 23, 24), (31, 32, 33, 34), ]); assert_eq!(opts.free, vec!["free".to_owned()]); } #[test] fn test_opt_tuple_error() { #[derive(Options)] struct Opts { foo: Option<(i32, i32)>, } is_err!(Opts::parse_args_default(&["--foo"]), "insufficient arguments to option `--foo`: expected 2; found 0"); is_err!(Opts::parse_args_default(&["--foo=0", "1"]), "option `--foo` expects 2 arguments; found 1"); is_err!(Opts::parse_args_default(&["--foo", "0"]), "insufficient arguments to option `--foo`: expected 2; found 1"); } #[test] fn test_opt_push() { #[derive(Options)] struct Opts { thing: Vec, } let opts = Opts::parse_args_default(EMPTY).unwrap(); assert!(opts.thing.is_empty()); let opts = Opts::parse_args_default( &["-t", "a", "-tb", "--thing=c", "--thing", "d"]).unwrap(); assert_eq!(opts.thing, ["a", "b", "c", "d"]); } #[test] fn test_opt_count() { #[derive(Options)] struct Opts { #[options(count)] number: i32, } let opts = Opts::parse_args_default(EMPTY).unwrap(); assert_eq!(opts.number, 0); let opts = Opts::parse_args_default(&["--number"]).unwrap(); assert_eq!(opts.number, 1); let opts = Opts::parse_args_default(&["-nnn"]).unwrap(); assert_eq!(opts.number, 3); } #[test] fn test_opt_long() { #[derive(Options)] struct Opts { #[options(long = "thing", no_short)] foo: bool, } let opts = Opts::parse_args_default(&["--thing"]).unwrap(); assert_eq!(opts.foo, true); is_err!(Opts::parse_args_default(&["-f"]), "unrecognized option `-f`"); is_err!(Opts::parse_args_default(&["--foo"]), "unrecognized option `--foo`"); } #[test] fn test_opt_short() { #[derive(Options)] struct Opts { #[options(short = "x", no_long)] foo: bool, } let opts = Opts::parse_args_default(&["-x"]).unwrap(); assert_eq!(opts.foo, true); is_err!(Opts::parse_args_default(&["-f"]), "unrecognized option `-f`"); is_err!(Opts::parse_args_default(&["--foo"]), "unrecognized option `--foo`"); } #[test] fn test_opt_short_override() { // Ensures that the generated code sees the manual assignment of short // option for `option_1` before generating a short option for `option_0`. // Thus, giving `option_0` an automatic short option of `O`, // rather than causing a collision. #[derive(Options)] struct Opts { #[options(no_long)] option_0: bool, #[options(short = "o", no_long)] option_1: bool, } let opts = Opts::parse_args_default(&["-o"]).unwrap(); assert_eq!(opts.option_0, false); assert_eq!(opts.option_1, true); let opts = Opts::parse_args_default(&["-O"]).unwrap(); assert_eq!(opts.option_0, true); assert_eq!(opts.option_1, false); } #[test] fn test_opt_free() { #[derive(Options)] struct Opts { #[options(free)] free: Vec, } let opts = Opts::parse_args_default(&["a", "b", "c"]).unwrap(); assert_eq!(opts.free, ["a", "b", "c"]); } #[test] fn test_opt_no_free() { #[derive(Options)] struct Opts { } assert!(Opts::parse_args_default(EMPTY).is_ok()); is_err!(Opts::parse_args_default(&["a"]), "unexpected free argument `a`"); } #[test] fn test_typed_free() { #[derive(Options)] struct Opts { #[options(free)] free: Vec, } let opts = Opts::parse_args_default(&["1", "2", "3"]).unwrap(); assert_eq!(opts.free, [1, 2, 3]); } #[test] fn test_multi_free() { #[derive(Options)] struct Opts { #[options(free, help = "alpha help")] alpha: u32, #[options(free, help = "bravo help")] bravo: Option, #[options(free, help = "charlie help")] charlie: Option, } let opts = Opts::parse_args_default(EMPTY).unwrap(); assert_eq!(opts.alpha, 0); assert_eq!(opts.bravo, None); assert_eq!(opts.charlie, None); let opts = Opts::parse_args_default(&["1"]).unwrap(); assert_eq!(opts.alpha, 1); assert_eq!(opts.bravo, None); assert_eq!(opts.charlie, None); let opts = Opts::parse_args_default(&["1", "two", "3"]).unwrap(); assert_eq!(opts.alpha, 1); assert_eq!(opts.bravo, Some("two".to_owned())); assert_eq!(opts.charlie, Some(3)); is_err!(Opts::parse_args_default(&["1", "two", "3", "4"]), "unexpected free argument `4`"); assert_eq!(Opts::usage(), &" Positional arguments: alpha alpha help bravo bravo help charlie charlie help" // Skip leading newline [1..]); #[derive(Options)] struct ManyOpts { #[options(free, help = "alpha help")] alpha: u32, #[options(free, help = "bravo help")] bravo: Option, #[options(free, help = "charlie help")] charlie: Option, #[options(free)] rest: Vec, } let opts = ManyOpts::parse_args_default(EMPTY).unwrap(); assert_eq!(opts.alpha, 0); assert_eq!(opts.bravo, None); assert_eq!(opts.charlie, None); assert_eq!(opts.rest, Vec::::new()); let opts = ManyOpts::parse_args_default(&["1", "two", "3", "4", "five", "VI"]).unwrap(); assert_eq!(opts.alpha, 1); assert_eq!(opts.bravo, Some("two".to_owned())); assert_eq!(opts.charlie, Some(3)); assert_eq!(opts.rest, vec!["4".to_owned(), "five".to_owned(), "VI".to_owned()]); } #[test] fn test_usage() { #[derive(Options)] struct Opts { #[options(help = "alpha help")] alpha: bool, #[options(no_short, help = "bravo help")] bravo: String, #[options(no_long, help = "charlie help")] charlie: bool, #[options(help = "delta help", meta = "X")] delta: i32, #[options(help = "echo help", meta = "Y")] echo: Vec, #[options(help = "foxtrot help", meta = "Z", default = "99")] foxtrot: u32, #[options(no_short, help = "long option help")] very_very_long_option_with_very_very_long_name: bool, } assert_eq!(Opts::usage(), &" Optional arguments: -a, --alpha alpha help --bravo BRAVO bravo help -c charlie help -d, --delta X delta help -e, --echo Y echo help -f, --foxtrot Z foxtrot help (default: 99) --very-very-long-option-with-very-very-long-name long option help" // Skip leading newline [1..]); #[derive(Options)] struct TupleOpts { #[options(help = "alpha help")] alpha: (), #[options(help = "bravo help")] bravo: (i32,), #[options(help = "charlie help")] charlie: (i32, i32), #[options(help = "delta help")] delta: (i32, i32, i32), #[options(help = "echo help")] echo: (i32, i32, i32, i32), } assert_eq!(TupleOpts::usage(), &" Optional arguments: -a, --alpha alpha help -b, --bravo BRAVO bravo help -c, --charlie CHARLIE VALUE charlie help -d, --delta DELTA VALUE0 VALUE1 delta help -e, --echo ECHO VALUE0 VALUE1 VALUE2 echo help" // Skip leading newline [1..]); #[derive(Options)] struct FreeOpts { #[options(free, help = "a help")] a: u32, #[options(free, help = "b help")] b: u32, #[options(free, help = "c help")] c: u32, #[options(help = "option help")] option: bool, } assert_eq!(FreeOpts::usage(), &" Positional arguments: a a help b b help c c help Optional arguments: -o, --option option help" // Skip leading newline [1..]); } #[test] fn test_help_flag() { #[derive(Options)] struct Opts { help: bool, } let opts = Opts::parse_args_default(EMPTY).unwrap(); assert_eq!(opts.help_requested(), false); let opts = Opts::parse_args_default(&["--help"]).unwrap(); assert_eq!(opts.help_requested(), true); } #[test] fn test_no_help_flag() { #[derive(Options)] struct Opts { #[options(no_help_flag)] help: bool, } let opts = Opts::parse_args_default(&["--help"]).unwrap(); assert_eq!(opts.help_requested(), false); } #[test] fn test_many_help_flags() { #[derive(Options)] struct Opts { #[options(help_flag)] help: bool, #[options(help_flag)] halp: bool, #[options(help_flag)] help_please: bool, } let opts = Opts::parse_args_default(EMPTY).unwrap(); assert_eq!(opts.help_requested(), false); let opts = Opts::parse_args_default(&["--help"]).unwrap(); assert_eq!(opts.help_requested(), true); let opts = Opts::parse_args_default(&["--halp"]).unwrap(); assert_eq!(opts.help_requested(), true); let opts = Opts::parse_args_default(&["--help-please"]).unwrap(); assert_eq!(opts.help_requested(), true); } #[test] fn test_help_flag_command() { #[derive(Options)] struct Opts { help: bool, #[options(command)] cmd: Option, } #[derive(Options)] struct Opts2 { #[options(command)] cmd: Option, } #[derive(Options)] struct Opts3 { help: bool, #[options(help_flag)] help2: bool, #[options(command)] cmd: Option, } #[derive(Options)] enum Cmd { Foo(CmdOpts), Bar(CmdOpts), Baz(CmdOpts), } #[derive(Options)] struct CmdOpts { help: bool, } let opts = Opts::parse_args_default(EMPTY).unwrap(); assert_eq!(opts.help_requested(), false); let opts = Opts::parse_args_default(&["-h"]).unwrap(); assert_eq!(opts.help_requested(), true); let opts = Opts::parse_args_default(&["foo", "-h"]).unwrap(); assert_eq!(opts.help_requested(), true); let opts = Opts::parse_args_default(&["bar", "-h"]).unwrap(); assert_eq!(opts.help_requested(), true); let opts = Opts::parse_args_default(&["baz", "-h"]).unwrap(); assert_eq!(opts.help_requested(), true); let opts = Opts2::parse_args_default(EMPTY).unwrap(); assert_eq!(opts.help_requested(), false); let opts = Opts3::parse_args_default(EMPTY).unwrap(); assert_eq!(opts.help_requested(), false); } #[test] fn test_type_attrs() { #[derive(Options)] #[options(no_help_flag, no_short, no_long)] struct Opts { #[options(long = "help")] help: bool, #[options(long = "foo")] foo: bool, #[options(short = "b")] bar: bool, } is_err!(Opts::parse_args_default(&["-f"]), "unrecognized option `-f`"); is_err!(Opts::parse_args_default(&["--bar"]), "unrecognized option `--bar`"); is_err!(Opts::parse_args_default(&["-h"]), "unrecognized option `-h`"); let opts = Opts::parse_args_default(&["--help"]).unwrap(); assert_eq!(opts.help, true); assert_eq!(opts.help_requested(), false); let opts = Opts::parse_args_default(&["--foo"]).unwrap(); assert_eq!(opts.foo, true); let opts = Opts::parse_args_default(&["-b"]).unwrap(); assert_eq!(opts.bar, true); #[derive(Options)] #[options(no_short)] struct Opts2 { foo: bool, #[options(short = "b")] bar: bool, } is_err!(Opts2::parse_args_default(&["-f"]), "unrecognized option `-f`"); let opts = Opts2::parse_args_default(&["--foo", "-b"]).unwrap(); assert_eq!(opts.foo, true); assert_eq!(opts.bar, true); let opts = Opts2::parse_args_default(&["--bar"]).unwrap(); assert_eq!(opts.bar, true); #[derive(Options)] #[options(no_long)] struct Opts3 { foo: bool, #[options(long = "bar")] bar: bool, } is_err!(Opts3::parse_args_default(&["--foo"]), "unrecognized option `--foo`"); let opts = Opts3::parse_args_default(&["--bar"]).unwrap(); assert_eq!(opts.bar, true); let opts = Opts3::parse_args_default(&["-f", "-b"]).unwrap(); assert_eq!(opts.foo, true); assert_eq!(opts.bar, true); #[derive(Options)] #[options(no_help_flag)] struct Opts4 { #[options(help_flag)] help: bool, } let opts = Opts4::parse_args_default(&["-h"]).unwrap(); assert_eq!(opts.help, true); assert_eq!(opts.help_requested(), true); #[derive(Options)] #[options(required)] struct Opts5 { #[options(no_long)] foo: i32, #[options(not_required)] bar: i32, } is_err!(Opts5::parse_args_default(EMPTY), "missing required option `-f`"); let opts = Opts5::parse_args_default(&["-f", "1"]).unwrap(); assert_eq!(opts.foo, 1); assert_eq!(opts.bar, 0); let opts = Opts5::parse_args_default(&["-f", "1", "--bar", "2"]).unwrap(); assert_eq!(opts.foo, 1); assert_eq!(opts.bar, 2); } #[test] fn test_required() { #[derive(Options)] struct Opts { #[options(required)] foo: i32, optional: i32, } #[derive(Options)] struct Opts2 { #[options(command, required)] command: Option, optional: i32, } #[derive(Options)] enum Cmd { Foo(NoOpts), } #[derive(Options)] struct Opts3 { #[options(free, required)] bar: i32, optional: i32, } is_err!(Opts::parse_args_default(EMPTY), "missing required option `--foo`"); is_err!(Opts2::parse_args_default(EMPTY), "missing required command"); is_err!(Opts3::parse_args_default(EMPTY), "missing required free argument"); let opts = Opts::parse_args_default(&["-f", "1"]).unwrap(); assert_eq!(opts.foo, 1); let opts = Opts::parse_args_default(&["-f1"]).unwrap(); assert_eq!(opts.foo, 1); let opts = Opts::parse_args_default(&["--foo", "1"]).unwrap(); assert_eq!(opts.foo, 1); let opts = Opts::parse_args_default(&["--foo=1"]).unwrap(); assert_eq!(opts.foo, 1); let opts = Opts2::parse_args_default(&["foo"]).unwrap(); assert!(opts.command.is_some()); let opts = Opts3::parse_args_default(&["1"]).unwrap(); assert_eq!(opts.bar, 1); } #[test] fn test_required_help() { #[derive(Options)] struct Opts { #[options(required)] thing: Option, help: bool, } #[derive(Options)] struct Opts2 { #[options(required)] thing: Option, help: bool, #[options(help_flag)] secondary_help: bool, } let opts = Opts::parse_args_default(&["-h"]).unwrap(); assert_eq!(opts.help, true); let opts = Opts2::parse_args_default(&["--secondary-help"]).unwrap(); assert_eq!(opts.secondary_help, true); } #[test] fn test_parse() { #[derive(Options)] struct Opts { #[options(help = "foo", parse(from_str = "parse_foo"))] foo: Option, #[options(help = "bar", parse(try_from_str = "parse_bar"))] bar: Option, #[options(help = "baz", parse(from_str))] baz: Option, #[options(help = "quux", parse(try_from_str))] quux: Option, } #[derive(Debug)] struct Foo(String); #[derive(Debug)] struct Bar(u32); #[derive(Debug)] struct Baz(String); #[derive(Debug)] struct Quux(u32); fn parse_foo(s: &str) -> Foo { Foo(s.to_owned()) } fn parse_bar(s: &str) -> Result::Err> { s.parse().map(Bar) } impl<'a> From<&'a str> for Baz { fn from(s: &str) -> Baz { Baz(s.to_owned()) } } impl FromStr for Quux { type Err = ::Err; fn from_str(s: &str) -> Result { s.parse().map(Quux) } } let opts = Opts::parse_args_default(&[ "-ffoo", "--bar=123", "--baz", "sup", "-q", "456"]).unwrap(); assert_matches!(opts.foo, Some(Foo(ref s)) if s == "foo"); assert_matches!(opts.bar, Some(Bar(123))); assert_matches!(opts.baz, Some(Baz(ref s)) if s == "sup"); assert_matches!(opts.quux, Some(Quux(456))); is_err!(Opts::parse_args_default(&["--bar", "xyz"]), |e| e.starts_with("invalid argument to option `--bar`: ")); is_err!(Opts::parse_args_default(&["--quux", "xyz"]), |e| e.starts_with("invalid argument to option `--quux`: ")); } #[test] fn test_default() { #[derive(Options)] struct Opts { foo: u32, #[options(default = "123")] bar: u32, #[options(default = "456")] baz: Baz, #[options(count, default = "789")] count: u32, } #[derive(Copy, Clone, Debug, Eq, PartialEq)] struct Baz(u32); impl FromStr for Baz { type Err = ::Err; fn from_str(s: &str) -> Result { s.parse().map(Baz) } } let opts = Opts::parse_args_default(EMPTY).unwrap(); assert_eq!(opts.foo, 0); assert_eq!(opts.bar, 123); assert_eq!(opts.baz, Baz(456)); assert_eq!(opts.count, 789); let opts = Opts::parse_args_default(&["-b99", "--baz=4387", "-c", "-f1"]).unwrap(); assert_eq!(opts.foo, 1); assert_eq!(opts.bar, 99); assert_eq!(opts.baz, Baz(4387)); assert_eq!(opts.count, 790); } #[test] fn test_failed_default() { #[derive(Options)] struct Opts { #[options(default = "lolwut")] foo: u32, } is_err!(Opts::parse_args_default(EMPTY), |e| e.starts_with(r#"invalid default value for `foo` ("lolwut"): "#)); } #[test] fn test_default_parse() { #[derive(Options)] struct Opts { #[options(default = "1", parse(try_from_str = "parse_foo"))] foo: Foo, } #[derive(Debug, Eq, PartialEq)] struct Foo(u32); fn parse_foo(s: &str) -> Result::Err> { s.parse().map(Foo) } let opts = Opts::parse_args_default(EMPTY).unwrap(); assert_eq!(opts.foo, Foo(1)); } #[test] fn test_multi() { use std::collections::VecDeque; #[derive(Options)] struct Opts { #[options(multi = "push_back")] foo: VecDeque, } #[derive(Options)] struct Opts2 { #[options(multi = "push_back")] foo: VecDeque<(i32, i32)>, } #[derive(Options)] struct Opts3 { #[options(free, multi = "push_front")] free: VecDeque, } let opts = Opts::parse_args_default(&["-f", "foo", "-f", "bar"]).unwrap(); assert_eq!(opts.foo, ["foo", "bar"]); let opts = Opts2::parse_args_default(&["-f", "1", "2", "-f", "3", "4"]).unwrap(); assert_eq!(opts.foo, [(1, 2), (3, 4)]); let opts = Opts3::parse_args_default(&["1", "2", "3"]).unwrap(); assert_eq!(opts.free, [3, 2, 1]); } #[test] fn test_no_multi() { #[derive(Options)] struct Opts { #[options(no_multi, parse(from_str = "comma_list"))] list_things: Vec, } #[derive(Options)] #[options(no_multi)] struct Opts2 { #[options(parse(from_str = "comma_list"))] list_things: Vec, } #[derive(Options)] struct Opts3 { #[options(free, no_multi, parse(from_str = "comma_list"))] list_things: Vec, } fn comma_list(s: &str) -> Vec { s.split(',').map(|s| s.to_string()).collect() } let opts = Opts::parse_args_default(&["-l", "foo,bar,baz"]).unwrap(); assert_eq!(opts.list_things, ["foo", "bar", "baz"]); let opts = Opts2::parse_args_default(&["-l", "foo,bar,baz"]).unwrap(); assert_eq!(opts.list_things, ["foo", "bar", "baz"]); let opts = Opts3::parse_args_default(&["foo,bar,baz"]).unwrap(); assert_eq!(opts.list_things, ["foo", "bar", "baz"]); is_err!(Opts3::parse_args_default(&["foo,bar,baz", "error"]), "unexpected free argument `error`"); } #[test] fn test_doc_help() { /// type-level help comment #[derive(Options)] struct Opts { /// free help comment #[options(free)] free: i32, /// help comment foo: i32, /// help comment #[options(help = "help attribute")] bar: i32, } #[derive(Options)] enum Cmd { /// help comment Alpha(NoOpts), /// help comment #[options(help = "help attribute")] Bravo(NoOpts), } assert_eq!(Opts::usage(), &" type-level help comment Positional arguments: free free help comment Optional arguments: -f, --foo FOO help comment -b, --bar BAR help attribute" // Skip leading newline [1..]); assert_eq!(Cmd::usage(), &" alpha help comment bravo help attribute" // Skip leading newline [1..]); } #[test] fn test_doc_help_multiline() { /// type-level help comment /// second line of text #[derive(Options)] struct Opts { /// help comment foo: i32, } assert_eq!(Opts::usage(), &" type-level help comment second line of text Optional arguments: -f, --foo FOO help comment" // Skip leading newline [1..]); } #[test] fn test_failed_parse_free() { #[derive(Options)] struct Opts { #[options(free)] foo: u32, #[options(free, parse(try_from_str = "parse"))] bar: u32, #[options(free)] baz: Vec, } fn parse(s: &str) -> Result::Err> { s.parse() } is_err!(Opts::parse_args_default(&["x"]), |e| e.starts_with("invalid argument to option `foo`: ")); is_err!(Opts::parse_args_default(&["0", "x"]), |e| e.starts_with("invalid argument to option `bar`: ")); is_err!(Opts::parse_args_default(&["0", "0", "x"]), |e| e.starts_with("invalid argument to option `baz`: ")); } #[cfg(feature = "default_expr")] #[test] fn test_default_expr() { #[derive(Options)] struct Opts { #[options(default_expr = "foo()")] foo: u32, } fn foo() -> u32 { 123 } let opts = Opts::parse_args_default(EMPTY).unwrap(); assert_eq!(opts.foo, foo()); }