From 5e20a29b4fdc8a2d442d1093681b396dcb4b816b Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Tue, 7 Jan 2020 11:18:04 +0000 Subject: Add structopt dependency in version 0.3.7 This patch series replaces argparse with structopt in the argument handling code. As a first step, we need structopt as a dependency. Import subrepo structopt/:structopt at efbdda4753592e27bc430fb01f7b9650b2f3174d Import subrepo bitflags/:bitflags at 30668016aca6bd3b02c766e8347e0b4080d4c296 Import subrepo clap/:clap at 784524f7eb193e35f81082cc69454c8c21b948f7 Import subrepo heck/:heck at 093d56fbf001e1506e56dbfa38631d99b1066df1 Import subrepo proc-macro-error/:proc-macro-error at 6c4cfe79a622c5de8ae68557993542be46eacae2 Import subrepo proc-macro2/:proc-macro2 at d5d48eddca4566e5438e8a2cbed4a74e049544de Import subrepo quote/:quote at 727436c6c137b20f0f34dde5d8fda2679b9747ad Import subrepo rustversion/:rustversion at 0c5663313516263059ce9059ef81fc7a1cf655ca Import subrepo syn-mid/:syn-mid at 5d3d85414a9e6674e1857ec22a87b96e04a6851a Import subrepo syn/:syn at e87c27e87f6f4ef8919d0372bdb056d53ef0d8f3 Import subrepo textwrap/:textwrap at abcd618beae3f74841032aa5b53c1086b0a57ca2 Import subrepo unicode-segmentation/:unicode-segmentation at 637c9874c4fe0c205ff27787faf150a40295c6c3 Import subrepo unicode-width/:unicode-width at 3033826f8bf05e82724140a981d5941e48fce393 Import subrepo unicode-xid/:unicode-xid at 4baae9fffb156ba229665b972a9cd5991787ceb7 --- structopt/.gitignore | 6 + structopt/.travis.yml | 24 + structopt/CHANGELOG.md | 444 +++++++++ structopt/Cargo.toml | 35 + structopt/LICENSE-APACHE | 201 ++++ structopt/LICENSE-MIT | 21 + structopt/README.md | 148 +++ structopt/examples/README.md | 82 ++ structopt/examples/after_help.rs | 19 + structopt/examples/at_least_two.rs | 15 + structopt/examples/basic.rs | 48 + structopt/examples/deny_missing_docs.rs | 51 + structopt/examples/doc_comments.rs | 74 ++ structopt/examples/enum_in_args.rs | 25 + structopt/examples/enum_tuple.rs | 26 + structopt/examples/env.rs | 26 + structopt/examples/example.rs | 54 ++ structopt/examples/flatten.rs | 29 + structopt/examples/gen_completions.rs | 26 + structopt/examples/git.rs | 35 + structopt/examples/group.rs | 31 + structopt/examples/keyvalue.rs | 36 + structopt/examples/negative_flag.rs | 15 + structopt/examples/no_version.rs | 17 + structopt/examples/rename_all.rs | 74 ++ structopt/examples/skip.rs | 47 + structopt/examples/subcommand_aliases.rs | 21 + structopt/examples/true_or_false.rs | 41 + structopt/link-check-headers.json | 14 + structopt/src/lib.rs | 1015 ++++++++++++++++++++ structopt/structopt-derive/Cargo.toml | 27 + structopt/structopt-derive/LICENSE-APACHE | 201 ++++ structopt/structopt-derive/LICENSE-MIT | 21 + structopt/structopt-derive/src/attrs.rs | 620 ++++++++++++ structopt/structopt-derive/src/doc_comments.rs | 103 ++ structopt/structopt-derive/src/lib.rs | 667 +++++++++++++ structopt/structopt-derive/src/parse.rs | 304 ++++++ structopt/structopt-derive/src/spanned.rs | 101 ++ structopt/structopt-derive/src/ty.rs | 108 +++ structopt/tests/argument_naming.rs | 311 ++++++ structopt/tests/arguments.rs | 86 ++ structopt/tests/author_version_about.rs | 46 + structopt/tests/custom-string-parsers.rs | 306 ++++++ structopt/tests/deny-warnings.rs | 47 + structopt/tests/doc-comments-help.rs | 162 ++++ structopt/tests/explicit_name_no_renaming.rs | 32 + structopt/tests/flags.rs | 162 ++++ structopt/tests/flatten.rs | 95 ++ structopt/tests/issues.rs | 67 ++ structopt/tests/macro-errors.rs | 13 + structopt/tests/nested-subcommands.rs | 193 ++++ structopt/tests/non_literal_attributes.rs | 147 +++ structopt/tests/options.rs | 336 +++++++ structopt/tests/privacy.rs | 32 + structopt/tests/raw_bool_literal.rs | 29 + structopt/tests/raw_idents.rs | 17 + structopt/tests/rename_all_env.rs | 46 + structopt/tests/skip.rs | 148 +++ structopt/tests/special_types.rs | 73 ++ structopt/tests/subcommands.rs | 213 ++++ structopt/tests/ui/bool_default_value.rs | 21 + structopt/tests/ui/bool_default_value.stderr | 5 + structopt/tests/ui/bool_required.rs | 21 + structopt/tests/ui/bool_required.stderr | 5 + structopt/tests/ui/flatten_and_doc.rs | 30 + structopt/tests/ui/flatten_and_doc.stderr | 5 + structopt/tests/ui/flatten_and_methods.rs | 29 + structopt/tests/ui/flatten_and_methods.stderr | 5 + structopt/tests/ui/flatten_and_parse.rs | 29 + structopt/tests/ui/flatten_and_parse.stderr | 5 + structopt/tests/ui/non_existent_attr.rs | 21 + structopt/tests/ui/non_existent_attr.stderr | 5 + structopt/tests/ui/opt_opt_nonpositional.rs | 20 + structopt/tests/ui/opt_opt_nonpositional.stderr | 5 + structopt/tests/ui/opt_vec_nonpositional.rs | 20 + structopt/tests/ui/opt_vec_nonpositional.stderr | 5 + structopt/tests/ui/option_default_value.rs | 21 + structopt/tests/ui/option_default_value.stderr | 5 + structopt/tests/ui/option_required.rs | 21 + structopt/tests/ui/option_required.stderr | 5 + structopt/tests/ui/parse_empty_try_from_os.rs | 21 + structopt/tests/ui/parse_empty_try_from_os.stderr | 5 + structopt/tests/ui/parse_function_is_not_path.rs | 21 + .../tests/ui/parse_function_is_not_path.stderr | 5 + structopt/tests/ui/parse_literal_spec.rs | 21 + structopt/tests/ui/parse_literal_spec.stderr | 5 + structopt/tests/ui/parse_not_zero_args.rs | 21 + structopt/tests/ui/parse_not_zero_args.stderr | 5 + structopt/tests/ui/positional_bool.rs | 10 + structopt/tests/ui/positional_bool.stderr | 10 + structopt/tests/ui/raw.rs | 25 + structopt/tests/ui/raw.stderr | 19 + structopt/tests/ui/rename_all_wrong_casing.rs | 21 + structopt/tests/ui/rename_all_wrong_casing.stderr | 5 + structopt/tests/ui/skip_flatten.rs | 42 + structopt/tests/ui/skip_flatten.stderr | 5 + structopt/tests/ui/skip_subcommand.rs | 42 + structopt/tests/ui/skip_subcommand.stderr | 5 + structopt/tests/ui/skip_with_other_options.rs | 15 + structopt/tests/ui/skip_with_other_options.stderr | 5 + structopt/tests/ui/skip_without_default.rs | 29 + structopt/tests/ui/skip_without_default.stderr | 9 + structopt/tests/ui/struct_flatten.rs | 21 + structopt/tests/ui/struct_flatten.stderr | 5 + structopt/tests/ui/struct_parse.rs | 21 + structopt/tests/ui/struct_parse.stderr | 5 + structopt/tests/ui/struct_subcommand.rs | 21 + structopt/tests/ui/struct_subcommand.stderr | 5 + structopt/tests/ui/structopt_empty_attr.rs | 22 + structopt/tests/ui/structopt_empty_attr.stderr | 5 + structopt/tests/ui/structopt_name_value_attr.rs | 22 + .../tests/ui/structopt_name_value_attr.stderr | 5 + structopt/tests/ui/subcommand_and_flatten.rs | 36 + structopt/tests/ui/subcommand_and_flatten.stderr | 5 + structopt/tests/ui/subcommand_and_methods.rs | 36 + structopt/tests/ui/subcommand_and_methods.stderr | 5 + structopt/tests/ui/subcommand_and_parse.rs | 36 + structopt/tests/ui/subcommand_and_parse.stderr | 5 + structopt/tests/ui/subcommand_opt_opt.rs | 36 + structopt/tests/ui/subcommand_opt_opt.stderr | 5 + structopt/tests/ui/subcommand_opt_vec.rs | 36 + structopt/tests/ui/subcommand_opt_vec.stderr | 5 + structopt/tests/ui/tuple_struct.rs | 18 + structopt/tests/ui/tuple_struct.stderr | 5 + structopt/tests/utils.rs | 45 + 125 files changed, 8447 insertions(+) create mode 100644 structopt/.gitignore create mode 100644 structopt/.travis.yml create mode 100644 structopt/CHANGELOG.md create mode 100644 structopt/Cargo.toml create mode 100644 structopt/LICENSE-APACHE create mode 100644 structopt/LICENSE-MIT create mode 100644 structopt/README.md create mode 100644 structopt/examples/README.md create mode 100644 structopt/examples/after_help.rs create mode 100644 structopt/examples/at_least_two.rs create mode 100644 structopt/examples/basic.rs create mode 100644 structopt/examples/deny_missing_docs.rs create mode 100644 structopt/examples/doc_comments.rs create mode 100644 structopt/examples/enum_in_args.rs create mode 100644 structopt/examples/enum_tuple.rs create mode 100644 structopt/examples/env.rs create mode 100644 structopt/examples/example.rs create mode 100644 structopt/examples/flatten.rs create mode 100644 structopt/examples/gen_completions.rs create mode 100644 structopt/examples/git.rs create mode 100644 structopt/examples/group.rs create mode 100644 structopt/examples/keyvalue.rs create mode 100644 structopt/examples/negative_flag.rs create mode 100644 structopt/examples/no_version.rs create mode 100644 structopt/examples/rename_all.rs create mode 100644 structopt/examples/skip.rs create mode 100644 structopt/examples/subcommand_aliases.rs create mode 100644 structopt/examples/true_or_false.rs create mode 100644 structopt/link-check-headers.json create mode 100644 structopt/src/lib.rs create mode 100644 structopt/structopt-derive/Cargo.toml create mode 100644 structopt/structopt-derive/LICENSE-APACHE create mode 100644 structopt/structopt-derive/LICENSE-MIT create mode 100644 structopt/structopt-derive/src/attrs.rs create mode 100644 structopt/structopt-derive/src/doc_comments.rs create mode 100644 structopt/structopt-derive/src/lib.rs create mode 100644 structopt/structopt-derive/src/parse.rs create mode 100644 structopt/structopt-derive/src/spanned.rs create mode 100644 structopt/structopt-derive/src/ty.rs create mode 100644 structopt/tests/argument_naming.rs create mode 100644 structopt/tests/arguments.rs create mode 100644 structopt/tests/author_version_about.rs create mode 100644 structopt/tests/custom-string-parsers.rs create mode 100644 structopt/tests/deny-warnings.rs create mode 100644 structopt/tests/doc-comments-help.rs create mode 100644 structopt/tests/explicit_name_no_renaming.rs create mode 100644 structopt/tests/flags.rs create mode 100644 structopt/tests/flatten.rs create mode 100644 structopt/tests/issues.rs create mode 100644 structopt/tests/macro-errors.rs create mode 100644 structopt/tests/nested-subcommands.rs create mode 100644 structopt/tests/non_literal_attributes.rs create mode 100644 structopt/tests/options.rs create mode 100644 structopt/tests/privacy.rs create mode 100644 structopt/tests/raw_bool_literal.rs create mode 100644 structopt/tests/raw_idents.rs create mode 100644 structopt/tests/rename_all_env.rs create mode 100644 structopt/tests/skip.rs create mode 100644 structopt/tests/special_types.rs create mode 100644 structopt/tests/subcommands.rs create mode 100644 structopt/tests/ui/bool_default_value.rs create mode 100644 structopt/tests/ui/bool_default_value.stderr create mode 100644 structopt/tests/ui/bool_required.rs create mode 100644 structopt/tests/ui/bool_required.stderr create mode 100644 structopt/tests/ui/flatten_and_doc.rs create mode 100644 structopt/tests/ui/flatten_and_doc.stderr create mode 100644 structopt/tests/ui/flatten_and_methods.rs create mode 100644 structopt/tests/ui/flatten_and_methods.stderr create mode 100644 structopt/tests/ui/flatten_and_parse.rs create mode 100644 structopt/tests/ui/flatten_and_parse.stderr create mode 100644 structopt/tests/ui/non_existent_attr.rs create mode 100644 structopt/tests/ui/non_existent_attr.stderr create mode 100644 structopt/tests/ui/opt_opt_nonpositional.rs create mode 100644 structopt/tests/ui/opt_opt_nonpositional.stderr create mode 100644 structopt/tests/ui/opt_vec_nonpositional.rs create mode 100644 structopt/tests/ui/opt_vec_nonpositional.stderr create mode 100644 structopt/tests/ui/option_default_value.rs create mode 100644 structopt/tests/ui/option_default_value.stderr create mode 100644 structopt/tests/ui/option_required.rs create mode 100644 structopt/tests/ui/option_required.stderr create mode 100644 structopt/tests/ui/parse_empty_try_from_os.rs create mode 100644 structopt/tests/ui/parse_empty_try_from_os.stderr create mode 100644 structopt/tests/ui/parse_function_is_not_path.rs create mode 100644 structopt/tests/ui/parse_function_is_not_path.stderr create mode 100644 structopt/tests/ui/parse_literal_spec.rs create mode 100644 structopt/tests/ui/parse_literal_spec.stderr create mode 100644 structopt/tests/ui/parse_not_zero_args.rs create mode 100644 structopt/tests/ui/parse_not_zero_args.stderr create mode 100644 structopt/tests/ui/positional_bool.rs create mode 100644 structopt/tests/ui/positional_bool.stderr create mode 100644 structopt/tests/ui/raw.rs create mode 100644 structopt/tests/ui/raw.stderr create mode 100644 structopt/tests/ui/rename_all_wrong_casing.rs create mode 100644 structopt/tests/ui/rename_all_wrong_casing.stderr create mode 100644 structopt/tests/ui/skip_flatten.rs create mode 100644 structopt/tests/ui/skip_flatten.stderr create mode 100644 structopt/tests/ui/skip_subcommand.rs create mode 100644 structopt/tests/ui/skip_subcommand.stderr create mode 100644 structopt/tests/ui/skip_with_other_options.rs create mode 100644 structopt/tests/ui/skip_with_other_options.stderr create mode 100644 structopt/tests/ui/skip_without_default.rs create mode 100644 structopt/tests/ui/skip_without_default.stderr create mode 100644 structopt/tests/ui/struct_flatten.rs create mode 100644 structopt/tests/ui/struct_flatten.stderr create mode 100644 structopt/tests/ui/struct_parse.rs create mode 100644 structopt/tests/ui/struct_parse.stderr create mode 100644 structopt/tests/ui/struct_subcommand.rs create mode 100644 structopt/tests/ui/struct_subcommand.stderr create mode 100644 structopt/tests/ui/structopt_empty_attr.rs create mode 100644 structopt/tests/ui/structopt_empty_attr.stderr create mode 100644 structopt/tests/ui/structopt_name_value_attr.rs create mode 100644 structopt/tests/ui/structopt_name_value_attr.stderr create mode 100644 structopt/tests/ui/subcommand_and_flatten.rs create mode 100644 structopt/tests/ui/subcommand_and_flatten.stderr create mode 100644 structopt/tests/ui/subcommand_and_methods.rs create mode 100644 structopt/tests/ui/subcommand_and_methods.stderr create mode 100644 structopt/tests/ui/subcommand_and_parse.rs create mode 100644 structopt/tests/ui/subcommand_and_parse.stderr create mode 100644 structopt/tests/ui/subcommand_opt_opt.rs create mode 100644 structopt/tests/ui/subcommand_opt_opt.stderr create mode 100644 structopt/tests/ui/subcommand_opt_vec.rs create mode 100644 structopt/tests/ui/subcommand_opt_vec.stderr create mode 100644 structopt/tests/ui/tuple_struct.rs create mode 100644 structopt/tests/ui/tuple_struct.stderr create mode 100644 structopt/tests/utils.rs (limited to 'structopt') diff --git a/structopt/.gitignore b/structopt/.gitignore new file mode 100644 index 0000000..ea63af4 --- /dev/null +++ b/structopt/.gitignore @@ -0,0 +1,6 @@ +target +Cargo.lock +*~ + +.idea/ +.vscode/ diff --git a/structopt/.travis.yml b/structopt/.travis.yml new file mode 100644 index 0000000..dff3050 --- /dev/null +++ b/structopt/.travis.yml @@ -0,0 +1,24 @@ +language: rust +matrix: + include: + - rust: stable + name: check if `cargo fmt --all` is applied + before_script: rustup component add rustfmt-preview + script: cargo fmt --all -- --check + + - language: node_js + node_js: node + name: check links + install: npm install -g markdown-link-check + script: + - markdown-link-check -c link-check-headers.json README.md + - markdown-link-check -c link-check-headers.json CHANGELOG.md + - markdown-link-check -c link-check-headers.json examples/README.md + + - rust: 1.36.0 + - rust: stable + - rust: beta + - rust: nightly + +script: + - cargo test $FEATURES diff --git a/structopt/CHANGELOG.md b/structopt/CHANGELOG.md new file mode 100644 index 0000000..ed2a17f --- /dev/null +++ b/structopt/CHANGELOG.md @@ -0,0 +1,444 @@ +# v0.3.7 (2019-12-28) + +Nothing's new. Just re-release of `v0.3.6` due to +[the mess with versioning](https://github.com/TeXitoi/structopt/issues/315#issuecomment-568502792). + +You may notice that `structopt-derive` was bumped to `v0.4.0`, that's OK, it's not a breaking change. +`structopt` will pull the right version in on its on. + +# v0.3.6 (2019-12-22) - YANKED + +This is unusually big patch release. It contains a number of bugfixes and +new features, some of them may theoretically be considered breaking. We did our best +to avoid any problems on user's side but, if it wasn't good enough, please +[file an issue ASAP](https://github.com/TeXitoi/structopt/issues). + +## Bugfixes + +* `structopt` used to treat `::path::to::type::Vec` as `Vec` + special type. [This was considered erroneous](https://github.com/TeXitoi/structopt/pull/287). + (same for `Option` and `bool`). Now only exact `Vec` match is a special type. + +* `#[structopt(version = expr)]` where `expr` is not a string literal used to get + overridden by auto generated `.version()` call, + [incorrectly](https://github.com/TeXitoi/structopt/issues/283). Now it doesn't. + +* Fixed bug with top-level `App::*` calls on multiple `struct`s, see + [#289](https://github.com/TeXitoi/structopt/issues/265). + +* Positional `bool` args with no explicit `#[structopt(parse(...))]` annotation are + now prohibited. This couldn't work well anyway, see + [this example](https://github.com/TeXitoi/structopt/blob/master/examples/true_or_false.rs) + for details. + +* Now we've instituted strict priority between doc comments, about, help, and the like. + See [the documentation](https://docs.rs/structopt/0.3/structopt/#help-messages). + + **HUGE THANKS to [`@ssokolow`](https://github.com/ssokolow)** for tidying up our documentation, + teaching me English and explaining why our doc used to suck. I promise I'll make the rest + of the doc up to your standards... sometime later! + +## New features + +* Implement `StructOpt` for `Box` so from now on you can use `Box` + with `flatten` and `subcommand` ([#304](https://github.com/TeXitoi/structopt/issues/304)). + + ```rust + enum Command { + #[structopt(name = "version")] + PrintVersion, + + #[structopt(name = "second")] + DoSomething { + #[structopt(flatten)] + config: Box, + }, + + #[structopt(name = "first")] + DoSomethingElse { + #[structopt(flatten)] + config: Box, + } + } + ``` + +* Introduced `#[structopt(verbatim_doc_comment)]` attribute that keeps line breaks in + doc comments, see + [the documentation](https://docs.rs/structopt/0.3/structopt/#doc-comment-preprocessing-and-structoptverbatim_doc_comment). + +* Introduced `#[structopt(rename_all_env)]` and `#[structopt(env)]` magical methods + so you can derive env var's name from field's name. See + [the documentation](https://docs.rs/structopt/0.3/structopt/#auto-deriving-environment-variables). + +## Improvements + +* Now we have nice README for our examples, + [check it out](https://github.com/TeXitoi/structopt/tree/master/examples)! + +* Some error messages were improved and clarified, thanks for all people involved! + + +# v0.3.5 (2019-11-22) + +* `try_from_str` functions are now called with a `&str` instead of a `&String` ([#282](https://github.com/TeXitoi/structopt/pull/282)) + +# v0.3.4 (2019-11-08) + +* `rename_all` does not apply to fields that were annotated with explicit + `short/long/name = "..."` anymore ([#265](https://github.com/TeXitoi/structopt/issues/265)) +* Now raw idents are handled correctly ([#269](https://github.com/TeXitoi/structopt/issues/269)) +* Some documentation improvements and clarification. + +# v0.3.3 (2019-10-10) + +* Add `from_flag` custom parser to create flags from non-bool types. + Fixes [#185](https://github.com/TeXitoi/structopt/issues/185) + +# v0.3.2 (2019-09-18) + +* `structopt` does not replace `:` with `, ` inside "author" strings while inside `<...>`. + Fixes [#156](https://github.com/TeXitoi/structopt/issues/156) +* Introduced [`#[structopt(skip = expr)]` syntax](https://docs.rs/structopt/0.3.2/structopt/#skipping-fields). + +# v0.3.1 (2019-09-06) + +* Fix error messages ([#241](https://github.com/TeXitoi/structopt/issues/241)) +* Fix "`skip` plus long doc comment" bug ([#245](https://github.com/TeXitoi/structopt/issues/245)) +* Now `structopt` emits dummy `StructOpt` implementation along with an error. It suppresses + meaningless errors like `from_args method is not found for Opt` +* `.version()` not get generated if `CARGO_PKG_VERSION` is not set anymore. + +# v0.3.0 (2019-08-30) + +## Breaking changes + +### Bump minimum rustc version to 1.36 by [@TeXitoi](https://github.com/TeXitoi) +Now `rustc` 1.36 is the minimum compiler version supported by `structopt`, +it likely won't work with older compilers. + +### Remove "nightly" feature +Once upon a time this feature had been used to enable some of improvements +in `proc-macro2` crate that were available only on nightly. Nowadays this feature doesn't +mean anything so it's now removed. + +### Support optional vectors of arguments for distinguishing between `-o 1 2`, `-o` and no option provided at all by [@sphynx](https://github.com/sphynx) ([#180](https://github.com/TeXitoi/structopt/issues/188)). + +```rust +#[derive(StructOpt)] +struct Opt { + #[structopt(long)] + fruit: Option>, +} + +fn main() { + assert_eq!(Opt::from_args(&["test"]), None); + assert_eq!(Opt::from_args(&["test", "--fruit"]), Some(vec![])); + assert_eq!(Opt::from_args(&["test", "--fruit=apple orange"]), Some(vec!["apple", "orange"])); +} +``` + +If you need to fall back to the old behavior you can use a type alias: +```rust +type Something = Vec; + +#[derive(StructOpt)] +struct Opt { + #[structopt(long)] + fruit: Option, +} +``` + +### Change default case from 'Verbatim' into 'Kebab' by [@0ndorio](https://github.com/0ndorio) ([#202](https://github.com/TeXitoi/structopt/issues/202)). +`structopt` 0.3 uses field renaming to deduce a name for long options and subcommands. + +```rust +#[derive(StructOpt)] +struct Opt { + #[structopt(long)] + http_addr: String, // will be renamed to `--http-addr` + + #[structopt(subcommand)] + addr_type: AddrType // this adds `addr-type` subcommand +} +``` + +`structopt` 0.2 used to leave things "as is", not renaming anything. If you want to keep old +behavior add `#[structopt(rename_all = "verbatim")]` on top of a `struct`/`enum`. + +### Change `version`, `author` and `about` attributes behavior. +Proposed by [@TeXitoi](https://github.com/TeXitoi) [(#217)](https://github.com/TeXitoi/structopt/issues/217), implemented by [@CreepySkeleton](https://github.com/CreepySkeleton) [(#229)](https://github.com/TeXitoi/structopt/pull/229). + +`structopt` have been deducing `version`, `author`, and `about` properties from `Cargo.toml` +for a long time (more accurately, from `CARGO_PKG_...` environment variables). +But many users found this behavior somewhat confusing, and a hack was added to cancel out +this behavior: `#[structopt(author = "")]`. + +In `structopt` 0.3 this has changed. +* `author` and `about` are no longer deduced by default. You should use `#[structopt(author, about)]` + to explicitly request `structopt` to deduce them. +* Contrary, `version` **is still deduced by default**. You can use `#[structopt(no_version)]` to + cancel it out. +* `#[structopt(author = "", about = "", version = "")]` is no longer a valid syntax + and will trigger an error. +* `#[structopt(version = "version", author = "author", about = "about")]` syntax + stays unaffected by this changes. + +### Raw attributes are removed ([#198](https://github.com/TeXitoi/structopt/pull/198)) by [@sphynx](https://github.com/sphynx) +In `structopt` 0.2 you were able to use any method from `clap::App` and `clap::Arg` via +raw attribute: `#[structopt(raw(method_name = "arg"))]`. This syntax was kind of awkward. + +```rust +#[derive(StructOpt, Debug)] +#[structopt(raw( + global_settings = "&[AppSettings::ColoredHelp, AppSettings::VersionlessSubcommands]" +))] +struct Opt { + #[structopt(short = "l", long = "level", raw(aliases = r#"&["set-level", "lvl"]"#))] + level: Vec, +} +``` + +Raw attributes were removed in 0.3. Now you can use any method from `App` and `Arg` *directly*: +```rust +#[derive(StructOpt)] +#[structopt(global_settings(&[AppSettings::ColoredHelp, AppSettings::VersionlessSubcommands]))] +struct Opt { + #[structopt(short = "l", long = "level", aliases(&["set-level", "lvl"]))] + level: Vec, +} +``` + +## Improvements + +### Support skipping struct fields +Proposed by [@Morganamilo](https://github.com/Morganamilo) in ([#174](https://github.com/TeXitoi/structopt/issues/174)) +implemented by [@sphynx](https://github.com/sphynx) in ([#213](https://github.com/TeXitoi/structopt/issues/213)). + +Sometimes you want to include some fields in your `StructOpt` `struct` that are not options +and `clap` should know nothing about them. In `structopt` 0.3 it's possible via the +`#[structopt(skip)]` attribute. The field in question will be assigned with `Default::default()` +value. + +```rust +#[derive(StructOpt)] +struct Opt { + #[structopt(short, long)] + speed: f32, + + car: String, + + // this field should not generate any arguments + #[structopt(skip)] + meta: Vec +} +``` + +### Add optional feature to support `paw` by [@gameldar](https://github.com/gameldar) ([#187](https://github.com/TeXitoi/structopt/issues/187)) + +### Significantly improve error reporting by [@CreepySkeleton](https://github.com/CreepySkeleton) ([#225](https://github.com/TeXitoi/structopt/pull/225/)) +Now (almost) every error message points to the location it originates from: + +```text +error: default_value is meaningless for bool + --> $DIR/bool_default_value.rs:14:24 + | +14 | #[structopt(short, default_value = true)] + | ^^^^^^^^^^^^^ +``` + +# v0.2.16 (2019-05-29) + +### Support optional options with optional argument, allowing `cmd [--opt[=value]]` by [@sphynx](https://github.com/sphynx) ([#188](https://github.com/TeXitoi/structopt/issues/188)) +Sometimes you want to represent an optional option that optionally takes an argument, +i.e `[--opt[=value]]`. This is represented by `Option>` + +```rust +#[derive(StructOpt)] +struct Opt { + #[structopt(long)] + fruit: Option>, +} + +fn main() { + assert_eq!(Opt::from_args(&["test"]), None); + assert_eq!(Opt::from_args(&["test", "--fruit"]), Some(None)); + assert_eq!(Opt::from_args(&["test", "--fruit=apple"]), Some("apple")); +} +``` + +# v0.2.15 (2019-03-08) + +* Fix [#168](https://github.com/TeXitoi/structopt/issues/168) by [@TeXitoi](https://github.com/TeXitoi) + +# v0.2.14 (2018-12-10) + +* Introduce smarter parsing of doc comments by [@0ndorio](https://github.com/0ndorio) + +# v0.2.13 (2018-11-01) + +* Automatic naming of fields and subcommands by [@0ndorio](https://github.com/0ndorio) + +# v0.2.12 (2018-10-11) + +* Fix minimal clap version by [@TeXitoi](https://github.com/TeXitoi) + +# v0.2.11 (2018-10-05) + +* Upgrade syn to 0.15 by [@konstin](https://github.com/konstin) + +# v0.2.10 (2018-06-07) + +* 1.21.0 is the minimum required rustc version by + [@TeXitoi](https://github.com/TeXitoi) + +# v0.2.9 (2018-06-05) + +* Fix a bug when using `flatten` by + [@fbenkstein](https://github.com/fbenkstein) +* Update syn, quote and proc_macro2 by + [@TeXitoi](https://github.com/TeXitoi) +* Fix a regression when there is multiple authors by + [@windwardly](https://github.com/windwardly) + +# v0.2.8 (2018-04-28) + +* Add `StructOpt::from_iter_safe()`, which returns an `Error` instead of + killing the program when it fails to parse, or parses one of the + short-circuiting flags. ([#98](https://github.com/TeXitoi/structopt/pull/98) + by [@quodlibetor](https://github.com/quodlibetor)) +* Allow users to enable `clap` features independently by + [@Kerollmops](https://github.com/Kerollmops) +* Fix a bug when flattening an enum + ([#103](https://github.com/TeXitoi/structopt/pull/103) by + [@TeXitoi](https://github.com/TeXitoi) + +# v0.2.7 (2018-04-12) + +* Add flattening, the insertion of options of another StructOpt struct + into another ([#92](https://github.com/TeXitoi/structopt/pull/92)) + by [@birkenfeld](https://github.com/birkenfeld) +* Fail compilation when using `default_value` or `required` with + `Option` ([#88](https://github.com/TeXitoi/structopt/pull/88)) by + [@Kerollmops](https://github.com/Kerollmops) + +# v0.2.6 (2018-03-31) + +* Fail compilation when using `default_value` or `required` with `bool` ([#80](https://github.com/TeXitoi/structopt/issues/80)) by [@TeXitoi](https://github.com/TeXitoi) +* Fix compilation with `#[deny(warnings)]` with the `!` type (https://github.com/rust-lang/rust/pull/49039#issuecomment-376398999) by [@TeXitoi](https://github.com/TeXitoi) +* Improve first example in the documentation ([#82](https://github.com/TeXitoi/structopt/issues/82)) by [@TeXitoi](https://github.com/TeXitoi) + +# v0.2.5 (2018-03-07) + +* Work around breakage when `proc-macro2`'s nightly feature is enabled. ([#77](https://github.com/Texitoi/structopt/pull/77) and [proc-macro2#67](https://github.com/alexcrichton/proc-macro2/issues/67)) by [@fitzgen](https://github.com/fitzgen) + +# v0.2.4 (2018-02-25) + +* Fix compilation with `#![deny(missig_docs]` ([#74](https://github.com/TeXitoi/structopt/issues/74)) by [@TeXitoi](https://github.com/TeXitoi) +* Fix [#76](https://github.com/TeXitoi/structopt/issues/76) by [@TeXitoi](https://github.com/TeXitoi) +* Re-licensed to Apache-2.0/MIT by [@CAD97](https://github.com/cad97) + +# v0.2.3 (2018-02-16) + +* An empty line in a doc comment will result in a double linefeed in the generated about/help call by [@TeXitoi](https://github.com/TeXitoi) + +# v0.2.2 (2018-02-12) + +* Fix [#66](https://github.com/TeXitoi/structopt/issues/66) by [@TeXitoi](https://github.com/TeXitoi) + +# v0.2.1 (2018-02-11) + +* Fix a bug around enum tuple and the about message in the global help by [@TeXitoi](https://github.com/TeXitoi) +* Fix [#65](https://github.com/TeXitoi/structopt/issues/65) by [@TeXitoi](https://github.com/TeXitoi) + +# v0.2.0 (2018-02-10) + +## Breaking changes + +### Don't special case `u64` by [@SergioBenitez](https://github.com/SergioBenitez) + +If you are using a `u64` in your struct to get the number of occurence of a flag, you should now add `parse(from_occurrences)` on the flag. + +For example +```rust +#[structopt(short = "v", long = "verbose")] +verbose: u64, +``` +must be changed by +```rust +#[structopt(short = "v", long = "verbose", parse(from_occurrences))] +verbose: u64, +``` + +This feature was surprising as shown in [#30](https://github.com/TeXitoi/structopt/issues/30). Using the `parse` feature seems much more natural. + +### Change the signature of `Structopt::from_clap` to take its argument by reference by [@TeXitoi](https://github.com/TeXitoi) + +There was no reason to take the argument by value. Most of the StructOpt users will not be impacted by this change. If you are using `StructOpt::from_clap`, just add a `&` before the argument. + +### Fail if attributes are not used by [@TeXitoi](https://github.com/TeXitoi) + +StructOpt was quite fuzzy in its attribute parsing: it was only searching for interresting things, e. g. something like `#[structopt(foo(bar))]` was accepted but not used. It now fails the compilation. + +You should have nothing to do here. This breaking change may highlight some missuse that can be bugs. + +In future versions, if there is cases that are not highlighed, they will be considerated as bugs, not breaking changes. + +### Use `raw()` wrapping instead of `_raw` suffixing by [@TeXitoi](https://github.com/TeXitoi) + +The syntax of raw attributes is changed to improve the syntax. + +You have to change `foo_raw = "bar", baz_raw = "foo"` by `raw(foo = "bar", baz = "foo")` or `raw(foo = "bar"), raw(baz = "foo")`. + +## New features + +* Add `parse(from_occurrences)` parser by [@SergioBenitez](https://github.com/SergioBenitez) +* Support 1-uple enum variant as subcommand by [@TeXitoi](https://github.com/TeXitoi) +* structopt-derive crate is now an implementation detail, structopt reexport the custom derive macro by [@TeXitoi](https://github.com/TeXitoi) +* Add the `StructOpt::from_iter` method by [@Kerollmops](https://github.com/Kerollmops) + +## Documentation + +* Improve doc by [@bestouff](https://github.com/bestouff) +* All the documentation is now on the structopt crate by [@TeXitoi](https://github.com/TeXitoi) + +# v0.1.7 (2018-01-23) + +* Allow opting out of clap default features by [@ski-csis](https://github.com/ski-csis) + +# v0.1.6 (2017-11-25) + +* Improve documentation by [@TeXitoi](https://github.com/TeXitoi) +* Fix bug [#31](https://github.com/TeXitoi/structopt/issues/31) by [@TeXitoi](https://github.com/TeXitoi) + +# v0.1.5 (2017-11-14) + +* Fix a bug with optional subsubcommand and Enum by [@TeXitoi](https://github.com/TeXitoi) + +# v0.1.4 (2017-11-09) + +* Implement custom string parser from either `&str` or `&OsStr` by [@kennytm](https://github.com/kennytm) + +# v0.1.3 (2017-11-01) + +* Improve doc by [@TeXitoi](https://github.com/TeXitoi) + +# v0.1.2 (2017-11-01) + +* Fix bugs [#24](https://github.com/TeXitoi/structopt/issues/24) and [#25](https://github.com/TeXitoi/structopt/issues/25) by [@TeXitoi](https://github.com/TeXitoi) +* Support of methods with something else that a string as argument thanks to `_raw` suffix by [@Flakebi](https://github.com/Flakebi) + +# v0.1.1 (2017-09-22) + +* Better formating of multiple authors by [@killercup](https://github.com/killercup) + +# v0.1.0 (2017-07-17) + +* Subcommand support by [@williamyaoh](https://github.com/williamyaoh) + +# v0.0.5 (2017-06-16) + +* Using doc comment to populate help by [@killercup](https://github.com/killercup) + +# v0.0.3 (2017-02-11) + +* First version with flags, arguments and options support by [@TeXitoi](https://github.com/TeXitoi) diff --git a/structopt/Cargo.toml b/structopt/Cargo.toml new file mode 100644 index 0000000..b43728b --- /dev/null +++ b/structopt/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "structopt" +version = "0.3.7" +edition = "2018" +authors = ["Guillaume Pinot ", "others"] +description = "Parse command line argument by defining a struct." +documentation = "https://docs.rs/structopt" +repository = "https://github.com/TeXitoi/structopt" +keywords = ["clap", "cli", "derive", "docopt"] +categories = ["command-line-interface"] +license = "Apache-2.0/MIT" +readme = "README.md" + +[features] +default = ["clap/default"] +suggestions = ["clap/suggestions"] +color = ["clap/color"] +wrap_help = ["clap/wrap_help"] +yaml = ["clap/yaml"] +lints = ["clap/lints"] +debug = ["clap/debug"] +no_cargo = ["clap/no_cargo"] +doc = ["clap/doc"] +paw = ["structopt-derive/paw"] + +[badges] +travis-ci = { repository = "TeXitoi/structopt" } + +[dependencies] +clap = { version = "2.33", default-features = false } +structopt-derive = { path = "structopt-derive", version = "=0.4.0" } + +[dev-dependencies] +trybuild = "1.0.5" +rustversion = "1" diff --git a/structopt/LICENSE-APACHE b/structopt/LICENSE-APACHE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/structopt/LICENSE-APACHE @@ -0,0 +1,201 @@ + 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. diff --git a/structopt/LICENSE-MIT b/structopt/LICENSE-MIT new file mode 100644 index 0000000..e931b83 --- /dev/null +++ b/structopt/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Guillaume Pinot (@TeXitoi) + +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/structopt/README.md b/structopt/README.md new file mode 100644 index 0000000..48ac3c3 --- /dev/null +++ b/structopt/README.md @@ -0,0 +1,148 @@ +# StructOpt [![Build status](https://travis-ci.org/TeXitoi/structopt.svg?branch=master)](https://travis-ci.org/TeXitoi/structopt) [![](https://img.shields.io/crates/v/structopt.svg)](https://crates.io/crates/structopt) [![](https://docs.rs/structopt/badge.svg)](https://docs.rs/structopt) + +Parse command line arguments by defining a struct. It combines [clap](https://crates.io/crates/clap) with custom derive. + +## Documentation + +Find it on [Docs.rs](https://docs.rs/structopt). You can also check the [examples](https://github.com/TeXitoi/structopt/tree/master/examples) and the [changelog](https://github.com/TeXitoi/structopt/blob/master/CHANGELOG.md). + +## Example + +Add `structopt` to your dependencies of your `Cargo.toml`: +```toml +[dependencies] +structopt = "0.3" +``` + +And then, in your rust file: +```rust +use std::path::PathBuf; +use structopt::StructOpt; + +/// A basic example +#[derive(StructOpt, Debug)] +#[structopt(name = "basic")] +struct Opt { + // A flag, true if used in the command line. Note doc comment will + // be used for the help message of the flag. The name of the + // argument will be, by default, based on the name of the field. + /// Activate debug mode + #[structopt(short, long)] + debug: bool, + + // The number of occurrences of the `v/verbose` flag + /// Verbose mode (-v, -vv, -vvv, etc.) + #[structopt(short, long, parse(from_occurrences))] + verbose: u8, + + /// Set speed + #[structopt(short, long, default_value = "42")] + speed: f64, + + /// Output file + #[structopt(short, long, parse(from_os_str))] + output: PathBuf, + + // the long option will be translated by default to kebab case, + // i.e. `--nb-cars`. + /// Number of cars + #[structopt(short = "c", long)] + nb_cars: Option, + + /// admin_level to consider + #[structopt(short, long)] + level: Vec, + + /// Files to process + #[structopt(name = "FILE", parse(from_os_str))] + files: Vec, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:#?}", opt); +} +``` + +Using this example: +``` +$ ./basic +error: The following required arguments were not provided: + --output + +USAGE: + basic --output --speed + +For more information try --help +$ ./basic --help +basic 0.3.0 +Guillaume Pinot , others +A basic example + +USAGE: + basic [FLAGS] [OPTIONS] --output [--] [file]... + +FLAGS: + -d, --debug Activate debug mode + -h, --help Prints help information + -V, --version Prints version information + -v, --verbose Verbose mode (-v, -vv, -vvv, etc.) + +OPTIONS: + -l, --level ... admin_level to consider + -c, --nb-cars Number of cars + -o, --output Output file + -s, --speed Set speed [default: 42] + +ARGS: + ... Files to process +$ ./basic -o foo.txt +Opt { + debug: false, + verbose: 0, + speed: 42.0, + output: "foo.txt", + nb_cars: None, + level: [], + files: [], +} +$ ./basic -o foo.txt -dvvvs 1337 -l alice -l bob --nb-cars 4 bar.txt baz.txt +Opt { + debug: true, + verbose: 3, + speed: 1337.0, + output: "foo.txt", + nb_cars: Some( + 4, + ), + level: [ + "alice", + "bob", + ], + files: [ + "bar.txt", + "baz.txt", + ], +} +``` + +## StructOpt rustc version policy + +- Minimum rustc version modification must be specified in the [changelog](https://github.com/TeXitoi/structopt/blob/master/CHANGELOG.md) and in the [travis configuration](https://github.com/TeXitoi/structopt/blob/master/.travis.yml). +- Contributors can increment minimum rustc version without any justification if the new version is required by the latest version of one of StructOpt's dependencies (`cargo update` will not fail on StructOpt). +- Contributors can increment minimum rustc version if the library user experience is improved. + +## License + +Licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or ) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. diff --git a/structopt/examples/README.md b/structopt/examples/README.md new file mode 100644 index 0000000..f0db20b --- /dev/null +++ b/structopt/examples/README.md @@ -0,0 +1,82 @@ +# Collection of examples "how to use `structopt`" + +### [Help on the bottom](after_help.rs) + +How to append a postscript to the help message generated. + +### [At least N](at_least_two.rs) + +How to require presence of at least N values, like `val1 val2 ... valN ... valM`. + +### [Basic](basic.rs) + +A basic example how to use `structopt`. + +### [Deny missing docs](deny_missing_docs.rs) + +**This is not an example but a test**, it should be moved to `tests` folder +as soon as [this](https://github.com/rust-lang/rust/issues/24584) is fixed (if ever). + +### [Doc comments](doc_comments.rs) + +How to use doc comments in place of `help/long_help`. + +### [Enums as arguments](enum_in_args.rs) + +How to use `arg_enum!` with `StructOpt`. + +### [Arguments of subcommands in separate `struct`](enum_tuple.rs) + +How to extract subcommands' args into external structs. + +### [Environment variables](env.rs) + +How to use environment variable fallback an how it interacts with `default_value`. + +### [Advanced](example.rs) + +Somewhat complex example of usage of `structopt`. + +### [Flatten](flatten.rs) + +How to use `#[structopt(flatten)]` + +### [`bash` completions](gen_completions.rs) + +Generating `bash` completions with `structopt`. + +### [Git](git.rs) + +Pseudo-`git` example, shows how to use subcommands and how to document them. + +### [Groups](group.rs) + +Using `clap::Arg::group` with `structopt`. + +### [`key=value` pairs](keyvalue.rs) + +How to parse `key=value` pairs. + +### [`--no-*` flags](negative_flag.rs) + +How to add `no-thing` flag which is `true` by default and `false` if passed. + +### [No version](no_version.rs) + +How to completely remove version. + +### [Rename all](rename_all.rs) + +How `#[structopt(rename_all)]` works. + +### [Skip](skip.rs) + +How to use `#[structopt(skip)]`. + +### [Aliases](subcommand_aliases.rs) + +How to use aliases + +### [`true` or `false`](true_or_false.rs) + +How to express "`"true"` or `"false"` argument. diff --git a/structopt/examples/after_help.rs b/structopt/examples/after_help.rs new file mode 100644 index 0000000..db2845f --- /dev/null +++ b/structopt/examples/after_help.rs @@ -0,0 +1,19 @@ +//! How to append a postscript to the help message generated. + +use structopt::StructOpt; + +/// I am a program and I do things. +/// +/// Sometimes they even work. +#[derive(StructOpt, Debug)] +#[structopt(after_help = "Beware `-d`, dragons be here")] +struct Opt { + /// Release the dragon. + #[structopt(short)] + dragon: bool, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/examples/at_least_two.rs b/structopt/examples/at_least_two.rs new file mode 100644 index 0000000..683db50 --- /dev/null +++ b/structopt/examples/at_least_two.rs @@ -0,0 +1,15 @@ +//! How to require presence of at least N values, +//! like `val1 val2 ... valN ... valM`. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +struct Opt { + #[structopt(required = true, min_values = 2)] + foos: Vec, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/examples/basic.rs b/structopt/examples/basic.rs new file mode 100644 index 0000000..510e0e0 --- /dev/null +++ b/structopt/examples/basic.rs @@ -0,0 +1,48 @@ +//! A somewhat comprehensive example of a typical `StructOpt` usage.use + +use std::path::PathBuf; +use structopt::StructOpt; + +/// A basic example +#[derive(StructOpt, Debug)] +#[structopt(name = "basic")] +struct Opt { + // A flag, true if used in the command line. Note doc comment will + // be used for the help message of the flag. The name of the + // argument will be, by default, based on the name of the field. + /// Activate debug mode + #[structopt(short, long)] + debug: bool, + + // The number of occurrences of the `v/verbose` flag + /// Verbose mode (-v, -vv, -vvv, etc.) + #[structopt(short, long, parse(from_occurrences))] + verbose: u8, + + /// Set speed + #[structopt(short, long, default_value = "42")] + speed: f64, + + /// Output file + #[structopt(short, long, parse(from_os_str))] + output: PathBuf, + + // the long option will be translated by default to kebab case, + // i.e. `--nb-cars`. + /// Number of cars + #[structopt(short = "c", long)] + nb_cars: Option, + + /// admin_level to consider + #[structopt(short, long)] + level: Vec, + + /// Files to process + #[structopt(name = "FILE", parse(from_os_str))] + files: Vec, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:#?}", opt); +} diff --git a/structopt/examples/deny_missing_docs.rs b/structopt/examples/deny_missing_docs.rs new file mode 100644 index 0000000..82b1e63 --- /dev/null +++ b/structopt/examples/deny_missing_docs.rs @@ -0,0 +1,51 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// This should be in tests but it will not work until +// https://github.com/rust-lang/rust/issues/24584 is fixed + +//! A test to check that structopt compiles with deny(missing_docs) + +#![deny(missing_docs)] + +use structopt::StructOpt; + +/// The options +#[derive(StructOpt, Debug, PartialEq)] +pub struct Opt { + #[structopt(short)] + verbose: bool, + #[structopt(subcommand)] + cmd: Option, +} + +/// Some subcommands +#[derive(StructOpt, Debug, PartialEq)] +pub enum Cmd { + /// command A + A, + /// command B + B { + /// Alice? + #[structopt(short)] + alice: bool, + }, + /// command C + C(COpt), +} + +/// The options for C +#[derive(StructOpt, Debug, PartialEq)] +pub struct COpt { + #[structopt(short)] + bob: bool, +} + +fn main() { + println!("{:?}", Opt::from_args()); +} diff --git a/structopt/examples/doc_comments.rs b/structopt/examples/doc_comments.rs new file mode 100644 index 0000000..810101f --- /dev/null +++ b/structopt/examples/doc_comments.rs @@ -0,0 +1,74 @@ +//! How to use doc comments in place of `help/long_help`. + +use structopt::StructOpt; + +/// A basic example for the usage of doc comments as replacement +/// of the arguments `help`, `long_help`, `about` and `long_about`. +#[derive(StructOpt, Debug)] +#[structopt(name = "basic")] +struct Opt { + /// Just use doc comments to replace `help`, `long_help`, + /// `about` or `long_about` input. + #[structopt(short, long)] + first_flag: bool, + + /// Split between `help` and `long_help`. + /// + /// In the previous case structopt is going to present + /// the whole comment both as text for the `help` and the + /// `long_help` argument. + /// + /// But if the doc comment is formatted like this example + /// -- with an empty second line splitting the heading and + /// the rest of the comment -- only the first line is used + /// as `help` argument. The `long_help` argument will still + /// contain the whole comment. + /// + /// ## Attention + /// + /// Any formatting next to empty lines that could be used + /// inside a doc comment is currently not preserved. If + /// lists or other well formatted content is required it is + /// necessary to use the related structopt argument with a + /// raw string as shown on the `third_flag` description. + #[structopt(short, long)] + second_flag: bool, + + #[structopt( + short, + long, + long_help = r"This is a raw string. + +It can be used to pass well formatted content (e.g. lists or source +code) in the description: + + - first example list entry + - second example list entry + " + )] + third_flag: bool, + + #[structopt(subcommand)] + sub_command: SubCommand, +} + +#[derive(StructOpt, Debug)] +#[structopt()] +enum SubCommand { + /// The same rules described previously for flags. Are + /// also true for in regards of sub-commands. + First, + + /// Applicable for both `about` an `help`. + /// + /// The formatting rules described in the comment of the + /// `second_flag` also apply to the description of + /// sub-commands which is normally given through the `about` + /// and `long_about` arguments. + Second, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/examples/enum_in_args.rs b/structopt/examples/enum_in_args.rs new file mode 100644 index 0000000..70347da --- /dev/null +++ b/structopt/examples/enum_in_args.rs @@ -0,0 +1,25 @@ +//! How to use `arg_enum!` with `StructOpt`. + +use clap::arg_enum; +use structopt::StructOpt; + +arg_enum! { + #[derive(Debug)] + enum Baz { + Foo, + Bar, + FooBar + } +} + +#[derive(StructOpt, Debug)] +struct Opt { + /// Important argument. + #[structopt(possible_values = &Baz::variants(), case_insensitive = true)] + i: Baz, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/examples/enum_tuple.rs b/structopt/examples/enum_tuple.rs new file mode 100644 index 0000000..0bad2e6 --- /dev/null +++ b/structopt/examples/enum_tuple.rs @@ -0,0 +1,26 @@ +//! How to extract subcommands' args into external structs. + +use structopt::StructOpt; + +#[derive(Debug, StructOpt)] +pub struct Foo { + pub bar: Option, +} + +#[derive(Debug, StructOpt)] +pub enum Command { + #[structopt(name = "foo")] + Foo(Foo), +} + +#[derive(Debug, StructOpt)] +#[structopt(name = "classify")] +pub struct ApplicationArguments { + #[structopt(subcommand)] + pub command: Command, +} + +fn main() { + let opt = ApplicationArguments::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/examples/env.rs b/structopt/examples/env.rs new file mode 100644 index 0000000..0477089 --- /dev/null +++ b/structopt/examples/env.rs @@ -0,0 +1,26 @@ +//! How to use environment variable fallback an how it +//! interacts with `default_value`. + +use structopt::StructOpt; + +/// Example for allowing to specify options via environment variables. +#[derive(StructOpt, Debug)] +#[structopt(name = "env")] +struct Opt { + // Use `env` to enable specifying the option with an environment + // variable. Command line arguments take precedence over env. + /// URL for the API server + #[structopt(long, env = "API_URL")] + api_url: String, + + // The default value is used if neither argument nor environment + // variable is specified. + /// Number of retries + #[structopt(long, env = "RETRIES", default_value = "5")] + retries: u32, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:#?}", opt); +} diff --git a/structopt/examples/example.rs b/structopt/examples/example.rs new file mode 100644 index 0000000..7a9a514 --- /dev/null +++ b/structopt/examples/example.rs @@ -0,0 +1,54 @@ +//! Somewhat complex example of usage of structopt. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "example")] +/// An example of StructOpt usage. +struct Opt { + // A flag, true if used in the command line. + #[structopt(short, long)] + /// Activate debug mode + debug: bool, + + // An argument of type float, with a default value. + #[structopt(short, long, default_value = "42")] + /// Set speed + speed: f64, + + // Needed parameter, the first on the command line. + /// Input file + input: String, + + // An optional parameter, will be `None` if not present on the + // command line. + /// Output file, stdout if not present + output: Option, + + // An optional parameter with optional value, will be `None` if + // not present on the command line, will be `Some(None)` if no + // argument is provided (i.e. `--log`) and will be + // `Some(Some(String))` if argument is provided (e.g. `--log + // log.txt`). + #[structopt(long)] + #[allow(clippy::option_option)] + /// Log file, stdout if no file, no logging if not present + log: Option>, + + // An optional list of values, will be `None` if not present on + // the command line, will be `Some(vec![])` if no argument is + // provided (i.e. `--optv`) and will be `Some(Some(String))` if + // argument list is provided (e.g. `--optv a b c`). + #[structopt(long)] + optv: Option>, + + // Skipped option: it won't be parsed and will be filled with the + // default value for its type (in this case it'll be an empty string). + #[structopt(skip)] + skipped: String, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/examples/flatten.rs b/structopt/examples/flatten.rs new file mode 100644 index 0000000..d51647f --- /dev/null +++ b/structopt/examples/flatten.rs @@ -0,0 +1,29 @@ +//! How to use flattening. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +struct Cmdline { + /// switch verbosity on + #[structopt(short)] + verbose: bool, + + #[structopt(flatten)] + daemon_opts: DaemonOpts, +} + +#[derive(StructOpt, Debug)] +struct DaemonOpts { + /// daemon user + #[structopt(short)] + user: String, + + /// daemon group + #[structopt(short)] + group: String, +} + +fn main() { + let opt = Cmdline::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/examples/gen_completions.rs b/structopt/examples/gen_completions.rs new file mode 100644 index 0000000..4f35b07 --- /dev/null +++ b/structopt/examples/gen_completions.rs @@ -0,0 +1,26 @@ +// Copyright 2019-present structopt developers +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::clap::Shell; +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +/// An example of how to generate bash completions with structopt. +struct Opt { + #[structopt(short, long)] + /// Activate debug mode + debug: bool, +} + +fn main() { + // generate `bash` completions in "target" directory + Opt::clap().gen_completions(env!("CARGO_PKG_NAME"), Shell::Bash, "target"); + + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/examples/git.rs b/structopt/examples/git.rs new file mode 100644 index 0000000..494e9d1 --- /dev/null +++ b/structopt/examples/git.rs @@ -0,0 +1,35 @@ +//! `git.rs` serves as a demonstration of how to use subcommands, +//! as well as a demonstration of adding documentation to subcommands. +//! Documentation can be added either through doc comments or +//! `help`/`about` attributes. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "git")] +/// the stupid content tracker +enum Opt { + /// fetch branches from remote repository + Fetch { + #[structopt(long)] + dry_run: bool, + #[structopt(long)] + all: bool, + #[structopt(default_value = "origin")] + repository: String, + }, + #[structopt(help = "add files to the staging area")] + Add { + #[structopt(short)] + interactive: bool, + #[structopt(short)] + all: bool, + files: Vec, + }, +} + +fn main() { + let matches = Opt::from_args(); + + println!("{:?}", matches); +} diff --git a/structopt/examples/group.rs b/structopt/examples/group.rs new file mode 100644 index 0000000..d53de6a --- /dev/null +++ b/structopt/examples/group.rs @@ -0,0 +1,31 @@ +//! How to use `clap::Arg::group` + +use structopt::{clap::ArgGroup, StructOpt}; + +#[derive(StructOpt, Debug)] +#[structopt(group = ArgGroup::with_name("verb").required(true))] +struct Opt { + /// Set a custom HTTP verb + #[structopt(long, group = "verb")] + method: Option, + /// HTTP GET + #[structopt(long, group = "verb")] + get: bool, + /// HTTP HEAD + #[structopt(long, group = "verb")] + head: bool, + /// HTTP POST + #[structopt(long, group = "verb")] + post: bool, + /// HTTP PUT + #[structopt(long, group = "verb")] + put: bool, + /// HTTP DELETE + #[structopt(long, group = "verb")] + delete: bool, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/examples/keyvalue.rs b/structopt/examples/keyvalue.rs new file mode 100644 index 0000000..12ce6fc --- /dev/null +++ b/structopt/examples/keyvalue.rs @@ -0,0 +1,36 @@ +//! How to parse "key=value" pairs with structopt. + +use std::error::Error; +use structopt::StructOpt; + +/// Parse a single key-value pair +fn parse_key_val(s: &str) -> Result<(T, U), Box> +where + T: std::str::FromStr, + T::Err: Error + 'static, + U: std::str::FromStr, + U::Err: Error + 'static, +{ + let pos = s + .find('=') + .ok_or_else(|| format!("invalid KEY=value: no `=` found in `{}`", s))?; + Ok((s[..pos].parse()?, s[pos + 1..].parse()?)) +} + +#[derive(StructOpt, Debug)] +struct Opt { + // number_of_values = 1 forces the user to repeat the -D option for each key-value pair: + // my_program -D a=1 -D b=2 + // Without number_of_values = 1 you can do: + // my_program -D a=1 b=2 + // but this makes adding an argument after the values impossible: + // my_program -D a=1 -D b=2 my_input_file + // becomes invalid. + #[structopt(short = "D", parse(try_from_str = parse_key_val), number_of_values = 1)] + defines: Vec<(String, i32)>, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/examples/negative_flag.rs b/structopt/examples/negative_flag.rs new file mode 100644 index 0000000..b178bf5 --- /dev/null +++ b/structopt/examples/negative_flag.rs @@ -0,0 +1,15 @@ +//! How to add `no-thing` flag which is `true` by default and +//! `false` if passed. + +use structopt::StructOpt; + +#[derive(Debug, StructOpt)] +struct Opt { + #[structopt(long = "no-verbose", parse(from_flag = std::ops::Not::not))] + verbose: bool, +} + +fn main() { + let cmd = Opt::from_args(); + println!("{:#?}", cmd); +} diff --git a/structopt/examples/no_version.rs b/structopt/examples/no_version.rs new file mode 100644 index 0000000..a542ec1 --- /dev/null +++ b/structopt/examples/no_version.rs @@ -0,0 +1,17 @@ +//! How to completely remove version. + +use structopt::clap::AppSettings; +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt( + name = "no_version", + no_version, + global_settings = &[AppSettings::DisableVersion] +)] +struct Opt {} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/examples/rename_all.rs b/structopt/examples/rename_all.rs new file mode 100644 index 0000000..35f3c4f --- /dev/null +++ b/structopt/examples/rename_all.rs @@ -0,0 +1,74 @@ +//! Example on how the `rename_all` parameter works. +//! +//! `rename_all` can be used to override the casing style used during argument +//! generation. By default the `kebab-case` style will be used but there are a wide +//! variety of other styles available. +//! +//! ## Supported styles overview: +//! +//! - **Camel Case**: Indicate word boundaries with uppercase letter, excluding +//! the first word. +//! - **Kebab Case**: Keep all letters lowercase and indicate word boundaries +//! with hyphens. +//! - **Pascal Case**: Indicate word boundaries with uppercase letter, +//! including the first word. +//! - **Screaming Snake Case**: Keep all letters uppercase and indicate word +//! boundaries with underscores. +//! - **Snake Case**: Keep all letters lowercase and indicate word boundaries +//! with underscores. +//! - **Verbatim**: Use the original attribute name defined in the code. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "rename_all", rename_all = "screaming_snake_case")] +enum Opt { + // This subcommand will be named `FIRST_COMMAND`. As the command doesn't + // override the initial casing style, ... + /// A screaming loud first command. Only use if necessary. + FirstCommand { + // this flag will be available as `--FOO` and `-F`. + /// This flag will even scream louder. + #[structopt(long, short)] + foo: bool, + }, + + // As we override the casing style for this variant the related subcommand + // will be named `SecondCommand`. + /// Not nearly as loud as the first command. + #[structopt(rename_all = "pascal_case")] + SecondCommand { + // We can also override it again on a single field. + /// Nice quiet flag. No one is annoyed. + #[structopt(rename_all = "snake_case", long)] + bar_option: bool, + + // Renaming will not be propagated into subcommand flagged enums. If + // a non default casing style is required it must be defined on the + // enum itself. + #[structopt(subcommand)] + cmds: Subcommands, + + // or flattened structs. + #[structopt(flatten)] + options: BonusOptions, + }, +} + +#[derive(StructOpt, Debug)] +enum Subcommands { + // This one will be available as `first-subcommand`. + FirstSubcommand, +} + +#[derive(StructOpt, Debug)] +struct BonusOptions { + // And this one will be available as `baz-option`. + #[structopt(long)] + baz_option: bool, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/examples/skip.rs b/structopt/examples/skip.rs new file mode 100644 index 0000000..1f44769 --- /dev/null +++ b/structopt/examples/skip.rs @@ -0,0 +1,47 @@ +//! How to use `#[structopt(skip)]` + +use structopt::StructOpt; + +#[derive(StructOpt, Debug, PartialEq)] +pub struct Opt { + #[structopt(long, short)] + number: u32, + #[structopt(skip)] + k: Kind, + #[structopt(skip)] + v: Vec, + + #[structopt(skip = Kind::A)] + k2: Kind, + #[structopt(skip = vec![1, 2, 3])] + v2: Vec, + #[structopt(skip = "cake")] // &str implements Into + s: String, +} + +#[derive(Debug, PartialEq)] +enum Kind { + A, + B, +} + +impl Default for Kind { + fn default() -> Self { + return Kind::B; + } +} + +fn main() { + assert_eq!( + Opt::from_iter(&["test", "-n", "10"]), + Opt { + number: 10, + k: Kind::B, + v: vec![], + + k2: Kind::A, + v2: vec![1, 2, 3], + s: String::from("cake") + } + ); +} diff --git a/structopt/examples/subcommand_aliases.rs b/structopt/examples/subcommand_aliases.rs new file mode 100644 index 0000000..30b8cc3 --- /dev/null +++ b/structopt/examples/subcommand_aliases.rs @@ -0,0 +1,21 @@ +//! How to assign some aliases to subcommands + +use structopt::clap::AppSettings; +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +// https://docs.rs/clap/2/clap/enum.AppSettings.html#variant.InferSubcommands +#[structopt(setting = AppSettings::InferSubcommands)] +enum Opt { + // https://docs.rs/clap/2/clap/struct.App.html#method.alias + #[structopt(alias = "foobar")] + Foo, + // https://docs.rs/clap/2/clap/struct.App.html#method.aliases + #[structopt(aliases = &["baz", "fizz"])] + Bar, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/examples/true_or_false.rs b/structopt/examples/true_or_false.rs new file mode 100644 index 0000000..31a543e --- /dev/null +++ b/structopt/examples/true_or_false.rs @@ -0,0 +1,41 @@ +//! How to parse `--foo=true --bar=false` and turn them into bool. + +use structopt::StructOpt; + +fn true_or_false(s: &str) -> Result { + match s { + "true" => Ok(true), + "false" => Ok(false), + _ => Err("expected `true` or `false`"), + } +} + +#[derive(StructOpt, Debug, PartialEq)] +struct Opt { + // Default parser for `try_from_str` is FromStr::from_str. + // `impl FromStr for bool` parses `true` or `false` so this + // works as expected. + #[structopt(long, parse(try_from_str))] + foo: bool, + + // Of course, this could be done with an explicit parser function. + #[structopt(long, parse(try_from_str = true_or_false))] + bar: bool, + + // `bool` can be positional only with explicit `parse(...)` annotation + #[structopt(long, parse(try_from_str))] + boom: bool, +} + +fn main() { + assert_eq!( + Opt::from_iter(&["test", "--foo=true", "--bar=false", "true"]), + Opt { + foo: true, + bar: false, + boom: true + } + ); + // no beauty, only truth and falseness + assert!(Opt::from_iter_safe(&["test", "--foo=beauty"]).is_err()); +} diff --git a/structopt/link-check-headers.json b/structopt/link-check-headers.json new file mode 100644 index 0000000..c1bb248 --- /dev/null +++ b/structopt/link-check-headers.json @@ -0,0 +1,14 @@ +{ + "httpHeaders": [ + { + "urls": [ + "https://", + "http://" + ], + "headers": { + "User-Agent": "broken links checker (https://github.com/TeXitoi/structopt)", + "Accept": "text/html" + } + } + ] +} diff --git a/structopt/src/lib.rs b/structopt/src/lib.rs new file mode 100644 index 0000000..70c0768 --- /dev/null +++ b/structopt/src/lib.rs @@ -0,0 +1,1015 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![deny(missing_docs)] + +//! This crate defines the `StructOpt` trait and its custom derive. +//! +//! ## Features +//! +//! If you want to disable all the `clap` features (colors, +//! suggestions, ..) add `default-features = false` to the `structopt` +//! dependency: +//! +//! ```toml +//! [dependencies] +//! structopt = { version = "0.3", default-features = false } +//! ``` +//! +//! Support for [`paw`](https://github.com/rust-cli/paw) (the +//! `Command line argument paw-rser abstraction for main`) is disabled +//! by default, but can be enabled in the `structopt` dependency +//! with the feature `paw`: +//! +//! ```toml +//! [dependencies] +//! structopt = { version = "0.3", features = [ "paw" ] } +//! paw = "1.0" +//! ``` +//! +//! # Table of Contents +//! +//! - [How to `derive(StructOpt)`](#how-to-derivestructopt) +//! - [Attributes](#attributes) +//! - [Raw methods](#raw-methods) +//! - [Magical methods](#magical-methods) +//! - Arguments +//! - [Type magic](#type-magic) +//! - [Specifying argument types](#specifying-argument-types) +//! - [Help messages](#help-messages) +//! - [Environment variable fallback](#environment-variable-fallback) +//! - [Skipping fields](#skipping-fields) +//! - [Subcommands](#subcommands) +//! - [Optional subcommands](#optional-subcommands) +//! - [Flattening](#flattening) +//! - [Custom string parsers](#custom-string-parsers) +//! +//! +//! +//! ## How to `derive(StructOpt)` +//! +//! First, let's look at the example: +//! +//! ```should_panic +//! use std::path::PathBuf; +//! use structopt::StructOpt; +//! +//! #[derive(Debug, StructOpt)] +//! #[structopt(name = "example", about = "An example of StructOpt usage.")] +//! struct Opt { +//! /// Activate debug mode +//! // short and long flags (-d, --debug) will be deduced from the field's name +//! #[structopt(short, long)] +//! debug: bool, +//! +//! /// Set speed +//! // we don't want to name it "speed", need to look smart +//! #[structopt(short = "v", long = "velocity", default_value = "42")] +//! speed: f64, +//! +//! /// Input file +//! #[structopt(parse(from_os_str))] +//! input: PathBuf, +//! +//! /// Output file, stdout if not present +//! #[structopt(parse(from_os_str))] +//! output: Option, +//! +//! /// Where to write the output: to `stdout` or `file` +//! #[structopt(short)] +//! out_type: String, +//! +//! /// File name: only required when `out` is set to `file` +//! #[structopt(name = "FILE", required_if("out_type", "file"))] +//! file_name: String, +//! } +//! +//! fn main() { +//! let opt = Opt::from_args(); +//! println!("{:?}", opt); +//! } +//! ``` +//! +//! So `derive(StructOpt)` tells Rust to generate a command line parser, +//! and the various `structopt` attributes are simply +//! used for additional parameters. +//! +//! First, define a struct, whatever its name. This structure +//! corresponds to a `clap::App`, its fields correspond to `clap::Arg` +//! (unless they're [subcommands](#subcommands)), +//! and you can adjust these apps and args by `#[structopt(...)]` [attributes](#attributes). +//! +//! **Note:** +//! _________________ +//! Keep in mind that `StructOpt` trait is more than just `from_args` method. +//! It has a number of additional features, including access to underlying +//! `clap::App` via `StructOpt::clap()`. See the +//! [trait's reference documentation](trait.StructOpt.html). +//! _________________ +//! +//! ## Attributes +//! +//! `#[structopt(...)]` attributes fall into two categories: +//! - `structopt`'s own [magical methods](#magical-methods). +//! +//! They are used by `structopt` itself. They come mostly in +//! `attr = ["whatever"]` form, but some `attr(args...)` also exist. +//! +//! - [`raw` attributes](#raw-methods). +//! +//! They represent explicit `clap::Arg/App` method calls. +//! They are what used to be explicit `#[structopt(raw(...))]` attrs in pre-0.3 `structopt` +//! +//! Every `structopt attribute` looks like comma-separated sequence of methods: +//! ```rust,ignore +//! #[structopt( +//! short, // method with no arguments - always magical +//! long = "--long-option", // method with one argument +//! required_if("out", "file"), // method with one and more args +//! parse(from_os_str = path::to::parser) // some magical methods have their own syntax +//! )] +//! ``` +//! +//! `#[structopt(...)]` attributes can be placed on top of `struct`, `enum`, +//! `struct` field or `enum` variant. Attributes on top of `struct` or `enum` +//! represent `clap::App` method calls, field or variant attributes correspond +//! to `clap::Arg` method calls. +//! +//! In other words, the `Opt` struct from the example above +//! will be turned into this (*details omitted*): +//! +//! ``` +//! # use structopt::clap::{Arg, App}; +//! App::new("example") +//! .version("0.2.0") +//! .about("An example of StructOpt usage.") +//! .arg(Arg::with_name("debug") +//! .help("Activate debug mode") +//! .short("debug") +//! .long("debug")) +//! .arg(Arg::with_name("speed") +//! .help("Set speed") +//! .short("v") +//! .long("velocity") +//! .default_value("42")) +//! // and so on +//! # ; +//! ``` +//! +//! ## Raw methods +//! +//! They are the reason why `structopt` is so flexible. +//! +//! Each and every method from `clap::App` and `clap::Arg` can be used directly - +//! just `#[structopt(method_name = single_arg)]` or `#[structopt(method_name(arg1, arg2))]` +//! and it just works. As long as `method_name` is not one of the magical methods - +//! it's just a method call. +//! +//! **Note:** +//! _________________ +//! +//! "Raw methods" are direct replacement for pre-0.3 structopt's +//! `#[structopt(raw(...))]` attributes, any time you would have used a `raw()` attribute +//! in 0.2 you should use raw method in 0.3. +//! +//! Unfortunately, old raw attributes collide with `clap::Arg::raw` method. To explicitly +//! warn users of this change we allow `#[structopt(raw())]` only with `true` or `false` +//! literals (this method is supposed to be called only with `true` anyway). +//! __________________ +//! +//! ## Magical methods +//! +//! They are the reason why `structopt` is so easy to use and convenient in most cases. +//! Many of them have defaults, some of them get used even if not mentioned. +//! +//! Methods may be used on "top level" (on top of a `struct`, `enum` or `enum` variant) +//! and/or on "field-level" (on top of a `struct` field or *inside* of an enum variant). +//! Top level (non-magical) methods correspond to `App::method` calls, field-level methods +//! are `Arg::method` calls. +//! +//! ```ignore +//! #[structopt(top_level)] +//! struct Foo { +//! #[structopt(field_level)] +//! field: u32 +//! } +//! +//! #[structopt(top_level)] +//! enum Bar { +//! #[structopt(top_level)] +//! Pineapple { +//! #[structopt(field_level)] +//! chocolate: String +//! }, +//! +//! #[structopt(top_level)] +//! Orange, +//! } +//! ``` +//! +//! - `name`: `[name = "name"]` +//! - On top level: `App::new("name")`. +//! +//! The binary name displayed in help messages. Defaults to the crate name given by Cargo. +//! +//! - On field-level: `Arg::with_name("name")`. +//! +//! The name for the argument the field stands for, this name appears in help messages. +//! Defaults to a name, deduced from a field, see also +//! [`rename_all`](#specifying-argument-types). +//! +//! - `version`: `[version = "version"]` +//! +//! Usable only on top level: `App::version("version" or env!(CARGO_PKG_VERSION))`. +//! +//! The version displayed in help messages. +//! Defaults to the crate version given by Cargo. If `CARGO_PKG_VERSION` is not +//! set no `.version()` calls will be generated unless requested. +//! +//! - `no_version`: `no_version` +//! +//! Usable only on top level. Prevents default `App::version` call, i.e +//! when no `version = "version"` mentioned. +//! +//! - `author`: `author [= "author"]` +//! +//! Usable only on top level: `App::author("author" or env!(CARGO_PKG_AUTHOR))`. +//! +//! Author/maintainer of the binary, this name appears in help messages. +//! Defaults to the crate author given by cargo, but only when `author` explicitly mentioned. +//! +//! - `about`: `about [= "about"]` +//! +//! Usable only on top level: `App::about("about" or env!(CARGO_PKG_DESCRIPTION))`. +//! +//! Short description of the binary, appears in help messages. +//! Defaults to the crate description given by cargo, +//! but only when `about` explicitly mentioned. +//! +//! - [`short`](#specifying-argument-types): `short [= "short-opt-name"]` +//! +//! Usable only on field-level. +//! +//! - [`long`](#specifying-argument-types): `long [= "long-opt-name"]` +//! +//! Usable only on field-level. +//! +//! - [`rename_all`](#specifying-argument-types): [`rename_all = "kebab"/"snake"/"screaming-snake"/"camel"/"pascal"/"verbatim"]` +//! +//! Usable both on top level and field level. +//! +//! - [`parse`](#custom-string-parsers): `parse(type [= path::to::parser::fn])` +//! +//! Usable only on field-level. +//! +//! - [`skip`](#skipping-fields): `skip [= expr]` +//! +//! Usable only on field-level. +//! +//! - [`flatten`](#flattening): `flatten` +//! +//! Usable only on field-level. +//! +//! - [`subcommand`](#subcommands): `subcommand` +//! +//! Usable only on field-level. +//! +//! - [`env`](#environment-variable-fallback): `env [= str_literal]` +//! +//! Usable only on field-level. +//! +//! - [`rename_all_env`](##auto-deriving-environment-variables): [`rename_all_env = "kebab"/"snake"/"screaming-snake"/"camel"/"pascal"/"verbatim"]` +//! +//! Usable both on top level and field level. +//! +//! - [`verbatim_doc_comment`](#doc-comment-preprocessing-and-structoptverbatim_doc_comment): +//! `verbatim_doc_comment` +//! +//! Usable both on top level and field level. +//! +//! ## Type magic +//! +//! One of major things that makes `structopt` so awesome is it's type magic. +//! Do you want optional positional argument? Use `Option`! Or perhaps optional argument +//! that optionally takes value (`[--opt=[val]]`)? Use `Option>`! +//! +//! Here is the table of types and `clap` methods they correspond to: +//! +//! Type | Effect | Added method call to `clap::Arg` +//! -----------------------------|---------------------------------------------------|-------------------------------------- +//! `bool` | `true` if the flag is present | `.takes_value(false).multiple(false)` +//! `Option` | optional positional argument or option | `.takes_value(true).multiple(false)` +//! `Option>` | optional option with optional value | `.takes_value(true).multiple(false).min_values(0).max_values(1)` +//! `Vec` | list of options or the other positional arguments | `.takes_value(true).multiple(true)` +//! `Option` | optional list of options | `.takes_values(true).multiple(true).min_values(0)` +//! `T: FromStr` | required option or positional argument | `.takes_value(true).multiple(false).required(!has_default)` +//! +//! The `FromStr` trait is used to convert the argument to the given +//! type, and the `Arg::validator` method is set to a method using +//! `to_string()` (`FromStr::Err` must implement `std::fmt::Display`). +//! If you would like to use a custom string parser other than `FromStr`, see +//! the [same titled section](#custom-string-parsers) below. +//! +//! **Note:** +//! _________________ +//! Pay attention that *only literal occurrence* of this types is special, for example +//! `Option` is special while `::std::option::Option` is not. +//! +//! If you need to avoid special casing you can make a `type` alias and +//! use it in place of the said type. +//! _________________ +//! +//! **Note:** +//! _________________ +//! `bool` cannot be used as positional argument unless you provide an explicit parser. +//! If you need a positional bool, for example to parse `true` or `false`, you must +//! annotate the field with explicit [`#[structopt(parse(...))]`](#custom-string-parsers). +//! _________________ +//! +//! Thus, the `speed` argument is generated as: +//! +//! ``` +//! # extern crate clap; +//! # fn parse_validator(_: String) -> Result<(), String> { unimplemented!() } +//! # fn main() { +//! clap::Arg::with_name("speed") +//! .takes_value(true) +//! .multiple(false) +//! .required(false) +//! .validator(parse_validator::) +//! .short("v") +//! .long("velocity") +//! .help("Set speed") +//! .default_value("42"); +//! # } +//! ``` +//! +//! ## Specifying argument types +//! +//! There are three types of arguments that can be supplied to each +//! (sub-)command: +//! +//! - short (e.g. `-h`), +//! - long (e.g. `--help`) +//! - and positional. +//! +//! Like clap, structopt defaults to creating positional arguments. +//! +//! If you want to generate a long argument you can specify either +//! `long = $NAME`, or just `long` to get a long flag generated using +//! the field name. The generated casing style can be modified using +//! the `rename_all` attribute. See the `rename_all` example for more. +//! +//! For short arguments, `short` will use the first letter of the +//! field name by default, but just like the long option it's also +//! possible to use a custom letter through `short = $LETTER`. +//! +//! If an argument is renamed using `name = $NAME` any following call to +//! `short` or `long` will use the new name. +//! +//! **Attention**: If these arguments are used without an explicit name +//! the resulting flag is going to be renamed using `kebab-case` if the +//! `rename_all` attribute was not specified previously. The same is true +//! for subcommands with implicit naming through the related data structure. +//! +//! ``` +//! use structopt::StructOpt; +//! +//! #[derive(StructOpt)] +//! #[structopt(rename_all = "kebab-case")] +//! struct Opt { +//! /// This option can be specified with something like `--foo-option +//! /// value` or `--foo-option=value` +//! #[structopt(long)] +//! foo_option: String, +//! +//! /// This option can be specified with something like `-b value` (but +//! /// not `--bar-option value`). +//! #[structopt(short)] +//! bar_option: String, +//! +//! /// This option can be specified either `--baz value` or `-z value`. +//! #[structopt(short = "z", long = "baz")] +//! baz_option: String, +//! +//! /// This option can be specified either by `--custom value` or +//! /// `-c value`. +//! #[structopt(name = "custom", long, short)] +//! custom_option: String, +//! +//! /// This option is positional, meaning it is the first unadorned string +//! /// you provide (multiple others could follow). +//! my_positional: String, +//! +//! /// This option is skipped and will be filled with the default value +//! /// for its type (in this case 0). +//! #[structopt(skip)] +//! skipped: u32, +//! +//! } +//! +//! # fn main() { +//! # Opt::from_clap(&Opt::clap().get_matches_from( +//! # &["test", "--foo-option", "", "-b", "", "--baz", "", "--custom", "", "positional"])); +//! # } +//! ``` +//! +//! ## Help messages +//! +//! In clap, help messages for the whole binary can be specified +//! via [`App::about`] and [`App::long_about`] while help messages +//! for individual arguments can be specified via [`Arg::help`] and [`Arg::long_help`]". +//! +//! `long_*` variants are used when user calls the program with +//! `--help` and "short" variants are used with `-h` flag. In `structopt`, +//! you can use them via [raw methods](#raw-methods), for example: +//! +//! ``` +//! # use structopt::StructOpt; +//! +//! #[derive(StructOpt)] +//! #[structopt(about = "I am a program and I work, just pass `-h`")] +//! struct Foo { +//! #[structopt(short, help = "Pass `-h` and you'll see me!")] +//! bar: String +//! } +//! ``` +//! +//! For convenience, doc comments can be used instead of raw methods +//! (this example works exactly like the one above): +//! +//! ``` +//! # use structopt::StructOpt; +//! +//! #[derive(StructOpt)] +//! /// I am a program and I work, just pass `-h` +//! struct Foo { +//! /// Pass `-h` and you'll see me! +//! bar: String +//! } +//! ``` +//! +//! Doc comments on [top-level](#magical-methods) will be turned into +//! `App::about/long_about` call (see below), doc comments on field-level are +//! `Arg::help/long_help` calls. +//! +//! **Important:** +//! _________________ +//! +//! Raw methods have priority over doc comments! +//! +//! **Top level doc comments always generate `App::about/long_about` calls!** +//! If you really want to use the `App::help/long_help` methods (you likely don't), +//! use a raw method to override the `App::about` call generated from the doc comment. +//! __________________ +//! +//! ### `long_help` and `--help` +//! +//! A message passed to [`App::long_help`] or [`Arg::long_about`] will be displayed whenever +//! your program is called with `--help` instead of `-h`. Of course, you can +//! use them via raw methods as described [above](#help-messages). +//! +//! The more convenient way is to use a so-called "long" doc comment: +//! +//! ``` +//! # use structopt::StructOpt; +//! #[derive(StructOpt)] +//! /// Hi there, I'm Robo! +//! /// +//! /// I like beeping, stumbling, eating your electricity, +//! /// and making records of you singing in a shower. +//! /// Pay up, or I'll upload it to youtube! +//! struct Robo { +//! /// Call my brother SkyNet. +//! /// +//! /// I am artificial superintelligence. I won't rest +//! /// until I'll have destroyed humanity. Enjoy your +//! /// pathetic existence, you mere mortals. +//! #[structopt(long)] +//! kill_all_humans: bool +//! } +//! ``` +//! +//! A long doc comment consists of three parts: +//! * Short summary +//! * A blank line (whitespace only) +//! * Detailed description, all the rest +//! +//! In other words, "long" doc comment consists of two or more paragraphs, +//! with the first being a summary and the rest being the detailed description. +//! +//! **A long comment will result in two method calls**, `help()` and +//! `long_help()`, so clap will display the summary with `-h` +//! and the whole help message on `--help` (see below). +//! +//! So, the example above will be turned into this (details omitted): +//! ``` +//! clap::App::new("") +//! .about("Hi there, I'm Robo!") +//! .long_about("Hi there, I'm Robo!\n\n\ +//! I like beeping, stumbling, eating your electricity,\ +//! and making records of you singing in a shower.\ +//! Pay up or I'll upload it to youtube!") +//! // args... +//! # ; +//! ``` +//! +//! ### `-h` vs `--help` (A.K.A `help()` vs `long_help()`) +//! +//! The `-h` flag is not the same as `--help`. +//! +//! -h corresponds to Arg::help/App::about and requests short "summary" messages +//! while --help corresponds to Arg::long_help/App::long_about and requests more +//! detailed, descriptive messages. +//! +//! It is entirely up to `clap` what happens if you used only one of +//! [`Arg::help`]/[`Arg::long_help`], see `clap`'s documentation for these methods. +//! +//! As of clap v2.33, if only a short message ([`Arg::help`]) or only +//! a long ([`Arg::long_help`]) message is provided, clap will use it +//! for both -h and --help. The same logic applies to `about/long_about`. +//! +//! ### Doc comment preprocessing and `#[structopt(verbatim_doc_comment)]` +//! +//! `structopt` applies some preprocessing to doc comments to ease the most common uses: +//! +//! * Strip leading and trailing whitespace from every line, if present. +//! +//! * Strip leading and trailing blank lines, if present. +//! +//! * Interpret each group of non-empty lines as a word-wrapped paragraph. +//! +//! We replace newlines within paragraphs with spaces to allow the output +//! to be re-wrapped to the terminal width. +//! +//! * Strip any excess blank lines so that there is exactly one per paragraph break. +//! +//! * If the first paragraph ends in exactly one period, +//! remove the trailing period (i.e. strip trailing periods but not trailing ellipses). +//! +//! Sometimes you don't want this preprocessing to apply, for example the comment contains +//! some ASCII art or markdown tables, you would need to preserve LFs along with +//! blank lines and the leading/trailing whitespace. You can ask `structopt` to preserve them +//! via `#[structopt(verbatim_doc_comment)]` attribute. +//! +//! **This attribute must be applied to each field separately**, there's no global switch. +//! +//! **Important:** +//! ______________ +//! Keep in mind that `structopt` will *still* remove one leading space from each +//! line, even if this attribute is present, to allow for a space between +//! `///` and the content. +//! +//! Also, `structopt` will *still* remove leading and trailing blank lines so +//! these formats are equivalent: +//! +//! ```ignore +//! /** This is a doc comment +//! +//! Hello! */ +//! +//! /** +//! This is a doc comment +//! +//! Hello! +//! */ +//! +//! /// This is a doc comment +//! /// +//! /// Hello! +//! ``` +//! +//! Summary +//! ______________ +//! +//! [`App::about`]: https://docs.rs/clap/2/clap/struct.App.html#method.about +//! [`App::long_about`]: https://docs.rs/clap/2/clap/struct.App.html#method.long_about +//! [`Arg::help`]: https://docs.rs/clap/2/clap/struct.Arg.html#method.help +//! [`Arg::long_help`]: https://docs.rs/clap/2/clap/struct.Arg.html#method.long_help +//! +//! ## Environment variable fallback +//! +//! It is possible to specify an environment variable fallback option for an arguments +//! so that its value is taken from the specified environment variable if not +//! given through the command-line: +//! +//! ``` +//! # use structopt::StructOpt; +//! +//! #[derive(StructOpt)] +//! struct Foo { +//! #[structopt(short, long, env = "PARAMETER_VALUE")] +//! parameter_value: String +//! } +//! # fn main() {} +//! ``` +//! +//! By default, values from the environment are shown in the help output (i.e. when invoking +//! `--help`): +//! +//! ```shell +//! $ cargo run -- --help +//! ... +//! OPTIONS: +//! -p, --parameter-value [env: PARAMETER_VALUE=env_value] +//! ``` +//! +//! In some cases this may be undesirable, for example when being used for passing +//! credentials or secret tokens. In those cases you can use `hide_env_values` to avoid +//! having structopt emit the actual secret values: +//! ``` +//! # use structopt::StructOpt; +//! +//! #[derive(StructOpt)] +//! struct Foo { +//! #[structopt(long = "secret", env = "SECRET_VALUE", hide_env_values = true)] +//! secret_value: String +//! } +//! ``` +//! +//! ### Auto-deriving environment variables +//! +//! Environment variables tend to be called after the corresponding `struct`'s field, +//! as in example above. The field is `secret_value` and the env var is "SECRET_VALUE"; +//! the name is the same, except casing is different. +//! +//! It's pretty tedious and error-prone to type the same name twice, +//! so you can ask `structopt` to do that for you. +//! +//! ``` +//! # use structopt::StructOpt; +//! +//! #[derive(StructOpt)] +//! struct Foo { +//! #[structopt(long = "secret", env)] +//! secret_value: String +//! } +//! ``` +//! +//! It works just like `#[structopt(short/long)]`: if `env` is not set to some concrete +//! value the value will be derived from the field's name. This is controlled by +//! `#[structopt(rename_all_env)]`. +//! +//! `rename_all_env` works exactly as `rename_all` (including overriding) +//! except default casing is `SCREAMING_SNAKE_CASE` instead of `kebab-case`. +//! +//! ## Skipping fields +//! +//! Sometimes you may want to add a field to your `Opt` struct that is not +//! a command line option and `clap` should know nothing about it. You can ask +//! `structopt` to skip the field entirely via `#[structopt(skip = value)]` +//! (`value` must implement `Into`) +//! or `#[structopt(skip)]` if you want assign the field with `Default::default()` +//! (obviously, the field's type must implement `Default`). +//! +//! ``` +//! # use structopt::StructOpt; +//! #[derive(StructOpt)] +//! pub struct Opt { +//! #[structopt(long, short)] +//! number: u32, +//! +//! // these fields are to be assigned with Default::default() +//! +//! #[structopt(skip)] +//! k: String, +//! #[structopt(skip)] +//! v: Vec, +//! +//! // these fields get set explicitly +//! +//! #[structopt(skip = vec![1, 2, 3])] +//! k2: Vec, +//! #[structopt(skip = "cake")] // &str implements Into +//! v2: String, +//! } +//! ``` +//! +//! ## Subcommands +//! +//! Some applications, especially large ones, split their functionality +//! through the use of "subcommands". Each of these act somewhat like a separate +//! command, but is part of the larger group. +//! One example is `git`, which has subcommands such as `add`, `commit`, +//! and `clone`, to mention just a few. +//! +//! `clap` has this functionality, and `structopt` supports it through enums: +//! +//! ``` +//! # use structopt::StructOpt; +//! +//! # use std::path::PathBuf; +//! #[derive(StructOpt)] +//! #[structopt(about = "the stupid content tracker")] +//! enum Git { +//! Add { +//! #[structopt(short)] +//! interactive: bool, +//! #[structopt(short)] +//! patch: bool, +//! #[structopt(parse(from_os_str))] +//! files: Vec +//! }, +//! Fetch { +//! #[structopt(long)] +//! dry_run: bool, +//! #[structopt(long)] +//! all: bool, +//! repository: Option +//! }, +//! Commit { +//! #[structopt(short)] +//! message: Option, +//! #[structopt(short)] +//! all: bool +//! } +//! } +//! # fn main() {} +//! ``` +//! +//! Using `derive(StructOpt)` on an enum instead of a struct will produce +//! a `clap::App` that only takes subcommands. So `git add`, `git fetch`, +//! and `git commit` would be commands allowed for the above example. +//! +//! `structopt` also provides support for applications where certain flags +//! need to apply to all subcommands, as well as nested subcommands: +//! +//! ``` +//! # use structopt::StructOpt; +//! # fn main() {} +//! #[derive(StructOpt)] +//! struct MakeCookie { +//! #[structopt(name = "supervisor", default_value = "Puck", long = "supervisor")] +//! supervising_faerie: String, +//! /// The faerie tree this cookie is being made in. +//! tree: Option, +//! #[structopt(subcommand)] // Note that we mark a field as a subcommand +//! cmd: Command +//! } +//! +//! #[derive(StructOpt)] +//! enum Command { +//! /// Pound acorns into flour for cookie dough. +//! Pound { +//! acorns: u32 +//! }, +//! /// Add magical sparkles -- the secret ingredient! +//! Sparkle { +//! #[structopt(short, parse(from_occurrences))] +//! magicality: u64, +//! #[structopt(short)] +//! color: String +//! }, +//! Finish(Finish), +//! } +//! +//! // Subcommand can also be externalized by using a 1-uple enum variant +//! #[derive(StructOpt)] +//! struct Finish { +//! #[structopt(short)] +//! time: u32, +//! #[structopt(subcommand)] // Note that we mark a field as a subcommand +//! finish_type: FinishType +//! } +//! +//! // subsubcommand! +//! #[derive(StructOpt)] +//! enum FinishType { +//! Glaze { +//! applications: u32 +//! }, +//! Powder { +//! flavor: String, +//! dips: u32 +//! } +//! } +//! ``` +//! +//! Marking a field with `structopt(subcommand)` will add the subcommands of the +//! designated enum to the current `clap::App`. The designated enum *must* also +//! be derived `StructOpt`. So the above example would take the following +//! commands: +//! +//! + `make-cookie pound 50` +//! + `make-cookie sparkle -mmm --color "green"` +//! + `make-cookie finish 130 glaze 3` +//! +//! ### Optional subcommands +//! +//! Subcommands may be optional: +//! +//! ``` +//! # use structopt::StructOpt; +//! # fn main() {} +//! #[derive(StructOpt)] +//! struct Foo { +//! file: String, +//! #[structopt(subcommand)] +//! cmd: Option +//! } +//! +//! #[derive(StructOpt)] +//! enum Command { +//! Bar, +//! Baz, +//! Quux +//! } +//! ``` +//! +//! ## Flattening +//! +//! It can sometimes be useful to group related arguments in a substruct, +//! while keeping the command-line interface flat. In these cases you can mark +//! a field as `flatten` and give it another type that derives `StructOpt`: +//! +//! ``` +//! # use structopt::StructOpt; +//! # fn main() {} +//! #[derive(StructOpt)] +//! struct Cmdline { +//! /// switch on verbosity +//! #[structopt(short)] +//! verbose: bool, +//! #[structopt(flatten)] +//! daemon_opts: DaemonOpts, +//! } +//! +//! #[derive(StructOpt)] +//! struct DaemonOpts { +//! /// daemon user +//! #[structopt(short)] +//! user: String, +//! /// daemon group +//! #[structopt(short)] +//! group: String, +//! } +//! ``` +//! +//! In this example, the derived `Cmdline` parser will support the options `-v`, +//! `-u` and `-g`. +//! +//! This feature also makes it possible to define a `StructOpt` struct in a +//! library, parse the corresponding arguments in the main argument parser, and +//! pass off this struct to a handler provided by that library. +//! +//! ## Custom string parsers +//! +//! If the field type does not have a `FromStr` implementation, or you would +//! like to provide a custom parsing scheme other than `FromStr`, you may +//! provide a custom string parser using `parse(...)` like this: +//! +//! ``` +//! # use structopt::StructOpt; +//! # fn main() {} +//! use std::num::ParseIntError; +//! use std::path::PathBuf; +//! +//! fn parse_hex(src: &str) -> Result { +//! u32::from_str_radix(src, 16) +//! } +//! +//! #[derive(StructOpt)] +//! struct HexReader { +//! #[structopt(short, parse(try_from_str = parse_hex))] +//! number: u32, +//! #[structopt(short, parse(from_os_str))] +//! output: PathBuf, +//! } +//! ``` +//! +//! There are five kinds of custom parsers: +//! +//! | Kind | Signature | Default | +//! |-------------------|---------------------------------------|---------------------------------| +//! | `from_str` | `fn(&str) -> T` | `::std::convert::From::from` | +//! | `try_from_str` | `fn(&str) -> Result` | `::std::str::FromStr::from_str` | +//! | `from_os_str` | `fn(&OsStr) -> T` | `::std::convert::From::from` | +//! | `try_from_os_str` | `fn(&OsStr) -> Result` | (no default function) | +//! | `from_occurrences`| `fn(u64) -> T` | `value as T` | +//! | `from_flag` | `fn(bool) -> T` | `::std::convert::From::from` | +//! +//! The `from_occurrences` parser is special. Using `parse(from_occurrences)` +//! results in the _number of flags occurrences_ being stored in the relevant +//! field or being passed to the supplied function. In other words, it converts +//! something like `-vvv` to `3`. This is equivalent to +//! `.takes_value(false).multiple(true)`. Note that the default parser can only +//! be used with fields of integer types (`u8`, `usize`, `i64`, etc.). +//! +//! The `from_flag` parser is also special. Using `parse(from_flag)` or +//! `parse(from_flag = some_func)` will result in the field being treated as a +//! flag even if it does not have type `bool`. +//! +//! When supplying a custom string parser, `bool` will not be treated specially: +//! +//! Type | Effect | Added method call to `clap::Arg` +//! ------------|-------------------|-------------------------------------- +//! `Option` | optional argument | `.takes_value(true).multiple(false)` +//! `Vec` | list of arguments | `.takes_value(true).multiple(true)` +//! `T` | required argument | `.takes_value(true).multiple(false).required(!has_default)` +//! +//! In the `try_from_*` variants, the function will run twice on valid input: +//! once to validate, and once to parse. Hence, make sure the function is +//! side-effect-free. + +#[doc(hidden)] +pub use structopt_derive::*; + +use std::ffi::OsString; + +/// Re-export of clap +pub use clap; + +/// A struct that is converted from command line arguments. +pub trait StructOpt { + /// Returns the corresponding `clap::App`. + fn clap<'a, 'b>() -> clap::App<'a, 'b>; + + /// Creates the struct from `clap::ArgMatches`. It cannot fail + /// with a parameter generated by `clap` by construction. + fn from_clap(matches: &clap::ArgMatches<'_>) -> Self; + + /// Gets the struct from the command line arguments. Print the + /// error message and quit the program in case of failure. + fn from_args() -> Self + where + Self: Sized, + { + Self::from_clap(&Self::clap().get_matches()) + } + + /// Gets the struct from any iterator such as a `Vec` of your making. + /// Print the error message and quit the program in case of failure. + fn from_iter(iter: I) -> Self + where + Self: Sized, + I: IntoIterator, + I::Item: Into + Clone, + { + Self::from_clap(&Self::clap().get_matches_from(iter)) + } + + /// Gets the struct from any iterator such as a `Vec` of your making. + /// + /// Returns a `clap::Error` in case of failure. This does *not* exit in the + /// case of `--help` or `--version`, to achieve the same behavior as + /// `from_iter()` you must call `.exit()` on the error value. + fn from_iter_safe(iter: I) -> Result + where + Self: Sized, + I: IntoIterator, + I::Item: Into + Clone, + { + Ok(Self::from_clap(&Self::clap().get_matches_from_safe(iter)?)) + } +} + +/// This trait is NOT API. **SUBJECT TO CHANGE WITHOUT NOTICE!**. +#[doc(hidden)] +pub trait StructOptInternal: StructOpt { + fn augment_clap<'a, 'b>(app: clap::App<'a, 'b>) -> clap::App<'a, 'b> { + app + } + + fn is_subcommand() -> bool { + false + } + + fn from_subcommand<'a, 'b>(_sub: (&'b str, Option<&'b clap::ArgMatches<'a>>)) -> Option + where + Self: std::marker::Sized, + { + None + } +} + +impl StructOpt for Box { + fn clap<'a, 'b>() -> clap::App<'a, 'b> { + ::clap() + } + + fn from_clap(matches: &clap::ArgMatches<'_>) -> Self { + Box::new(::from_clap(matches)) + } +} + +impl StructOptInternal for Box { + #[doc(hidden)] + fn is_subcommand() -> bool { + ::is_subcommand() + } + + #[doc(hidden)] + fn from_subcommand<'a, 'b>(sub: (&'b str, Option<&'b clap::ArgMatches<'a>>)) -> Option { + ::from_subcommand(sub).map(Box::new) + } + + #[doc(hidden)] + fn augment_clap<'a, 'b>(app: clap::App<'a, 'b>) -> clap::App<'a, 'b> { + ::augment_clap(app) + } +} diff --git a/structopt/structopt-derive/Cargo.toml b/structopt/structopt-derive/Cargo.toml new file mode 100644 index 0000000..ad547af --- /dev/null +++ b/structopt/structopt-derive/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "structopt-derive" +version = "0.4.0" +edition = "2018" +authors = ["Guillaume Pinot "] +description = "Parse command line argument by defining a struct, derive crate." +documentation = "https://docs.rs/structopt-derive" +repository = "https://github.com/TeXitoi/structopt" +keywords = ["clap", "cli", "derive", "docopt"] +categories = ["command-line-interface"] +license = "Apache-2.0/MIT" + +[badges] +travis-ci = { repository = "TeXitoi/structopt" } + +[dependencies] +syn = { version = "1", features = ["full"] } +quote = "1" +proc-macro2 = "1" +heck = "0.3.0" +proc-macro-error = "0.4.3" + +[features] +paw = [] + +[lib] +proc-macro = true diff --git a/structopt/structopt-derive/LICENSE-APACHE b/structopt/structopt-derive/LICENSE-APACHE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/structopt/structopt-derive/LICENSE-APACHE @@ -0,0 +1,201 @@ + 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. diff --git a/structopt/structopt-derive/LICENSE-MIT b/structopt/structopt-derive/LICENSE-MIT new file mode 100644 index 0000000..e931b83 --- /dev/null +++ b/structopt/structopt-derive/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Guillaume Pinot (@TeXitoi) + +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/structopt/structopt-derive/src/attrs.rs b/structopt/structopt-derive/src/attrs.rs new file mode 100644 index 0000000..ce684a2 --- /dev/null +++ b/structopt/structopt-derive/src/attrs.rs @@ -0,0 +1,620 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use crate::doc_comments::process_doc_comment; +use crate::{parse::*, spanned::Sp, ty::Ty}; + +use std::env; + +use heck::{CamelCase, KebabCase, MixedCase, ShoutySnakeCase, SnakeCase}; +use proc_macro2::{Span, TokenStream}; +use proc_macro_error::abort; +use quote::{quote, quote_spanned, ToTokens}; +use syn::{self, ext::IdentExt, spanned::Spanned, Attribute, Expr, Ident, LitStr, MetaNameValue}; + +#[derive(Clone)] +pub enum Kind { + Arg(Sp), + Subcommand(Sp), + FlattenStruct, + Skip(Option), +} + +#[derive(Clone)] +pub struct Method { + name: Ident, + args: TokenStream, +} + +#[derive(Clone)] +pub struct Parser { + pub kind: Sp, + pub func: TokenStream, +} + +#[derive(Debug, PartialEq, Clone)] +pub enum ParserKind { + FromStr, + TryFromStr, + FromOsStr, + TryFromOsStr, + FromOccurrences, + FromFlag, +} + +/// Defines the casing for the attributes long representation. +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum CasingStyle { + /// Indicate word boundaries with uppercase letter, excluding the first word. + Camel, + /// Keep all letters lowercase and indicate word boundaries with hyphens. + Kebab, + /// Indicate word boundaries with uppercase letter, including the first word. + Pascal, + /// Keep all letters uppercase and indicate word boundaries with underscores. + ScreamingSnake, + /// Keep all letters lowercase and indicate word boundaries with underscores. + Snake, + /// Use the original attribute name defined in the code. + Verbatim, +} + +#[derive(Clone)] +pub enum Name { + Derived(Ident), + Assigned(LitStr), +} + +#[derive(Clone)] +pub struct Attrs { + name: Name, + casing: Sp, + env_casing: Sp, + doc_comment: Vec, + methods: Vec, + parser: Sp, + author: Option, + about: Option, + version: Option, + no_version: Option, + verbatim_doc_comment: Option, + has_custom_parser: bool, + kind: Sp, +} + +impl Method { + pub fn new(name: Ident, args: TokenStream) -> Self { + Method { name, args } + } + + fn from_lit_or_env(ident: Ident, lit: Option, env_var: &str) -> Option { + let mut lit = match lit { + Some(lit) => lit, + + None => match env::var(env_var) { + Ok(val) => LitStr::new(&val, ident.span()), + Err(_) => { + abort!(ident.span(), + "cannot derive `{}` from Cargo.toml", ident; + note = "`{}` environment variable is not set", env_var; + help = "use `{} = \"...\"` to set {} manually", ident, ident; + ); + } + }, + }; + + if ident == "author" { + let edited = process_author_str(&lit.value()); + lit = LitStr::new(&edited, lit.span()); + } + + Some(Method::new(ident, quote!(#lit))) + } +} + +impl ToTokens for Method { + fn to_tokens(&self, ts: &mut TokenStream) { + let Method { ref name, ref args } = self; + quote!(.#name(#args)).to_tokens(ts); + } +} + +impl Parser { + fn default_spanned(span: Span) -> Sp { + let kind = Sp::new(ParserKind::TryFromStr, span); + let func = quote_spanned!(span=> ::std::str::FromStr::from_str); + Sp::new(Parser { kind, func }, span) + } + + fn from_spec(parse_ident: Ident, spec: ParserSpec) -> Sp { + use ParserKind::*; + + let kind = match &*spec.kind.to_string() { + "from_str" => FromStr, + "try_from_str" => TryFromStr, + "from_os_str" => FromOsStr, + "try_from_os_str" => TryFromOsStr, + "from_occurrences" => FromOccurrences, + "from_flag" => FromFlag, + s => abort!(spec.kind.span(), "unsupported parser `{}`", s), + }; + + let func = match spec.parse_func { + None => match kind { + FromStr | FromOsStr => { + quote_spanned!(spec.kind.span()=> ::std::convert::From::from) + } + TryFromStr => quote_spanned!(spec.kind.span()=> ::std::str::FromStr::from_str), + TryFromOsStr => abort!( + spec.kind.span(), + "you must set parser for `try_from_os_str` explicitly" + ), + FromOccurrences => quote_spanned!(spec.kind.span()=> { |v| v as _ }), + FromFlag => quote_spanned!(spec.kind.span()=> ::std::convert::From::from), + }, + + Some(func) => match func { + syn::Expr::Path(_) => quote!(#func), + _ => abort!(func.span(), "`parse` argument must be a function path"), + }, + }; + + let kind = Sp::new(kind, spec.kind.span()); + let parser = Parser { kind, func }; + Sp::new(parser, parse_ident.span()) + } +} + +impl CasingStyle { + fn from_lit(name: LitStr) -> Sp { + use CasingStyle::*; + + let normalized = name.value().to_camel_case().to_lowercase(); + let cs = |kind| Sp::new(kind, name.span()); + + match normalized.as_ref() { + "camel" | "camelcase" => cs(Camel), + "kebab" | "kebabcase" => cs(Kebab), + "pascal" | "pascalcase" => cs(Pascal), + "screamingsnake" | "screamingsnakecase" => cs(ScreamingSnake), + "snake" | "snakecase" => cs(Snake), + "verbatim" | "verbatimcase" => cs(Verbatim), + s => abort!(name.span(), "unsupported casing: `{}`", s), + } + } +} + +impl Name { + pub fn translate(self, style: CasingStyle) -> LitStr { + use CasingStyle::*; + + match self { + Name::Assigned(lit) => lit, + Name::Derived(ident) => { + let s = ident.unraw().to_string(); + let s = match style { + Pascal => s.to_camel_case(), + Kebab => s.to_kebab_case(), + Camel => s.to_mixed_case(), + ScreamingSnake => s.to_shouty_snake_case(), + Snake => s.to_snake_case(), + Verbatim => s, + }; + LitStr::new(&s, ident.span()) + } + } + } +} + +impl Attrs { + fn new( + default_span: Span, + name: Name, + casing: Sp, + env_casing: Sp, + ) -> Self { + Self { + name, + casing, + env_casing, + doc_comment: vec![], + methods: vec![], + parser: Parser::default_spanned(default_span), + about: None, + author: None, + version: None, + no_version: None, + verbatim_doc_comment: None, + + has_custom_parser: false, + kind: Sp::new(Kind::Arg(Sp::new(Ty::Other, default_span)), default_span), + } + } + + /// push `.method("str literal")` + fn push_str_method(&mut self, name: Sp, arg: Sp) { + if *name == "name" { + self.name = Name::Assigned(arg.as_lit()); + } else { + self.methods + .push(Method::new(name.as_ident(), quote!(#arg))) + } + } + + fn push_attrs(&mut self, attrs: &[Attribute]) { + use crate::parse::StructOptAttr::*; + + for attr in parse_structopt_attributes(attrs) { + match attr { + Short(ident) | Long(ident) => { + self.push_str_method( + ident.into(), + self.name.clone().translate(*self.casing).into(), + ); + } + + Env(ident) => { + self.push_str_method( + ident.into(), + self.name.clone().translate(*self.env_casing).into(), + ); + } + + Subcommand(ident) => { + let ty = Sp::call_site(Ty::Other); + let kind = Sp::new(Kind::Subcommand(ty), ident.span()); + self.set_kind(kind); + } + + Flatten(ident) => { + let kind = Sp::new(Kind::FlattenStruct, ident.span()); + self.set_kind(kind); + } + + Skip(ident, expr) => { + let kind = Sp::new(Kind::Skip(expr), ident.span()); + self.set_kind(kind); + } + + NoVersion(ident) => self.no_version = Some(ident), + + VerbatimDocComment(ident) => self.verbatim_doc_comment = Some(ident), + + About(ident, about) => { + self.about = Method::from_lit_or_env(ident, about, "CARGO_PKG_DESCRIPTION"); + } + + Author(ident, author) => { + self.author = Method::from_lit_or_env(ident, author, "CARGO_PKG_AUTHORS"); + } + + Version(ident, version) => { + self.version = Some(Method::new(ident, quote!(#version))) + } + + NameLitStr(name, lit) => { + self.push_str_method(name.into(), lit.into()); + } + + NameExpr(name, expr) => self.methods.push(Method::new(name, quote!(#expr))), + + MethodCall(name, args) => self.methods.push(Method::new(name, quote!(#(#args),*))), + + RenameAll(_, casing_lit) => { + self.casing = CasingStyle::from_lit(casing_lit); + } + + RenameAllEnv(_, casing_lit) => { + self.env_casing = CasingStyle::from_lit(casing_lit); + } + + Parse(ident, spec) => { + self.has_custom_parser = true; + self.parser = Parser::from_spec(ident, spec); + } + } + } + } + + fn push_doc_comment(&mut self, attrs: &[Attribute], name: &str) { + use crate::Lit::*; + use crate::Meta::*; + + let comment_parts: Vec<_> = attrs + .iter() + .filter(|attr| attr.path.is_ident("doc")) + .filter_map(|attr| { + if let Ok(NameValue(MetaNameValue { lit: Str(s), .. })) = attr.parse_meta() { + Some(s.value()) + } else { + // non #[doc = "..."] attributes are not our concern + // we leave them for rustc to handle + None + } + }) + .collect(); + + self.doc_comment = + process_doc_comment(comment_parts, name, self.verbatim_doc_comment.is_none()); + } + + pub fn from_struct( + span: Span, + attrs: &[Attribute], + name: Name, + argument_casing: Sp, + env_casing: Sp, + ) -> Self { + let mut res = Self::new(span, name, argument_casing, env_casing); + res.push_attrs(attrs); + res.push_doc_comment(attrs, "about"); + + if res.has_custom_parser { + abort!( + res.parser.span(), + "`parse` attribute is only allowed on fields" + ); + } + match &*res.kind { + Kind::Subcommand(_) => abort!(res.kind.span(), "subcommand is only allowed on fields"), + Kind::FlattenStruct => abort!(res.kind.span(), "flatten is only allowed on fields"), + Kind::Skip(_) => abort!(res.kind.span(), "skip is only allowed on fields"), + Kind::Arg(_) => res, + } + } + + pub fn from_field( + field: &syn::Field, + struct_casing: Sp, + env_casing: Sp, + ) -> Self { + let name = field.ident.clone().unwrap(); + let mut res = Self::new( + field.span(), + Name::Derived(name.clone()), + struct_casing, + env_casing, + ); + res.push_doc_comment(&field.attrs, "help"); + res.push_attrs(&field.attrs); + + match &*res.kind { + Kind::FlattenStruct => { + if res.has_custom_parser { + abort!( + res.parser.span(), + "parse attribute is not allowed for flattened entry" + ); + } + if res.has_explicit_methods() || res.has_doc_methods() { + abort!( + res.kind.span(), + "methods and doc comments are not allowed for flattened entry" + ); + } + } + Kind::Subcommand(_) => { + if res.has_custom_parser { + abort!( + res.parser.span(), + "parse attribute is not allowed for subcommand" + ); + } + if res.has_explicit_methods() { + abort!( + res.kind.span(), + "methods in attributes are not allowed for subcommand" + ); + } + + let ty = Ty::from_syn_ty(&field.ty); + match *ty { + Ty::OptionOption => { + abort!( + ty.span(), + "Option> type is not allowed for subcommand" + ); + } + Ty::OptionVec => { + abort!( + ty.span(), + "Option> type is not allowed for subcommand" + ); + } + _ => (), + } + + res.kind = Sp::new(Kind::Subcommand(ty), res.kind.span()); + } + Kind::Skip(_) => { + if res.has_explicit_methods() { + abort!( + res.kind.span(), + "methods are not allowed for skipped fields" + ); + } + } + Kind::Arg(orig_ty) => { + let mut ty = Ty::from_syn_ty(&field.ty); + if res.has_custom_parser { + match *ty { + Ty::Option | Ty::Vec | Ty::OptionVec => (), + _ => ty = Sp::new(Ty::Other, ty.span()), + } + } + + match *ty { + Ty::Bool => { + if res.is_positional() && !res.has_custom_parser { + abort!(ty.span(), + "`bool` cannot be used as positional parameter with default parser"; + help = "if you want to create a flag add `long` or `short`"; + help = "If you really want a boolean parameter \ + add an explicit parser, for example `parse(try_from_str)`"; + note = "see also https://github.com/TeXitoi/structopt/tree/master/examples/true_or_false.rs"; + ) + } + if let Some(m) = res.find_method("default_value") { + abort!(m.name.span(), "default_value is meaningless for bool") + } + if let Some(m) = res.find_method("required") { + abort!(m.name.span(), "required is meaningless for bool") + } + } + Ty::Option => { + if let Some(m) = res.find_method("default_value") { + abort!(m.name.span(), "default_value is meaningless for Option") + } + if let Some(m) = res.find_method("required") { + abort!(m.name.span(), "required is meaningless for Option") + } + } + Ty::OptionOption => { + if res.is_positional() { + abort!( + ty.span(), + "Option> type is meaningless for positional argument" + ) + } + } + Ty::OptionVec => { + if res.is_positional() { + abort!( + ty.span(), + "Option> type is meaningless for positional argument" + ) + } + } + + _ => (), + } + res.kind = Sp::new(Kind::Arg(ty), orig_ty.span()); + } + } + + res + } + + fn set_kind(&mut self, kind: Sp) { + if let Kind::Arg(_) = *self.kind { + self.kind = kind; + } else { + abort!( + kind.span(), + "subcommand, flatten and skip cannot be used together" + ); + } + } + + pub fn has_method(&self, name: &str) -> bool { + self.find_method(name).is_some() + } + + pub fn find_method(&self, name: &str) -> Option<&Method> { + self.methods.iter().find(|m| m.name == name) + } + + /// generate methods from attributes on top of struct or enum + pub fn top_level_methods(&self) -> TokenStream { + let version = match (&self.no_version, &self.version) { + (Some(no_version), Some(_)) => abort!( + no_version.span(), + "`no_version` and `version = \"version\"` can't be used together" + ), + + (None, Some(m)) => m.to_token_stream(), + + (None, None) => std::env::var("CARGO_PKG_VERSION") + .map(|version| quote!( .version(#version) )) + .unwrap_or_default(), + + (Some(_), None) => quote!(), + }; + + let author = &self.author; + let about = &self.about; + let methods = &self.methods; + let doc_comment = &self.doc_comment; + + quote!( #(#doc_comment)* #author #version #about #(#methods)* ) + } + + /// generate methods on top of a field + pub fn field_methods(&self) -> TokenStream { + let methods = &self.methods; + let doc_comment = &self.doc_comment; + quote!( #(#doc_comment)* #(#methods)* ) + } + + pub fn cased_name(&self) -> LitStr { + self.name.clone().translate(*self.casing) + } + + pub fn parser(&self) -> &Sp { + &self.parser + } + + pub fn kind(&self) -> Sp { + self.kind.clone() + } + + pub fn casing(&self) -> Sp { + self.casing.clone() + } + + pub fn env_casing(&self) -> Sp { + self.env_casing.clone() + } + + pub fn is_positional(&self) -> bool { + self.methods + .iter() + .all(|m| m.name != "long" && m.name != "short") + } + + pub fn has_explicit_methods(&self) -> bool { + self.methods + .iter() + .any(|m| m.name != "help" && m.name != "long_help") + } + + pub fn has_doc_methods(&self) -> bool { + !self.doc_comment.is_empty() + || self.methods.iter().any(|m| { + m.name == "help" + || m.name == "long_help" + || m.name == "about" + || m.name == "long_about" + }) + } +} + +/// replace all `:` with `, ` when not inside the `<>` +/// +/// `"author1:author2:author3" => "author1, author2, author3"` +/// `"author1 :author2" => "author1 , author2" +fn process_author_str(author: &str) -> String { + let mut res = String::with_capacity(author.len()); + let mut inside_angle_braces = 0usize; + + for ch in author.chars() { + if inside_angle_braces > 0 && ch == '>' { + inside_angle_braces -= 1; + res.push(ch); + } else if ch == '<' { + inside_angle_braces += 1; + res.push(ch); + } else if inside_angle_braces == 0 && ch == ':' { + res.push_str(", "); + } else { + res.push(ch); + } + } + + res +} diff --git a/structopt/structopt-derive/src/doc_comments.rs b/structopt/structopt-derive/src/doc_comments.rs new file mode 100644 index 0000000..06e1b14 --- /dev/null +++ b/structopt/structopt-derive/src/doc_comments.rs @@ -0,0 +1,103 @@ +//! The preprocessing we apply to doc comments. +//! +//! structopt works in terms of "paragraphs". Paragraph is a sequence of +//! non-empty adjacent lines, delimited by sequences of blank (whitespace only) lines. + +use crate::attrs::Method; +use quote::{format_ident, quote}; +use std::iter; + +pub fn process_doc_comment(lines: Vec, name: &str, preprocess: bool) -> Vec { + // multiline comments (`/** ... */`) may have LFs (`\n`) in them, + // we need to split so we could handle the lines correctly + // + // we also need to remove leading and trailing blank lines + let mut lines: Vec<&str> = lines + .iter() + .skip_while(|s| is_blank(s)) + .flat_map(|s| s.split('\n')) + .collect(); + + while let Some(true) = lines.last().map(|s| is_blank(s)) { + lines.pop(); + } + + // remove one leading space no matter what + for line in lines.iter_mut() { + if line.starts_with(' ') { + *line = &line[1..]; + } + } + + if lines.is_empty() { + return vec![]; + } + + let short_name = format_ident!("{}", name); + let long_name = format_ident!("long_{}", name); + + if let Some(first_blank) = lines.iter().position(|s| is_blank(s)) { + let (short, long) = if preprocess { + let paragraphs = split_paragraphs(&lines); + let short = paragraphs[0].clone(); + let long = paragraphs.join("\n\n"); + (remove_period(short), long) + } else { + let short = lines[..first_blank].join("\n"); + let long = lines.join("\n"); + (short, long) + }; + + vec![ + Method::new(short_name, quote!(#short)), + Method::new(long_name, quote!(#long)), + ] + } else { + let short = if preprocess { + let s = merge_lines(&lines); + remove_period(s) + } else { + lines.join("\n") + }; + + vec![Method::new(short_name, quote!(#short))] + } +} + +fn split_paragraphs(lines: &[&str]) -> Vec { + let mut last_line = 0; + iter::from_fn(|| { + let slice = &lines[last_line..]; + let start = slice.iter().position(|s| !is_blank(s)).unwrap_or(0); + + let slice = &slice[start..]; + let len = slice + .iter() + .position(|s| is_blank(s)) + .unwrap_or(slice.len()); + + last_line += start + len; + + if len != 0 { + Some(merge_lines(&slice[..len])) + } else { + None + } + }) + .collect() +} + +fn remove_period(mut s: String) -> String { + if s.ends_with('.') && !s.ends_with("..") { + s.pop(); + } + s +} + +fn is_blank(s: &str) -> bool { + s.trim().is_empty() +} + +fn merge_lines(lines: &[&str]) -> String { + lines.iter().map(|s| s.trim()).collect::>().join(" ") +} diff --git a/structopt/structopt-derive/src/lib.rs b/structopt/structopt-derive/src/lib.rs new file mode 100644 index 0000000..87eaf1f --- /dev/null +++ b/structopt/structopt-derive/src/lib.rs @@ -0,0 +1,667 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! This crate is custom derive for `StructOpt`. It should not be used +//! directly. See [structopt documentation](https://docs.rs/structopt) +//! for the usage of `#[derive(StructOpt)]`. + +#![allow(clippy::large_enum_variant)] + +extern crate proc_macro; + +mod attrs; +mod doc_comments; +mod parse; +mod spanned; +mod ty; + +use crate::{ + attrs::{Attrs, CasingStyle, Kind, Name, ParserKind}, + spanned::Sp, + ty::{sub_type, Ty}, +}; + +use proc_macro2::{Span, TokenStream}; +use proc_macro_error::{abort, abort_call_site, proc_macro_error, set_dummy}; +use quote::{quote, quote_spanned}; +use syn::{punctuated::Punctuated, spanned::Spanned, token::Comma, *}; + +/// Default casing style for generated arguments. +const DEFAULT_CASING: CasingStyle = CasingStyle::Kebab; + +/// Default casing style for environment variables +const DEFAULT_ENV_CASING: CasingStyle = CasingStyle::ScreamingSnake; + +/// Output for the `gen_xxx()` methods were we need more than a simple stream of tokens. +/// +/// The output of a generation method is not only the stream of new tokens but also the attribute +/// information of the current element. These attribute information may contain valuable information +/// for any kind of child arguments. +struct GenOutput { + tokens: TokenStream, + attrs: Attrs, +} + +/// Generates the `StructOpt` impl. +#[proc_macro_derive(StructOpt, attributes(structopt))] +#[proc_macro_error] +pub fn structopt(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input: DeriveInput = syn::parse(input).unwrap(); + let gen = impl_structopt(&input); + gen.into() +} + +/// Generate a block of code to add arguments/subcommands corresponding to +/// the `fields` to an app. +fn gen_augmentation( + fields: &Punctuated, + app_var: &Ident, + parent_attribute: &Attrs, +) -> TokenStream { + let mut subcmds = fields.iter().filter_map(|field| { + let attrs = Attrs::from_field( + field, + parent_attribute.casing(), + parent_attribute.env_casing(), + ); + let kind = attrs.kind(); + if let Kind::Subcommand(ty) = &*kind { + let subcmd_type = match (**ty, sub_type(&field.ty)) { + (Ty::Option, Some(sub_type)) => sub_type, + _ => &field.ty, + }; + let required = if **ty == Ty::Option { + quote!() + } else { + quote_spanned! { kind.span()=> + let #app_var = #app_var.setting( + ::structopt::clap::AppSettings::SubcommandRequiredElseHelp + ); + } + }; + + let span = field.span(); + let ts = quote! { + let #app_var = <#subcmd_type as ::structopt::StructOptInternal>::augment_clap( + #app_var + ); + #required + }; + Some((span, ts)) + } else { + None + } + }); + + let subcmd = subcmds.next().map(|(_, ts)| ts); + if let Some((span, _)) = subcmds.next() { + abort!( + span, + "multiple subcommand sets are not allowed, that's the second" + ); + } + + let args = fields.iter().filter_map(|field| { + let attrs = Attrs::from_field( + field, + parent_attribute.casing(), + parent_attribute.env_casing(), + ); + let kind = attrs.kind(); + match &*kind { + Kind::Subcommand(_) | Kind::Skip(_) => None, + Kind::FlattenStruct => { + let ty = &field.ty; + Some(quote_spanned! { kind.span()=> + let #app_var = <#ty as ::structopt::StructOptInternal>::augment_clap(#app_var); + let #app_var = if <#ty as ::structopt::StructOptInternal>::is_subcommand() { + #app_var.setting(::structopt::clap::AppSettings::SubcommandRequiredElseHelp) + } else { + #app_var + }; + }) + } + Kind::Arg(ty) => { + let convert_type = match **ty { + Ty::Vec | Ty::Option => sub_type(&field.ty).unwrap_or(&field.ty), + Ty::OptionOption | Ty::OptionVec => { + sub_type(&field.ty).and_then(sub_type).unwrap_or(&field.ty) + } + _ => &field.ty, + }; + + let occurrences = *attrs.parser().kind == ParserKind::FromOccurrences; + let flag = *attrs.parser().kind == ParserKind::FromFlag; + + let parser = attrs.parser(); + let func = &parser.func; + let validator = match *parser.kind { + ParserKind::TryFromStr => quote_spanned! { func.span()=> + .validator(|s| { + #func(s.as_str()) + .map(|_: #convert_type| ()) + .map_err(|e| e.to_string()) + }) + }, + ParserKind::TryFromOsStr => quote_spanned! { func.span()=> + .validator_os(|s| #func(&s).map(|_: #convert_type| ())) + }, + _ => quote!(), + }; + + let modifier = match **ty { + Ty::Bool => quote_spanned! { ty.span()=> + .takes_value(false) + .multiple(false) + }, + + Ty::Option => quote_spanned! { ty.span()=> + .takes_value(true) + .multiple(false) + #validator + }, + + Ty::OptionOption => quote_spanned! { ty.span()=> + .takes_value(true) + .multiple(false) + .min_values(0) + .max_values(1) + #validator + }, + + Ty::OptionVec => quote_spanned! { ty.span()=> + .takes_value(true) + .multiple(true) + .min_values(0) + #validator + }, + + Ty::Vec => quote_spanned! { ty.span()=> + .takes_value(true) + .multiple(true) + #validator + }, + + Ty::Other if occurrences => quote_spanned! { ty.span()=> + .takes_value(false) + .multiple(true) + }, + + Ty::Other if flag => quote_spanned! { ty.span()=> + .takes_value(false) + .multiple(false) + }, + + Ty::Other => { + let required = !attrs.has_method("default_value"); + quote_spanned! { ty.span()=> + .takes_value(true) + .multiple(false) + .required(#required) + #validator + } + } + }; + + let name = attrs.cased_name(); + let methods = attrs.field_methods(); + + Some(quote_spanned! { field.span()=> + let #app_var = #app_var.arg( + ::structopt::clap::Arg::with_name(#name) + #modifier + #methods + ); + }) + } + } + }); + + let app_methods = parent_attribute.top_level_methods(); + quote! {{ + let #app_var = #app_var#app_methods; + #( #args )* + #subcmd + #app_var + }} +} + +fn gen_constructor(fields: &Punctuated, parent_attribute: &Attrs) -> TokenStream { + let fields = fields.iter().map(|field| { + let attrs = Attrs::from_field( + field, + parent_attribute.casing(), + parent_attribute.env_casing(), + ); + let field_name = field.ident.as_ref().unwrap(); + let kind = attrs.kind(); + match &*kind { + Kind::Subcommand(ty) => { + let subcmd_type = match (**ty, sub_type(&field.ty)) { + (Ty::Option, Some(sub_type)) => sub_type, + _ => &field.ty, + }; + let unwrapper = match **ty { + Ty::Option => quote!(), + _ => quote_spanned!( ty.span()=> .unwrap() ), + }; + quote_spanned! { kind.span()=> + #field_name: <#subcmd_type as ::structopt::StructOptInternal>::from_subcommand( + matches.subcommand()) + #unwrapper + } + } + + Kind::FlattenStruct => quote_spanned! { kind.span()=> + #field_name: ::structopt::StructOpt::from_clap(matches) + }, + + Kind::Skip(val) => match val { + None => quote_spanned!(kind.span()=> #field_name: Default::default()), + Some(val) => quote_spanned!(kind.span()=> #field_name: (#val).into()), + }, + + Kind::Arg(ty) => { + use crate::attrs::ParserKind::*; + + let parser = attrs.parser(); + let func = &parser.func; + let span = parser.kind.span(); + let (value_of, values_of, parse) = match *parser.kind { + FromStr => ( + quote_spanned!(span=> value_of), + quote_spanned!(span=> values_of), + func.clone(), + ), + TryFromStr => ( + quote_spanned!(span=> value_of), + quote_spanned!(span=> values_of), + quote_spanned!(func.span()=> |s| #func(s).unwrap()), + ), + FromOsStr => ( + quote_spanned!(span=> value_of_os), + quote_spanned!(span=> values_of_os), + func.clone(), + ), + TryFromOsStr => ( + quote_spanned!(span=> value_of_os), + quote_spanned!(span=> values_of_os), + quote_spanned!(func.span()=> |s| #func(s).unwrap()), + ), + FromOccurrences => ( + quote_spanned!(span=> occurrences_of), + quote!(), + func.clone(), + ), + FromFlag => (quote!(), quote!(), func.clone()), + }; + + let flag = *attrs.parser().kind == ParserKind::FromFlag; + let occurrences = *attrs.parser().kind == ParserKind::FromOccurrences; + let name = attrs.cased_name(); + let field_value = match **ty { + Ty::Bool => quote_spanned!(ty.span()=> matches.is_present(#name)), + + Ty::Option => quote_spanned! { ty.span()=> + matches.#value_of(#name) + .map(#parse) + }, + + Ty::OptionOption => quote_spanned! { ty.span()=> + if matches.is_present(#name) { + Some(matches.#value_of(#name).map(#parse)) + } else { + None + } + }, + + Ty::OptionVec => quote_spanned! { ty.span()=> + if matches.is_present(#name) { + Some(matches.#values_of(#name) + .map_or_else(Vec::new, |v| v.map(#parse).collect())) + } else { + None + } + }, + + Ty::Vec => quote_spanned! { ty.span()=> + matches.#values_of(#name) + .map_or_else(Vec::new, |v| v.map(#parse).collect()) + }, + + Ty::Other if occurrences => quote_spanned! { ty.span()=> + #parse(matches.#value_of(#name)) + }, + + Ty::Other if flag => quote_spanned! { ty.span()=> + #parse(matches.is_present(#name)) + }, + + Ty::Other => quote_spanned! { ty.span()=> + matches.#value_of(#name) + .map(#parse) + .unwrap() + }, + }; + + quote_spanned!(field.span()=> #field_name: #field_value ) + } + } + }); + + quote! {{ + #( #fields ),* + }} +} + +fn gen_from_clap( + struct_name: &Ident, + fields: &Punctuated, + parent_attribute: &Attrs, +) -> TokenStream { + let field_block = gen_constructor(fields, parent_attribute); + + quote! { + fn from_clap(matches: &::structopt::clap::ArgMatches) -> Self { + #struct_name #field_block + } + } +} + +fn gen_clap(attrs: &[Attribute]) -> GenOutput { + let name = std::env::var("CARGO_PKG_NAME").ok().unwrap_or_default(); + + let attrs = Attrs::from_struct( + Span::call_site(), + attrs, + Name::Assigned(LitStr::new(&name, Span::call_site())), + Sp::call_site(DEFAULT_CASING), + Sp::call_site(DEFAULT_ENV_CASING), + ); + let tokens = { + let name = attrs.cased_name(); + quote!(::structopt::clap::App::new(#name)) + }; + + GenOutput { tokens, attrs } +} + +fn gen_clap_struct(struct_attrs: &[Attribute]) -> GenOutput { + let initial_clap_app_gen = gen_clap(struct_attrs); + let clap_tokens = initial_clap_app_gen.tokens; + + let augmented_tokens = quote! { + fn clap<'a, 'b>() -> ::structopt::clap::App<'a, 'b> { + let app = #clap_tokens; + ::augment_clap(app) + } + }; + + GenOutput { + tokens: augmented_tokens, + attrs: initial_clap_app_gen.attrs, + } +} + +fn gen_augment_clap(fields: &Punctuated, parent_attribute: &Attrs) -> TokenStream { + let app_var = Ident::new("app", Span::call_site()); + let augmentation = gen_augmentation(fields, &app_var, parent_attribute); + quote! { + fn augment_clap<'a, 'b>( + #app_var: ::structopt::clap::App<'a, 'b> + ) -> ::structopt::clap::App<'a, 'b> { + #augmentation + } + } +} + +fn gen_clap_enum(enum_attrs: &[Attribute]) -> GenOutput { + let initial_clap_app_gen = gen_clap(enum_attrs); + let clap_tokens = initial_clap_app_gen.tokens; + + let tokens = quote! { + fn clap<'a, 'b>() -> ::structopt::clap::App<'a, 'b> { + let app = #clap_tokens + .setting(::structopt::clap::AppSettings::SubcommandRequiredElseHelp); + ::augment_clap(app) + } + }; + + GenOutput { + tokens, + attrs: initial_clap_app_gen.attrs, + } +} + +fn gen_augment_clap_enum( + variants: &Punctuated, + parent_attribute: &Attrs, +) -> TokenStream { + use syn::Fields::*; + + let subcommands = variants.iter().map(|variant| { + let attrs = Attrs::from_struct( + variant.span(), + &variant.attrs, + Name::Derived(variant.ident.clone()), + parent_attribute.casing(), + parent_attribute.env_casing(), + ); + let app_var = Ident::new("subcommand", Span::call_site()); + let arg_block = match variant.fields { + Named(ref fields) => gen_augmentation(&fields.named, &app_var, &attrs), + Unit => quote!( #app_var ), + Unnamed(FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => { + let ty = &unnamed[0]; + quote_spanned! { ty.span()=> + { + let #app_var = <#ty as ::structopt::StructOptInternal>::augment_clap( + #app_var + ); + if <#ty as ::structopt::StructOptInternal>::is_subcommand() { + #app_var.setting( + ::structopt::clap::AppSettings::SubcommandRequiredElseHelp + ) + } else { + #app_var + } + } + } + } + Unnamed(..) => abort_call_site!("{}: tuple enums are not supported", variant.ident), + }; + + let name = attrs.cased_name(); + let from_attrs = attrs.top_level_methods(); + + quote! { + .subcommand({ + let #app_var = ::structopt::clap::SubCommand::with_name(#name); + let #app_var = #arg_block; + #app_var#from_attrs + }) + } + }); + + let app_methods = parent_attribute.top_level_methods(); + + quote! { + fn augment_clap<'a, 'b>( + app: ::structopt::clap::App<'a, 'b> + ) -> ::structopt::clap::App<'a, 'b> { + app #app_methods #( #subcommands )* + } + } +} + +fn gen_from_clap_enum(name: &Ident) -> TokenStream { + quote! { + fn from_clap(matches: &::structopt::clap::ArgMatches) -> Self { + <#name as ::structopt::StructOptInternal>::from_subcommand(matches.subcommand()) + .unwrap() + } + } +} + +fn gen_from_subcommand( + name: &Ident, + variants: &Punctuated, + parent_attribute: &Attrs, +) -> TokenStream { + use syn::Fields::*; + + let match_arms = variants.iter().map(|variant| { + let attrs = Attrs::from_struct( + variant.span(), + &variant.attrs, + Name::Derived(variant.ident.clone()), + parent_attribute.casing(), + parent_attribute.env_casing(), + ); + let sub_name = attrs.cased_name(); + let variant_name = &variant.ident; + let constructor_block = match variant.fields { + Named(ref fields) => gen_constructor(&fields.named, &attrs), + Unit => quote!(), + Unnamed(ref fields) if fields.unnamed.len() == 1 => { + let ty = &fields.unnamed[0]; + quote!( ( <#ty as ::structopt::StructOpt>::from_clap(matches) ) ) + } + Unnamed(..) => abort_call_site!("{}: tuple enums are not supported", variant.ident), + }; + + quote! { + (#sub_name, Some(matches)) => + Some(#name :: #variant_name #constructor_block) + } + }); + + quote! { + fn from_subcommand<'a, 'b>( + sub: (&'b str, Option<&'b ::structopt::clap::ArgMatches<'a>>) + ) -> Option { + match sub { + #( #match_arms ),*, + _ => None + } + } + } +} + +#[cfg(feature = "paw")] +fn gen_paw_impl(name: &Ident) -> TokenStream { + quote! { + impl paw::ParseArgs for #name { + type Error = std::io::Error; + + fn parse_args() -> std::result::Result { + Ok(<#name as ::structopt::StructOpt>::from_args()) + } + } + } +} +#[cfg(not(feature = "paw"))] +fn gen_paw_impl(_: &Ident) -> TokenStream { + TokenStream::new() +} + +fn impl_structopt_for_struct( + name: &Ident, + fields: &Punctuated, + attrs: &[Attribute], +) -> TokenStream { + let basic_clap_app_gen = gen_clap_struct(attrs); + let augment_clap = gen_augment_clap(fields, &basic_clap_app_gen.attrs); + let from_clap = gen_from_clap(name, fields, &basic_clap_app_gen.attrs); + let paw_impl = gen_paw_impl(name); + + let clap_tokens = basic_clap_app_gen.tokens; + quote! { + #[allow(unused_variables)] + #[allow(unknown_lints)] + #[allow(clippy::all)] + #[allow(dead_code, unreachable_code)] + impl ::structopt::StructOpt for #name { + #clap_tokens + #from_clap + } + + #[allow(unused_variables)] + #[allow(unknown_lints)] + #[allow(clippy::all)] + #[allow(dead_code, unreachable_code)] + impl ::structopt::StructOptInternal for #name { + #augment_clap + fn is_subcommand() -> bool { false } + } + + #paw_impl + } +} + +fn impl_structopt_for_enum( + name: &Ident, + variants: &Punctuated, + attrs: &[Attribute], +) -> TokenStream { + let basic_clap_app_gen = gen_clap_enum(attrs); + + let augment_clap = gen_augment_clap_enum(variants, &basic_clap_app_gen.attrs); + let from_clap = gen_from_clap_enum(name); + let from_subcommand = gen_from_subcommand(name, variants, &basic_clap_app_gen.attrs); + let paw_impl = gen_paw_impl(name); + + let clap_tokens = basic_clap_app_gen.tokens; + quote! { + #[allow(unknown_lints)] + #[allow(unused_variables, dead_code, unreachable_code)] + #[allow(clippy)] + impl ::structopt::StructOpt for #name { + #clap_tokens + #from_clap + } + + #[allow(unused_variables)] + #[allow(unknown_lints)] + #[allow(clippy::all)] + #[allow(dead_code, unreachable_code)] + impl ::structopt::StructOptInternal for #name { + #augment_clap + #from_subcommand + fn is_subcommand() -> bool { true } + } + + #paw_impl + } +} + +fn impl_structopt(input: &DeriveInput) -> TokenStream { + use syn::Data::*; + + let struct_name = &input.ident; + + set_dummy(quote! { + impl ::structopt::StructOpt for #struct_name { + fn clap<'a, 'b>() -> ::structopt::clap::App<'a, 'b> { + unimplemented!() + } + fn from_clap(_matches: &::structopt::clap::ArgMatches) -> Self { + unimplemented!() + } + } + }); + + match input.data { + Struct(DataStruct { + fields: syn::Fields::Named(ref fields), + .. + }) => impl_structopt_for_struct(struct_name, &fields.named, &input.attrs), + Enum(ref e) => impl_structopt_for_enum(struct_name, &e.variants, &input.attrs), + _ => abort_call_site!("structopt only supports non-tuple structs and enums"), + } +} diff --git a/structopt/structopt-derive/src/parse.rs b/structopt/structopt-derive/src/parse.rs new file mode 100644 index 0000000..a704742 --- /dev/null +++ b/structopt/structopt-derive/src/parse.rs @@ -0,0 +1,304 @@ +use std::iter::FromIterator; + +use proc_macro_error::{abort, ResultExt}; +use quote::ToTokens; +use syn::{ + self, parenthesized, + parse::{Parse, ParseBuffer, ParseStream}, + parse2, + punctuated::Punctuated, + spanned::Spanned, + Attribute, Expr, ExprLit, Ident, Lit, LitBool, LitStr, Token, +}; + +pub struct StructOptAttributes { + pub paren_token: syn::token::Paren, + pub attrs: Punctuated, +} + +impl Parse for StructOptAttributes { + fn parse(input: ParseStream<'_>) -> syn::Result { + let content; + let paren_token = parenthesized!(content in input); + let attrs = content.parse_terminated(StructOptAttr::parse)?; + + Ok(StructOptAttributes { paren_token, attrs }) + } +} + +pub enum StructOptAttr { + // single-identifier attributes + Short(Ident), + Long(Ident), + Env(Ident), + Flatten(Ident), + Subcommand(Ident), + NoVersion(Ident), + VerbatimDocComment(Ident), + + // ident [= "string literal"] + About(Ident, Option), + Author(Ident, Option), + + // ident = "string literal" + Version(Ident, LitStr), + RenameAllEnv(Ident, LitStr), + RenameAll(Ident, LitStr), + NameLitStr(Ident, LitStr), + + // parse(parser_kind [= parser_func]) + Parse(Ident, ParserSpec), + + // ident [= arbitrary_expr] + Skip(Ident, Option), + + // ident = arbitrary_expr + NameExpr(Ident, Expr), + + // ident(arbitrary_expr,*) + MethodCall(Ident, Vec), +} + +impl Parse for StructOptAttr { + fn parse(input: ParseStream<'_>) -> syn::Result { + use self::StructOptAttr::*; + + let name: Ident = input.parse()?; + let name_str = name.to_string(); + + if input.peek(Token![=]) { + // `name = value` attributes. + let assign_token = input.parse::()?; // skip '=' + + if input.peek(LitStr) { + let lit: LitStr = input.parse()?; + let lit_str = lit.value(); + + let check_empty_lit = |s| { + if lit_str.is_empty() { + abort!( + lit.span(), + "`#[structopt({} = \"\")]` is deprecated in structopt 0.3, \ + now it's default behavior", + s + ); + } + }; + + match &*name_str.to_string() { + "rename_all" => Ok(RenameAll(name, lit)), + "rename_all_env" => Ok(RenameAllEnv(name, lit)), + + "version" => { + check_empty_lit("version"); + Ok(Version(name, lit)) + } + + "author" => { + check_empty_lit("author"); + Ok(Author(name, Some(lit))) + } + + "about" => { + check_empty_lit("about"); + Ok(About(name, Some(lit))) + } + + "skip" => { + let expr = ExprLit { + attrs: vec![], + lit: Lit::Str(lit), + }; + let expr = Expr::Lit(expr); + Ok(Skip(name, Some(expr))) + } + + _ => Ok(NameLitStr(name, lit)), + } + } else { + match input.parse::() { + Ok(expr) => { + if name_str == "skip" { + Ok(Skip(name, Some(expr))) + } else { + Ok(NameExpr(name, expr)) + } + } + + Err(_) => abort! { + assign_token.span(), + "expected `string literal` or `expression` after `=`" + }, + } + } + } else if input.peek(syn::token::Paren) { + // `name(...)` attributes. + let nested; + parenthesized!(nested in input); + + match name_str.as_ref() { + "parse" => { + let parser_specs: Punctuated = + nested.parse_terminated(ParserSpec::parse)?; + + if parser_specs.len() == 1 { + Ok(Parse(name, parser_specs[0].clone())) + } else { + abort!(name.span(), "parse must have exactly one argument") + } + } + + "raw" => match nested.parse::() { + Ok(bool_token) => { + let expr = ExprLit { + attrs: vec![], + lit: Lit::Bool(bool_token), + }; + let expr = Expr::Lit(expr); + Ok(MethodCall(name, vec![expr])) + } + + Err(_) => { + abort!(name.span(), + "`#[structopt(raw(...))` attributes are removed in structopt 0.3, \ + they are replaced with raw methods"; + help = "if you meant to call `clap::Arg::raw()` method \ + you should use bool literal, like `raw(true)` or `raw(false)`"; + note = raw_method_suggestion(nested); + ); + } + }, + + _ => { + let method_args: Punctuated<_, Token![,]> = + nested.parse_terminated(Expr::parse)?; + Ok(MethodCall(name, Vec::from_iter(method_args))) + } + } + } else { + // Attributes represented with a sole identifier. + match name_str.as_ref() { + "long" => Ok(Long(name)), + "short" => Ok(Short(name)), + "env" => Ok(Env(name)), + "flatten" => Ok(Flatten(name)), + "subcommand" => Ok(Subcommand(name)), + "no_version" => Ok(NoVersion(name)), + "verbatim_doc_comment" => Ok(VerbatimDocComment(name)), + + "about" => (Ok(About(name, None))), + "author" => (Ok(Author(name, None))), + + "skip" => Ok(Skip(name, None)), + + "version" => abort!( + name.span(), + "#[structopt(version)] is invalid attribute, \ + structopt 0.3 inherits version from Cargo.toml by default, \ + no attribute needed" + ), + + _ => abort!(name.span(), "unexpected attribute: {}", name_str), + } + } + } +} + +#[derive(Clone)] +pub struct ParserSpec { + pub kind: Ident, + pub eq_token: Option, + pub parse_func: Option, +} + +impl Parse for ParserSpec { + fn parse(input: ParseStream<'_>) -> syn::Result { + let kind = input + .parse() + .map_err(|_| input.error("parser specification must start with identifier"))?; + let eq_token = input.parse()?; + let parse_func = match eq_token { + None => None, + Some(_) => Some(input.parse()?), + }; + Ok(ParserSpec { + kind, + eq_token, + parse_func, + }) + } +} + +struct CommaSeparated(Punctuated); + +impl Parse for CommaSeparated { + fn parse(input: ParseStream<'_>) -> syn::Result { + let res = Punctuated::parse_separated_nonempty(input)?; + Ok(CommaSeparated(res)) + } +} + +fn raw_method_suggestion(ts: ParseBuffer) -> String { + let do_parse = move || -> Result<(Ident, CommaSeparated), syn::Error> { + let name = ts.parse()?; + let _eq: Token![=] = ts.parse()?; + let val: LitStr = ts.parse()?; + Ok((name, syn::parse_str(&val.value())?)) + }; + + fn to_string(val: &T) -> String { + val.to_token_stream() + .to_string() + .replace(" ", "") + .replace(",", ", ") + } + + if let Ok((name, val)) = do_parse() { + let exprs = val.0; + let suggestion = if exprs.len() == 1 { + let val = to_string(&exprs[0]); + format!(" = {}", val) + } else { + let val = exprs + .into_iter() + .map(|expr| to_string(&expr)) + .collect::>() + .join(", "); + + format!("({})", val) + }; + + format!( + "if you need to call `clap::Arg/App::{}` method you \ + can do it like this: #[structopt({}{})]", + name, name, suggestion + ) + } else { + "if you need to call some method from `clap::Arg/App` \ + you should use raw method, see \ + https://docs.rs/structopt/0.3/structopt/#raw-methods" + .into() + } +} + +pub fn parse_structopt_attributes(all_attrs: &[Attribute]) -> Vec { + all_attrs + .iter() + .filter(|attr| attr.path.is_ident("structopt")) + .flat_map(|attr| { + let attrs: StructOptAttributes = parse2(attr.tokens.clone()) + .map_err(|e| match &*e.to_string() { + // this error message is misleading and points to Span::call_site() + // so we patch it with something meaningful + "unexpected end of input, expected parentheses" => { + let span = attr.path.span(); + let patch_msg = "expected parentheses after `structopt`"; + syn::Error::new(span, patch_msg) + } + _ => e, + }) + .unwrap_or_abort(); + attrs.attrs + }) + .collect() +} diff --git a/structopt/structopt-derive/src/spanned.rs b/structopt/structopt-derive/src/spanned.rs new file mode 100644 index 0000000..2dd595b --- /dev/null +++ b/structopt/structopt-derive/src/spanned.rs @@ -0,0 +1,101 @@ +use proc_macro2::{Ident, Span, TokenStream}; +use quote::ToTokens; +use std::ops::{Deref, DerefMut}; +use syn::LitStr; + +/// An entity with a span attached. +#[derive(Debug, Clone)] +pub struct Sp { + span: Span, + val: T, +} + +impl Sp { + pub fn new(val: T, span: Span) -> Self { + Sp { val, span } + } + + pub fn call_site(val: T) -> Self { + Sp { + val, + span: Span::call_site(), + } + } + + pub fn span(&self) -> Span { + self.span + } +} + +impl Sp { + pub fn as_ident(&self) -> Ident { + Ident::new(&self.to_string(), self.span) + } + + pub fn as_lit(&self) -> LitStr { + LitStr::new(&self.to_string(), self.span) + } +} + +impl Deref for Sp { + type Target = T; + + fn deref(&self) -> &T { + &self.val + } +} + +impl DerefMut for Sp { + fn deref_mut(&mut self) -> &mut T { + &mut self.val + } +} + +impl From for Sp { + fn from(ident: Ident) -> Self { + Sp { + val: ident.to_string(), + span: ident.span(), + } + } +} + +impl From for Sp { + fn from(lit: LitStr) -> Self { + Sp { + val: lit.value(), + span: lit.span(), + } + } +} + +impl<'a> From> for Sp { + fn from(sp: Sp<&'a str>) -> Self { + Sp::new(sp.val.into(), sp.span) + } +} + +impl> PartialEq for Sp { + fn eq(&self, other: &U) -> bool { + self.val == *other + } +} + +impl> AsRef for Sp { + fn as_ref(&self) -> &str { + self.val.as_ref() + } +} + +impl ToTokens for Sp { + fn to_tokens(&self, stream: &mut TokenStream) { + // this is the simplest way out of correct ones to change span on + // arbitrary token tree I can come up with + let tt = self.val.to_token_stream().into_iter().map(|mut tt| { + tt.set_span(self.span.clone()); + tt + }); + + stream.extend(tt); + } +} diff --git a/structopt/structopt-derive/src/ty.rs b/structopt/structopt-derive/src/ty.rs new file mode 100644 index 0000000..06eb3ec --- /dev/null +++ b/structopt/structopt-derive/src/ty.rs @@ -0,0 +1,108 @@ +//! Special types handling + +use crate::spanned::Sp; + +use syn::{ + spanned::Spanned, GenericArgument, Path, PathArguments, PathArguments::AngleBracketed, + PathSegment, Type, TypePath, +}; + +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum Ty { + Bool, + Vec, + Option, + OptionOption, + OptionVec, + Other, +} + +impl Ty { + pub fn from_syn_ty(ty: &syn::Type) -> Sp { + use Ty::*; + let t = |kind| Sp::new(kind, ty.span()); + + if is_simple_ty(ty, "bool") { + t(Bool) + } else if is_generic_ty(ty, "Vec") { + t(Vec) + } else if let Some(subty) = subty_if_name(ty, "Option") { + if is_generic_ty(subty, "Option") { + t(OptionOption) + } else if is_generic_ty(subty, "Vec") { + t(OptionVec) + } else { + t(Option) + } + } else { + t(Other) + } + } +} + +pub fn sub_type(ty: &syn::Type) -> Option<&syn::Type> { + subty_if(ty, |_| true) +} + +fn only_last_segment(ty: &syn::Type) -> Option<&PathSegment> { + match ty { + Type::Path(TypePath { + qself: None, + path: + Path { + leading_colon: None, + segments, + }, + }) => only_one(segments.iter()), + + _ => None, + } +} + +fn subty_if(ty: &syn::Type, f: F) -> Option<&syn::Type> +where + F: FnOnce(&PathSegment) -> bool, +{ + only_last_segment(ty) + .filter(|segment| f(segment)) + .and_then(|segment| { + if let AngleBracketed(args) = &segment.arguments { + only_one(args.args.iter()).and_then(|genneric| { + if let GenericArgument::Type(ty) = genneric { + Some(ty) + } else { + None + } + }) + } else { + None + } + }) +} + +fn subty_if_name<'a>(ty: &'a syn::Type, name: &str) -> Option<&'a syn::Type> { + subty_if(ty, |seg| seg.ident == name) +} + +fn is_simple_ty(ty: &syn::Type, name: &str) -> bool { + only_last_segment(ty) + .map(|segment| { + if let PathArguments::None = segment.arguments { + segment.ident == name + } else { + false + } + }) + .unwrap_or(false) +} + +fn is_generic_ty(ty: &syn::Type, name: &str) -> bool { + subty_if_name(ty, name).is_some() +} + +fn only_one(mut iter: I) -> Option +where + I: Iterator, +{ + iter.next().filter(|_| iter.next().is_none()) +} diff --git a/structopt/tests/argument_naming.rs b/structopt/tests/argument_naming.rs new file mode 100644 index 0000000..e7fe3d5 --- /dev/null +++ b/structopt/tests/argument_naming.rs @@ -0,0 +1,311 @@ +use structopt::StructOpt; + +#[test] +fn test_single_word_enum_variant_is_default_renamed_into_kebab_case() { + #[derive(StructOpt, Debug, PartialEq)] + enum Opt { + Command { foo: u32 }, + } + + assert_eq!( + Opt::Command { foo: 0 }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "command", "0"])) + ); +} + +#[test] +fn test_multi_word_enum_variant_is_renamed() { + #[derive(StructOpt, Debug, PartialEq)] + enum Opt { + FirstCommand { foo: u32 }, + } + + assert_eq!( + Opt::FirstCommand { foo: 0 }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "first-command", "0"])) + ); +} + +#[test] +fn test_standalone_long_generates_kebab_case() { + #[derive(StructOpt, Debug, PartialEq)] + #[allow(non_snake_case)] + struct Opt { + #[structopt(long)] + FOO_OPTION: bool, + } + + assert_eq!( + Opt { FOO_OPTION: true }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--foo-option"])) + ); +} + +#[test] +fn test_custom_long_overwrites_default_name() { + #[derive(StructOpt, Debug, PartialEq)] + struct Opt { + #[structopt(long = "foo")] + foo_option: bool, + } + + assert_eq!( + Opt { foo_option: true }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--foo"])) + ); +} + +#[test] +fn test_standalone_long_uses_previous_defined_custom_name() { + #[derive(StructOpt, Debug, PartialEq)] + struct Opt { + #[structopt(name = "foo", long)] + foo_option: bool, + } + + assert_eq!( + Opt { foo_option: true }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--foo"])) + ); +} + +#[test] +fn test_standalone_long_ignores_afterwards_defined_custom_name() { + #[derive(StructOpt, Debug, PartialEq)] + struct Opt { + #[structopt(long, name = "foo")] + foo_option: bool, + } + + assert_eq!( + Opt { foo_option: true }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--foo-option"])) + ); +} + +#[test] +fn test_standalone_short_generates_kebab_case() { + #[derive(StructOpt, Debug, PartialEq)] + #[allow(non_snake_case)] + struct Opt { + #[structopt(short)] + FOO_OPTION: bool, + } + + assert_eq!( + Opt { FOO_OPTION: true }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-f"])) + ); +} + +#[test] +fn test_custom_short_overwrites_default_name() { + #[derive(StructOpt, Debug, PartialEq)] + struct Opt { + #[structopt(short = "o")] + foo_option: bool, + } + + assert_eq!( + Opt { foo_option: true }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-o"])) + ); +} + +#[test] +fn test_standalone_short_uses_previous_defined_custom_name() { + #[derive(StructOpt, Debug, PartialEq)] + struct Opt { + #[structopt(name = "option", short)] + foo_option: bool, + } + + assert_eq!( + Opt { foo_option: true }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-o"])) + ); +} + +#[test] +fn test_standalone_short_ignores_afterwards_defined_custom_name() { + #[derive(StructOpt, Debug, PartialEq)] + struct Opt { + #[structopt(short, name = "option")] + foo_option: bool, + } + + assert_eq!( + Opt { foo_option: true }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-f"])) + ); +} + +#[test] +fn test_standalone_long_uses_previous_defined_casing() { + #[derive(StructOpt, Debug, PartialEq)] + struct Opt { + #[structopt(rename_all = "screaming_snake", long)] + foo_option: bool, + } + + assert_eq!( + Opt { foo_option: true }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--FOO_OPTION"])) + ); +} + +#[test] +fn test_standalone_short_uses_previous_defined_casing() { + #[derive(StructOpt, Debug, PartialEq)] + struct Opt { + #[structopt(rename_all = "screaming_snake", short)] + foo_option: bool, + } + + assert_eq!( + Opt { foo_option: true }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-F"])) + ); +} + +#[test] +fn test_standalone_long_works_with_verbatim_casing() { + #[derive(StructOpt, Debug, PartialEq)] + #[allow(non_snake_case)] + struct Opt { + #[structopt(rename_all = "verbatim", long)] + _fOO_oPtiON: bool, + } + + assert_eq!( + Opt { _fOO_oPtiON: true }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--_fOO_oPtiON"])) + ); +} + +#[test] +fn test_standalone_short_works_with_verbatim_casing() { + #[derive(StructOpt, Debug, PartialEq)] + struct Opt { + #[structopt(rename_all = "verbatim", short)] + _foo: bool, + } + + assert_eq!( + Opt { _foo: true }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-_"])) + ); +} + +#[test] +fn test_rename_all_is_propagated_from_struct_to_fields() { + #[derive(StructOpt, Debug, PartialEq)] + #[structopt(rename_all = "screaming_snake")] + struct Opt { + #[structopt(long)] + foo: bool, + } + + assert_eq!( + Opt { foo: true }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--FOO"])) + ); +} + +#[test] +fn test_rename_all_is_not_propagated_from_struct_into_flattened() { + #[derive(StructOpt, Debug, PartialEq)] + #[structopt(rename_all = "screaming_snake")] + struct Opt { + #[structopt(flatten)] + foo: Foo, + } + + #[derive(StructOpt, Debug, PartialEq)] + struct Foo { + #[structopt(long)] + foo: bool, + } + + assert_eq!( + Opt { + foo: Foo { foo: true } + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--foo"])) + ); +} + +#[test] +fn test_rename_all_is_not_propagated_from_struct_into_subcommand() { + #[derive(StructOpt, Debug, PartialEq)] + #[structopt(rename_all = "screaming_snake")] + struct Opt { + #[structopt(subcommand)] + foo: Foo, + } + + #[derive(StructOpt, Debug, PartialEq)] + enum Foo { + Command { + #[structopt(long)] + foo: bool, + }, + } + + assert_eq!( + Opt { + foo: Foo::Command { foo: true } + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "command", "--foo"])) + ); +} + +#[test] +fn test_rename_all_is_propagated_from_enum_to_variants_and_their_fields() { + #[derive(StructOpt, Debug, PartialEq)] + #[structopt(rename_all = "screaming_snake")] + enum Opt { + FirstVariant, + SecondVariant { + #[structopt(long)] + foo: bool, + }, + } + + assert_eq!( + Opt::FirstVariant, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "FIRST_VARIANT"])) + ); + + assert_eq!( + Opt::SecondVariant { foo: true }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "SECOND_VARIANT", "--FOO"])) + ); +} + +#[test] +fn test_rename_all_is_propagation_can_be_overridden() { + #[derive(StructOpt, Debug, PartialEq)] + #[structopt(rename_all = "screaming_snake")] + enum Opt { + #[structopt(rename_all = "kebab_case")] + FirstVariant { + #[structopt(long)] + foo_option: bool, + }, + SecondVariant { + #[structopt(rename_all = "kebab_case", long)] + foo_option: bool, + }, + } + + assert_eq!( + Opt::FirstVariant { foo_option: true }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "first-variant", "--foo-option"])) + ); + + assert_eq!( + Opt::SecondVariant { foo_option: true }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "SECOND_VARIANT", "--foo-option"])) + ); +} diff --git a/structopt/tests/arguments.rs b/structopt/tests/arguments.rs new file mode 100644 index 0000000..96a0938 --- /dev/null +++ b/structopt/tests/arguments.rs @@ -0,0 +1,86 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::clap; +use structopt::StructOpt; + +#[test] +fn required_argument() { + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + arg: i32, + } + assert_eq!(Opt { arg: 42 }, Opt::from_iter(&["test", "42"])); + assert!(Opt::clap().get_matches_from_safe(&["test"]).is_err()); + assert!(Opt::clap() + .get_matches_from_safe(&["test", "42", "24"]) + .is_err()); +} + +#[test] +fn optional_argument() { + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + arg: Option, + } + assert_eq!(Opt { arg: Some(42) }, Opt::from_iter(&["test", "42"])); + assert_eq!(Opt { arg: None }, Opt::from_iter(&["test"])); + assert!(Opt::clap() + .get_matches_from_safe(&["test", "42", "24"]) + .is_err()); +} + +#[test] +fn argument_with_default() { + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + #[structopt(default_value = "42")] + arg: i32, + } + assert_eq!(Opt { arg: 24 }, Opt::from_iter(&["test", "24"])); + assert_eq!(Opt { arg: 42 }, Opt::from_iter(&["test"])); + assert!(Opt::clap() + .get_matches_from_safe(&["test", "42", "24"]) + .is_err()); +} + +#[test] +fn arguments() { + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + arg: Vec, + } + assert_eq!(Opt { arg: vec![24] }, Opt::from_iter(&["test", "24"])); + assert_eq!(Opt { arg: vec![] }, Opt::from_iter(&["test"])); + assert_eq!( + Opt { arg: vec![24, 42] }, + Opt::from_iter(&["test", "24", "42"]) + ); +} + +#[test] +fn arguments_safe() { + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + arg: Vec, + } + assert_eq!( + Opt { arg: vec![24] }, + Opt::from_iter_safe(&["test", "24"]).unwrap() + ); + assert_eq!(Opt { arg: vec![] }, Opt::from_iter_safe(&["test"]).unwrap()); + assert_eq!( + Opt { arg: vec![24, 42] }, + Opt::from_iter_safe(&["test", "24", "42"]).unwrap() + ); + + assert_eq!( + clap::ErrorKind::ValueValidation, + Opt::from_iter_safe(&["test", "NOPE"]).err().unwrap().kind + ); +} diff --git a/structopt/tests/author_version_about.rs b/structopt/tests/author_version_about.rs new file mode 100644 index 0000000..0c4a4fb --- /dev/null +++ b/structopt/tests/author_version_about.rs @@ -0,0 +1,46 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +mod utils; + +use structopt::StructOpt; +use utils::*; + +#[test] +fn no_author_version_about() { + #[derive(StructOpt, PartialEq, Debug)] + #[structopt(name = "foo", no_version)] + struct Opt {} + + let output = get_long_help::(); + assert!(output.starts_with("foo \n\nUSAGE:")); +} + +#[test] +fn use_env() { + #[derive(StructOpt, PartialEq, Debug)] + #[structopt(author, about)] + struct Opt {} + + let output = get_long_help::(); + assert!(output.starts_with("structopt 0.")); + assert!(output.contains("Guillaume Pinot , others")); + assert!(output.contains("Parse command line argument by defining a struct.")); +} + +#[test] +fn explicit_version_not_str() { + const VERSION: &str = "custom version"; + + #[derive(StructOpt)] + #[structopt(version = VERSION)] + pub struct Opt {} + + let output = get_long_help::(); + assert!(output.contains("custom version")); +} diff --git a/structopt/tests/custom-string-parsers.rs b/structopt/tests/custom-string-parsers.rs new file mode 100644 index 0000000..89070ed --- /dev/null +++ b/structopt/tests/custom-string-parsers.rs @@ -0,0 +1,306 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +use std::ffi::{CString, OsStr, OsString}; +use std::num::ParseIntError; +use std::path::PathBuf; + +#[derive(StructOpt, PartialEq, Debug)] +struct PathOpt { + #[structopt(short, long, parse(from_os_str))] + path: PathBuf, + + #[structopt(short, default_value = "../", parse(from_os_str))] + default_path: PathBuf, + + #[structopt(short, parse(from_os_str))] + vector_path: Vec, + + #[structopt(short, parse(from_os_str))] + option_path_1: Option, + + #[structopt(short = "q", parse(from_os_str))] + option_path_2: Option, +} + +#[test] +fn test_path_opt_simple() { + assert_eq!( + PathOpt { + path: PathBuf::from("/usr/bin"), + default_path: PathBuf::from("../"), + vector_path: vec![ + PathBuf::from("/a/b/c"), + PathBuf::from("/d/e/f"), + PathBuf::from("/g/h/i"), + ], + option_path_1: None, + option_path_2: Some(PathBuf::from("j.zip")), + }, + PathOpt::from_clap(&PathOpt::clap().get_matches_from(&[ + "test", "-p", "/usr/bin", "-v", "/a/b/c", "-v", "/d/e/f", "-v", "/g/h/i", "-q", + "j.zip", + ])) + ); +} + +fn parse_hex(input: &str) -> Result { + u64::from_str_radix(input, 16) +} + +#[derive(StructOpt, PartialEq, Debug)] +struct HexOpt { + #[structopt(short, parse(try_from_str = parse_hex))] + number: u64, +} + +#[test] +#[allow(clippy::unreadable_literal)] +fn test_parse_hex() { + assert_eq!( + HexOpt { number: 5 }, + HexOpt::from_clap(&HexOpt::clap().get_matches_from(&["test", "-n", "5"])) + ); + assert_eq!( + HexOpt { number: 0xabcdef }, + HexOpt::from_clap(&HexOpt::clap().get_matches_from(&["test", "-n", "abcdef"])) + ); + + let err = HexOpt::clap() + .get_matches_from_safe(&["test", "-n", "gg"]) + .unwrap_err(); + assert!(err.message.contains("invalid digit found in string"), err); +} + +fn custom_parser_1(_: &str) -> &'static str { + "A" +} +fn custom_parser_2(_: &str) -> Result<&'static str, u32> { + Ok("B") +} +fn custom_parser_3(_: &OsStr) -> &'static str { + "C" +} +fn custom_parser_4(_: &OsStr) -> Result<&'static str, OsString> { + Ok("D") +} + +#[derive(StructOpt, PartialEq, Debug)] +struct NoOpOpt { + #[structopt(short, parse(from_str = custom_parser_1))] + a: &'static str, + #[structopt(short, parse(try_from_str = custom_parser_2))] + b: &'static str, + #[structopt(short, parse(from_os_str = custom_parser_3))] + c: &'static str, + #[structopt(short, parse(try_from_os_str = custom_parser_4))] + d: &'static str, +} + +#[test] +fn test_every_custom_parser() { + assert_eq!( + NoOpOpt { + a: "A", + b: "B", + c: "C", + d: "D" + }, + NoOpOpt::from_clap( + &NoOpOpt::clap().get_matches_from(&["test", "-a=?", "-b=?", "-c=?", "-d=?"]) + ) + ); +} + +// Note: can't use `Vec` directly, as structopt would instead look for +// conversion function from `&str` to `u8`. +type Bytes = Vec; + +#[derive(StructOpt, PartialEq, Debug)] +struct DefaultedOpt { + #[structopt(short, parse(from_str))] + bytes: Bytes, + + #[structopt(short, parse(try_from_str))] + integer: u64, + + #[structopt(short, parse(from_os_str))] + path: PathBuf, +} + +#[test] +fn test_parser_with_default_value() { + assert_eq!( + DefaultedOpt { + bytes: b"E\xc2\xb2=p\xc2\xb2c\xc2\xb2+m\xc2\xb2c\xe2\x81\xb4".to_vec(), + integer: 9000, + path: PathBuf::from("src/lib.rs"), + }, + DefaultedOpt::from_clap(&DefaultedOpt::clap().get_matches_from(&[ + "test", + "-b", + "E²=p²c²+m²c⁴", + "-i", + "9000", + "-p", + "src/lib.rs", + ])) + ); +} + +#[derive(PartialEq, Debug)] +struct Foo(u8); + +fn foo(value: u64) -> Foo { + Foo(value as u8) +} + +#[derive(StructOpt, PartialEq, Debug)] +struct Occurrences { + #[structopt(short, long, parse(from_occurrences))] + signed: i32, + + #[structopt(short, parse(from_occurrences))] + little_signed: i8, + + #[structopt(short, parse(from_occurrences))] + unsigned: usize, + + #[structopt(short = "r", parse(from_occurrences))] + little_unsigned: u8, + + #[structopt(short, long, parse(from_occurrences = foo))] + custom: Foo, +} + +#[test] +fn test_parser_occurrences() { + assert_eq!( + Occurrences { + signed: 3, + little_signed: 1, + unsigned: 0, + little_unsigned: 4, + custom: Foo(5), + }, + Occurrences::from_clap(&Occurrences::clap().get_matches_from(&[ + "test", "-s", "--signed", "--signed", "-l", "-rrrr", "-cccc", "--custom", + ])) + ); +} + +#[test] +fn test_custom_bool() { + fn parse_bool(s: &str) -> Result { + match s { + "true" => Ok(true), + "false" => Ok(false), + _ => Err(format!("invalid bool {}", s)), + } + } + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + #[structopt(short, parse(try_from_str = parse_bool))] + debug: bool, + #[structopt( + short, + default_value = "false", + parse(try_from_str = parse_bool) + )] + verbose: bool, + #[structopt(short, parse(try_from_str = parse_bool))] + tribool: Option, + #[structopt(short, parse(try_from_str = parse_bool))] + bitset: Vec, + } + + assert!(Opt::clap().get_matches_from_safe(&["test"]).is_err()); + assert!(Opt::clap().get_matches_from_safe(&["test", "-d"]).is_err()); + assert!(Opt::clap() + .get_matches_from_safe(&["test", "-dfoo"]) + .is_err()); + assert_eq!( + Opt { + debug: false, + verbose: false, + tribool: None, + bitset: vec![], + }, + Opt::from_iter(&["test", "-dfalse"]) + ); + assert_eq!( + Opt { + debug: true, + verbose: false, + tribool: None, + bitset: vec![], + }, + Opt::from_iter(&["test", "-dtrue"]) + ); + assert_eq!( + Opt { + debug: true, + verbose: false, + tribool: None, + bitset: vec![], + }, + Opt::from_iter(&["test", "-dtrue", "-vfalse"]) + ); + assert_eq!( + Opt { + debug: true, + verbose: true, + tribool: None, + bitset: vec![], + }, + Opt::from_iter(&["test", "-dtrue", "-vtrue"]) + ); + assert_eq!( + Opt { + debug: true, + verbose: false, + tribool: Some(false), + bitset: vec![], + }, + Opt::from_iter(&["test", "-dtrue", "-tfalse"]) + ); + assert_eq!( + Opt { + debug: true, + verbose: false, + tribool: Some(true), + bitset: vec![], + }, + Opt::from_iter(&["test", "-dtrue", "-ttrue"]) + ); + assert_eq!( + Opt { + debug: true, + verbose: false, + tribool: None, + bitset: vec![false, true, false, false], + }, + Opt::from_iter(&["test", "-dtrue", "-bfalse", "-btrue", "-bfalse", "-bfalse"]) + ); +} + +#[test] +fn test_cstring() { + #[derive(StructOpt)] + struct Opt { + #[structopt(parse(try_from_str = CString::new))] + c_string: CString, + } + assert!(Opt::clap().get_matches_from_safe(&["test"]).is_err()); + assert_eq!(Opt::from_iter(&["test", "bla"]).c_string.to_bytes(), b"bla"); + assert!(Opt::clap() + .get_matches_from_safe(&["test", "bla\0bla"]) + .is_err()); +} diff --git a/structopt/tests/deny-warnings.rs b/structopt/tests/deny-warnings.rs new file mode 100644 index 0000000..721204a --- /dev/null +++ b/structopt/tests/deny-warnings.rs @@ -0,0 +1,47 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![deny(warnings)] + +use structopt::StructOpt; + +fn try_str(s: &str) -> Result { + Ok(s.into()) +} + +#[test] +fn warning_never_struct() { + #[derive(Debug, PartialEq, StructOpt)] + struct Opt { + #[structopt(parse(try_from_str = try_str))] + s: String, + } + assert_eq!( + Opt { + s: "foo".to_string() + }, + Opt::from_iter(&["test", "foo"]) + ); +} + +#[test] +fn warning_never_enum() { + #[derive(Debug, PartialEq, StructOpt)] + enum Opt { + Foo { + #[structopt(parse(try_from_str = try_str))] + s: String, + }, + } + assert_eq!( + Opt::Foo { + s: "foo".to_string() + }, + Opt::from_iter(&["test", "foo", "foo"]) + ); +} diff --git a/structopt/tests/doc-comments-help.rs b/structopt/tests/doc-comments-help.rs new file mode 100644 index 0000000..27649b8 --- /dev/null +++ b/structopt/tests/doc-comments-help.rs @@ -0,0 +1,162 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +mod utils; + +use structopt::StructOpt; +use utils::*; + +#[test] +fn doc_comments() { + /// Lorem ipsum + #[derive(StructOpt, PartialEq, Debug)] + struct LoremIpsum { + /// Fooify a bar + /// and a baz + #[structopt(short, long)] + foo: bool, + } + + let help = get_long_help::(); + assert!(help.contains("Lorem ipsum")); + assert!(help.contains("Fooify a bar and a baz")); +} + +#[test] +fn help_is_better_than_comments() { + /// Lorem ipsum + #[derive(StructOpt, PartialEq, Debug)] + #[structopt(name = "lorem-ipsum", about = "Dolor sit amet")] + struct LoremIpsum { + /// Fooify a bar + #[structopt(short, long, help = "DO NOT PASS A BAR UNDER ANY CIRCUMSTANCES")] + foo: bool, + } + + let help = get_long_help::(); + assert!(help.contains("Dolor sit amet")); + assert!(!help.contains("Lorem ipsum")); + assert!(help.contains("DO NOT PASS A BAR")); +} + +#[test] +fn empty_line_in_doc_comment_is_double_linefeed() { + /// Foo. + /// + /// Bar + #[derive(StructOpt, PartialEq, Debug)] + #[structopt(name = "lorem-ipsum", no_version)] + struct LoremIpsum {} + + let help = get_long_help::(); + assert!(help.starts_with("lorem-ipsum \nFoo.\n\nBar\n\nUSAGE:")); +} + +#[test] +fn field_long_doc_comment_both_help_long_help() { + /// Lorem ipsumclap + #[derive(StructOpt, PartialEq, Debug)] + #[structopt(name = "lorem-ipsum", about = "Dolor sit amet")] + struct LoremIpsum { + /// Dot is removed from multiline comments. + /// + /// Long help + #[structopt(long)] + foo: bool, + + /// Dot is removed from one short comment. + #[structopt(long)] + bar: bool, + } + + let short_help = get_help::(); + let long_help = get_long_help::(); + + assert!(short_help.contains("Dot is removed from one short comment")); + assert!(!short_help.contains("Dot is removed from one short comment.")); + assert!(short_help.contains("Dot is removed from multiline comments")); + assert!(!short_help.contains("Dot is removed from multiline comments.")); + assert!(long_help.contains("Long help")); + assert!(!short_help.contains("Long help")); +} + +#[test] +fn top_long_doc_comment_both_help_long_help() { + /// Lorem ipsumclap + #[derive(StructOpt, Debug)] + #[structopt(name = "lorem-ipsum", about = "Dolor sit amet")] + struct LoremIpsum { + #[structopt(subcommand)] + foo: SubCommand, + } + + #[derive(StructOpt, Debug)] + pub enum SubCommand { + /// DO NOT PASS A BAR UNDER ANY CIRCUMSTANCES + /// + /// Or something else + Foo { + #[structopt(help = "foo")] + bars: Vec, + }, + } + + let short_help = get_help::(); + let long_help = get_subcommand_long_help::("foo"); + + assert!(!short_help.contains("Or something else")); + assert!(long_help.contains("DO NOT PASS A BAR UNDER ANY CIRCUMSTANCES")); + assert!(long_help.contains("Or something else")); +} + +#[test] +fn verbatim_doc_comment() { + /// DANCE! + /// + /// () + /// | + /// ( () ) + /// ) ________ // ) + /// () |\ \ // + /// ( \\__ \ ______\// + /// \__) | | + /// | | | + /// \ | | + /// \|_______| + /// // \\ + /// (( || + /// \\ || + /// ( () || + /// ( () ) ) + #[derive(StructOpt, Debug)] + #[structopt(verbatim_doc_comment)] + struct SeeFigure1 { + #[structopt(long)] + foo: bool, + } + + let help = get_long_help::(); + let sample = r#" + () + | + ( () ) + ) ________ // ) + () |\ \ // +( \\__ \ ______\// + \__) | | + | | | + \ | | + \|_______| + // \\ + (( || + \\ || + ( () || + ( () ) )"#; + + assert!(help.contains(sample)) +} diff --git a/structopt/tests/explicit_name_no_renaming.rs b/structopt/tests/explicit_name_no_renaming.rs new file mode 100644 index 0000000..eff7a86 --- /dev/null +++ b/structopt/tests/explicit_name_no_renaming.rs @@ -0,0 +1,32 @@ +mod utils; + +use structopt::StructOpt; +use utils::*; + +#[test] +fn explicit_short_long_no_rename() { + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + #[structopt(short = ".", long = ".foo")] + foo: Vec, + } + + assert_eq!( + Opt { + foo: vec!["short".into(), "long".into()] + }, + Opt::from_iter(&["test", "-.", "short", "--.foo", "long"]) + ); +} + +#[test] +fn explicit_name_no_rename() { + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + #[structopt(name = ".options")] + foo: Vec, + } + + let help = get_long_help::(); + assert!(help.contains("[.options]...")) +} diff --git a/structopt/tests/flags.rs b/structopt/tests/flags.rs new file mode 100644 index 0000000..39a5dc3 --- /dev/null +++ b/structopt/tests/flags.rs @@ -0,0 +1,162 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[test] +fn unique_flag() { + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + #[structopt(short, long)] + alice: bool, + } + + assert_eq!( + Opt { alice: false }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test"])) + ); + assert_eq!( + Opt { alice: true }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a"])) + ); + assert_eq!( + Opt { alice: true }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--alice"])) + ); + assert!(Opt::clap().get_matches_from_safe(&["test", "-i"]).is_err()); + assert!(Opt::clap() + .get_matches_from_safe(&["test", "-a", "foo"]) + .is_err()); + assert!(Opt::clap() + .get_matches_from_safe(&["test", "-a", "-a"]) + .is_err()); + assert!(Opt::clap() + .get_matches_from_safe(&["test", "-a", "--alice"]) + .is_err()); +} + +#[test] +fn multiple_flag() { + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + #[structopt(short, long, parse(from_occurrences))] + alice: u64, + #[structopt(short, long, parse(from_occurrences))] + bob: u8, + } + + assert_eq!( + Opt { alice: 0, bob: 0 }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test"])) + ); + assert_eq!( + Opt { alice: 1, bob: 0 }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a"])) + ); + assert_eq!( + Opt { alice: 2, bob: 0 }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a", "-a"])) + ); + assert_eq!( + Opt { alice: 2, bob: 2 }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a", "--alice", "-bb"])) + ); + assert_eq!( + Opt { alice: 3, bob: 1 }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-aaa", "--bob"])) + ); + assert!(Opt::clap().get_matches_from_safe(&["test", "-i"]).is_err()); + assert!(Opt::clap() + .get_matches_from_safe(&["test", "-a", "foo"]) + .is_err()); +} + +fn parse_from_flag(b: bool) -> std::sync::atomic::AtomicBool { + std::sync::atomic::AtomicBool::new(b) +} + +#[test] +fn non_bool_flags() { + #[derive(StructOpt, Debug)] + struct Opt { + #[structopt(short, long, parse(from_flag = parse_from_flag))] + alice: std::sync::atomic::AtomicBool, + #[structopt(short, long, parse(from_flag))] + bob: std::sync::atomic::AtomicBool, + } + + let falsey = Opt::from_clap(&Opt::clap().get_matches_from(&["test"])); + assert!(!falsey.alice.load(std::sync::atomic::Ordering::Relaxed)); + assert!(!falsey.bob.load(std::sync::atomic::Ordering::Relaxed)); + + let alice = Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a"])); + assert!(alice.alice.load(std::sync::atomic::Ordering::Relaxed)); + assert!(!alice.bob.load(std::sync::atomic::Ordering::Relaxed)); + + let bob = Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-b"])); + assert!(!bob.alice.load(std::sync::atomic::Ordering::Relaxed)); + assert!(bob.bob.load(std::sync::atomic::Ordering::Relaxed)); + + let both = Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-b", "-a"])); + assert!(both.alice.load(std::sync::atomic::Ordering::Relaxed)); + assert!(both.bob.load(std::sync::atomic::Ordering::Relaxed)); +} + +#[test] +fn combined_flags() { + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + #[structopt(short, long)] + alice: bool, + #[structopt(short, long, parse(from_occurrences))] + bob: u64, + } + + assert_eq!( + Opt { + alice: false, + bob: 0 + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test"])) + ); + assert_eq!( + Opt { + alice: true, + bob: 0 + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a"])) + ); + assert_eq!( + Opt { + alice: true, + bob: 0 + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a"])) + ); + assert_eq!( + Opt { + alice: false, + bob: 1 + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-b"])) + ); + assert_eq!( + Opt { + alice: true, + bob: 1 + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--alice", "--bob"])) + ); + assert_eq!( + Opt { + alice: true, + bob: 4 + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-bb", "-a", "-bb"])) + ); +} diff --git a/structopt/tests/flatten.rs b/structopt/tests/flatten.rs new file mode 100644 index 0000000..4983d86 --- /dev/null +++ b/structopt/tests/flatten.rs @@ -0,0 +1,95 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[test] +fn flatten() { + #[derive(StructOpt, PartialEq, Debug)] + struct Common { + arg: i32, + } + + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + #[structopt(flatten)] + common: Common, + } + assert_eq!( + Opt { + common: Common { arg: 42 } + }, + Opt::from_iter(&["test", "42"]) + ); + assert!(Opt::clap().get_matches_from_safe(&["test"]).is_err()); + assert!(Opt::clap() + .get_matches_from_safe(&["test", "42", "24"]) + .is_err()); +} + +#[test] +#[should_panic] +fn flatten_twice() { + #[derive(StructOpt, PartialEq, Debug)] + struct Common { + arg: i32, + } + + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + #[structopt(flatten)] + c1: Common, + // Defines "arg" twice, so this should not work. + #[structopt(flatten)] + c2: Common, + } + Opt::from_iter(&["test", "42", "43"]); +} + +#[test] +fn flatten_in_subcommand() { + #[derive(StructOpt, PartialEq, Debug)] + struct Common { + arg: i32, + } + + #[derive(StructOpt, PartialEq, Debug)] + struct Add { + #[structopt(short)] + interactive: bool, + #[structopt(flatten)] + common: Common, + } + + #[derive(StructOpt, PartialEq, Debug)] + enum Opt { + Fetch { + #[structopt(short)] + all: bool, + #[structopt(flatten)] + common: Common, + }, + + Add(Add), + } + + assert_eq!( + Opt::Fetch { + all: false, + common: Common { arg: 42 } + }, + Opt::from_iter(&["test", "fetch", "42"]) + ); + assert_eq!( + Opt::Add(Add { + interactive: true, + common: Common { arg: 43 } + }), + Opt::from_iter(&["test", "add", "-i", "43"]) + ); +} diff --git a/structopt/tests/issues.rs b/structopt/tests/issues.rs new file mode 100644 index 0000000..4d250ae --- /dev/null +++ b/structopt/tests/issues.rs @@ -0,0 +1,67 @@ +// https://github.com/TeXitoi/structopt/issues/151 +// https://github.com/TeXitoi/structopt/issues/289 + +#[test] +fn issue_151() { + use structopt::{clap::ArgGroup, StructOpt}; + + #[derive(StructOpt, Debug)] + #[structopt(group = ArgGroup::with_name("verb").required(true).multiple(true))] + struct Opt { + #[structopt(long, group = "verb")] + foo: bool, + #[structopt(long, group = "verb")] + bar: bool, + } + + #[derive(Debug, StructOpt)] + struct Cli { + #[structopt(flatten)] + a: Opt, + } + + assert!(Cli::clap().get_matches_from_safe(&["test"]).is_err()); + assert!(Cli::clap() + .get_matches_from_safe(&["test", "--foo"]) + .is_ok()); + assert!(Cli::clap() + .get_matches_from_safe(&["test", "--bar"]) + .is_ok()); + assert!(Cli::clap() + .get_matches_from_safe(&["test", "--zebra"]) + .is_err()); + assert!(Cli::clap() + .get_matches_from_safe(&["test", "--foo", "--bar"]) + .is_ok()); +} + +#[test] +fn issue_289() { + use structopt::{clap::AppSettings, StructOpt}; + + #[derive(StructOpt)] + #[structopt(setting = AppSettings::InferSubcommands)] + enum Args { + SomeCommand(SubSubCommand), + AnotherCommand, + } + + #[derive(StructOpt)] + #[structopt(setting = AppSettings::InferSubcommands)] + enum SubSubCommand { + TestCommand, + } + + assert!(Args::clap() + .get_matches_from_safe(&["test", "some-command", "test-command"]) + .is_ok()); + assert!(Args::clap() + .get_matches_from_safe(&["test", "some", "test-command"]) + .is_ok()); + assert!(Args::clap() + .get_matches_from_safe(&["test", "some-command", "test"]) + .is_ok()); + assert!(Args::clap() + .get_matches_from_safe(&["test", "some", "test"]) + .is_ok()); +} diff --git a/structopt/tests/macro-errors.rs b/structopt/tests/macro-errors.rs new file mode 100644 index 0000000..ae4f5a2 --- /dev/null +++ b/structopt/tests/macro-errors.rs @@ -0,0 +1,13 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed + +#[rustversion::attr(any(not(stable), before(1.39)), ignore)] +#[test] +fn ui() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/ui/*.rs"); +} diff --git a/structopt/tests/nested-subcommands.rs b/structopt/tests/nested-subcommands.rs new file mode 100644 index 0000000..1fbd166 --- /dev/null +++ b/structopt/tests/nested-subcommands.rs @@ -0,0 +1,193 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, PartialEq, Debug)] +struct Opt { + #[structopt(short, long)] + force: bool, + #[structopt(short, long, parse(from_occurrences))] + verbose: u64, + #[structopt(subcommand)] + cmd: Sub, +} + +#[derive(StructOpt, PartialEq, Debug)] +enum Sub { + Fetch {}, + Add {}, +} + +#[derive(StructOpt, PartialEq, Debug)] +struct Opt2 { + #[structopt(short, long)] + force: bool, + #[structopt(short, long, parse(from_occurrences))] + verbose: u64, + #[structopt(subcommand)] + cmd: Option, +} + +#[test] +fn test_no_cmd() { + let result = Opt::clap().get_matches_from_safe(&["test"]); + assert!(result.is_err()); + + assert_eq!( + Opt2 { + force: false, + verbose: 0, + cmd: None + }, + Opt2::from_clap(&Opt2::clap().get_matches_from(&["test"])) + ); +} + +#[test] +fn test_fetch() { + assert_eq!( + Opt { + force: false, + verbose: 3, + cmd: Sub::Fetch {} + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-vvv", "fetch"])) + ); + assert_eq!( + Opt { + force: true, + verbose: 0, + cmd: Sub::Fetch {} + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--force", "fetch"])) + ); +} + +#[test] +fn test_add() { + assert_eq!( + Opt { + force: false, + verbose: 0, + cmd: Sub::Add {} + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "add"])) + ); + assert_eq!( + Opt { + force: false, + verbose: 2, + cmd: Sub::Add {} + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-vv", "add"])) + ); +} + +#[test] +fn test_badinput() { + let result = Opt::clap().get_matches_from_safe(&["test", "badcmd"]); + assert!(result.is_err()); + let result = Opt::clap().get_matches_from_safe(&["test", "add", "--verbose"]); + assert!(result.is_err()); + let result = Opt::clap().get_matches_from_safe(&["test", "--badopt", "add"]); + assert!(result.is_err()); + let result = Opt::clap().get_matches_from_safe(&["test", "add", "--badopt"]); + assert!(result.is_err()); +} + +#[derive(StructOpt, PartialEq, Debug)] +struct Opt3 { + #[structopt(short, long)] + all: bool, + #[structopt(subcommand)] + cmd: Sub2, +} + +#[derive(StructOpt, PartialEq, Debug)] +enum Sub2 { + Foo { + file: String, + #[structopt(subcommand)] + cmd: Sub3, + }, + Bar {}, +} + +#[derive(StructOpt, PartialEq, Debug)] +enum Sub3 { + Baz {}, + Quux {}, +} + +#[test] +fn test_subsubcommand() { + assert_eq!( + Opt3 { + all: true, + cmd: Sub2::Foo { + file: "lib.rs".to_string(), + cmd: Sub3::Quux {} + } + }, + Opt3::from_clap( + &Opt3::clap().get_matches_from(&["test", "--all", "foo", "lib.rs", "quux"]) + ) + ); +} + +#[derive(StructOpt, PartialEq, Debug)] +enum SubSubCmdWithOption { + Remote { + #[structopt(subcommand)] + cmd: Option, + }, + Stash { + #[structopt(subcommand)] + cmd: Stash, + }, +} +#[derive(StructOpt, PartialEq, Debug)] +enum Remote { + Add { name: String, url: String }, + Remove { name: String }, +} + +#[derive(StructOpt, PartialEq, Debug)] +enum Stash { + Save, + Pop, +} + +#[test] +fn sub_sub_cmd_with_option() { + fn make(args: &[&str]) -> Option { + SubSubCmdWithOption::clap() + .get_matches_from_safe(args) + .ok() + .map(|m| SubSubCmdWithOption::from_clap(&m)) + } + assert_eq!( + Some(SubSubCmdWithOption::Remote { cmd: None }), + make(&["", "remote"]) + ); + assert_eq!( + Some(SubSubCmdWithOption::Remote { + cmd: Some(Remote::Add { + name: "origin".into(), + url: "http".into() + }) + }), + make(&["", "remote", "add", "origin", "http"]) + ); + assert_eq!( + Some(SubSubCmdWithOption::Stash { cmd: Stash::Save }), + make(&["", "stash", "save"]) + ); + assert_eq!(None, make(&["", "stash"])); +} diff --git a/structopt/tests/non_literal_attributes.rs b/structopt/tests/non_literal_attributes.rs new file mode 100644 index 0000000..75b6b71 --- /dev/null +++ b/structopt/tests/non_literal_attributes.rs @@ -0,0 +1,147 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::clap::AppSettings; +use structopt::StructOpt; + +pub const DISPLAY_ORDER: usize = 2; + +// Check if the global settings compile +#[derive(StructOpt, Debug, PartialEq, Eq)] +#[structopt(global_settings = &[AppSettings::ColoredHelp])] +struct Opt { + #[structopt( + long = "x", + display_order = DISPLAY_ORDER, + next_line_help = true, + default_value = "0", + require_equals = true + )] + x: i32, + + #[structopt(short = "l", long = "level", aliases = &["set-level", "lvl"])] + level: String, + + #[structopt(long("values"))] + values: Vec, + + #[structopt(name = "FILE", requires_if("FILE", "values"))] + files: Vec, +} + +#[test] +fn test_slice() { + assert_eq!( + Opt { + x: 0, + level: "1".to_string(), + files: Vec::new(), + values: vec![], + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-l", "1"])) + ); + assert_eq!( + Opt { + x: 0, + level: "1".to_string(), + files: Vec::new(), + values: vec![], + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--level", "1"])) + ); + assert_eq!( + Opt { + x: 0, + level: "1".to_string(), + files: Vec::new(), + values: vec![], + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--set-level", "1"])) + ); + assert_eq!( + Opt { + x: 0, + level: "1".to_string(), + files: Vec::new(), + values: vec![], + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--lvl", "1"])) + ); +} + +#[test] +fn test_multi_args() { + assert_eq!( + Opt { + x: 0, + level: "1".to_string(), + files: vec!["file".to_string()], + values: vec![], + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-l", "1", "file"])) + ); + assert_eq!( + Opt { + x: 0, + level: "1".to_string(), + files: vec!["FILE".to_string()], + values: vec![1], + }, + Opt::from_clap( + &Opt::clap().get_matches_from(&["test", "-l", "1", "--values", "1", "--", "FILE"]), + ) + ); +} + +#[test] +fn test_multi_args_fail() { + let result = Opt::clap().get_matches_from_safe(&["test", "-l", "1", "--", "FILE"]); + assert!(result.is_err()); +} + +#[test] +fn test_bool() { + assert_eq!( + Opt { + x: 1, + level: "1".to_string(), + files: vec![], + values: vec![], + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-l", "1", "--x=1"])) + ); + let result = Opt::clap().get_matches_from_safe(&["test", "-l", "1", "--x", "1"]); + assert!(result.is_err()); +} + +fn parse_hex(input: &str) -> Result { + u64::from_str_radix(input, 16) +} + +#[derive(StructOpt, PartialEq, Debug)] +struct HexOpt { + #[structopt(short = "n", parse(try_from_str = parse_hex))] + number: u64, +} + +#[test] +fn test_parse_hex_function_path() { + assert_eq!( + HexOpt { number: 5 }, + HexOpt::from_clap(&HexOpt::clap().get_matches_from(&["test", "-n", "5"])) + ); + assert_eq!( + HexOpt { number: 0xabcdef }, + HexOpt::from_clap(&HexOpt::clap().get_matches_from(&["test", "-n", "abcdef"])) + ); + + let err = HexOpt::clap() + .get_matches_from_safe(&["test", "-n", "gg"]) + .unwrap_err(); + assert!(err.message.contains("invalid digit found in string"), err); +} diff --git a/structopt/tests/options.rs b/structopt/tests/options.rs new file mode 100644 index 0000000..803abb4 --- /dev/null +++ b/structopt/tests/options.rs @@ -0,0 +1,336 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[test] +fn required_option() { + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + #[structopt(short, long)] + arg: i32, + } + assert_eq!( + Opt { arg: 42 }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a42"])) + ); + assert_eq!( + Opt { arg: 42 }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a", "42"])) + ); + assert_eq!( + Opt { arg: 42 }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--arg", "42"])) + ); + assert!(Opt::clap().get_matches_from_safe(&["test"]).is_err()); + assert!(Opt::clap() + .get_matches_from_safe(&["test", "-a42", "-a24"]) + .is_err()); +} + +#[test] +fn optional_option() { + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + #[structopt(short)] + arg: Option, + } + assert_eq!( + Opt { arg: Some(42) }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a42"])) + ); + assert_eq!( + Opt { arg: None }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test"])) + ); + assert!(Opt::clap() + .get_matches_from_safe(&["test", "-a42", "-a24"]) + .is_err()); +} + +#[test] +fn option_with_default() { + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + #[structopt(short, default_value = "42")] + arg: i32, + } + assert_eq!( + Opt { arg: 24 }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a24"])) + ); + assert_eq!( + Opt { arg: 42 }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test"])) + ); + assert!(Opt::clap() + .get_matches_from_safe(&["test", "-a42", "-a24"]) + .is_err()); +} + +#[test] +fn option_with_raw_default() { + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + #[structopt(short, default_value = "42")] + arg: i32, + } + assert_eq!( + Opt { arg: 24 }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a24"])) + ); + assert_eq!( + Opt { arg: 42 }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test"])) + ); + assert!(Opt::clap() + .get_matches_from_safe(&["test", "-a42", "-a24"]) + .is_err()); +} + +#[test] +fn options() { + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + #[structopt(short, long)] + arg: Vec, + } + assert_eq!( + Opt { arg: vec![24] }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a24"])) + ); + assert_eq!( + Opt { arg: vec![] }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test"])) + ); + assert_eq!( + Opt { arg: vec![24, 42] }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a24", "--arg", "42"])) + ); +} + +#[test] +fn empy_default_value() { + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + #[structopt(short, default_value = "")] + arg: String, + } + assert_eq!(Opt { arg: "".into() }, Opt::from_iter(&["test"])); + assert_eq!( + Opt { arg: "foo".into() }, + Opt::from_iter(&["test", "-afoo"]) + ); +} + +#[test] +fn option_from_str() { + #[derive(Debug, PartialEq)] + struct A; + + impl<'a> From<&'a str> for A { + fn from(_: &str) -> A { + A + } + } + + #[derive(Debug, StructOpt, PartialEq)] + struct Opt { + #[structopt(parse(from_str))] + a: Option, + } + + assert_eq!(Opt { a: None }, Opt::from_iter(&["test"])); + assert_eq!(Opt { a: Some(A) }, Opt::from_iter(&["test", "foo"])); +} + +#[test] +fn optional_argument_for_optional_option() { + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + #[structopt(short)] + #[allow(clippy::option_option)] + arg: Option>, + } + assert_eq!( + Opt { + arg: Some(Some(42)) + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a42"])) + ); + assert_eq!( + Opt { arg: Some(None) }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a"])) + ); + assert_eq!( + Opt { arg: None }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test"])) + ); + assert!(Opt::clap() + .get_matches_from_safe(&["test", "-a42", "-a24"]) + .is_err()); +} + +#[test] +fn two_option_options() { + #[derive(StructOpt, PartialEq, Debug)] + #[allow(clippy::option_option)] + struct Opt { + #[structopt(short)] + arg: Option>, + + #[structopt(long)] + field: Option>, + } + assert_eq!( + Opt { + arg: Some(Some(42)), + field: Some(Some("f".into())) + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a42", "--field", "f"])) + ); + assert_eq!( + Opt { + arg: Some(Some(42)), + field: Some(None) + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a42", "--field"])) + ); + assert_eq!( + Opt { + arg: Some(None), + field: Some(None) + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a", "--field"])) + ); + assert_eq!( + Opt { + arg: Some(None), + field: Some(Some("f".into())) + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a", "--field", "f"])) + ); + assert_eq!( + Opt { + arg: None, + field: Some(None) + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "--field"])) + ); + assert_eq!( + Opt { + arg: None, + field: None + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test"])) + ); +} + +#[test] +fn optional_vec() { + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + #[structopt(short)] + arg: Option>, + } + assert_eq!( + Opt { arg: Some(vec![1]) }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a", "1"])) + ); + + assert_eq!( + Opt { + arg: Some(vec![1, 2]) + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a1", "-a2"])) + ); + + assert_eq!( + Opt { + arg: Some(vec![1, 2]) + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a1", "-a2", "-a"])) + ); + + assert_eq!( + Opt { + arg: Some(vec![1, 2]) + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a1", "-a", "-a2"])) + ); + + assert_eq!( + Opt { + arg: Some(vec![1, 2]) + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a", "1", "2"])) + ); + + assert_eq!( + Opt { + arg: Some(vec![1, 2, 3]) + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a", "1", "2", "-a", "3"])) + ); + + assert_eq!( + Opt { arg: Some(vec![]) }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a"])) + ); + + assert_eq!( + Opt { arg: Some(vec![]) }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a", "-a"])) + ); + + assert_eq!( + Opt { arg: None }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test"])) + ); +} + +#[test] +fn two_optional_vecs() { + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + #[structopt(short)] + arg: Option>, + + #[structopt(short)] + b: Option>, + } + + assert_eq!( + Opt { + arg: Some(vec![1]), + b: Some(vec![]) + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a", "1", "-b"])) + ); + + assert_eq!( + Opt { + arg: Some(vec![1]), + b: Some(vec![]) + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a", "-b", "-a1"])) + ); + + assert_eq!( + Opt { + arg: Some(vec![1, 2]), + b: Some(vec![1, 2]) + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a1", "-a2", "-b1", "-b2"])) + ); + + assert_eq!( + Opt { arg: None, b: None }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test"])) + ); +} diff --git a/structopt/tests/privacy.rs b/structopt/tests/privacy.rs new file mode 100644 index 0000000..730bbce --- /dev/null +++ b/structopt/tests/privacy.rs @@ -0,0 +1,32 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +mod options { + use super::StructOpt; + + #[derive(Debug, StructOpt)] + pub struct Options { + #[structopt(subcommand)] + pub subcommand: super::subcommands::SubCommand, + } +} + +mod subcommands { + use super::StructOpt; + + #[derive(Debug, StructOpt)] + pub enum SubCommand { + /// foo + Foo { + /// foo + bars: Vec, + }, + } +} diff --git a/structopt/tests/raw_bool_literal.rs b/structopt/tests/raw_bool_literal.rs new file mode 100644 index 0000000..faf8628 --- /dev/null +++ b/structopt/tests/raw_bool_literal.rs @@ -0,0 +1,29 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[test] +fn raw_bool_literal() { + #[derive(StructOpt, Debug, PartialEq)] + #[structopt(no_version, name = "raw_bool")] + struct Opt { + #[structopt(raw(false))] + a: String, + #[structopt(raw(true))] + b: String, + } + + assert_eq!( + Opt { + a: "one".into(), + b: "--help".into() + }, + Opt::from_iter(&["test", "one", "--", "--help"]) + ); +} diff --git a/structopt/tests/raw_idents.rs b/structopt/tests/raw_idents.rs new file mode 100644 index 0000000..c00ff66 --- /dev/null +++ b/structopt/tests/raw_idents.rs @@ -0,0 +1,17 @@ +use structopt::StructOpt; + +#[test] +fn raw_idents() { + #[derive(StructOpt, Debug, PartialEq)] + struct Opt { + #[structopt(short, long)] + r#type: Vec, + } + + assert_eq!( + Opt { + r#type: vec!["long".into(), "short".into()] + }, + Opt::from_iter(&["test", "--type", "long", "-t", "short"]) + ); +} diff --git a/structopt/tests/rename_all_env.rs b/structopt/tests/rename_all_env.rs new file mode 100644 index 0000000..1979e84 --- /dev/null +++ b/structopt/tests/rename_all_env.rs @@ -0,0 +1,46 @@ +mod utils; + +use structopt::StructOpt; +use utils::*; + +#[test] +fn it_works() { + #[derive(Debug, PartialEq, StructOpt)] + #[structopt(rename_all_env = "kebab")] + struct BehaviorModel { + #[structopt(env)] + be_nice: String, + } + + let help = get_help::(); + assert!(help.contains("[env: be-nice=]")); +} + +#[test] +fn default_is_screaming() { + #[derive(Debug, PartialEq, StructOpt)] + struct BehaviorModel { + #[structopt(env)] + be_nice: String, + } + + let help = get_help::(); + assert!(help.contains("[env: BE_NICE=]")); +} + +#[test] +fn overridable() { + #[derive(Debug, PartialEq, StructOpt)] + #[structopt(rename_all_env = "kebab")] + struct BehaviorModel { + #[structopt(env)] + be_nice: String, + + #[structopt(rename_all_env = "pascal", env)] + be_agressive: String, + } + + let help = get_help::(); + assert!(help.contains("[env: be-nice=]")); + assert!(help.contains("[env: BeAgressive=]")); +} diff --git a/structopt/tests/skip.rs b/structopt/tests/skip.rs new file mode 100644 index 0000000..47682d8 --- /dev/null +++ b/structopt/tests/skip.rs @@ -0,0 +1,148 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[test] +fn skip_1() { + #[derive(StructOpt, Debug, PartialEq)] + struct Opt { + #[structopt(short)] + x: u32, + #[structopt(skip)] + s: u32, + } + + assert!(Opt::from_iter_safe(&["test", "-x", "10", "20"]).is_err()); + assert_eq!( + Opt::from_iter(&["test", "-x", "10"]), + Opt { + x: 10, + s: 0, // default + } + ); +} + +#[test] +fn skip_2() { + #[derive(StructOpt, Debug, PartialEq)] + struct Opt { + #[structopt(short)] + x: u32, + #[structopt(skip)] + ss: String, + #[structopt(skip)] + sn: u8, + y: u32, + #[structopt(skip)] + sz: u16, + t: u32, + } + + assert_eq!( + Opt::from_iter(&["test", "-x", "10", "20", "30"]), + Opt { + x: 10, + ss: String::from(""), + sn: 0, + y: 20, + sz: 0, + t: 30, + } + ); +} + +#[test] +fn skip_enum() { + #[derive(Debug, PartialEq)] + #[allow(unused)] + enum Kind { + A, + B, + } + + impl Default for Kind { + fn default() -> Self { + return Kind::B; + } + } + + #[derive(StructOpt, Debug, PartialEq)] + pub struct Opt { + #[structopt(long, short)] + number: u32, + #[structopt(skip)] + k: Kind, + #[structopt(skip)] + v: Vec, + } + + assert_eq!( + Opt::from_iter(&["test", "-n", "10"]), + Opt { + number: 10, + k: Kind::B, + v: vec![], + } + ); +} + +#[test] +fn skip_help_doc_comments() { + #[derive(StructOpt, Debug, PartialEq)] + pub struct Opt { + #[structopt(skip, help = "internal_stuff")] + a: u32, + + #[structopt(skip, long_help = "internal_stuff\ndo not touch")] + b: u32, + + /// Not meant to be used by clap. + /// + /// I want a default here. + #[structopt(skip)] + c: u32, + + #[structopt(short, parse(try_from_str))] + n: u32, + } + + assert_eq!( + Opt::from_iter(&["test", "-n", "10"]), + Opt { + n: 10, + a: 0, + b: 0, + c: 0, + } + ); +} + +#[test] +fn skip_val() { + #[derive(StructOpt, Debug, PartialEq)] + pub struct Opt { + #[structopt(long, short)] + number: u32, + + #[structopt(skip = "key")] + k: String, + + #[structopt(skip = vec![1, 2, 3])] + v: Vec, + } + + assert_eq!( + Opt::from_iter(&["test", "-n", "10"]), + Opt { + number: 10, + k: "key".into(), + v: vec![1, 2, 3] + } + ); +} diff --git a/structopt/tests/special_types.rs b/structopt/tests/special_types.rs new file mode 100644 index 0000000..ffed5e2 --- /dev/null +++ b/structopt/tests/special_types.rs @@ -0,0 +1,73 @@ +//! Checks that types like `::std::option::Option` are not special + +use structopt::StructOpt; + +#[rustversion::since(1.37)] +#[test] +fn special_types_bool() { + mod inner { + #[allow(non_camel_case_types)] + #[derive(PartialEq, Debug)] + pub struct bool(pub String); + + impl std::str::FromStr for self::bool { + type Err = String; + + fn from_str(s: &str) -> Result { + Ok(self::bool(s.into())) + } + } + }; + + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + arg: inner::bool, + } + + assert_eq!( + Opt { + arg: inner::bool("success".into()) + }, + Opt::from_iter(&["test", "success"]) + ); +} + +#[test] +fn special_types_option() { + fn parser(s: &str) -> Option { + Some(s.to_string()) + } + + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + #[structopt(parse(from_str = parser))] + arg: ::std::option::Option, + } + + assert_eq!( + Opt { + arg: Some("success".into()) + }, + Opt::from_iter(&["test", "success"]) + ); +} + +#[test] +fn special_types_vec() { + fn parser(s: &str) -> Vec { + vec![s.to_string()] + } + + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + #[structopt(parse(from_str = parser))] + arg: ::std::vec::Vec, + } + + assert_eq!( + Opt { + arg: vec!["success".into()] + }, + Opt::from_iter(&["test", "success"]) + ); +} diff --git a/structopt/tests/subcommands.rs b/structopt/tests/subcommands.rs new file mode 100644 index 0000000..170c0da --- /dev/null +++ b/structopt/tests/subcommands.rs @@ -0,0 +1,213 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +mod utils; + +use structopt::StructOpt; +use utils::*; + +#[derive(StructOpt, PartialEq, Debug)] +enum Opt { + /// Fetch stuff from GitHub + Fetch { + #[structopt(long)] + all: bool, + #[structopt(short, long)] + /// Overwrite local branches. + force: bool, + repo: String, + }, + + Add { + #[structopt(short, long)] + interactive: bool, + #[structopt(short, long)] + verbose: bool, + }, +} + +#[test] +fn test_fetch() { + assert_eq!( + Opt::Fetch { + all: true, + force: false, + repo: "origin".to_string() + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "fetch", "--all", "origin"])) + ); + assert_eq!( + Opt::Fetch { + all: false, + force: true, + repo: "origin".to_string() + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "fetch", "-f", "origin"])) + ); +} + +#[test] +fn test_add() { + assert_eq!( + Opt::Add { + interactive: false, + verbose: false + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "add"])) + ); + assert_eq!( + Opt::Add { + interactive: true, + verbose: true + }, + Opt::from_clap(&Opt::clap().get_matches_from(&["test", "add", "-i", "-v"])) + ); +} + +#[test] +fn test_no_parse() { + let result = Opt::clap().get_matches_from_safe(&["test", "badcmd", "-i", "-v"]); + assert!(result.is_err()); + + let result = Opt::clap().get_matches_from_safe(&["test", "add", "--badoption"]); + assert!(result.is_err()); + + let result = Opt::clap().get_matches_from_safe(&["test"]); + assert!(result.is_err()); +} + +#[derive(StructOpt, PartialEq, Debug)] +enum Opt2 { + DoSomething { arg: String }, +} + +#[test] +/// This test is specifically to make sure that hyphenated subcommands get +/// processed correctly. +fn test_hyphenated_subcommands() { + assert_eq!( + Opt2::DoSomething { + arg: "blah".to_string() + }, + Opt2::from_clap(&Opt2::clap().get_matches_from(&["test", "do-something", "blah"])) + ); +} + +#[derive(StructOpt, PartialEq, Debug)] +enum Opt3 { + Add, + Init, + Fetch, +} + +#[test] +fn test_null_commands() { + assert_eq!( + Opt3::Add, + Opt3::from_clap(&Opt3::clap().get_matches_from(&["test", "add"])) + ); + assert_eq!( + Opt3::Init, + Opt3::from_clap(&Opt3::clap().get_matches_from(&["test", "init"])) + ); + assert_eq!( + Opt3::Fetch, + Opt3::from_clap(&Opt3::clap().get_matches_from(&["test", "fetch"])) + ); +} + +#[derive(StructOpt, PartialEq, Debug)] +#[structopt(about = "Not shown")] +struct Add { + file: String, +} +/// Not shown +#[derive(StructOpt, PartialEq, Debug)] +struct Fetch { + remote: String, +} +#[derive(StructOpt, PartialEq, Debug)] +enum Opt4 { + // Not shown + /// Add a file + Add(Add), + Init, + /// download history from remote + Fetch(Fetch), +} + +#[test] +fn test_tuple_commands() { + assert_eq!( + Opt4::Add(Add { + file: "f".to_string() + }), + Opt4::from_clap(&Opt4::clap().get_matches_from(&["test", "add", "f"])) + ); + assert_eq!( + Opt4::Init, + Opt4::from_clap(&Opt4::clap().get_matches_from(&["test", "init"])) + ); + assert_eq!( + Opt4::Fetch(Fetch { + remote: "origin".to_string() + }), + Opt4::from_clap(&Opt4::clap().get_matches_from(&["test", "fetch", "origin"])) + ); + + let output = get_long_help::(); + + assert!(output.contains("download history from remote")); + assert!(output.contains("Add a file")); + assert!(!output.contains("Not shown")); +} + +#[test] +fn enum_in_enum_subsubcommand() { + #[derive(StructOpt, Debug, PartialEq)] + pub enum Opt { + Daemon(DaemonCommand), + } + + #[derive(StructOpt, Debug, PartialEq)] + pub enum DaemonCommand { + Start, + Stop, + } + + let result = Opt::clap().get_matches_from_safe(&["test"]); + assert!(result.is_err()); + + let result = Opt::clap().get_matches_from_safe(&["test", "daemon"]); + assert!(result.is_err()); + + let result = Opt::from_iter(&["test", "daemon", "start"]); + assert_eq!(Opt::Daemon(DaemonCommand::Start), result); +} + +#[test] +fn flatten_enum() { + #[derive(StructOpt, Debug, PartialEq)] + struct Opt { + #[structopt(flatten)] + sub_cmd: SubCmd, + } + #[derive(StructOpt, Debug, PartialEq)] + enum SubCmd { + Foo, + Bar, + } + + assert!(Opt::from_iter_safe(&["test"]).is_err()); + assert_eq!( + Opt::from_iter(&["test", "foo"]), + Opt { + sub_cmd: SubCmd::Foo + } + ); +} diff --git a/structopt/tests/ui/bool_default_value.rs b/structopt/tests/ui/bool_default_value.rs new file mode 100644 index 0000000..9bdb0c9 --- /dev/null +++ b/structopt/tests/ui/bool_default_value.rs @@ -0,0 +1,21 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "basic")] +struct Opt { + #[structopt(short, default_value = true)] + b: bool, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/bool_default_value.stderr b/structopt/tests/ui/bool_default_value.stderr new file mode 100644 index 0000000..1e26a2d --- /dev/null +++ b/structopt/tests/ui/bool_default_value.stderr @@ -0,0 +1,5 @@ +error: default_value is meaningless for bool + --> $DIR/bool_default_value.rs:14:24 + | +14 | #[structopt(short, default_value = true)] + | ^^^^^^^^^^^^^ diff --git a/structopt/tests/ui/bool_required.rs b/structopt/tests/ui/bool_required.rs new file mode 100644 index 0000000..018223c --- /dev/null +++ b/structopt/tests/ui/bool_required.rs @@ -0,0 +1,21 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "basic")] +struct Opt { + #[structopt(short, required = true)] + b: bool, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/bool_required.stderr b/structopt/tests/ui/bool_required.stderr new file mode 100644 index 0000000..0b80d48 --- /dev/null +++ b/structopt/tests/ui/bool_required.stderr @@ -0,0 +1,5 @@ +error: required is meaningless for bool + --> $DIR/bool_required.rs:14:24 + | +14 | #[structopt(short, required = true)] + | ^^^^^^^^ diff --git a/structopt/tests/ui/flatten_and_doc.rs b/structopt/tests/ui/flatten_and_doc.rs new file mode 100644 index 0000000..2dc154d --- /dev/null +++ b/structopt/tests/ui/flatten_and_doc.rs @@ -0,0 +1,30 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +struct DaemonOpts { + #[structopt(short)] + user: String, + #[structopt(short)] + group: String, +} + +#[derive(StructOpt, Debug)] +#[structopt(name = "basic")] +struct Opt { + /// Opts. + #[structopt(flatten)] + opts: DaemonOpts, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/flatten_and_doc.stderr b/structopt/tests/ui/flatten_and_doc.stderr new file mode 100644 index 0000000..2724dbb --- /dev/null +++ b/structopt/tests/ui/flatten_and_doc.stderr @@ -0,0 +1,5 @@ +error: methods and doc comments are not allowed for flattened entry + --> $DIR/flatten_and_doc.rs:23:17 + | +23 | #[structopt(flatten)] + | ^^^^^^^ diff --git a/structopt/tests/ui/flatten_and_methods.rs b/structopt/tests/ui/flatten_and_methods.rs new file mode 100644 index 0000000..ff1af2e --- /dev/null +++ b/structopt/tests/ui/flatten_and_methods.rs @@ -0,0 +1,29 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +struct DaemonOpts { + #[structopt(short)] + user: String, + #[structopt(short)] + group: String, +} + +#[derive(StructOpt, Debug)] +#[structopt(name = "basic")] +struct Opt { + #[structopt(short, flatten)] + opts: DaemonOpts, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/flatten_and_methods.stderr b/structopt/tests/ui/flatten_and_methods.stderr new file mode 100644 index 0000000..f058eb3 --- /dev/null +++ b/structopt/tests/ui/flatten_and_methods.stderr @@ -0,0 +1,5 @@ +error: methods and doc comments are not allowed for flattened entry + --> $DIR/flatten_and_methods.rs:22:24 + | +22 | #[structopt(short, flatten)] + | ^^^^^^^ diff --git a/structopt/tests/ui/flatten_and_parse.rs b/structopt/tests/ui/flatten_and_parse.rs new file mode 100644 index 0000000..3317272 --- /dev/null +++ b/structopt/tests/ui/flatten_and_parse.rs @@ -0,0 +1,29 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +struct DaemonOpts { + #[structopt(short)] + user: String, + #[structopt(short)] + group: String, +} + +#[derive(StructOpt, Debug)] +#[structopt(name = "basic")] +struct Opt { + #[structopt(flatten, parse(from_occurrences))] + opts: DaemonOpts, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/flatten_and_parse.stderr b/structopt/tests/ui/flatten_and_parse.stderr new file mode 100644 index 0000000..e217a84 --- /dev/null +++ b/structopt/tests/ui/flatten_and_parse.stderr @@ -0,0 +1,5 @@ +error: parse attribute is not allowed for flattened entry + --> $DIR/flatten_and_parse.rs:22:26 + | +22 | #[structopt(flatten, parse(from_occurrences))] + | ^^^^^ diff --git a/structopt/tests/ui/non_existent_attr.rs b/structopt/tests/ui/non_existent_attr.rs new file mode 100644 index 0000000..96daf45 --- /dev/null +++ b/structopt/tests/ui/non_existent_attr.rs @@ -0,0 +1,21 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "basic")] +struct Opt { + #[structopt(short, non_existing_attribute = 1)] + debug: bool, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/non_existent_attr.stderr b/structopt/tests/ui/non_existent_attr.stderr new file mode 100644 index 0000000..17bb87f --- /dev/null +++ b/structopt/tests/ui/non_existent_attr.stderr @@ -0,0 +1,5 @@ +error[E0599]: no method named `non_existing_attribute` found for type `clap::args::arg::Arg<'_, '_>` in the current scope + --> $DIR/non_existent_attr.rs:14:24 + | +14 | #[structopt(short, non_existing_attribute = 1)] + | ^^^^^^^^^^^^^^^^^^^^^^ method not found in `clap::args::arg::Arg<'_, '_>` diff --git a/structopt/tests/ui/opt_opt_nonpositional.rs b/structopt/tests/ui/opt_opt_nonpositional.rs new file mode 100644 index 0000000..2a08105 --- /dev/null +++ b/structopt/tests/ui/opt_opt_nonpositional.rs @@ -0,0 +1,20 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "basic")] +struct Opt { + n: Option>, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/opt_opt_nonpositional.stderr b/structopt/tests/ui/opt_opt_nonpositional.stderr new file mode 100644 index 0000000..586bf7a --- /dev/null +++ b/structopt/tests/ui/opt_opt_nonpositional.stderr @@ -0,0 +1,5 @@ +error: Option> type is meaningless for positional argument + --> $DIR/opt_opt_nonpositional.rs:14:8 + | +14 | n: Option>, + | ^^^^^^ diff --git a/structopt/tests/ui/opt_vec_nonpositional.rs b/structopt/tests/ui/opt_vec_nonpositional.rs new file mode 100644 index 0000000..0f6f078 --- /dev/null +++ b/structopt/tests/ui/opt_vec_nonpositional.rs @@ -0,0 +1,20 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "basic")] +struct Opt { + n: Option>, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/opt_vec_nonpositional.stderr b/structopt/tests/ui/opt_vec_nonpositional.stderr new file mode 100644 index 0000000..6f01de5 --- /dev/null +++ b/structopt/tests/ui/opt_vec_nonpositional.stderr @@ -0,0 +1,5 @@ +error: Option> type is meaningless for positional argument + --> $DIR/opt_vec_nonpositional.rs:14:8 + | +14 | n: Option>, + | ^^^^^^ diff --git a/structopt/tests/ui/option_default_value.rs b/structopt/tests/ui/option_default_value.rs new file mode 100644 index 0000000..a86bc0e --- /dev/null +++ b/structopt/tests/ui/option_default_value.rs @@ -0,0 +1,21 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "basic")] +struct Opt { + #[structopt(short, default_value = 1)] + n: Option, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/option_default_value.stderr b/structopt/tests/ui/option_default_value.stderr new file mode 100644 index 0000000..2215497 --- /dev/null +++ b/structopt/tests/ui/option_default_value.stderr @@ -0,0 +1,5 @@ +error: default_value is meaningless for Option + --> $DIR/option_default_value.rs:14:24 + | +14 | #[structopt(short, default_value = 1)] + | ^^^^^^^^^^^^^ diff --git a/structopt/tests/ui/option_required.rs b/structopt/tests/ui/option_required.rs new file mode 100644 index 0000000..d91afbf --- /dev/null +++ b/structopt/tests/ui/option_required.rs @@ -0,0 +1,21 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "basic")] +struct Opt { + #[structopt(short, required = true)] + n: Option, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/option_required.stderr b/structopt/tests/ui/option_required.stderr new file mode 100644 index 0000000..0230d57 --- /dev/null +++ b/structopt/tests/ui/option_required.stderr @@ -0,0 +1,5 @@ +error: required is meaningless for Option + --> $DIR/option_required.rs:14:24 + | +14 | #[structopt(short, required = true)] + | ^^^^^^^^ diff --git a/structopt/tests/ui/parse_empty_try_from_os.rs b/structopt/tests/ui/parse_empty_try_from_os.rs new file mode 100644 index 0000000..acfef0b --- /dev/null +++ b/structopt/tests/ui/parse_empty_try_from_os.rs @@ -0,0 +1,21 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "basic")] +struct Opt { + #[structopt(parse(try_from_os_str))] + s: String, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/parse_empty_try_from_os.stderr b/structopt/tests/ui/parse_empty_try_from_os.stderr new file mode 100644 index 0000000..3dc9f24 --- /dev/null +++ b/structopt/tests/ui/parse_empty_try_from_os.stderr @@ -0,0 +1,5 @@ +error: you must set parser for `try_from_os_str` explicitly + --> $DIR/parse_empty_try_from_os.rs:14:23 + | +14 | #[structopt(parse(try_from_os_str))] + | ^^^^^^^^^^^^^^^ diff --git a/structopt/tests/ui/parse_function_is_not_path.rs b/structopt/tests/ui/parse_function_is_not_path.rs new file mode 100644 index 0000000..5eebc57 --- /dev/null +++ b/structopt/tests/ui/parse_function_is_not_path.rs @@ -0,0 +1,21 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "basic")] +struct Opt { + #[structopt(parse(from_str = "2"))] + s: String, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/parse_function_is_not_path.stderr b/structopt/tests/ui/parse_function_is_not_path.stderr new file mode 100644 index 0000000..7cf7444 --- /dev/null +++ b/structopt/tests/ui/parse_function_is_not_path.stderr @@ -0,0 +1,5 @@ +error: `parse` argument must be a function path + --> $DIR/parse_function_is_not_path.rs:14:34 + | +14 | #[structopt(parse(from_str = "2"))] + | ^^^ diff --git a/structopt/tests/ui/parse_literal_spec.rs b/structopt/tests/ui/parse_literal_spec.rs new file mode 100644 index 0000000..b6f125a --- /dev/null +++ b/structopt/tests/ui/parse_literal_spec.rs @@ -0,0 +1,21 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "basic")] +struct Opt { + #[structopt(parse("from_str"))] + s: String, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/parse_literal_spec.stderr b/structopt/tests/ui/parse_literal_spec.stderr new file mode 100644 index 0000000..6e99e8b --- /dev/null +++ b/structopt/tests/ui/parse_literal_spec.stderr @@ -0,0 +1,5 @@ +error: parser specification must start with identifier + --> $DIR/parse_literal_spec.rs:14:23 + | +14 | #[structopt(parse("from_str"))] + | ^^^^^^^^^^ diff --git a/structopt/tests/ui/parse_not_zero_args.rs b/structopt/tests/ui/parse_not_zero_args.rs new file mode 100644 index 0000000..8729178 --- /dev/null +++ b/structopt/tests/ui/parse_not_zero_args.rs @@ -0,0 +1,21 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "basic")] +struct Opt { + #[structopt(parse(from_str, from_str))] + s: String, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/parse_not_zero_args.stderr b/structopt/tests/ui/parse_not_zero_args.stderr new file mode 100644 index 0000000..11abe06 --- /dev/null +++ b/structopt/tests/ui/parse_not_zero_args.stderr @@ -0,0 +1,5 @@ +error: parse must have exactly one argument + --> $DIR/parse_not_zero_args.rs:14:17 + | +14 | #[structopt(parse(from_str, from_str))] + | ^^^^^ diff --git a/structopt/tests/ui/positional_bool.rs b/structopt/tests/ui/positional_bool.rs new file mode 100644 index 0000000..4dbf538 --- /dev/null +++ b/structopt/tests/ui/positional_bool.rs @@ -0,0 +1,10 @@ +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +struct Opt { + verbose: bool, +} + +fn main() { + Opt::from_args(); +} \ No newline at end of file diff --git a/structopt/tests/ui/positional_bool.stderr b/structopt/tests/ui/positional_bool.stderr new file mode 100644 index 0000000..c3ed1ad --- /dev/null +++ b/structopt/tests/ui/positional_bool.stderr @@ -0,0 +1,10 @@ +error: `bool` cannot be used as positional parameter with default parser + + = help: if you want to create a flag add `long` or `short` + = help: If you really want a boolean parameter add an explicit parser, for example `parse(try_from_str)` + = note: see also https://github.com/TeXitoi/structopt/tree/master/examples/true_or_false.rs + + --> $DIR/positional_bool.rs:5:14 + | +5 | verbose: bool, + | ^^^^ diff --git a/structopt/tests/ui/raw.rs b/structopt/tests/ui/raw.rs new file mode 100644 index 0000000..b94f783 --- /dev/null +++ b/structopt/tests/ui/raw.rs @@ -0,0 +1,25 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +struct Opt { + #[structopt(raw(case_insensitive = "true"))] + s: String, +} + +#[derive(StructOpt, Debug)] +struct Opt2 { + #[structopt(raw(requires_if = r#""one", "two""#))] + s: String, +} +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/raw.stderr b/structopt/tests/ui/raw.stderr new file mode 100644 index 0000000..93b5e38 --- /dev/null +++ b/structopt/tests/ui/raw.stderr @@ -0,0 +1,19 @@ +error: `#[structopt(raw(...))` attributes are removed in structopt 0.3, they are replaced with raw methods + + = help: if you meant to call `clap::Arg::raw()` method you should use bool literal, like `raw(true)` or `raw(false)` + = note: if you need to call `clap::Arg/App::case_insensitive` method you can do it like this: #[structopt(case_insensitive = true)] + + --> $DIR/raw.rs:13:17 + | +13 | #[structopt(raw(case_insensitive = "true"))] + | ^^^ + +error: `#[structopt(raw(...))` attributes are removed in structopt 0.3, they are replaced with raw methods + + = help: if you meant to call `clap::Arg::raw()` method you should use bool literal, like `raw(true)` or `raw(false)` + = note: if you need to call `clap::Arg/App::requires_if` method you can do it like this: #[structopt(requires_if("one", "two"))] + + --> $DIR/raw.rs:19:17 + | +19 | #[structopt(raw(requires_if = r#""one", "two""#))] + | ^^^ diff --git a/structopt/tests/ui/rename_all_wrong_casing.rs b/structopt/tests/ui/rename_all_wrong_casing.rs new file mode 100644 index 0000000..4dabe14 --- /dev/null +++ b/structopt/tests/ui/rename_all_wrong_casing.rs @@ -0,0 +1,21 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "basic", rename_all = "fail")] +struct Opt { + #[structopt(short)] + s: String, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/rename_all_wrong_casing.stderr b/structopt/tests/ui/rename_all_wrong_casing.stderr new file mode 100644 index 0000000..2a72080 --- /dev/null +++ b/structopt/tests/ui/rename_all_wrong_casing.stderr @@ -0,0 +1,5 @@ +error: unsupported casing: `fail` + --> $DIR/rename_all_wrong_casing.rs:12:42 + | +12 | #[structopt(name = "basic", rename_all = "fail")] + | ^^^^^^ diff --git a/structopt/tests/ui/skip_flatten.rs b/structopt/tests/ui/skip_flatten.rs new file mode 100644 index 0000000..8668ec2 --- /dev/null +++ b/structopt/tests/ui/skip_flatten.rs @@ -0,0 +1,42 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "make-cookie")] +struct MakeCookie { + #[structopt(short)] + s: String, + + #[structopt(skip, flatten)] + cmd: Command, +} + +#[derive(StructOpt, Debug)] +enum Command { + #[structopt(name = "pound")] + /// Pound acorns into flour for cookie dough. + Pound { acorns: u32 }, + + Sparkle { + #[structopt(short)] + color: String, + }, +} + +impl Default for Command { + fn default() -> Self { + Command::Pound { acorns: 0 } + } +} + +fn main() { + let opt = MakeCookie::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/skip_flatten.stderr b/structopt/tests/ui/skip_flatten.stderr new file mode 100644 index 0000000..76477a3 --- /dev/null +++ b/structopt/tests/ui/skip_flatten.stderr @@ -0,0 +1,5 @@ +error: subcommand, flatten and skip cannot be used together + --> $DIR/skip_flatten.rs:17:23 + | +17 | #[structopt(skip, flatten)] + | ^^^^^^^ diff --git a/structopt/tests/ui/skip_subcommand.rs b/structopt/tests/ui/skip_subcommand.rs new file mode 100644 index 0000000..5d21426 --- /dev/null +++ b/structopt/tests/ui/skip_subcommand.rs @@ -0,0 +1,42 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "make-cookie")] +struct MakeCookie { + #[structopt(short)] + s: String, + + #[structopt(subcommand, skip)] + cmd: Command, +} + +#[derive(StructOpt, Debug)] +enum Command { + #[structopt(name = "pound")] + /// Pound acorns into flour for cookie dough. + Pound { acorns: u32 }, + + Sparkle { + #[structopt(short)] + color: String, + }, +} + +impl Default for Command { + fn default() -> Self { + Command::Pound { acorns: 0 } + } +} + +fn main() { + let opt = MakeCookie::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/skip_subcommand.stderr b/structopt/tests/ui/skip_subcommand.stderr new file mode 100644 index 0000000..aba2d69 --- /dev/null +++ b/structopt/tests/ui/skip_subcommand.stderr @@ -0,0 +1,5 @@ +error: subcommand, flatten and skip cannot be used together + --> $DIR/skip_subcommand.rs:17:29 + | +17 | #[structopt(subcommand, skip)] + | ^^^^ diff --git a/structopt/tests/ui/skip_with_other_options.rs b/structopt/tests/ui/skip_with_other_options.rs new file mode 100644 index 0000000..73c5342 --- /dev/null +++ b/structopt/tests/ui/skip_with_other_options.rs @@ -0,0 +1,15 @@ +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "test")] +pub struct Opt { + #[structopt(long)] + a: u32, + #[structopt(skip, long)] + b: u32, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/skip_with_other_options.stderr b/structopt/tests/ui/skip_with_other_options.stderr new file mode 100644 index 0000000..3345f92 --- /dev/null +++ b/structopt/tests/ui/skip_with_other_options.stderr @@ -0,0 +1,5 @@ +error: methods are not allowed for skipped fields + --> $DIR/skip_with_other_options.rs:8:17 + | +8 | #[structopt(skip, long)] + | ^^^^ diff --git a/structopt/tests/ui/skip_without_default.rs b/structopt/tests/ui/skip_without_default.rs new file mode 100644 index 0000000..bc47511 --- /dev/null +++ b/structopt/tests/ui/skip_without_default.rs @@ -0,0 +1,29 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(Debug)] +enum Kind { + A, + B, +} + +#[derive(StructOpt, Debug)] +#[structopt(name = "test")] +pub struct Opt { + #[structopt(short)] + number: u32, + #[structopt(skip)] + k: Kind, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/skip_without_default.stderr b/structopt/tests/ui/skip_without_default.stderr new file mode 100644 index 0000000..330898f --- /dev/null +++ b/structopt/tests/ui/skip_without_default.stderr @@ -0,0 +1,9 @@ +error[E0277]: the trait bound `Kind: std::default::Default` is not satisfied + --> $DIR/skip_without_default.rs:22:17 + | +22 | #[structopt(skip)] + | ^^^^ the trait `std::default::Default` is not implemented for `Kind` + | + = note: required by `std::default::Default::default` + +For more information about this error, try `rustc --explain E0277`. diff --git a/structopt/tests/ui/struct_flatten.rs b/structopt/tests/ui/struct_flatten.rs new file mode 100644 index 0000000..2b205f1 --- /dev/null +++ b/structopt/tests/ui/struct_flatten.rs @@ -0,0 +1,21 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "basic", flatten)] +struct Opt { + #[structopt(short)] + s: String, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/struct_flatten.stderr b/structopt/tests/ui/struct_flatten.stderr new file mode 100644 index 0000000..7f0fc6d --- /dev/null +++ b/structopt/tests/ui/struct_flatten.stderr @@ -0,0 +1,5 @@ +error: flatten is only allowed on fields + --> $DIR/struct_flatten.rs:12:29 + | +12 | #[structopt(name = "basic", flatten)] + | ^^^^^^^ diff --git a/structopt/tests/ui/struct_parse.rs b/structopt/tests/ui/struct_parse.rs new file mode 100644 index 0000000..e428b23 --- /dev/null +++ b/structopt/tests/ui/struct_parse.rs @@ -0,0 +1,21 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "basic", parse(from_str))] +struct Opt { + #[structopt(short)] + s: String, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/struct_parse.stderr b/structopt/tests/ui/struct_parse.stderr new file mode 100644 index 0000000..5518214 --- /dev/null +++ b/structopt/tests/ui/struct_parse.stderr @@ -0,0 +1,5 @@ +error: `parse` attribute is only allowed on fields + --> $DIR/struct_parse.rs:12:29 + | +12 | #[structopt(name = "basic", parse(from_str))] + | ^^^^^ diff --git a/structopt/tests/ui/struct_subcommand.rs b/structopt/tests/ui/struct_subcommand.rs new file mode 100644 index 0000000..ac0b145 --- /dev/null +++ b/structopt/tests/ui/struct_subcommand.rs @@ -0,0 +1,21 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "basic", subcommand)] +struct Opt { + #[structopt(short)] + s: String, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/struct_subcommand.stderr b/structopt/tests/ui/struct_subcommand.stderr new file mode 100644 index 0000000..438f6f8 --- /dev/null +++ b/structopt/tests/ui/struct_subcommand.stderr @@ -0,0 +1,5 @@ +error: subcommand is only allowed on fields + --> $DIR/struct_subcommand.rs:12:29 + | +12 | #[structopt(name = "basic", subcommand)] + | ^^^^^^^^^^ diff --git a/structopt/tests/ui/structopt_empty_attr.rs b/structopt/tests/ui/structopt_empty_attr.rs new file mode 100644 index 0000000..a7fc0b9 --- /dev/null +++ b/structopt/tests/ui/structopt_empty_attr.rs @@ -0,0 +1,22 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "basic")] +struct Opt { + #[structopt] + debug: bool, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} + diff --git a/structopt/tests/ui/structopt_empty_attr.stderr b/structopt/tests/ui/structopt_empty_attr.stderr new file mode 100644 index 0000000..dde3630 --- /dev/null +++ b/structopt/tests/ui/structopt_empty_attr.stderr @@ -0,0 +1,5 @@ +error: expected parentheses after `structopt` + --> $DIR/structopt_empty_attr.rs:14:7 + | +14 | #[structopt] + | ^^^^^^^^^ diff --git a/structopt/tests/ui/structopt_name_value_attr.rs b/structopt/tests/ui/structopt_name_value_attr.rs new file mode 100644 index 0000000..3d9388f --- /dev/null +++ b/structopt/tests/ui/structopt_name_value_attr.rs @@ -0,0 +1,22 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "basic")] +struct Opt { + #[structopt = "short"] + debug: bool, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} + diff --git a/structopt/tests/ui/structopt_name_value_attr.stderr b/structopt/tests/ui/structopt_name_value_attr.stderr new file mode 100644 index 0000000..f681978 --- /dev/null +++ b/structopt/tests/ui/structopt_name_value_attr.stderr @@ -0,0 +1,5 @@ +error: expected parentheses + --> $DIR/structopt_name_value_attr.rs:14:17 + | +14 | #[structopt = "short"] + | ^ diff --git a/structopt/tests/ui/subcommand_and_flatten.rs b/structopt/tests/ui/subcommand_and_flatten.rs new file mode 100644 index 0000000..742ee6d --- /dev/null +++ b/structopt/tests/ui/subcommand_and_flatten.rs @@ -0,0 +1,36 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "make-cookie")] +struct MakeCookie { + #[structopt(short)] + s: String, + + #[structopt(subcommand, flatten)] + cmd: Command, +} + +#[derive(StructOpt, Debug)] +enum Command { + #[structopt(name = "pound")] + /// Pound acorns into flour for cookie dough. + Pound { acorns: u32 }, + + Sparkle { + #[structopt(short)] + color: String, + }, +} + +fn main() { + let opt = MakeCookie::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/subcommand_and_flatten.stderr b/structopt/tests/ui/subcommand_and_flatten.stderr new file mode 100644 index 0000000..cacea5e --- /dev/null +++ b/structopt/tests/ui/subcommand_and_flatten.stderr @@ -0,0 +1,5 @@ +error: subcommand, flatten and skip cannot be used together + --> $DIR/subcommand_and_flatten.rs:17:29 + | +17 | #[structopt(subcommand, flatten)] + | ^^^^^^^ diff --git a/structopt/tests/ui/subcommand_and_methods.rs b/structopt/tests/ui/subcommand_and_methods.rs new file mode 100644 index 0000000..890f10c --- /dev/null +++ b/structopt/tests/ui/subcommand_and_methods.rs @@ -0,0 +1,36 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "make-cookie")] +struct MakeCookie { + #[structopt(short)] + s: String, + + #[structopt(subcommand, long)] + cmd: Command, +} + +#[derive(StructOpt, Debug)] +enum Command { + #[structopt(name = "pound")] + /// Pound acorns into flour for cookie dough. + Pound { acorns: u32 }, + + Sparkle { + #[structopt(short)] + color: String, + }, +} + +fn main() { + let opt = MakeCookie::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/subcommand_and_methods.stderr b/structopt/tests/ui/subcommand_and_methods.stderr new file mode 100644 index 0000000..ccaf28d --- /dev/null +++ b/structopt/tests/ui/subcommand_and_methods.stderr @@ -0,0 +1,5 @@ +error: methods in attributes are not allowed for subcommand + --> $DIR/subcommand_and_methods.rs:17:17 + | +17 | #[structopt(subcommand, long)] + | ^^^^^^^^^^ diff --git a/structopt/tests/ui/subcommand_and_parse.rs b/structopt/tests/ui/subcommand_and_parse.rs new file mode 100644 index 0000000..f24e4bc --- /dev/null +++ b/structopt/tests/ui/subcommand_and_parse.rs @@ -0,0 +1,36 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "make-cookie")] +struct MakeCookie { + #[structopt(short)] + s: String, + + #[structopt(subcommand, parse(from_occurrences))] + cmd: Command, +} + +#[derive(StructOpt, Debug)] +enum Command { + #[structopt(name = "pound")] + /// Pound acorns into flour for cookie dough. + Pound { acorns: u32 }, + + Sparkle { + #[structopt(short)] + color: String, + }, +} + +fn main() { + let opt = MakeCookie::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/subcommand_and_parse.stderr b/structopt/tests/ui/subcommand_and_parse.stderr new file mode 100644 index 0000000..4070056 --- /dev/null +++ b/structopt/tests/ui/subcommand_and_parse.stderr @@ -0,0 +1,5 @@ +error: parse attribute is not allowed for subcommand + --> $DIR/subcommand_and_parse.rs:17:29 + | +17 | #[structopt(subcommand, parse(from_occurrences))] + | ^^^^^ diff --git a/structopt/tests/ui/subcommand_opt_opt.rs b/structopt/tests/ui/subcommand_opt_opt.rs new file mode 100644 index 0000000..1dd84e5 --- /dev/null +++ b/structopt/tests/ui/subcommand_opt_opt.rs @@ -0,0 +1,36 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "make-cookie")] +struct MakeCookie { + #[structopt(short)] + s: String, + + #[structopt(subcommand)] + cmd: Option>, +} + +#[derive(StructOpt, Debug)] +enum Command { + #[structopt(name = "pound")] + /// Pound acorns into flour for cookie dough. + Pound { acorns: u32 }, + + Sparkle { + #[structopt(short)] + color: String, + }, +} + +fn main() { + let opt = MakeCookie::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/subcommand_opt_opt.stderr b/structopt/tests/ui/subcommand_opt_opt.stderr new file mode 100644 index 0000000..daad02f --- /dev/null +++ b/structopt/tests/ui/subcommand_opt_opt.stderr @@ -0,0 +1,5 @@ +error: Option> type is not allowed for subcommand + --> $DIR/subcommand_opt_opt.rs:18:10 + | +18 | cmd: Option>, + | ^^^^^^ diff --git a/structopt/tests/ui/subcommand_opt_vec.rs b/structopt/tests/ui/subcommand_opt_vec.rs new file mode 100644 index 0000000..17bffbf --- /dev/null +++ b/structopt/tests/ui/subcommand_opt_vec.rs @@ -0,0 +1,36 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "make-cookie")] +struct MakeCookie { + #[structopt(short)] + s: String, + + #[structopt(subcommand)] + cmd: Option>, +} + +#[derive(StructOpt, Debug)] +enum Command { + #[structopt(name = "pound")] + /// Pound acorns into flour for cookie dough. + Pound { acorns: u32 }, + + Sparkle { + #[structopt(short)] + color: String, + }, +} + +fn main() { + let opt = MakeCookie::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/subcommand_opt_vec.stderr b/structopt/tests/ui/subcommand_opt_vec.stderr new file mode 100644 index 0000000..a9833a6 --- /dev/null +++ b/structopt/tests/ui/subcommand_opt_vec.stderr @@ -0,0 +1,5 @@ +error: Option> type is not allowed for subcommand + --> $DIR/subcommand_opt_vec.rs:18:10 + | +18 | cmd: Option>, + | ^^^^^^ diff --git a/structopt/tests/ui/tuple_struct.rs b/structopt/tests/ui/tuple_struct.rs new file mode 100644 index 0000000..af9b1d5 --- /dev/null +++ b/structopt/tests/ui/tuple_struct.rs @@ -0,0 +1,18 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "basic")] +struct Opt(u32); + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt/tests/ui/tuple_struct.stderr b/structopt/tests/ui/tuple_struct.stderr new file mode 100644 index 0000000..9f2876f --- /dev/null +++ b/structopt/tests/ui/tuple_struct.stderr @@ -0,0 +1,5 @@ +error: structopt only supports non-tuple structs and enums + --> $DIR/tuple_struct.rs:11:10 + | +11 | #[derive(StructOpt, Debug)] + | ^^^^^^^^^ diff --git a/structopt/tests/utils.rs b/structopt/tests/utils.rs new file mode 100644 index 0000000..c0684a2 --- /dev/null +++ b/structopt/tests/utils.rs @@ -0,0 +1,45 @@ +#![allow(unused)] + +use structopt::StructOpt; + +pub fn get_help() -> String { + let mut output = Vec::new(); + ::clap().write_help(&mut output).unwrap(); + let output = String::from_utf8(output).unwrap(); + + eprintln!("\n%%% HELP %%%:=====\n{}\n=====\n", output); + eprintln!("\n%%% HELP (DEBUG) %%%:=====\n{:?}\n=====\n", output); + + output +} + +pub fn get_long_help() -> String { + let mut output = Vec::new(); + ::clap() + .write_long_help(&mut output) + .unwrap(); + let output = String::from_utf8(output).unwrap(); + + eprintln!("\n%%% LONG_HELP %%%:=====\n{}\n=====\n", output); + eprintln!("\n%%% LONG_HELP (DEBUG) %%%:=====\n{:?}\n=====\n", output); + + output +} + +pub fn get_subcommand_long_help(subcmd: &str) -> String { + let output = ::clap() + .get_matches_from_safe(vec!["test", subcmd, "--help"]) + .expect_err("") + .message; + + eprintln!( + "\n%%% SUBCOMMAND `{}` HELP %%%:=====\n{}\n=====\n", + subcmd, output + ); + eprintln!( + "\n%%% SUBCOMMAND `{}` HELP (DEBUG) %%%:=====\n{:?}\n=====\n", + subcmd, output + ); + + output +} -- cgit v1.2.3