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 --- bitflags/.gitignore | 4 + bitflags/.travis.yml | 39 + bitflags/CHANGELOG.md | 149 + bitflags/CODE_OF_CONDUCT.md | 73 + bitflags/Cargo.toml | 37 + bitflags/LICENSE-APACHE | 201 + bitflags/LICENSE-MIT | 25 + bitflags/README.md | 34 + bitflags/bors.toml | 3 + bitflags/build.rs | 44 + bitflags/src/example_generated.rs | 14 + bitflags/src/lib.rs | 1430 +++++ bitflags/test_suite/Cargo.toml | 13 + .../test_suite/tests/compile-fail/private_flags.rs | 20 + bitflags/test_suite/tests/compiletest.rs | 33 + .../test_suite/tests/conflicting_trait_impls.rs | 17 + bitflags/test_suite/tests/external.rs | 19 + bitflags/test_suite/tests/external_no_std.rs | 21 + bitflags/test_suite/tests/i128_bitflags.rs | 30 + bitflags/test_suite/tests/serde.rs | 35 + clap/.appveyor.yml | 17 + clap/.clog.toml | 14 + clap/.github/CONTRIBUTING.md | 115 + clap/.github/ISSUE_TEMPLATE.md | 46 + clap/.gitignore | 27 + clap/.mention-bot | 9 + clap/.travis.yml | 59 + clap/CHANGELOG.md | 2855 ++++++++++ clap/CONTRIBUTORS.md | 91 + clap/Cargo.toml | 91 + clap/LICENSE-MIT | 21 + clap/README.md | 542 ++ clap/SPONSORS.md | 17 + clap/benches/01_default.rs | 18 + clap/benches/02_simple.rs | 114 + clap/benches/03_complex.rs | 230 + clap/benches/04_new_help.rs | 198 + clap/benches/05_ripgrep.rs | 777 +++ clap/benches/06_rustup.rs | 458 ++ clap/clap-perf/clap_perf.dat | 8 + clap/clap-perf/clap_perf.png | Bin 0 -> 9702 bytes clap/clap-perf/plot_perf.gp | 26 + clap/clap-test.rs | 86 + clap/clap_dep_graph.dot | 41 + clap/clap_dep_graph.png | Bin 0 -> 96195 bytes clap/examples/01a_quick_example.rs | 79 + clap/examples/01b_quick_example.rs | 93 + clap/examples/01c_quick_example.rs | 75 + clap/examples/02_apps.rs | 30 + clap/examples/03_args.rs | 84 + clap/examples/04_using_matches.rs | 54 + clap/examples/05_flag_args.rs | 56 + clap/examples/06_positional_args.rs | 56 + clap/examples/07_option_args.rs | 71 + clap/examples/08_subcommands.rs | 57 + clap/examples/09_auto_version.rs | 29 + clap/examples/10_default_values.rs | 40 + clap/examples/11_only_specific_values.rs | 33 + clap/examples/12_typed_values.rs | 50 + clap/examples/13a_enum_values_automatic.rs | 68 + clap/examples/13b_enum_values_manual.rs | 54 + clap/examples/14_groups.rs | 87 + clap/examples/15_custom_validator.rs | 37 + clap/examples/16_app_settings.rs | 41 + clap/examples/17_yaml.rs | 53 + clap/examples/17_yaml.yml | 97 + clap/examples/18_builder_macro.rs | 84 + clap/examples/19_auto_authors.rs | 15 + clap/examples/20_subcommands.rs | 143 + clap/examples/21_aliases.rs | 39 + clap/examples/22_stop_parsing_with_--.rs | 25 + clap/justfile | 39 + clap/rustfmt.toml | 4 + clap/src/app/help.rs | 1028 ++++ clap/src/app/meta.rs | 33 + clap/src/app/mod.rs | 1839 +++++++ clap/src/app/parser.rs | 2167 ++++++++ clap/src/app/settings.rs | 1174 ++++ clap/src/app/usage.rs | 479 ++ clap/src/app/validator.rs | 573 ++ clap/src/args/any_arg.rs | 74 + clap/src/args/arg.rs | 3954 ++++++++++++++ clap/src/args/arg_builder/base.rs | 38 + clap/src/args/arg_builder/flag.rs | 159 + clap/src/args/arg_builder/mod.rs | 13 + clap/src/args/arg_builder/option.rs | 244 + clap/src/args/arg_builder/positional.rs | 229 + clap/src/args/arg_builder/switched.rs | 38 + clap/src/args/arg_builder/valued.rs | 67 + clap/src/args/arg_matcher.rs | 218 + clap/src/args/arg_matches.rs | 963 ++++ clap/src/args/group.rs | 635 +++ clap/src/args/macros.rs | 109 + clap/src/args/matched_arg.rs | 24 + clap/src/args/mod.rs | 21 + clap/src/args/settings.rs | 231 + clap/src/args/subcommand.rs | 66 + clap/src/completions/bash.rs | 219 + clap/src/completions/elvish.rs | 126 + clap/src/completions/fish.rs | 99 + clap/src/completions/macros.rs | 28 + clap/src/completions/mod.rs | 179 + clap/src/completions/powershell.rs | 139 + clap/src/completions/shell.rs | 52 + clap/src/completions/zsh.rs | 472 ++ clap/src/errors.rs | 912 ++++ clap/src/fmt.rs | 189 + clap/src/lib.rs | 629 +++ clap/src/macros.rs | 1108 ++++ clap/src/map.rs | 74 + clap/src/osstringext.rs | 119 + clap/src/strext.rs | 16 + clap/src/suggestions.rs | 147 + clap/src/usage_parser.rs | 1347 +++++ clap/tests/app.yml | 121 + clap/tests/app_settings.rs | 965 ++++ clap/tests/arg_aliases.rs | 200 + clap/tests/borrowed.rs | 19 + clap/tests/completions.rs | 883 +++ clap/tests/conflicts.rs | 102 + clap/tests/default_vals.rs | 498 ++ clap/tests/delimiters.rs | 139 + clap/tests/derive_order.rs | 245 + clap/tests/env.rs | 263 + clap/tests/example1_tmpl_full.txt | 15 + clap/tests/example1_tmpl_simple.txt | 8 + clap/tests/flags.rs | 147 + clap/tests/global_args.rs | 37 + clap/tests/groups.rs | 207 + clap/tests/help.rs | 1205 +++++ clap/tests/hidden_args.rs | 178 + clap/tests/indices.rs | 175 + clap/tests/macros.rs | 391 ++ clap/tests/multiple_occurrences.rs | 75 + clap/tests/multiple_values.rs | 1122 ++++ clap/tests/opts.rs | 461 ++ clap/tests/positionals.rs | 274 + clap/tests/posix_compatible.rs | 292 + clap/tests/possible_values.rs | 266 + clap/tests/propagate_globals.rs | 148 + clap/tests/require.rs | 688 +++ clap/tests/subcommands.rs | 216 + clap/tests/template_help.rs | 117 + clap/tests/tests.rs | 435 ++ clap/tests/unique_args.rs | 22 + clap/tests/utf8.rs | 223 + clap/tests/version-numbers.rs | 12 + clap/tests/version.rs | 58 + clap/tests/yaml.rs | 40 + heck/.gitignore | 2 + heck/Cargo.toml | 14 + heck/LICENSE-APACHE | 201 + heck/LICENSE-MIT | 25 + heck/README.md | 56 + heck/no_step_on_snek.png | Bin 0 -> 43512 bytes heck/src/camel.rs | 52 + heck/src/kebab.rs | 51 + heck/src/lib.rs | 165 + heck/src/mixed.rs | 56 + heck/src/shouty_snake.rs | 67 + heck/src/snake.rs | 79 + heck/src/title.rs | 52 + nitrocli/CHANGELOG.md | 5 + nitrocli/Cargo.lock | 119 +- nitrocli/Cargo.toml | 22 +- proc-macro-error/.gitignore | 4 + proc-macro-error/.gitlab-ci.yml | 54 + proc-macro-error/.travis.yml | 19 + proc-macro-error/CHANGELOG.md | 86 + proc-macro-error/Cargo.toml | 7 + proc-macro-error/LICENSE-APACHE | 201 + proc-macro-error/LICENSE-MIT | 21 + proc-macro-error/README.md | 206 + proc-macro-error/proc-macro-error-attr/Cargo.toml | 18 + proc-macro-error/proc-macro-error-attr/src/lib.rs | 162 + proc-macro-error/proc-macro-error/Cargo.toml | 26 + proc-macro-error/proc-macro-error/build.rs | 11 + proc-macro-error/proc-macro-error/src/dummy.rs | 136 + proc-macro-error/proc-macro-error/src/lib.rs | 514 ++ proc-macro-error/proc-macro-error/src/macros.rs | 257 + proc-macro-error/proc-macro-error/src/nightly.rs | 49 + proc-macro-error/proc-macro-error/src/stable.rs | 26 + proc-macro-error/test-crate/Cargo.toml | 20 + proc-macro-error/test-crate/src/lib.rs | 123 + proc-macro-error/test-crate/tests/macro-errors.rs | 6 + proc-macro-error/test-crate/tests/ok.rs | 9 + proc-macro-error/test-crate/tests/ui/abort.rs | 6 + proc-macro-error/test-crate/tests/ui/abort.stderr | 8 + proc-macro-error/test-crate/tests/ui/call_site.rs | 6 + .../test-crate/tests/ui/call_site.stderr | 8 + .../test-crate/tests/ui/direct_abort.rs | 6 + .../test-crate/tests/ui/direct_abort.stderr | 5 + proc-macro-error/test-crate/tests/ui/dummy.rs | 16 + proc-macro-error/test-crate/tests/ui/dummy.stderr | 5 + .../test-crate/tests/ui/multi-error.rs | 6 + .../test-crate/tests/ui/multi-error.stderr | 32 + .../test-crate/tests/ui/not_proc_macro.rs | 4 + .../test-crate/tests/ui/not_proc_macro.stderr | 8 + .../test-crate/tests/ui/option_expect.rs | 6 + .../test-crate/tests/ui/option_expect.stderr | 5 + .../test-crate/tests/ui/result_expect.rs | 6 + .../test-crate/tests/ui/result_expect.stderr | 5 + .../test-crate/tests/ui/result_unwrap.rs | 6 + .../test-crate/tests/ui/result_unwrap.stderr | 5 + .../test-crate/tests/ui/unknown_setting.rs | 4 + .../test-crate/tests/ui/unknown_setting.stderr | 5 + .../test-crate/tests/ui/unrelated_panic.rs | 6 + .../test-crate/tests/ui/unrelated_panic.stderr | 7 + proc-macro2/.gitignore | 3 + proc-macro2/.travis.yml | 36 + proc-macro2/Cargo.toml | 57 + proc-macro2/LICENSE-APACHE | 201 + proc-macro2/LICENSE-MIT | 25 + proc-macro2/README.md | 93 + proc-macro2/benches/bench-libproc-macro/Cargo.toml | 13 + proc-macro2/benches/bench-libproc-macro/README.md | 10 + proc-macro2/benches/bench-libproc-macro/lib.rs | 49 + proc-macro2/benches/bench-libproc-macro/main.rs | 3 + proc-macro2/build.rs | 129 + proc-macro2/src/fallback.rs | 1458 +++++ proc-macro2/src/lib.rs | 1199 +++++ proc-macro2/src/strnom.rs | 391 ++ proc-macro2/src/wrapper.rs | 927 ++++ proc-macro2/tests/features.rs | 8 + proc-macro2/tests/marker.rs | 59 + proc-macro2/tests/test.rs | 466 ++ quote/.gitignore | 2 + quote/.travis.yml | 18 + quote/Cargo.toml | 32 + quote/LICENSE-APACHE | 201 + quote/LICENSE-MIT | 25 + quote/README.md | 237 + quote/benches/bench.rs | 193 + quote/src/ext.rs | 112 + quote/src/format.rs | 164 + quote/src/ident_fragment.rs | 72 + quote/src/lib.rs | 948 ++++ quote/src/runtime.rs | 373 ++ quote/src/spanned.rs | 42 + quote/src/to_tokens.rs | 209 + quote/tests/compiletest.rs | 6 + quote/tests/test.rs | 429 ++ .../ui/does-not-have-iter-interpolated-dup.rs | 9 + .../ui/does-not-have-iter-interpolated-dup.stderr | 11 + quote/tests/ui/does-not-have-iter-interpolated.rs | 9 + .../ui/does-not-have-iter-interpolated.stderr | 11 + quote/tests/ui/does-not-have-iter-separated.rs | 5 + quote/tests/ui/does-not-have-iter-separated.stderr | 11 + quote/tests/ui/does-not-have-iter.rs | 5 + quote/tests/ui/does-not-have-iter.stderr | 11 + quote/tests/ui/not-quotable.rs | 7 + quote/tests/ui/not-quotable.stderr | 10 + quote/tests/ui/not-repeatable.rs | 7 + quote/tests/ui/not-repeatable.stderr | 14 + quote/tests/ui/wrong-type-span.rs | 7 + quote/tests/ui/wrong-type-span.stderr | 10 + rustversion/.gitignore | 3 + rustversion/.travis.yml | 17 + rustversion/Cargo.toml | 21 + rustversion/LICENSE-APACHE | 201 + rustversion/LICENSE-MIT | 23 + rustversion/README.md | 138 + rustversion/src/attr.rs | 35 + rustversion/src/bound.rs | 84 + rustversion/src/date.rs | 77 + rustversion/src/expr.rs | 177 + rustversion/src/lib.rs | 254 + rustversion/src/rustc.rs | 195 + rustversion/src/time.rs | 44 + rustversion/src/version.rs | 16 + 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 + syn-mid/.editorconfig | 22 + syn-mid/.gitignore | 6 + syn-mid/.rustfmt.toml | 4 + syn-mid/CHANGELOG.md | 33 + syn-mid/Cargo.toml | 23 + syn-mid/LICENSE-APACHE | 202 + syn-mid/LICENSE-MIT | 23 + syn-mid/README.md | 72 + syn-mid/azure-pipelines.yml | 49 + syn-mid/bors.toml | 1 + syn-mid/ci/azure-clippy.yml | 31 + syn-mid/ci/azure-install-rust.yml | 33 + syn-mid/ci/azure-rustdoc.yml | 13 + syn-mid/ci/azure-rustfmt.yml | 18 + syn-mid/ci/azure-test.yml | 34 + syn-mid/examples/const_fn/Cargo.toml | 16 + syn-mid/examples/const_fn/lib.rs | 31 + syn-mid/examples/const_fn_test/Cargo.toml | 9 + syn-mid/examples/const_fn_test/build.rs | 16 + syn-mid/examples/const_fn_test/tests/test.rs | 25 + syn-mid/src/arg.rs | 99 + syn-mid/src/lib.rs | 190 + syn-mid/src/macros.rs | 107 + syn-mid/src/pat.rs | 413 ++ syn-mid/src/path.rs | 50 + syn/.gitignore | 3 + syn/.travis.yml | 76 + syn/Cargo.toml | 72 + syn/LICENSE-APACHE | 201 + syn/LICENSE-MIT | 23 + syn/README.md | 291 + syn/appveyor.yml | 16 + syn/benches/file.rs | 30 + syn/benches/rust.rs | 158 + syn/build.rs | 63 + syn/codegen/Cargo.toml | 31 + syn/codegen/README.md | 12 + syn/codegen/src/debug.rs | 308 ++ syn/codegen/src/file.rs | 32 + syn/codegen/src/fold.rs | 284 + syn/codegen/src/full.rs | 20 + syn/codegen/src/gen.rs | 45 + syn/codegen/src/json.rs | 18 + syn/codegen/src/main.rs | 36 + syn/codegen/src/operand.rs | 38 + syn/codegen/src/parse.rs | 657 +++ syn/codegen/src/version.rs | 24 + syn/codegen/src/visit.rs | 265 + syn/codegen/src/visit_mut.rs | 262 + syn/dev/Cargo.toml | 22 + syn/dev/README.md | 6 + syn/dev/main.rs | 4 + syn/dev/parse.rs | 18 + syn/examples/README.md | 19 + syn/examples/dump-syntax/Cargo.toml | 17 + syn/examples/dump-syntax/README.md | 28 + syn/examples/dump-syntax/src/main.rs | 149 + syn/examples/heapsize/Cargo.toml | 2 + syn/examples/heapsize/README.md | 72 + syn/examples/heapsize/example/Cargo.toml | 9 + syn/examples/heapsize/example/src/main.rs | 28 + syn/examples/heapsize/heapsize/Cargo.toml | 9 + syn/examples/heapsize/heapsize/src/lib.rs | 64 + syn/examples/heapsize/heapsize_derive/Cargo.toml | 14 + syn/examples/heapsize/heapsize_derive/src/lib.rs | 96 + syn/examples/lazy-static/Cargo.toml | 2 + syn/examples/lazy-static/README.md | 42 + syn/examples/lazy-static/example/Cargo.toml | 10 + syn/examples/lazy-static/example/src/main.rs | 20 + syn/examples/lazy-static/lazy-static/Cargo.toml | 14 + syn/examples/lazy-static/lazy-static/src/lib.rs | 143 + syn/examples/trace-var/Cargo.toml | 2 + syn/examples/trace-var/README.md | 61 + syn/examples/trace-var/example/Cargo.toml | 9 + syn/examples/trace-var/example/src/main.rs | 15 + syn/examples/trace-var/trace-var/Cargo.toml | 14 + syn/examples/trace-var/trace-var/src/lib.rs | 180 + syn/json/Cargo.toml | 18 + syn/json/src/lib.rs | 214 + syn/src/attr.rs | 682 +++ syn/src/await.rs | 2 + syn/src/bigint.rs | 66 + syn/src/buffer.rs | 382 ++ syn/src/custom_keyword.rs | 252 + syn/src/custom_punctuation.rs | 309 ++ syn/src/data.rs | 456 ++ syn/src/derive.rs | 273 + syn/src/discouraged.rs | 195 + syn/src/error.rs | 357 ++ syn/src/export.rs | 35 + syn/src/expr.rs | 3236 +++++++++++ syn/src/ext.rs | 135 + syn/src/file.rs | 113 + syn/src/gen/fold.rs | 3236 +++++++++++ syn/src/gen/visit.rs | 3792 +++++++++++++ syn/src/gen/visit_mut.rs | 3798 +++++++++++++ syn/src/gen_helper.rs | 154 + syn/src/generics.rs | 1152 ++++ syn/src/group.rs | 280 + syn/src/ident.rs | 101 + syn/src/item.rs | 3104 +++++++++++ syn/src/keyword.rs | 0 syn/src/lib.rs | 947 ++++ syn/src/lifetime.rs | 140 + syn/src/lit.rs | 1382 +++++ syn/src/lookahead.rs | 168 + syn/src/mac.rs | 239 + syn/src/macros.rs | 191 + syn/src/op.rs | 231 + syn/src/parse.rs | 1222 +++++ syn/src/parse_macro_input.rs | 110 + syn/src/parse_quote.rs | 131 + syn/src/pat.rs | 903 ++++ syn/src/path.rs | 744 +++ syn/src/print.rs | 16 + syn/src/punctuated.rs | 918 ++++ syn/src/sealed.rs | 4 + syn/src/span.rs | 67 + syn/src/spanned.rs | 114 + syn/src/stmt.rs | 333 ++ syn/src/thread.rs | 41 + syn/src/token.rs | 956 ++++ syn/src/tt.rs | 108 + syn/src/ty.rs | 1178 ++++ syn/syn.json | 5616 +++++++++++++++++++ syn/tests/common/eq.rs | 459 ++ syn/tests/common/mod.rs | 19 + syn/tests/common/parse.rs | 49 + syn/tests/debug/gen.rs | 5633 ++++++++++++++++++++ syn/tests/debug/mod.rs | 110 + syn/tests/features/error.rs | 1 + syn/tests/features/mod.rs | 22 + syn/tests/macros/mod.rs | 76 + syn/tests/repo/mod.rs | 109 + syn/tests/repo/progress.rs | 37 + syn/tests/test_asyncness.rs | 39 + syn/tests/test_attribute.rs | 296 + syn/tests/test_derive_input.rs | 894 ++++ syn/tests/test_expr.rs | 37 + syn/tests/test_generics.rs | 285 + syn/tests/test_grouping.rs | 55 + syn/tests/test_ident.rs | 87 + syn/tests/test_iterators.rs | 51 + syn/tests/test_lit.rs | 184 + syn/tests/test_meta.rs | 341 ++ syn/tests/test_parse_buffer.rs | 53 + syn/tests/test_pat.rs | 20 + syn/tests/test_precedence.rs | 394 ++ syn/tests/test_receiver.rs | 109 + syn/tests/test_round_trip.rs | 151 + syn/tests/test_should_parse.rs | 47 + syn/tests/test_size.rs | 31 + syn/tests/test_token_trees.rs | 28 + syn/tests/test_visibility.rs | 99 + syn/tests/zzz_stable.rs | 33 + textwrap/.appveyor.yml | 23 + textwrap/.circleci/config.yml | 13 + textwrap/.codecov.yml | 13 + textwrap/.dir-locals.el | 3 + textwrap/.gitignore | 5 + textwrap/.travis.yml | 17 + textwrap/Cargo.toml | 38 + textwrap/LICENSE | 21 + textwrap/README.md | 337 ++ textwrap/benches/linear.rs | 122 + textwrap/examples/layout.rs | 38 + textwrap/examples/termwidth.rs | 41 + textwrap/src/indentation.rs | 294 + textwrap/src/lib.rs | 987 ++++ textwrap/src/splitting.rs | 139 + textwrap/tests/version-numbers.rs | 17 + unicode-segmentation/.gitignore | 5 + unicode-segmentation/.travis.yml | 24 + unicode-segmentation/COPYRIGHT | 7 + unicode-segmentation/Cargo.toml | 25 + unicode-segmentation/LICENSE-APACHE | 201 + unicode-segmentation/LICENSE-MIT | 25 + unicode-segmentation/README.md | 89 + unicode-segmentation/scripts/unicode.py | 375 ++ .../scripts/unicode_gen_breaktests.py | 212 + unicode-segmentation/src/grapheme.rs | 708 +++ unicode-segmentation/src/lib.rs | 242 + unicode-segmentation/src/sentence.rs | 373 ++ unicode-segmentation/src/tables.rs | 2523 +++++++++ unicode-segmentation/src/test.rs | 197 + unicode-segmentation/src/testdata.rs | 2122 ++++++++ unicode-segmentation/src/word.rs | 664 +++ unicode-width/.gitignore | 3 + unicode-width/.travis.yml | 28 + unicode-width/COPYRIGHT | 7 + unicode-width/Cargo.toml | 29 + unicode-width/LICENSE-APACHE | 201 + unicode-width/LICENSE-MIT | 25 + unicode-width/README.md | 58 + unicode-width/scripts/unicode.py | 321 ++ unicode-width/src/lib.rs | 131 + unicode-width/src/tables.rs | 284 + unicode-width/src/tests.rs | 175 + unicode-xid/.gitignore | 3 + unicode-xid/.travis.yml | 25 + unicode-xid/COPYRIGHT | 7 + unicode-xid/Cargo.toml | 28 + unicode-xid/LICENSE-APACHE | 201 + unicode-xid/LICENSE-MIT | 25 + unicode-xid/README.md | 44 + unicode-xid/scripts/unicode.py | 187 + unicode-xid/src/lib.rs | 87 + unicode-xid/src/tables.rs | 451 ++ unicode-xid/src/tests.rs | 111 + 604 files changed, 127025 insertions(+), 17 deletions(-) create mode 100644 bitflags/.gitignore create mode 100644 bitflags/.travis.yml create mode 100644 bitflags/CHANGELOG.md create mode 100644 bitflags/CODE_OF_CONDUCT.md create mode 100644 bitflags/Cargo.toml create mode 100644 bitflags/LICENSE-APACHE create mode 100644 bitflags/LICENSE-MIT create mode 100644 bitflags/README.md create mode 100644 bitflags/bors.toml create mode 100644 bitflags/build.rs create mode 100644 bitflags/src/example_generated.rs create mode 100644 bitflags/src/lib.rs create mode 100644 bitflags/test_suite/Cargo.toml create mode 100644 bitflags/test_suite/tests/compile-fail/private_flags.rs create mode 100644 bitflags/test_suite/tests/compiletest.rs create mode 100644 bitflags/test_suite/tests/conflicting_trait_impls.rs create mode 100644 bitflags/test_suite/tests/external.rs create mode 100644 bitflags/test_suite/tests/external_no_std.rs create mode 100644 bitflags/test_suite/tests/i128_bitflags.rs create mode 100644 bitflags/test_suite/tests/serde.rs create mode 100644 clap/.appveyor.yml create mode 100644 clap/.clog.toml create mode 100644 clap/.github/CONTRIBUTING.md create mode 100644 clap/.github/ISSUE_TEMPLATE.md create mode 100644 clap/.gitignore create mode 100644 clap/.mention-bot create mode 100644 clap/.travis.yml create mode 100644 clap/CHANGELOG.md create mode 100644 clap/CONTRIBUTORS.md create mode 100644 clap/Cargo.toml create mode 100644 clap/LICENSE-MIT create mode 100644 clap/README.md create mode 100644 clap/SPONSORS.md create mode 100644 clap/benches/01_default.rs create mode 100644 clap/benches/02_simple.rs create mode 100644 clap/benches/03_complex.rs create mode 100644 clap/benches/04_new_help.rs create mode 100644 clap/benches/05_ripgrep.rs create mode 100644 clap/benches/06_rustup.rs create mode 100644 clap/clap-perf/clap_perf.dat create mode 100644 clap/clap-perf/clap_perf.png create mode 100755 clap/clap-perf/plot_perf.gp create mode 100644 clap/clap-test.rs create mode 100644 clap/clap_dep_graph.dot create mode 100644 clap/clap_dep_graph.png create mode 100644 clap/examples/01a_quick_example.rs create mode 100644 clap/examples/01b_quick_example.rs create mode 100644 clap/examples/01c_quick_example.rs create mode 100644 clap/examples/02_apps.rs create mode 100644 clap/examples/03_args.rs create mode 100644 clap/examples/04_using_matches.rs create mode 100644 clap/examples/05_flag_args.rs create mode 100644 clap/examples/06_positional_args.rs create mode 100644 clap/examples/07_option_args.rs create mode 100644 clap/examples/08_subcommands.rs create mode 100644 clap/examples/09_auto_version.rs create mode 100644 clap/examples/10_default_values.rs create mode 100644 clap/examples/11_only_specific_values.rs create mode 100644 clap/examples/12_typed_values.rs create mode 100644 clap/examples/13a_enum_values_automatic.rs create mode 100644 clap/examples/13b_enum_values_manual.rs create mode 100644 clap/examples/14_groups.rs create mode 100644 clap/examples/15_custom_validator.rs create mode 100644 clap/examples/16_app_settings.rs create mode 100644 clap/examples/17_yaml.rs create mode 100644 clap/examples/17_yaml.yml create mode 100644 clap/examples/18_builder_macro.rs create mode 100644 clap/examples/19_auto_authors.rs create mode 100644 clap/examples/20_subcommands.rs create mode 100644 clap/examples/21_aliases.rs create mode 100644 clap/examples/22_stop_parsing_with_--.rs create mode 100644 clap/justfile create mode 100644 clap/rustfmt.toml create mode 100644 clap/src/app/help.rs create mode 100644 clap/src/app/meta.rs create mode 100644 clap/src/app/mod.rs create mode 100644 clap/src/app/parser.rs create mode 100644 clap/src/app/settings.rs create mode 100644 clap/src/app/usage.rs create mode 100644 clap/src/app/validator.rs create mode 100644 clap/src/args/any_arg.rs create mode 100644 clap/src/args/arg.rs create mode 100644 clap/src/args/arg_builder/base.rs create mode 100644 clap/src/args/arg_builder/flag.rs create mode 100644 clap/src/args/arg_builder/mod.rs create mode 100644 clap/src/args/arg_builder/option.rs create mode 100644 clap/src/args/arg_builder/positional.rs create mode 100644 clap/src/args/arg_builder/switched.rs create mode 100644 clap/src/args/arg_builder/valued.rs create mode 100644 clap/src/args/arg_matcher.rs create mode 100644 clap/src/args/arg_matches.rs create mode 100644 clap/src/args/group.rs create mode 100644 clap/src/args/macros.rs create mode 100644 clap/src/args/matched_arg.rs create mode 100644 clap/src/args/mod.rs create mode 100644 clap/src/args/settings.rs create mode 100644 clap/src/args/subcommand.rs create mode 100644 clap/src/completions/bash.rs create mode 100644 clap/src/completions/elvish.rs create mode 100644 clap/src/completions/fish.rs create mode 100644 clap/src/completions/macros.rs create mode 100644 clap/src/completions/mod.rs create mode 100644 clap/src/completions/powershell.rs create mode 100644 clap/src/completions/shell.rs create mode 100644 clap/src/completions/zsh.rs create mode 100644 clap/src/errors.rs create mode 100644 clap/src/fmt.rs create mode 100644 clap/src/lib.rs create mode 100644 clap/src/macros.rs create mode 100644 clap/src/map.rs create mode 100644 clap/src/osstringext.rs create mode 100644 clap/src/strext.rs create mode 100644 clap/src/suggestions.rs create mode 100644 clap/src/usage_parser.rs create mode 100644 clap/tests/app.yml create mode 100644 clap/tests/app_settings.rs create mode 100644 clap/tests/arg_aliases.rs create mode 100644 clap/tests/borrowed.rs create mode 100644 clap/tests/completions.rs create mode 100644 clap/tests/conflicts.rs create mode 100644 clap/tests/default_vals.rs create mode 100644 clap/tests/delimiters.rs create mode 100644 clap/tests/derive_order.rs create mode 100644 clap/tests/env.rs create mode 100644 clap/tests/example1_tmpl_full.txt create mode 100644 clap/tests/example1_tmpl_simple.txt create mode 100644 clap/tests/flags.rs create mode 100644 clap/tests/global_args.rs create mode 100644 clap/tests/groups.rs create mode 100644 clap/tests/help.rs create mode 100644 clap/tests/hidden_args.rs create mode 100644 clap/tests/indices.rs create mode 100755 clap/tests/macros.rs create mode 100644 clap/tests/multiple_occurrences.rs create mode 100644 clap/tests/multiple_values.rs create mode 100644 clap/tests/opts.rs create mode 100644 clap/tests/positionals.rs create mode 100644 clap/tests/posix_compatible.rs create mode 100644 clap/tests/possible_values.rs create mode 100644 clap/tests/propagate_globals.rs create mode 100644 clap/tests/require.rs create mode 100644 clap/tests/subcommands.rs create mode 100644 clap/tests/template_help.rs create mode 100644 clap/tests/tests.rs create mode 100644 clap/tests/unique_args.rs create mode 100644 clap/tests/utf8.rs create mode 100644 clap/tests/version-numbers.rs create mode 100644 clap/tests/version.rs create mode 100644 clap/tests/yaml.rs create mode 100644 heck/.gitignore create mode 100644 heck/Cargo.toml create mode 100644 heck/LICENSE-APACHE create mode 100644 heck/LICENSE-MIT create mode 100644 heck/README.md create mode 100644 heck/no_step_on_snek.png create mode 100644 heck/src/camel.rs create mode 100644 heck/src/kebab.rs create mode 100644 heck/src/lib.rs create mode 100644 heck/src/mixed.rs create mode 100644 heck/src/shouty_snake.rs create mode 100644 heck/src/snake.rs create mode 100644 heck/src/title.rs create mode 100644 proc-macro-error/.gitignore create mode 100644 proc-macro-error/.gitlab-ci.yml create mode 100644 proc-macro-error/.travis.yml create mode 100644 proc-macro-error/CHANGELOG.md create mode 100644 proc-macro-error/Cargo.toml create mode 100644 proc-macro-error/LICENSE-APACHE create mode 100644 proc-macro-error/LICENSE-MIT create mode 100644 proc-macro-error/README.md create mode 100644 proc-macro-error/proc-macro-error-attr/Cargo.toml create mode 100644 proc-macro-error/proc-macro-error-attr/src/lib.rs create mode 100644 proc-macro-error/proc-macro-error/Cargo.toml create mode 100644 proc-macro-error/proc-macro-error/build.rs create mode 100644 proc-macro-error/proc-macro-error/src/dummy.rs create mode 100644 proc-macro-error/proc-macro-error/src/lib.rs create mode 100644 proc-macro-error/proc-macro-error/src/macros.rs create mode 100644 proc-macro-error/proc-macro-error/src/nightly.rs create mode 100644 proc-macro-error/proc-macro-error/src/stable.rs create mode 100644 proc-macro-error/test-crate/Cargo.toml create mode 100644 proc-macro-error/test-crate/src/lib.rs create mode 100644 proc-macro-error/test-crate/tests/macro-errors.rs create mode 100644 proc-macro-error/test-crate/tests/ok.rs create mode 100644 proc-macro-error/test-crate/tests/ui/abort.rs create mode 100644 proc-macro-error/test-crate/tests/ui/abort.stderr create mode 100644 proc-macro-error/test-crate/tests/ui/call_site.rs create mode 100644 proc-macro-error/test-crate/tests/ui/call_site.stderr create mode 100644 proc-macro-error/test-crate/tests/ui/direct_abort.rs create mode 100644 proc-macro-error/test-crate/tests/ui/direct_abort.stderr create mode 100644 proc-macro-error/test-crate/tests/ui/dummy.rs create mode 100644 proc-macro-error/test-crate/tests/ui/dummy.stderr create mode 100644 proc-macro-error/test-crate/tests/ui/multi-error.rs create mode 100644 proc-macro-error/test-crate/tests/ui/multi-error.stderr create mode 100644 proc-macro-error/test-crate/tests/ui/not_proc_macro.rs create mode 100644 proc-macro-error/test-crate/tests/ui/not_proc_macro.stderr create mode 100644 proc-macro-error/test-crate/tests/ui/option_expect.rs create mode 100644 proc-macro-error/test-crate/tests/ui/option_expect.stderr create mode 100644 proc-macro-error/test-crate/tests/ui/result_expect.rs create mode 100644 proc-macro-error/test-crate/tests/ui/result_expect.stderr create mode 100644 proc-macro-error/test-crate/tests/ui/result_unwrap.rs create mode 100644 proc-macro-error/test-crate/tests/ui/result_unwrap.stderr create mode 100644 proc-macro-error/test-crate/tests/ui/unknown_setting.rs create mode 100644 proc-macro-error/test-crate/tests/ui/unknown_setting.stderr create mode 100644 proc-macro-error/test-crate/tests/ui/unrelated_panic.rs create mode 100644 proc-macro-error/test-crate/tests/ui/unrelated_panic.stderr create mode 100644 proc-macro2/.gitignore create mode 100644 proc-macro2/.travis.yml create mode 100644 proc-macro2/Cargo.toml create mode 100644 proc-macro2/LICENSE-APACHE create mode 100644 proc-macro2/LICENSE-MIT create mode 100644 proc-macro2/README.md create mode 100644 proc-macro2/benches/bench-libproc-macro/Cargo.toml create mode 100644 proc-macro2/benches/bench-libproc-macro/README.md create mode 100644 proc-macro2/benches/bench-libproc-macro/lib.rs create mode 100644 proc-macro2/benches/bench-libproc-macro/main.rs create mode 100644 proc-macro2/build.rs create mode 100644 proc-macro2/src/fallback.rs create mode 100644 proc-macro2/src/lib.rs create mode 100644 proc-macro2/src/strnom.rs create mode 100644 proc-macro2/src/wrapper.rs create mode 100644 proc-macro2/tests/features.rs create mode 100644 proc-macro2/tests/marker.rs create mode 100644 proc-macro2/tests/test.rs create mode 100644 quote/.gitignore create mode 100644 quote/.travis.yml create mode 100644 quote/Cargo.toml create mode 100644 quote/LICENSE-APACHE create mode 100644 quote/LICENSE-MIT create mode 100644 quote/README.md create mode 100644 quote/benches/bench.rs create mode 100644 quote/src/ext.rs create mode 100644 quote/src/format.rs create mode 100644 quote/src/ident_fragment.rs create mode 100644 quote/src/lib.rs create mode 100644 quote/src/runtime.rs create mode 100644 quote/src/spanned.rs create mode 100644 quote/src/to_tokens.rs create mode 100644 quote/tests/compiletest.rs create mode 100644 quote/tests/test.rs create mode 100644 quote/tests/ui/does-not-have-iter-interpolated-dup.rs create mode 100644 quote/tests/ui/does-not-have-iter-interpolated-dup.stderr create mode 100644 quote/tests/ui/does-not-have-iter-interpolated.rs create mode 100644 quote/tests/ui/does-not-have-iter-interpolated.stderr create mode 100644 quote/tests/ui/does-not-have-iter-separated.rs create mode 100644 quote/tests/ui/does-not-have-iter-separated.stderr create mode 100644 quote/tests/ui/does-not-have-iter.rs create mode 100644 quote/tests/ui/does-not-have-iter.stderr create mode 100644 quote/tests/ui/not-quotable.rs create mode 100644 quote/tests/ui/not-quotable.stderr create mode 100644 quote/tests/ui/not-repeatable.rs create mode 100644 quote/tests/ui/not-repeatable.stderr create mode 100644 quote/tests/ui/wrong-type-span.rs create mode 100644 quote/tests/ui/wrong-type-span.stderr create mode 100644 rustversion/.gitignore create mode 100644 rustversion/.travis.yml create mode 100644 rustversion/Cargo.toml create mode 100644 rustversion/LICENSE-APACHE create mode 100644 rustversion/LICENSE-MIT create mode 100644 rustversion/README.md create mode 100644 rustversion/src/attr.rs create mode 100644 rustversion/src/bound.rs create mode 100644 rustversion/src/date.rs create mode 100644 rustversion/src/expr.rs create mode 100644 rustversion/src/lib.rs create mode 100644 rustversion/src/rustc.rs create mode 100644 rustversion/src/time.rs create mode 100644 rustversion/src/version.rs 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 create mode 100644 syn-mid/.editorconfig create mode 100644 syn-mid/.gitignore create mode 100644 syn-mid/.rustfmt.toml create mode 100644 syn-mid/CHANGELOG.md create mode 100644 syn-mid/Cargo.toml create mode 100644 syn-mid/LICENSE-APACHE create mode 100644 syn-mid/LICENSE-MIT create mode 100644 syn-mid/README.md create mode 100644 syn-mid/azure-pipelines.yml create mode 100644 syn-mid/bors.toml create mode 100644 syn-mid/ci/azure-clippy.yml create mode 100644 syn-mid/ci/azure-install-rust.yml create mode 100644 syn-mid/ci/azure-rustdoc.yml create mode 100644 syn-mid/ci/azure-rustfmt.yml create mode 100644 syn-mid/ci/azure-test.yml create mode 100644 syn-mid/examples/const_fn/Cargo.toml create mode 100644 syn-mid/examples/const_fn/lib.rs create mode 100644 syn-mid/examples/const_fn_test/Cargo.toml create mode 100644 syn-mid/examples/const_fn_test/build.rs create mode 100644 syn-mid/examples/const_fn_test/tests/test.rs create mode 100644 syn-mid/src/arg.rs create mode 100644 syn-mid/src/lib.rs create mode 100644 syn-mid/src/macros.rs create mode 100644 syn-mid/src/pat.rs create mode 100644 syn-mid/src/path.rs create mode 100644 syn/.gitignore create mode 100644 syn/.travis.yml create mode 100644 syn/Cargo.toml create mode 100644 syn/LICENSE-APACHE create mode 100644 syn/LICENSE-MIT create mode 100644 syn/README.md create mode 100644 syn/appveyor.yml create mode 100644 syn/benches/file.rs create mode 100644 syn/benches/rust.rs create mode 100644 syn/build.rs create mode 100644 syn/codegen/Cargo.toml create mode 100644 syn/codegen/README.md create mode 100644 syn/codegen/src/debug.rs create mode 100644 syn/codegen/src/file.rs create mode 100644 syn/codegen/src/fold.rs create mode 100644 syn/codegen/src/full.rs create mode 100644 syn/codegen/src/gen.rs create mode 100644 syn/codegen/src/json.rs create mode 100644 syn/codegen/src/main.rs create mode 100644 syn/codegen/src/operand.rs create mode 100644 syn/codegen/src/parse.rs create mode 100644 syn/codegen/src/version.rs create mode 100644 syn/codegen/src/visit.rs create mode 100644 syn/codegen/src/visit_mut.rs create mode 100644 syn/dev/Cargo.toml create mode 100644 syn/dev/README.md create mode 100644 syn/dev/main.rs create mode 100644 syn/dev/parse.rs create mode 100644 syn/examples/README.md create mode 100644 syn/examples/dump-syntax/Cargo.toml create mode 100644 syn/examples/dump-syntax/README.md create mode 100644 syn/examples/dump-syntax/src/main.rs create mode 100644 syn/examples/heapsize/Cargo.toml create mode 100644 syn/examples/heapsize/README.md create mode 100644 syn/examples/heapsize/example/Cargo.toml create mode 100644 syn/examples/heapsize/example/src/main.rs create mode 100644 syn/examples/heapsize/heapsize/Cargo.toml create mode 100644 syn/examples/heapsize/heapsize/src/lib.rs create mode 100644 syn/examples/heapsize/heapsize_derive/Cargo.toml create mode 100644 syn/examples/heapsize/heapsize_derive/src/lib.rs create mode 100644 syn/examples/lazy-static/Cargo.toml create mode 100644 syn/examples/lazy-static/README.md create mode 100644 syn/examples/lazy-static/example/Cargo.toml create mode 100644 syn/examples/lazy-static/example/src/main.rs create mode 100644 syn/examples/lazy-static/lazy-static/Cargo.toml create mode 100644 syn/examples/lazy-static/lazy-static/src/lib.rs create mode 100644 syn/examples/trace-var/Cargo.toml create mode 100644 syn/examples/trace-var/README.md create mode 100644 syn/examples/trace-var/example/Cargo.toml create mode 100644 syn/examples/trace-var/example/src/main.rs create mode 100644 syn/examples/trace-var/trace-var/Cargo.toml create mode 100644 syn/examples/trace-var/trace-var/src/lib.rs create mode 100644 syn/json/Cargo.toml create mode 100644 syn/json/src/lib.rs create mode 100644 syn/src/attr.rs create mode 100644 syn/src/await.rs create mode 100644 syn/src/bigint.rs create mode 100644 syn/src/buffer.rs create mode 100644 syn/src/custom_keyword.rs create mode 100644 syn/src/custom_punctuation.rs create mode 100644 syn/src/data.rs create mode 100644 syn/src/derive.rs create mode 100644 syn/src/discouraged.rs create mode 100644 syn/src/error.rs create mode 100644 syn/src/export.rs create mode 100644 syn/src/expr.rs create mode 100644 syn/src/ext.rs create mode 100644 syn/src/file.rs create mode 100644 syn/src/gen/fold.rs create mode 100644 syn/src/gen/visit.rs create mode 100644 syn/src/gen/visit_mut.rs create mode 100644 syn/src/gen_helper.rs create mode 100644 syn/src/generics.rs create mode 100644 syn/src/group.rs create mode 100644 syn/src/ident.rs create mode 100644 syn/src/item.rs create mode 100644 syn/src/keyword.rs create mode 100644 syn/src/lib.rs create mode 100644 syn/src/lifetime.rs create mode 100644 syn/src/lit.rs create mode 100644 syn/src/lookahead.rs create mode 100644 syn/src/mac.rs create mode 100644 syn/src/macros.rs create mode 100644 syn/src/op.rs create mode 100644 syn/src/parse.rs create mode 100644 syn/src/parse_macro_input.rs create mode 100644 syn/src/parse_quote.rs create mode 100644 syn/src/pat.rs create mode 100644 syn/src/path.rs create mode 100644 syn/src/print.rs create mode 100644 syn/src/punctuated.rs create mode 100644 syn/src/sealed.rs create mode 100644 syn/src/span.rs create mode 100644 syn/src/spanned.rs create mode 100644 syn/src/stmt.rs create mode 100644 syn/src/thread.rs create mode 100644 syn/src/token.rs create mode 100644 syn/src/tt.rs create mode 100644 syn/src/ty.rs create mode 100644 syn/syn.json create mode 100644 syn/tests/common/eq.rs create mode 100644 syn/tests/common/mod.rs create mode 100644 syn/tests/common/parse.rs create mode 100644 syn/tests/debug/gen.rs create mode 100644 syn/tests/debug/mod.rs create mode 100644 syn/tests/features/error.rs create mode 100644 syn/tests/features/mod.rs create mode 100644 syn/tests/macros/mod.rs create mode 100644 syn/tests/repo/mod.rs create mode 100644 syn/tests/repo/progress.rs create mode 100644 syn/tests/test_asyncness.rs create mode 100644 syn/tests/test_attribute.rs create mode 100644 syn/tests/test_derive_input.rs create mode 100644 syn/tests/test_expr.rs create mode 100644 syn/tests/test_generics.rs create mode 100644 syn/tests/test_grouping.rs create mode 100644 syn/tests/test_ident.rs create mode 100644 syn/tests/test_iterators.rs create mode 100644 syn/tests/test_lit.rs create mode 100644 syn/tests/test_meta.rs create mode 100644 syn/tests/test_parse_buffer.rs create mode 100644 syn/tests/test_pat.rs create mode 100644 syn/tests/test_precedence.rs create mode 100644 syn/tests/test_receiver.rs create mode 100644 syn/tests/test_round_trip.rs create mode 100644 syn/tests/test_should_parse.rs create mode 100644 syn/tests/test_size.rs create mode 100644 syn/tests/test_token_trees.rs create mode 100644 syn/tests/test_visibility.rs create mode 100644 syn/tests/zzz_stable.rs create mode 100644 textwrap/.appveyor.yml create mode 100644 textwrap/.circleci/config.yml create mode 100644 textwrap/.codecov.yml create mode 100644 textwrap/.dir-locals.el create mode 100644 textwrap/.gitignore create mode 100644 textwrap/.travis.yml create mode 100644 textwrap/Cargo.toml create mode 100644 textwrap/LICENSE create mode 100644 textwrap/README.md create mode 100644 textwrap/benches/linear.rs create mode 100644 textwrap/examples/layout.rs create mode 100644 textwrap/examples/termwidth.rs create mode 100644 textwrap/src/indentation.rs create mode 100644 textwrap/src/lib.rs create mode 100644 textwrap/src/splitting.rs create mode 100644 textwrap/tests/version-numbers.rs create mode 100644 unicode-segmentation/.gitignore create mode 100644 unicode-segmentation/.travis.yml create mode 100644 unicode-segmentation/COPYRIGHT create mode 100644 unicode-segmentation/Cargo.toml create mode 100644 unicode-segmentation/LICENSE-APACHE create mode 100644 unicode-segmentation/LICENSE-MIT create mode 100644 unicode-segmentation/README.md create mode 100755 unicode-segmentation/scripts/unicode.py create mode 100755 unicode-segmentation/scripts/unicode_gen_breaktests.py create mode 100644 unicode-segmentation/src/grapheme.rs create mode 100644 unicode-segmentation/src/lib.rs create mode 100644 unicode-segmentation/src/sentence.rs create mode 100644 unicode-segmentation/src/tables.rs create mode 100644 unicode-segmentation/src/test.rs create mode 100644 unicode-segmentation/src/testdata.rs create mode 100644 unicode-segmentation/src/word.rs create mode 100644 unicode-width/.gitignore create mode 100644 unicode-width/.travis.yml create mode 100644 unicode-width/COPYRIGHT create mode 100644 unicode-width/Cargo.toml create mode 100644 unicode-width/LICENSE-APACHE create mode 100644 unicode-width/LICENSE-MIT create mode 100644 unicode-width/README.md create mode 100755 unicode-width/scripts/unicode.py create mode 100644 unicode-width/src/lib.rs create mode 100644 unicode-width/src/tables.rs create mode 100644 unicode-width/src/tests.rs create mode 100644 unicode-xid/.gitignore create mode 100644 unicode-xid/.travis.yml create mode 100644 unicode-xid/COPYRIGHT create mode 100644 unicode-xid/Cargo.toml create mode 100644 unicode-xid/LICENSE-APACHE create mode 100644 unicode-xid/LICENSE-MIT create mode 100644 unicode-xid/README.md create mode 100755 unicode-xid/scripts/unicode.py create mode 100644 unicode-xid/src/lib.rs create mode 100644 unicode-xid/src/tables.rs create mode 100644 unicode-xid/src/tests.rs diff --git a/bitflags/.gitignore b/bitflags/.gitignore new file mode 100644 index 0000000..fbd9642 --- /dev/null +++ b/bitflags/.gitignore @@ -0,0 +1,4 @@ +target +Cargo.lock + +/.idea/ diff --git a/bitflags/.travis.yml b/bitflags/.travis.yml new file mode 100644 index 0000000..9dd45c4 --- /dev/null +++ b/bitflags/.travis.yml @@ -0,0 +1,39 @@ +branches: + except: + - /.*(.tmp)$/ + +language: rust +matrix: + include: + # This version is tested to avoid unintentional bumping of the minimum supported Rust version + - rust: 1.20.0 + env: + - LABEL="msrv" + script: + - cargo test + - rust: stable + env: + - LABEL="no-std" + script: + - rustup target add thumbv6m-none-eabi + - cargo build --features example_generated --target thumbv6m-none-eabi + - rust: nightly + env: + - LABEL="compiletest" + script: + - cargo test + - cargo test -p test_suite --features unstable + - rust: stable + - rust: stable + os: osx + - rust: beta + allow_failures: + - rust: nightly + +sudo: false +script: + - cargo test --all + +notifications: + email: + on_success: never diff --git a/bitflags/CHANGELOG.md b/bitflags/CHANGELOG.md new file mode 100644 index 0000000..0d49101 --- /dev/null +++ b/bitflags/CHANGELOG.md @@ -0,0 +1,149 @@ +# 1.2.1 + +- Remove extraneous `#[inline]` attributes ([#194]) + +[#194]: https://github.com/bitflags/bitflags/pull/194 + +# 1.2.0 + +- Fix typo: {Lower, Upper}Exp - {Lower, Upper}Hex ([#183]) + +- Add support for "unknown" bits ([#188]) + +[#183]: https://github.com/rust-lang-nursery/bitflags/pull/183 +[#188]: https://github.com/rust-lang-nursery/bitflags/pull/188 + +# 1.1.0 + +This is a re-release of `1.0.5`, which was yanked due to a bug in the RLS. + +# 1.0.5 + +- Use compiletest_rs flags supported by stable toolchain ([#171]) + +- Put the user provided attributes first ([#173]) + +- Make bitflags methods `const` on newer compilers ([#175]) + +[#171]: https://github.com/rust-lang-nursery/bitflags/pull/171 +[#173]: https://github.com/rust-lang-nursery/bitflags/pull/173 +[#175]: https://github.com/rust-lang-nursery/bitflags/pull/175 + +# 1.0.4 + +- Support Rust 2018 style macro imports ([#165]) + + ```rust + use bitflags::bitflags; + ``` + +[#165]: https://github.com/rust-lang-nursery/bitflags/pull/165 + +# 1.0.3 + +- Improve zero value flag handling and documentation ([#157]) + +[#157]: https://github.com/rust-lang-nursery/bitflags/pull/157 + +# 1.0.2 + +- 30% improvement in compile time of bitflags crate ([#156]) + +- Documentation improvements ([#153]) + +- Implementation cleanup ([#149]) + +[#156]: https://github.com/rust-lang-nursery/bitflags/pull/156 +[#153]: https://github.com/rust-lang-nursery/bitflags/pull/153 +[#149]: https://github.com/rust-lang-nursery/bitflags/pull/149 + +# 1.0.1 +- Add support for `pub(restricted)` specifier on the bitflags struct ([#135]) +- Optimize performance of `all()` when called from a separate crate ([#136]) + +[#135]: https://github.com/rust-lang-nursery/bitflags/pull/135 +[#136]: https://github.com/rust-lang-nursery/bitflags/pull/136 + +# 1.0.0 +- **[breaking change]** Macro now generates [associated constants](https://doc.rust-lang.org/reference/items.html#associated-constants) ([#24]) + +- **[breaking change]** Minimum supported version is Rust **1.20**, due to usage of associated constants + +- After being broken in 0.9, the `#[deprecated]` attribute is now supported again ([#112]) + +- Other improvements to unit tests and documentation ([#106] and [#115]) + +[#24]: https://github.com/rust-lang-nursery/bitflags/pull/24 +[#106]: https://github.com/rust-lang-nursery/bitflags/pull/106 +[#112]: https://github.com/rust-lang-nursery/bitflags/pull/112 +[#115]: https://github.com/rust-lang-nursery/bitflags/pull/115 + +## How to update your code to use associated constants +Assuming the following structure definition: +```rust +bitflags! { + struct Something: u8 { + const FOO = 0b01, + const BAR = 0b10 + } +} +``` +In 0.9 and older you could do: +```rust +let x = FOO.bits | BAR.bits; +``` +Now you must use: +```rust +let x = Something::FOO.bits | Something::BAR.bits; +``` + +# 0.9.1 +- Fix the implementation of `Formatting` traits when other formatting traits were present in scope ([#105]) + +[#105]: https://github.com/rust-lang-nursery/bitflags/pull/105 + +# 0.9.0 +- **[breaking change]** Use struct keyword instead of flags to define bitflag types ([#84]) + +- **[breaking change]** Terminate const items with semicolons instead of commas ([#87]) + +- Implement the `Hex`, `Octal`, and `Binary` formatting traits ([#86]) + +- Printing an empty flag value with the `Debug` trait now prints "(empty)" instead of nothing ([#85]) + +- The `bitflags!` macro can now be used inside of a fn body, to define a type local to that function ([#74]) + +[#74]: https://github.com/rust-lang-nursery/bitflags/pull/74 +[#84]: https://github.com/rust-lang-nursery/bitflags/pull/84 +[#85]: https://github.com/rust-lang-nursery/bitflags/pull/85 +[#86]: https://github.com/rust-lang-nursery/bitflags/pull/86 +[#87]: https://github.com/rust-lang-nursery/bitflags/pull/87 + +# 0.8.2 +- Update feature flag used when building bitflags as a dependency of the Rust toolchain + +# 0.8.1 +- Allow bitflags to be used as a dependency of the Rust toolchain + +# 0.8.0 +- Add support for the experimental `i128` and `u128` integer types ([#57]) +- Add set method: `flags.set(SOME_FLAG, true)` or `flags.set(SOME_FLAG, false)` ([#55]) + This may break code that defines its own set method + +[#55]: https://github.com/rust-lang-nursery/bitflags/pull/55 +[#57]: https://github.com/rust-lang-nursery/bitflags/pull/57 + +# 0.7.1 +*(yanked)* + +# 0.7.0 +- Implement the Extend trait ([#49]) +- Allow definitions inside the `bitflags!` macro to refer to items imported from other modules ([#51]) + +[#49]: https://github.com/rust-lang-nursery/bitflags/pull/49 +[#51]: https://github.com/rust-lang-nursery/bitflags/pull/51 + +# 0.6.0 +- The `no_std` feature was removed as it is now the default +- The `assignment_operators` feature was remove as it is now enabled by default +- Some clippy suggestions have been applied diff --git a/bitflags/CODE_OF_CONDUCT.md b/bitflags/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..f7add90 --- /dev/null +++ b/bitflags/CODE_OF_CONDUCT.md @@ -0,0 +1,73 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +education, socio-economic status, nationality, personal appearance, race, +religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at coc@senaite.org. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org \ No newline at end of file diff --git a/bitflags/Cargo.toml b/bitflags/Cargo.toml new file mode 100644 index 0000000..afbe066 --- /dev/null +++ b/bitflags/Cargo.toml @@ -0,0 +1,37 @@ +[package] + +name = "bitflags" +# NB: When modifying, also modify: +# 1. html_root_url in lib.rs +# 2. number in readme (for breaking changes) +version = "1.2.1" +authors = ["The Rust Project Developers"] +license = "MIT/Apache-2.0" +keywords = ["bit", "bitmask", "bitflags", "flags"] +readme = "README.md" +repository = "https://github.com/bitflags/bitflags" +homepage = "https://github.com/bitflags/bitflags" +documentation = "https://docs.rs/bitflags" +categories = ["no-std"] +description = """ +A macro to generate structures which behave like bitflags. +""" +exclude = [ + ".travis.yml", + "appveyor.yml", + "bors.toml" +] +build = "build.rs" + +[badges] +travis-ci = { repository = "bitflags/bitflags" } + +[features] +default = [] +example_generated = [] + +[package.metadata.docs.rs] +features = [ "example_generated" ] + +[workspace] +members = ["test_suite"] diff --git a/bitflags/LICENSE-APACHE b/bitflags/LICENSE-APACHE new file mode 100644 index 0000000..16fe87b --- /dev/null +++ b/bitflags/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/bitflags/LICENSE-MIT b/bitflags/LICENSE-MIT new file mode 100644 index 0000000..39d4bdb --- /dev/null +++ b/bitflags/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2014 The Rust Project Developers + +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/bitflags/README.md b/bitflags/README.md new file mode 100644 index 0000000..df12934 --- /dev/null +++ b/bitflags/README.md @@ -0,0 +1,34 @@ +bitflags +======== + +[![Build Status](https://travis-ci.com/bitflags/bitflags.svg?branch=master)](https://travis-ci.com/bitflags/bitflags) +[![Join the chat at https://gitter.im/bitflags/Lobby](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/bitflags/Lobby?utm_source=badge&utm_medium=badge&utm_content=badge) +[![Latest version](https://img.shields.io/crates/v/bitflags.svg)](https://crates.io/crates/bitflags) +[![Documentation](https://docs.rs/bitflags/badge.svg)](https://docs.rs/bitflags) +![Minimum rustc version](https://img.shields.io/badge/rustc-1.20+-yellow.svg) +![License](https://img.shields.io/crates/l/bitflags.svg) + +A Rust macro to generate structures which behave like a set of bitflags + +- [Documentation](https://docs.rs/bitflags) +- [Release notes](https://github.com/bitflags/bitflags/releases) + +## Usage + +Add this to your `Cargo.toml`: + +```toml +[dependencies] +bitflags = "1.0" +``` + +and this to your crate root: + +```rust +#[macro_use] +extern crate bitflags; +``` + +## Rust Version Support + +The minimum supported Rust version is 1.20 due to use of associated constants. diff --git a/bitflags/bors.toml b/bitflags/bors.toml new file mode 100644 index 0000000..713ea9b --- /dev/null +++ b/bitflags/bors.toml @@ -0,0 +1,3 @@ +status = [ + "continuous-integration/travis-ci/push", +] diff --git a/bitflags/build.rs b/bitflags/build.rs new file mode 100644 index 0000000..985757a --- /dev/null +++ b/bitflags/build.rs @@ -0,0 +1,44 @@ +use std::env; +use std::process::Command; +use std::str::{self, FromStr}; + +fn main(){ + let minor = match rustc_minor_version() { + Some(minor) => minor, + None => return, + }; + + // const fn stabilized in Rust 1.31: + if minor >= 31 { + println!("cargo:rustc-cfg=bitflags_const_fn"); + } +} + +fn rustc_minor_version() -> Option { + let rustc = match env::var_os("RUSTC") { + Some(rustc) => rustc, + None => return None, + }; + + let output = match Command::new(rustc).arg("--version").output() { + Ok(output) => output, + Err(_) => return None, + }; + + let version = match str::from_utf8(&output.stdout) { + Ok(version) => version, + Err(_) => return None, + }; + + let mut pieces = version.split('.'); + if pieces.next() != Some("rustc 1") { + return None; + } + + let next = match pieces.next() { + Some(next) => next, + None => return None, + }; + + u32::from_str(next).ok() +} \ No newline at end of file diff --git a/bitflags/src/example_generated.rs b/bitflags/src/example_generated.rs new file mode 100644 index 0000000..cf188d9 --- /dev/null +++ b/bitflags/src/example_generated.rs @@ -0,0 +1,14 @@ +//! This module shows an example of code generated by the macro. **IT MUST NOT BE USED OUTSIDE THIS +//! CRATE**. + +bitflags! { + /// This is the same `Flags` struct defined in the [crate level example](../index.html#example). + /// Note that this struct is just for documentation purposes only, it must not be used outside + /// this crate. + pub struct Flags: u32 { + const A = 0b00000001; + const B = 0b00000010; + const C = 0b00000100; + const ABC = Self::A.bits | Self::B.bits | Self::C.bits; + } +} diff --git a/bitflags/src/lib.rs b/bitflags/src/lib.rs new file mode 100644 index 0000000..3929b02 --- /dev/null +++ b/bitflags/src/lib.rs @@ -0,0 +1,1430 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// 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. + +//! A typesafe bitmask flag generator useful for sets of C-style bitmask flags. +//! It can be used for creating typesafe wrappers around C APIs. +//! +//! The `bitflags!` macro generates a `struct` that manages a set of flags. The +//! flags should only be defined for integer types, otherwise unexpected type +//! errors may occur at compile time. +//! +//! # Example +//! +//! ``` +//! #[macro_use] +//! extern crate bitflags; +//! +//! bitflags! { +//! struct Flags: u32 { +//! const A = 0b00000001; +//! const B = 0b00000010; +//! const C = 0b00000100; +//! const ABC = Self::A.bits | Self::B.bits | Self::C.bits; +//! } +//! } +//! +//! fn main() { +//! let e1 = Flags::A | Flags::C; +//! let e2 = Flags::B | Flags::C; +//! assert_eq!((e1 | e2), Flags::ABC); // union +//! assert_eq!((e1 & e2), Flags::C); // intersection +//! assert_eq!((e1 - e2), Flags::A); // set difference +//! assert_eq!(!e2, Flags::A); // set complement +//! } +//! ``` +//! +//! See [`example_generated::Flags`](./example_generated/struct.Flags.html) for documentation of code +//! generated by the above `bitflags!` expansion. +//! +//! The generated `struct`s can also be extended with type and trait +//! implementations: +//! +//! ``` +//! #[macro_use] +//! extern crate bitflags; +//! +//! use std::fmt; +//! +//! bitflags! { +//! struct Flags: u32 { +//! const A = 0b00000001; +//! const B = 0b00000010; +//! } +//! } +//! +//! impl Flags { +//! pub fn clear(&mut self) { +//! self.bits = 0; // The `bits` field can be accessed from within the +//! // same module where the `bitflags!` macro was invoked. +//! } +//! } +//! +//! impl fmt::Display for Flags { +//! fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +//! write!(f, "hi!") +//! } +//! } +//! +//! fn main() { +//! let mut flags = Flags::A | Flags::B; +//! flags.clear(); +//! assert!(flags.is_empty()); +//! assert_eq!(format!("{}", flags), "hi!"); +//! assert_eq!(format!("{:?}", Flags::A | Flags::B), "A | B"); +//! assert_eq!(format!("{:?}", Flags::B), "B"); +//! } +//! ``` +//! +//! # Visibility +//! +//! The generated struct and its associated flag constants are not exported +//! out of the current module by default. A definition can be exported out of +//! the current module by adding `pub` before `flags`: +//! +//! ``` +//! #[macro_use] +//! extern crate bitflags; +//! +//! mod example { +//! bitflags! { +//! pub struct Flags1: u32 { +//! const A = 0b00000001; +//! } +//! } +//! bitflags! { +//! # pub +//! struct Flags2: u32 { +//! const B = 0b00000010; +//! } +//! } +//! } +//! +//! fn main() { +//! let flag1 = example::Flags1::A; +//! let flag2 = example::Flags2::B; // error: const `B` is private +//! } +//! ``` +//! +//! # Attributes +//! +//! Attributes can be attached to the generated `struct` by placing them +//! before the `flags` keyword. +//! +//! # Trait implementations +//! +//! The `Copy`, `Clone`, `PartialEq`, `Eq`, `PartialOrd`, `Ord` and `Hash` +//! traits automatically derived for the `struct` using the `derive` attribute. +//! Additional traits can be derived by providing an explicit `derive` +//! attribute on `flags`. +//! +//! The `Extend` and `FromIterator` traits are implemented for the `struct`, +//! too: `Extend` adds the union of the instances of the `struct` iterated over, +//! while `FromIterator` calculates the union. +//! +//! The `Binary`, `Debug`, `LowerHex`, `Octal` and `UpperHex` trait is also +//! implemented by displaying the bits value of the internal struct. +//! +//! ## Operators +//! +//! The following operator traits are implemented for the generated `struct`: +//! +//! - `BitOr` and `BitOrAssign`: union +//! - `BitAnd` and `BitAndAssign`: intersection +//! - `BitXor` and `BitXorAssign`: toggle +//! - `Sub` and `SubAssign`: set difference +//! - `Not`: set complement +//! +//! # Methods +//! +//! The following methods are defined for the generated `struct`: +//! +//! - `empty`: an empty set of flags +//! - `all`: the set of all defined flags +//! - `bits`: the raw value of the flags currently stored +//! - `from_bits`: convert from underlying bit representation, unless that +//! representation contains bits that do not correspond to a +//! defined flag +//! - `from_bits_truncate`: convert from underlying bit representation, dropping +//! any bits that do not correspond to defined flags +//! - `from_bits_unchecked`: convert from underlying bit representation, keeping +//! all bits (even those not corresponding to defined +//! flags) +//! - `is_empty`: `true` if no flags are currently stored +//! - `is_all`: `true` if currently set flags exactly equal all defined flags +//! - `intersects`: `true` if there are flags common to both `self` and `other` +//! - `contains`: `true` all of the flags in `other` are contained within `self` +//! - `insert`: inserts the specified flags in-place +//! - `remove`: removes the specified flags in-place +//! - `toggle`: the specified flags will be inserted if not present, and removed +//! if they are. +//! - `set`: inserts or removes the specified flags depending on the passed value +//! +//! ## Default +//! +//! The `Default` trait is not automatically implemented for the generated struct. +//! +//! If your default value is equal to `0` (which is the same value as calling `empty()` +//! on the generated struct), you can simply derive `Default`: +//! +//! ``` +//! #[macro_use] +//! extern crate bitflags; +//! +//! bitflags! { +//! // Results in default value with bits: 0 +//! #[derive(Default)] +//! struct Flags: u32 { +//! const A = 0b00000001; +//! const B = 0b00000010; +//! const C = 0b00000100; +//! } +//! } +//! +//! fn main() { +//! let derived_default: Flags = Default::default(); +//! assert_eq!(derived_default.bits(), 0); +//! } +//! ``` +//! +//! If your default value is not equal to `0` you need to implement `Default` yourself: +//! +//! ``` +//! #[macro_use] +//! extern crate bitflags; +//! +//! bitflags! { +//! struct Flags: u32 { +//! const A = 0b00000001; +//! const B = 0b00000010; +//! const C = 0b00000100; +//! } +//! } +//! +//! // explicit `Default` implementation +//! impl Default for Flags { +//! fn default() -> Flags { +//! Flags::A | Flags::C +//! } +//! } +//! +//! fn main() { +//! let implemented_default: Flags = Default::default(); +//! assert_eq!(implemented_default, (Flags::A | Flags::C)); +//! } +//! ``` +//! +//! # Zero Flags +//! +//! Flags with a value equal to zero will have some strange behavior that one should be aware of. +//! +//! ``` +//! #[macro_use] +//! extern crate bitflags; +//! +//! bitflags! { +//! struct Flags: u32 { +//! const NONE = 0b00000000; +//! const SOME = 0b00000001; +//! } +//! } +//! +//! fn main() { +//! let empty = Flags::empty(); +//! let none = Flags::NONE; +//! let some = Flags::SOME; +//! +//! // Zero flags are treated as always present +//! assert!(empty.contains(Flags::NONE)); +//! assert!(none.contains(Flags::NONE)); +//! assert!(some.contains(Flags::NONE)); +//! +//! // Zero flags will be ignored when testing for emptiness +//! assert!(none.is_empty()); +//! } +//! ``` + +#![no_std] +#![doc(html_root_url = "https://docs.rs/bitflags/1.2.1")] + +#[cfg(test)] +#[macro_use] +extern crate std; + +// Re-export libcore using an alias so that the macros can work without +// requiring `extern crate core` downstream. +#[doc(hidden)] +pub extern crate core as _core; + +/// The macro used to generate the flag structure. +/// +/// See the [crate level docs](../bitflags/index.html) for complete documentation. +/// +/// # Example +/// +/// ``` +/// #[macro_use] +/// extern crate bitflags; +/// +/// bitflags! { +/// struct Flags: u32 { +/// const A = 0b00000001; +/// const B = 0b00000010; +/// const C = 0b00000100; +/// const ABC = Self::A.bits | Self::B.bits | Self::C.bits; +/// } +/// } +/// +/// fn main() { +/// let e1 = Flags::A | Flags::C; +/// let e2 = Flags::B | Flags::C; +/// assert_eq!((e1 | e2), Flags::ABC); // union +/// assert_eq!((e1 & e2), Flags::C); // intersection +/// assert_eq!((e1 - e2), Flags::A); // set difference +/// assert_eq!(!e2, Flags::A); // set complement +/// } +/// ``` +/// +/// The generated `struct`s can also be extended with type and trait +/// implementations: +/// +/// ``` +/// #[macro_use] +/// extern crate bitflags; +/// +/// use std::fmt; +/// +/// bitflags! { +/// struct Flags: u32 { +/// const A = 0b00000001; +/// const B = 0b00000010; +/// } +/// } +/// +/// impl Flags { +/// pub fn clear(&mut self) { +/// self.bits = 0; // The `bits` field can be accessed from within the +/// // same module where the `bitflags!` macro was invoked. +/// } +/// } +/// +/// impl fmt::Display for Flags { +/// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +/// write!(f, "hi!") +/// } +/// } +/// +/// fn main() { +/// let mut flags = Flags::A | Flags::B; +/// flags.clear(); +/// assert!(flags.is_empty()); +/// assert_eq!(format!("{}", flags), "hi!"); +/// assert_eq!(format!("{:?}", Flags::A | Flags::B), "A | B"); +/// assert_eq!(format!("{:?}", Flags::B), "B"); +/// } +/// ``` +#[macro_export(local_inner_macros)] +macro_rules! bitflags { + ( + $(#[$outer:meta])* + pub struct $BitFlags:ident: $T:ty { + $( + $(#[$inner:ident $($args:tt)*])* + const $Flag:ident = $value:expr; + )+ + } + ) => { + __bitflags! { + $(#[$outer])* + (pub) $BitFlags: $T { + $( + $(#[$inner $($args)*])* + $Flag = $value; + )+ + } + } + }; + ( + $(#[$outer:meta])* + struct $BitFlags:ident: $T:ty { + $( + $(#[$inner:ident $($args:tt)*])* + const $Flag:ident = $value:expr; + )+ + } + ) => { + __bitflags! { + $(#[$outer])* + () $BitFlags: $T { + $( + $(#[$inner $($args)*])* + $Flag = $value; + )+ + } + } + }; + ( + $(#[$outer:meta])* + pub ($($vis:tt)+) struct $BitFlags:ident: $T:ty { + $( + $(#[$inner:ident $($args:tt)*])* + const $Flag:ident = $value:expr; + )+ + } + ) => { + __bitflags! { + $(#[$outer])* + (pub ($($vis)+)) $BitFlags: $T { + $( + $(#[$inner $($args)*])* + $Flag = $value; + )+ + } + } + }; +} + +#[macro_export(local_inner_macros)] +#[doc(hidden)] +macro_rules! __bitflags { + ( + $(#[$outer:meta])* + ($($vis:tt)*) $BitFlags:ident: $T:ty { + $( + $(#[$inner:ident $($args:tt)*])* + $Flag:ident = $value:expr; + )+ + } + ) => { + $(#[$outer])* + #[derive(Copy, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)] + $($vis)* struct $BitFlags { + bits: $T, + } + + __impl_bitflags! { + $BitFlags: $T { + $( + $(#[$inner $($args)*])* + $Flag = $value; + )+ + } + } + }; +} + +#[macro_export(local_inner_macros)] +#[doc(hidden)] +#[cfg(bitflags_const_fn)] +macro_rules! __fn_bitflags { + ( + $(# $attr_args:tt)* + const fn $($item:tt)* + ) => { + $(# $attr_args)* + const fn $($item)* + }; + ( + $(# $attr_args:tt)* + pub const fn $($item:tt)* + ) => { + $(# $attr_args)* + pub const fn $($item)* + }; + ( + $(# $attr_args:tt)* + pub const unsafe fn $($item:tt)* + ) => { + $(# $attr_args)* + pub const unsafe fn $($item)* + }; +} + +#[macro_export(local_inner_macros)] +#[doc(hidden)] +#[cfg(not(bitflags_const_fn))] +macro_rules! __fn_bitflags { + ( + $(# $attr_args:tt)* + const fn $($item:tt)* + ) => { + $(# $attr_args)* + fn $($item)* + }; + ( + $(# $attr_args:tt)* + pub const fn $($item:tt)* + ) => { + $(# $attr_args)* + pub fn $($item)* + }; + ( + $(# $attr_args:tt)* + pub const unsafe fn $($item:tt)* + ) => { + $(# $attr_args)* + pub unsafe fn $($item)* + }; +} + +#[macro_export(local_inner_macros)] +#[doc(hidden)] +macro_rules! __impl_bitflags { + ( + $BitFlags:ident: $T:ty { + $( + $(#[$attr:ident $($args:tt)*])* + $Flag:ident = $value:expr; + )+ + } + ) => { + impl $crate::_core::fmt::Debug for $BitFlags { + fn fmt(&self, f: &mut $crate::_core::fmt::Formatter) -> $crate::_core::fmt::Result { + // This convoluted approach is to handle #[cfg]-based flag + // omission correctly. For example it needs to support: + // + // #[cfg(unix)] const A: Flag = /* ... */; + // #[cfg(windows)] const B: Flag = /* ... */; + + // Unconditionally define a check for every flag, even disabled + // ones. + #[allow(non_snake_case)] + trait __BitFlags { + $( + #[inline] + fn $Flag(&self) -> bool { false } + )+ + } + + // Conditionally override the check for just those flags that + // are not #[cfg]ed away. + impl __BitFlags for $BitFlags { + $( + __impl_bitflags! { + #[allow(deprecated)] + #[inline] + $(? #[$attr $($args)*])* + fn $Flag(&self) -> bool { + if Self::$Flag.bits == 0 && self.bits != 0 { + false + } else { + self.bits & Self::$Flag.bits == Self::$Flag.bits + } + } + } + )+ + } + + let mut first = true; + $( + if <$BitFlags as __BitFlags>::$Flag(self) { + if !first { + f.write_str(" | ")?; + } + first = false; + f.write_str(__bitflags_stringify!($Flag))?; + } + )+ + let extra_bits = self.bits & !$BitFlags::all().bits(); + if extra_bits != 0 { + if !first { + f.write_str(" | ")?; + } + first = false; + f.write_str("0x")?; + $crate::_core::fmt::LowerHex::fmt(&extra_bits, f)?; + } + if first { + f.write_str("(empty)")?; + } + Ok(()) + } + } + impl $crate::_core::fmt::Binary for $BitFlags { + fn fmt(&self, f: &mut $crate::_core::fmt::Formatter) -> $crate::_core::fmt::Result { + $crate::_core::fmt::Binary::fmt(&self.bits, f) + } + } + impl $crate::_core::fmt::Octal for $BitFlags { + fn fmt(&self, f: &mut $crate::_core::fmt::Formatter) -> $crate::_core::fmt::Result { + $crate::_core::fmt::Octal::fmt(&self.bits, f) + } + } + impl $crate::_core::fmt::LowerHex for $BitFlags { + fn fmt(&self, f: &mut $crate::_core::fmt::Formatter) -> $crate::_core::fmt::Result { + $crate::_core::fmt::LowerHex::fmt(&self.bits, f) + } + } + impl $crate::_core::fmt::UpperHex for $BitFlags { + fn fmt(&self, f: &mut $crate::_core::fmt::Formatter) -> $crate::_core::fmt::Result { + $crate::_core::fmt::UpperHex::fmt(&self.bits, f) + } + } + + #[allow(dead_code)] + impl $BitFlags { + $( + $(#[$attr $($args)*])* + pub const $Flag: $BitFlags = $BitFlags { bits: $value }; + )+ + + __fn_bitflags! { + /// Returns an empty set of flags + #[inline] + pub const fn empty() -> $BitFlags { + $BitFlags { bits: 0 } + } + } + + __fn_bitflags! { + /// Returns the set containing all flags. + #[inline] + pub const fn all() -> $BitFlags { + // See `Debug::fmt` for why this approach is taken. + #[allow(non_snake_case)] + trait __BitFlags { + $( + const $Flag: $T = 0; + )+ + } + impl __BitFlags for $BitFlags { + $( + __impl_bitflags! { + #[allow(deprecated)] + $(? #[$attr $($args)*])* + const $Flag: $T = Self::$Flag.bits; + } + )+ + } + $BitFlags { bits: $(<$BitFlags as __BitFlags>::$Flag)|+ } + } + } + + __fn_bitflags! { + /// Returns the raw value of the flags currently stored. + #[inline] + pub const fn bits(&self) -> $T { + self.bits + } + } + + /// Convert from underlying bit representation, unless that + /// representation contains bits that do not correspond to a flag. + #[inline] + pub fn from_bits(bits: $T) -> $crate::_core::option::Option<$BitFlags> { + if (bits & !$BitFlags::all().bits()) == 0 { + $crate::_core::option::Option::Some($BitFlags { bits }) + } else { + $crate::_core::option::Option::None + } + } + + __fn_bitflags! { + /// Convert from underlying bit representation, dropping any bits + /// that do not correspond to flags. + #[inline] + pub const fn from_bits_truncate(bits: $T) -> $BitFlags { + $BitFlags { bits: bits & $BitFlags::all().bits } + } + } + + __fn_bitflags! { + /// Convert from underlying bit representation, preserving all + /// bits (even those not corresponding to a defined flag). + #[inline] + pub const unsafe fn from_bits_unchecked(bits: $T) -> $BitFlags { + $BitFlags { bits } + } + } + + __fn_bitflags! { + /// Returns `true` if no flags are currently stored. + #[inline] + pub const fn is_empty(&self) -> bool { + self.bits() == $BitFlags::empty().bits() + } + } + + __fn_bitflags! { + /// Returns `true` if all flags are currently set. + #[inline] + pub const fn is_all(&self) -> bool { + self.bits == $BitFlags::all().bits + } + } + + __fn_bitflags! { + /// Returns `true` if there are flags common to both `self` and `other`. + #[inline] + pub const fn intersects(&self, other: $BitFlags) -> bool { + !$BitFlags{ bits: self.bits & other.bits}.is_empty() + } + } + + __fn_bitflags! { + /// Returns `true` all of the flags in `other` are contained within `self`. + #[inline] + pub const fn contains(&self, other: $BitFlags) -> bool { + (self.bits & other.bits) == other.bits + } + } + + /// Inserts the specified flags in-place. + #[inline] + pub fn insert(&mut self, other: $BitFlags) { + self.bits |= other.bits; + } + + /// Removes the specified flags in-place. + #[inline] + pub fn remove(&mut self, other: $BitFlags) { + self.bits &= !other.bits; + } + + /// Toggles the specified flags in-place. + #[inline] + pub fn toggle(&mut self, other: $BitFlags) { + self.bits ^= other.bits; + } + + /// Inserts or removes the specified flags depending on the passed value. + #[inline] + pub fn set(&mut self, other: $BitFlags, value: bool) { + if value { + self.insert(other); + } else { + self.remove(other); + } + } + } + + impl $crate::_core::ops::BitOr for $BitFlags { + type Output = $BitFlags; + + /// Returns the union of the two sets of flags. + #[inline] + fn bitor(self, other: $BitFlags) -> $BitFlags { + $BitFlags { bits: self.bits | other.bits } + } + } + + impl $crate::_core::ops::BitOrAssign for $BitFlags { + + /// Adds the set of flags. + #[inline] + fn bitor_assign(&mut self, other: $BitFlags) { + self.bits |= other.bits; + } + } + + impl $crate::_core::ops::BitXor for $BitFlags { + type Output = $BitFlags; + + /// Returns the left flags, but with all the right flags toggled. + #[inline] + fn bitxor(self, other: $BitFlags) -> $BitFlags { + $BitFlags { bits: self.bits ^ other.bits } + } + } + + impl $crate::_core::ops::BitXorAssign for $BitFlags { + + /// Toggles the set of flags. + #[inline] + fn bitxor_assign(&mut self, other: $BitFlags) { + self.bits ^= other.bits; + } + } + + impl $crate::_core::ops::BitAnd for $BitFlags { + type Output = $BitFlags; + + /// Returns the intersection between the two sets of flags. + #[inline] + fn bitand(self, other: $BitFlags) -> $BitFlags { + $BitFlags { bits: self.bits & other.bits } + } + } + + impl $crate::_core::ops::BitAndAssign for $BitFlags { + + /// Disables all flags disabled in the set. + #[inline] + fn bitand_assign(&mut self, other: $BitFlags) { + self.bits &= other.bits; + } + } + + impl $crate::_core::ops::Sub for $BitFlags { + type Output = $BitFlags; + + /// Returns the set difference of the two sets of flags. + #[inline] + fn sub(self, other: $BitFlags) -> $BitFlags { + $BitFlags { bits: self.bits & !other.bits } + } + } + + impl $crate::_core::ops::SubAssign for $BitFlags { + + /// Disables all flags enabled in the set. + #[inline] + fn sub_assign(&mut self, other: $BitFlags) { + self.bits &= !other.bits; + } + } + + impl $crate::_core::ops::Not for $BitFlags { + type Output = $BitFlags; + + /// Returns the complement of this set of flags. + #[inline] + fn not(self) -> $BitFlags { + $BitFlags { bits: !self.bits } & $BitFlags::all() + } + } + + impl $crate::_core::iter::Extend<$BitFlags> for $BitFlags { + fn extend>(&mut self, iterator: T) { + for item in iterator { + self.insert(item) + } + } + } + + impl $crate::_core::iter::FromIterator<$BitFlags> for $BitFlags { + fn from_iter>(iterator: T) -> $BitFlags { + let mut result = Self::empty(); + result.extend(iterator); + result + } + } + }; + + // Every attribute that the user writes on a const is applied to the + // corresponding const that we generate, but within the implementation of + // Debug and all() we want to ignore everything but #[cfg] attributes. In + // particular, including a #[deprecated] attribute on those items would fail + // to compile. + // https://github.com/bitflags/bitflags/issues/109 + // + // Input: + // + // ? #[cfg(feature = "advanced")] + // ? #[deprecated(note = "Use somthing else.")] + // ? #[doc = r"High quality documentation."] + // fn f() -> i32 { /* ... */ } + // + // Output: + // + // #[cfg(feature = "advanced")] + // fn f() -> i32 { /* ... */ } + ( + $(#[$filtered:meta])* + ? #[cfg $($cfgargs:tt)*] + $(? #[$rest:ident $($restargs:tt)*])* + fn $($item:tt)* + ) => { + __impl_bitflags! { + $(#[$filtered])* + #[cfg $($cfgargs)*] + $(? #[$rest $($restargs)*])* + fn $($item)* + } + }; + ( + $(#[$filtered:meta])* + // $next != `cfg` + ? #[$next:ident $($nextargs:tt)*] + $(? #[$rest:ident $($restargs:tt)*])* + fn $($item:tt)* + ) => { + __impl_bitflags! { + $(#[$filtered])* + // $next filtered out + $(? #[$rest $($restargs)*])* + fn $($item)* + } + }; + ( + $(#[$filtered:meta])* + fn $($item:tt)* + ) => { + $(#[$filtered])* + fn $($item)* + }; + + // Every attribute that the user writes on a const is applied to the + // corresponding const that we generate, but within the implementation of + // Debug and all() we want to ignore everything but #[cfg] attributes. In + // particular, including a #[deprecated] attribute on those items would fail + // to compile. + // https://github.com/bitflags/bitflags/issues/109 + // + // const version + // + // Input: + // + // ? #[cfg(feature = "advanced")] + // ? #[deprecated(note = "Use somthing else.")] + // ? #[doc = r"High quality documentation."] + // const f: i32 { /* ... */ } + // + // Output: + // + // #[cfg(feature = "advanced")] + // const f: i32 { /* ... */ } + ( + $(#[$filtered:meta])* + ? #[cfg $($cfgargs:tt)*] + $(? #[$rest:ident $($restargs:tt)*])* + const $($item:tt)* + ) => { + __impl_bitflags! { + $(#[$filtered])* + #[cfg $($cfgargs)*] + $(? #[$rest $($restargs)*])* + const $($item)* + } + }; + ( + $(#[$filtered:meta])* + // $next != `cfg` + ? #[$next:ident $($nextargs:tt)*] + $(? #[$rest:ident $($restargs:tt)*])* + const $($item:tt)* + ) => { + __impl_bitflags! { + $(#[$filtered])* + // $next filtered out + $(? #[$rest $($restargs)*])* + const $($item)* + } + }; + ( + $(#[$filtered:meta])* + const $($item:tt)* + ) => { + $(#[$filtered])* + const $($item)* + }; +} + +// Same as std::stringify but callable from __impl_bitflags, which needs to use +// local_inner_macros so can only directly call macros from this crate. +#[macro_export] +#[doc(hidden)] +macro_rules! __bitflags_stringify { + ($s:ident) => { + stringify!($s) + }; +} + +#[cfg(feature = "example_generated")] +pub mod example_generated; + +#[cfg(test)] +mod tests { + use std::collections::hash_map::DefaultHasher; + use std::hash::{Hash, Hasher}; + + bitflags! { + #[doc = "> The first principle is that you must not fool yourself — and"] + #[doc = "> you are the easiest person to fool."] + #[doc = "> "] + #[doc = "> - Richard Feynman"] + struct Flags: u32 { + const A = 0b00000001; + #[doc = " macros are way better at generating code than trans is"] + const B = 0b00000010; + const C = 0b00000100; + #[doc = "* cmr bed"] + #[doc = "* strcat table"] + #[doc = " wait what?"] + const ABC = Self::A.bits | Self::B.bits | Self::C.bits; + } + } + + bitflags! { + struct _CfgFlags: u32 { + #[cfg(unix)] + const _CFG_A = 0b01; + #[cfg(windows)] + const _CFG_B = 0b01; + #[cfg(unix)] + const _CFG_C = Self::_CFG_A.bits | 0b10; + } + } + + bitflags! { + struct AnotherSetOfFlags: i8 { + const ANOTHER_FLAG = -1_i8; + } + } + + bitflags! { + struct LongFlags: u32 { + const LONG_A = 0b1111111111111111; + } + } + + #[test] + fn test_bits() { + assert_eq!(Flags::empty().bits(), 0b00000000); + assert_eq!(Flags::A.bits(), 0b00000001); + assert_eq!(Flags::ABC.bits(), 0b00000111); + + assert_eq!(AnotherSetOfFlags::empty().bits(), 0b00); + assert_eq!(AnotherSetOfFlags::ANOTHER_FLAG.bits(), !0_i8); + } + + #[test] + fn test_from_bits() { + assert_eq!(Flags::from_bits(0), Some(Flags::empty())); + assert_eq!(Flags::from_bits(0b1), Some(Flags::A)); + assert_eq!(Flags::from_bits(0b10), Some(Flags::B)); + assert_eq!(Flags::from_bits(0b11), Some(Flags::A | Flags::B)); + assert_eq!(Flags::from_bits(0b1000), None); + + assert_eq!( + AnotherSetOfFlags::from_bits(!0_i8), + Some(AnotherSetOfFlags::ANOTHER_FLAG) + ); + } + + #[test] + fn test_from_bits_truncate() { + assert_eq!(Flags::from_bits_truncate(0), Flags::empty()); + assert_eq!(Flags::from_bits_truncate(0b1), Flags::A); + assert_eq!(Flags::from_bits_truncate(0b10), Flags::B); + assert_eq!(Flags::from_bits_truncate(0b11), (Flags::A | Flags::B)); + assert_eq!(Flags::from_bits_truncate(0b1000), Flags::empty()); + assert_eq!(Flags::from_bits_truncate(0b1001), Flags::A); + + assert_eq!( + AnotherSetOfFlags::from_bits_truncate(0_i8), + AnotherSetOfFlags::empty() + ); + } + + #[test] + fn test_from_bits_unchecked() { + let extra = unsafe { Flags::from_bits_unchecked(0b1000) }; + assert_eq!(unsafe { Flags::from_bits_unchecked(0) }, Flags::empty()); + assert_eq!(unsafe { Flags::from_bits_unchecked(0b1) }, Flags::A); + assert_eq!(unsafe { Flags::from_bits_unchecked(0b10) }, Flags::B); + assert_eq!(unsafe { Flags::from_bits_unchecked(0b11) }, (Flags::A | Flags::B)); + assert_eq!(unsafe { Flags::from_bits_unchecked(0b1000) }, (extra | Flags::empty())); + assert_eq!(unsafe { Flags::from_bits_unchecked(0b1001) }, (extra | Flags::A)); + } + + #[test] + fn test_is_empty() { + assert!(Flags::empty().is_empty()); + assert!(!Flags::A.is_empty()); + assert!(!Flags::ABC.is_empty()); + + assert!(!AnotherSetOfFlags::ANOTHER_FLAG.is_empty()); + } + + #[test] + fn test_is_all() { + assert!(Flags::all().is_all()); + assert!(!Flags::A.is_all()); + assert!(Flags::ABC.is_all()); + + assert!(AnotherSetOfFlags::ANOTHER_FLAG.is_all()); + } + + #[test] + fn test_two_empties_do_not_intersect() { + let e1 = Flags::empty(); + let e2 = Flags::empty(); + assert!(!e1.intersects(e2)); + + assert!(AnotherSetOfFlags::ANOTHER_FLAG.intersects(AnotherSetOfFlags::ANOTHER_FLAG)); + } + + #[test] + fn test_empty_does_not_intersect_with_full() { + let e1 = Flags::empty(); + let e2 = Flags::ABC; + assert!(!e1.intersects(e2)); + } + + #[test] + fn test_disjoint_intersects() { + let e1 = Flags::A; + let e2 = Flags::B; + assert!(!e1.intersects(e2)); + } + + #[test] + fn test_overlapping_intersects() { + let e1 = Flags::A; + let e2 = Flags::A | Flags::B; + assert!(e1.intersects(e2)); + } + + #[test] + fn test_contains() { + let e1 = Flags::A; + let e2 = Flags::A | Flags::B; + assert!(!e1.contains(e2)); + assert!(e2.contains(e1)); + assert!(Flags::ABC.contains(e2)); + + assert!(AnotherSetOfFlags::ANOTHER_FLAG.contains(AnotherSetOfFlags::ANOTHER_FLAG)); + } + + #[test] + fn test_insert() { + let mut e1 = Flags::A; + let e2 = Flags::A | Flags::B; + e1.insert(e2); + assert_eq!(e1, e2); + + let mut e3 = AnotherSetOfFlags::empty(); + e3.insert(AnotherSetOfFlags::ANOTHER_FLAG); + assert_eq!(e3, AnotherSetOfFlags::ANOTHER_FLAG); + } + + #[test] + fn test_remove() { + let mut e1 = Flags::A | Flags::B; + let e2 = Flags::A | Flags::C; + e1.remove(e2); + assert_eq!(e1, Flags::B); + + let mut e3 = AnotherSetOfFlags::ANOTHER_FLAG; + e3.remove(AnotherSetOfFlags::ANOTHER_FLAG); + assert_eq!(e3, AnotherSetOfFlags::empty()); + } + + #[test] + fn test_operators() { + let e1 = Flags::A | Flags::C; + let e2 = Flags::B | Flags::C; + assert_eq!((e1 | e2), Flags::ABC); // union + assert_eq!((e1 & e2), Flags::C); // intersection + assert_eq!((e1 - e2), Flags::A); // set difference + assert_eq!(!e2, Flags::A); // set complement + assert_eq!(e1 ^ e2, Flags::A | Flags::B); // toggle + let mut e3 = e1; + e3.toggle(e2); + assert_eq!(e3, Flags::A | Flags::B); + + let mut m4 = AnotherSetOfFlags::empty(); + m4.toggle(AnotherSetOfFlags::empty()); + assert_eq!(m4, AnotherSetOfFlags::empty()); + } + + #[test] + fn test_operators_unchecked() { + let extra = unsafe { Flags::from_bits_unchecked(0b1000) }; + let e1 = Flags::A | Flags::C | extra; + let e2 = Flags::B | Flags::C; + assert_eq!((e1 | e2), (Flags::ABC | extra)); // union + assert_eq!((e1 & e2), Flags::C); // intersection + assert_eq!((e1 - e2), (Flags::A | extra)); // set difference + assert_eq!(!e2, Flags::A); // set complement + assert_eq!(!e1, Flags::B); // set complement + assert_eq!(e1 ^ e2, Flags::A | Flags::B | extra); // toggle + let mut e3 = e1; + e3.toggle(e2); + assert_eq!(e3, Flags::A | Flags::B | extra); + } + + #[test] + fn test_set() { + let mut e1 = Flags::A | Flags::C; + e1.set(Flags::B, true); + e1.set(Flags::C, false); + + assert_eq!(e1, Flags::A | Flags::B); + } + + #[test] + fn test_assignment_operators() { + let mut m1 = Flags::empty(); + let e1 = Flags::A | Flags::C; + // union + m1 |= Flags::A; + assert_eq!(m1, Flags::A); + // intersection + m1 &= e1; + assert_eq!(m1, Flags::A); + // set difference + m1 -= m1; + assert_eq!(m1, Flags::empty()); + // toggle + m1 ^= e1; + assert_eq!(m1, e1); + } + + + #[cfg(bitflags_const_fn)] + #[test] + fn test_const_fn() { + const _M1: Flags = Flags::empty(); + + const M2: Flags = Flags::A; + assert_eq!(M2, Flags::A); + + const M3: Flags = Flags::C; + assert_eq!(M3, Flags::C); + } + + #[test] + fn test_extend() { + let mut flags; + + flags = Flags::empty(); + flags.extend([].iter().cloned()); + assert_eq!(flags, Flags::empty()); + + flags = Flags::empty(); + flags.extend([Flags::A, Flags::B].iter().cloned()); + assert_eq!(flags, Flags::A | Flags::B); + + flags = Flags::A; + flags.extend([Flags::A, Flags::B].iter().cloned()); + assert_eq!(flags, Flags::A | Flags::B); + + flags = Flags::B; + flags.extend([Flags::A, Flags::ABC].iter().cloned()); + assert_eq!(flags, Flags::ABC); + } + + #[test] + fn test_from_iterator() { + assert_eq!([].iter().cloned().collect::(), Flags::empty()); + assert_eq!( + [Flags::A, Flags::B].iter().cloned().collect::(), + Flags::A | Flags::B + ); + assert_eq!( + [Flags::A, Flags::ABC].iter().cloned().collect::(), + Flags::ABC + ); + } + + #[test] + fn test_lt() { + let mut a = Flags::empty(); + let mut b = Flags::empty(); + + assert!(!(a < b) && !(b < a)); + b = Flags::B; + assert!(a < b); + a = Flags::C; + assert!(!(a < b) && b < a); + b = Flags::C | Flags::B; + assert!(a < b); + } + + #[test] + fn test_ord() { + let mut a = Flags::empty(); + let mut b = Flags::empty(); + + assert!(a <= b && a >= b); + a = Flags::A; + assert!(a > b && a >= b); + assert!(b < a && b <= a); + b = Flags::B; + assert!(b > a && b >= a); + assert!(a < b && a <= b); + } + + fn hash(t: &T) -> u64 { + let mut s = DefaultHasher::new(); + t.hash(&mut s); + s.finish() + } + + #[test] + fn test_hash() { + let mut x = Flags::empty(); + let mut y = Flags::empty(); + assert_eq!(hash(&x), hash(&y)); + x = Flags::all(); + y = Flags::ABC; + assert_eq!(hash(&x), hash(&y)); + } + + #[test] + fn test_debug() { + assert_eq!(format!("{:?}", Flags::A | Flags::B), "A | B"); + assert_eq!(format!("{:?}", Flags::empty()), "(empty)"); + assert_eq!(format!("{:?}", Flags::ABC), "A | B | C | ABC"); + let extra = unsafe { Flags::from_bits_unchecked(0xb8) }; + assert_eq!(format!("{:?}", extra), "0xb8"); + assert_eq!(format!("{:?}", Flags::A | extra), "A | 0xb8"); + assert_eq!(format!("{:?}", Flags::ABC | extra), "A | B | C | ABC | 0xb8"); + } + + #[test] + fn test_binary() { + assert_eq!(format!("{:b}", Flags::ABC), "111"); + assert_eq!(format!("{:#b}", Flags::ABC), "0b111"); + let extra = unsafe { Flags::from_bits_unchecked(0b1010000) }; + assert_eq!(format!("{:b}", Flags::ABC | extra), "1010111"); + assert_eq!(format!("{:#b}", Flags::ABC | extra), "0b1010111"); + } + + #[test] + fn test_octal() { + assert_eq!(format!("{:o}", LongFlags::LONG_A), "177777"); + assert_eq!(format!("{:#o}", LongFlags::LONG_A), "0o177777"); + let extra = unsafe { LongFlags::from_bits_unchecked(0o5000000) }; + assert_eq!(format!("{:o}", LongFlags::LONG_A | extra), "5177777"); + assert_eq!(format!("{:#o}", LongFlags::LONG_A | extra), "0o5177777"); + } + + #[test] + fn test_lowerhex() { + assert_eq!(format!("{:x}", LongFlags::LONG_A), "ffff"); + assert_eq!(format!("{:#x}", LongFlags::LONG_A), "0xffff"); + let extra = unsafe { LongFlags::from_bits_unchecked(0xe00000) }; + assert_eq!(format!("{:x}", LongFlags::LONG_A | extra), "e0ffff"); + assert_eq!(format!("{:#x}", LongFlags::LONG_A | extra), "0xe0ffff"); + } + + #[test] + fn test_upperhex() { + assert_eq!(format!("{:X}", LongFlags::LONG_A), "FFFF"); + assert_eq!(format!("{:#X}", LongFlags::LONG_A), "0xFFFF"); + let extra = unsafe { LongFlags::from_bits_unchecked(0xe00000) }; + assert_eq!(format!("{:X}", LongFlags::LONG_A | extra), "E0FFFF"); + assert_eq!(format!("{:#X}", LongFlags::LONG_A | extra), "0xE0FFFF"); + } + + mod submodule { + bitflags! { + pub struct PublicFlags: i8 { + const X = 0; + } + } + bitflags! { + struct PrivateFlags: i8 { + const Y = 0; + } + } + + #[test] + fn test_private() { + let _ = PrivateFlags::Y; + } + } + + #[test] + fn test_public() { + let _ = submodule::PublicFlags::X; + } + + mod t1 { + mod foo { + pub type Bar = i32; + } + + bitflags! { + /// baz + struct Flags: foo::Bar { + const A = 0b00000001; + #[cfg(foo)] + const B = 0b00000010; + #[cfg(foo)] + const C = 0b00000010; + } + } + } + + #[test] + fn test_in_function() { + bitflags! { + struct Flags: u8 { + const A = 1; + #[cfg(any())] // false + const B = 2; + } + } + assert_eq!(Flags::all(), Flags::A); + assert_eq!(format!("{:?}", Flags::A), "A"); + } + + #[test] + fn test_deprecated() { + bitflags! { + pub struct TestFlags: u32 { + #[deprecated(note = "Use something else.")] + const ONE = 1; + } + } + } + + #[test] + fn test_pub_crate() { + mod module { + bitflags! { + pub (crate) struct Test: u8 { + const FOO = 1; + } + } + } + + assert_eq!(module::Test::FOO.bits(), 1); + } + + #[test] + fn test_pub_in_module() { + mod module { + mod submodule { + bitflags! { + // `pub (in super)` means only the module `module` will + // be able to access this. + pub (in super) struct Test: u8 { + const FOO = 1; + } + } + } + + mod test { + // Note: due to `pub (in super)`, + // this cannot be accessed directly by the testing code. + pub(super) fn value() -> u8 { + super::submodule::Test::FOO.bits() + } + } + + pub fn value() -> u8 { + test::value() + } + } + + assert_eq!(module::value(), 1) + } + + #[test] + fn test_zero_value_flags() { + bitflags! { + struct Flags: u32 { + const NONE = 0b0; + const SOME = 0b1; + } + } + + assert!(Flags::empty().contains(Flags::NONE)); + assert!(Flags::SOME.contains(Flags::NONE)); + assert!(Flags::NONE.is_empty()); + + assert_eq!(format!("{:?}", Flags::empty()), "NONE"); + assert_eq!(format!("{:?}", Flags::SOME), "SOME"); + } +} diff --git a/bitflags/test_suite/Cargo.toml b/bitflags/test_suite/Cargo.toml new file mode 100644 index 0000000..2a02d80 --- /dev/null +++ b/bitflags/test_suite/Cargo.toml @@ -0,0 +1,13 @@ +[project] +name = "test_suite" +version = "0.0.0" + +[features] +unstable = ["compiletest_rs"] + +[dependencies] +bitflags = { path = "../" } +compiletest_rs = { version = "0.3.18", optional = true, features=["stable"] } +serde = "1.0" +serde_derive = "1.0" +serde_json = "1.0" diff --git a/bitflags/test_suite/tests/compile-fail/private_flags.rs b/bitflags/test_suite/tests/compile-fail/private_flags.rs new file mode 100644 index 0000000..d31c28d --- /dev/null +++ b/bitflags/test_suite/tests/compile-fail/private_flags.rs @@ -0,0 +1,20 @@ +#[macro_use] +extern crate bitflags; + +mod example { + bitflags! { + pub struct Flags1: u32 { + const FLAG_A = 0b00000001; + } + } + bitflags! { + struct Flags2: u32 { + const FLAG_B = 0b00000010; + } + } +} + +fn main() { + let flag1 = example::Flags1::FLAG_A; + let flag2 = example::Flags2::FLAG_B; //~ ERROR struct `Flags2` is private +} diff --git a/bitflags/test_suite/tests/compiletest.rs b/bitflags/test_suite/tests/compiletest.rs new file mode 100644 index 0000000..2beeae0 --- /dev/null +++ b/bitflags/test_suite/tests/compiletest.rs @@ -0,0 +1,33 @@ +#![cfg(feature = "unstable")] + +extern crate compiletest_rs as compiletest; + +use std::fs; +use std::result::Result; + +use compiletest::common::Mode; + +fn run_mode(mode: Mode) { + let config = compiletest::Config { + mode: mode, + src_base: format!("tests/{}", mode).into(), + target_rustcflags: fs::read_dir("../target/debug/deps") + .unwrap() + .map(Result::unwrap) + .filter(|entry| { + let file_name = entry.file_name(); + let file_name = file_name.to_string_lossy(); + file_name.starts_with("libbitflags-") && file_name.ends_with(".rlib") + }) + .max_by_key(|entry| entry.metadata().unwrap().modified().unwrap()) + .map(|entry| format!("--extern bitflags={}", entry.path().to_string_lossy())), + ..Default::default() + }; + + compiletest::run_tests(&config); +} + +#[test] +fn compile_test() { + run_mode(Mode::CompileFail); +} diff --git a/bitflags/test_suite/tests/conflicting_trait_impls.rs b/bitflags/test_suite/tests/conflicting_trait_impls.rs new file mode 100644 index 0000000..eb7a325 --- /dev/null +++ b/bitflags/test_suite/tests/conflicting_trait_impls.rs @@ -0,0 +1,17 @@ +#![no_std] + +#[macro_use] +extern crate bitflags; + +#[allow(unused_imports)] +use core::fmt::Display; + +bitflags! { + /// baz + struct Flags: u32 { + const A = 0b00000001; + } +} + +#[test] +fn main() {} diff --git a/bitflags/test_suite/tests/external.rs b/bitflags/test_suite/tests/external.rs new file mode 100644 index 0000000..4c88387 --- /dev/null +++ b/bitflags/test_suite/tests/external.rs @@ -0,0 +1,19 @@ +#[macro_use] +extern crate bitflags; + +bitflags! { + /// baz + struct Flags: u32 { + const A = 0b00000001; + #[doc = "bar"] + const B = 0b00000010; + const C = 0b00000100; + #[doc = "foo"] + const ABC = Flags::A.bits | Flags::B.bits | Flags::C.bits; + } +} + +#[test] +fn smoke() { + assert_eq!(Flags::ABC, Flags::A | Flags::B | Flags::C); +} diff --git a/bitflags/test_suite/tests/external_no_std.rs b/bitflags/test_suite/tests/external_no_std.rs new file mode 100644 index 0000000..31f87e4 --- /dev/null +++ b/bitflags/test_suite/tests/external_no_std.rs @@ -0,0 +1,21 @@ +#![no_std] + +#[macro_use] +extern crate bitflags; + +bitflags! { + /// baz + struct Flags: u32 { + const A = 0b00000001; + #[doc = "bar"] + const B = 0b00000010; + const C = 0b00000100; + #[doc = "foo"] + const ABC = Flags::A.bits | Flags::B.bits | Flags::C.bits; + } +} + +#[test] +fn smoke() { + assert_eq!(Flags::ABC, Flags::A | Flags::B | Flags::C); +} diff --git a/bitflags/test_suite/tests/i128_bitflags.rs b/bitflags/test_suite/tests/i128_bitflags.rs new file mode 100644 index 0000000..1b6f7c5 --- /dev/null +++ b/bitflags/test_suite/tests/i128_bitflags.rs @@ -0,0 +1,30 @@ +#![cfg(feature = "unstable")] + +#[macro_use] +extern crate bitflags; + +bitflags! { + /// baz + struct Flags128: u128 { + const A = 0x0000_0000_0000_0000_0000_0000_0000_0001; + const B = 0x0000_0000_0000_1000_0000_0000_0000_0000; + const C = 0x8000_0000_0000_0000_0000_0000_0000_0000; + const ABC = Self::A.bits | Self::B.bits | Self::C.bits; + } +} + +#[test] +fn test_i128_bitflags() { + assert_eq!(Flags128::ABC, Flags128::A | Flags128::B | Flags128::C); + assert_eq!(Flags128::A.bits, 0x0000_0000_0000_0000_0000_0000_0000_0001); + assert_eq!(Flags128::B.bits, 0x0000_0000_0000_1000_0000_0000_0000_0000); + assert_eq!(Flags128::C.bits, 0x8000_0000_0000_0000_0000_0000_0000_0000); + assert_eq!( + Flags128::ABC.bits, + 0x8000_0000_0000_1000_0000_0000_0000_0001 + ); + assert_eq!(format!("{:?}", Flags128::A), "A"); + assert_eq!(format!("{:?}", Flags128::B), "B"); + assert_eq!(format!("{:?}", Flags128::C), "C"); + assert_eq!(format!("{:?}", Flags128::ABC), "A | B | C | ABC"); +} diff --git a/bitflags/test_suite/tests/serde.rs b/bitflags/test_suite/tests/serde.rs new file mode 100644 index 0000000..0424af5 --- /dev/null +++ b/bitflags/test_suite/tests/serde.rs @@ -0,0 +1,35 @@ +#[macro_use] +extern crate bitflags; + +#[macro_use] +extern crate serde_derive; +extern crate serde; +extern crate serde_json; + +bitflags! { + #[derive(Serialize, Deserialize)] + struct Flags: u32 { + const A = 1; + const B = 2; + const C = 4; + const D = 8; + } +} + +#[test] +fn serialize() { + let flags = Flags::A | Flags::B; + + let serialized = serde_json::to_string(&flags).unwrap(); + + assert_eq!(serialized, r#"{"bits":3}"#); +} + +#[test] +fn deserialize() { + let deserialized: Flags = serde_json::from_str(r#"{"bits":12}"#).unwrap(); + + let expected = Flags::C | Flags::D; + + assert_eq!(deserialized.bits, expected.bits); +} diff --git a/clap/.appveyor.yml b/clap/.appveyor.yml new file mode 100644 index 0000000..247a550 --- /dev/null +++ b/clap/.appveyor.yml @@ -0,0 +1,17 @@ +environment: + matrix: + - TARGET: x86_64-pc-windows-msvc + - TARGET: i686-pc-windows-msvc + - TARGET: x86_64-pc-windows-gnu + - TARGET: i686-pc-windows-gnu + RUST_BACKTRACE: full +install: + - curl -sSf -o rustup-init.exe https://win.rustup.rs/ + - rustup-init.exe -y --default-host %TARGET% + - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin + - rustc -vV + - cargo -vV +build: false +test_script: + - cargo build --verbose --features yaml + - cargo test --verbose --features yaml diff --git a/clap/.clog.toml b/clap/.clog.toml new file mode 100644 index 0000000..301dd92 --- /dev/null +++ b/clap/.clog.toml @@ -0,0 +1,14 @@ +[clog] +repository = "https://github.com/kbknapp/clap-rs" +outfile = "CHANGELOG.md" +from-latest-tag = true + +[sections] +Performance = ["perf"] +Improvements = ["impr", "im", "imp"] +Documentation = ["docs"] +Deprecations = ["depr"] +Examples = ["examples"] +"New Settings" = ["setting", "settings"] +"API Additions" = ["add", "api"] +"New Sponsor" = ["sponsor"] diff --git a/clap/.github/CONTRIBUTING.md b/clap/.github/CONTRIBUTING.md new file mode 100644 index 0000000..2c6f752 --- /dev/null +++ b/clap/.github/CONTRIBUTING.md @@ -0,0 +1,115 @@ +# How to Contribute + +Contributions are always welcome! And there is a multitude of ways in which you can help depending on what you like to do, or are good at. Anything from documentation, code cleanup, issue completion, new features, you name it, even filing issues is contributing and greatly appreciated! + +Another really great way to help is if you find an interesting, or helpful way in which to use `clap`. You can either add it to the [examples/](examples) directory, or file an issue and tell me. I'm all about giving credit where credit is due :) + +### Testing Code + +To test with all features both enabled and disabled, you can run these commands: + +```sh +$ cargo test --no-default-features +$ cargo test --features "yaml unstable" +``` + +Alternatively, if you have [`just`](https://github.com/casey/just) installed you can run the prebuilt recipes. *Not* using `just` is perfectly fine as well, it simply bundles commands automatically. + +For example, to test the code, as above simply run: + +```sh +$ just run-tests +``` + +From here on, I will list the appropriate `cargo` command as well as the `just` command. + +Sometimes it's helpful to only run a subset of the tests, which can be done via: + +```sh +$ cargo test --test + +# Or + +$ just run-test +``` + +### Linting Code + +During the CI process `clap` runs against many different lints using [`clippy`](https://github.com/rust-lang-nursery/rust-clippy). In order to check if these lints pass on your own computer prior to submitting a PR you'll need a nightly compiler. + +In order to check the code for lints run either: + +```sh +$ rustup override add nightly +$ cargo build --features lints +$ rustup override remove + +# Or + +$ just lint +``` + +### Debugging Code + +Another helpful technique is to see the `clap` debug output while developing features. In order to see the debug output while running the full test suite or individual tests, run: + +```sh +$ cargo test --features debug + +# Or for individual tests +$ cargo test --test --features debug + +# The corresponding just command for individual debugging tests is: +$ just debug +``` + +### Commit Messages + +I use a [conventional](https://github.com/ajoslin/conventional-changelog/blob/a5505865ff3dd710cf757f50530e73ef0ca641da/conventions/angular.md) changelog format so I can update my changelog automatically using [clog](https://github.com/clog-tool/clog-cli) + + * Please format your commit subject line using the following format: `TYPE(COMPONENT): MESSAGE` where `TYPE` is one of the following: + - `api` - An addition to the API + - `setting` - A new `AppSettings` variant + - `feat` - A new feature of an existing API + - `imp` - An improvement to an existing feature/API + - `perf` - A performance improvement + - `docs` - Changes to documentation only + - `tests` - Changes to the testing framework or tests only + - `fix` - A bug fix + - `refactor` - Code functionality doesn't change, but underlying structure may + - `style` - Stylistic changes only, no functionality changes + - `wip` - A work in progress commit (Should typically be `git rebase`'ed away) + - `chore` - Catch all or things that have to do with the build system, etc + - `examples` - Changes to existing example, or a new example + * The `COMPONENT` is optional, and may be a single file, directory, or logical component. Parenthesis can be omitted if you are opting not to use the `COMPONENT`. + +### Tests and Documentation + +1. Create tests for your changes +2. **Ensure the tests are passing.** Run the tests (`cargo test --features "yaml unstable"`), alternatively `just run-tests` if you have `just` installed. +3. **Optional** Run the lints (`cargo build --features lints`) (requires a nightly compiler), alternatively `just lint` +4. Ensure your changes contain documentation if adding new APIs or features. + +### Preparing the PR + +1. `git rebase` into concise commits and remove `--fixup`s or `wip` commits (`git rebase -i HEAD~NUM` where `NUM` is number of commits back to start the rebase) +2. Push your changes back to your fork (`git push origin $your-branch`) +3. Create a pull request against `master`! (You can also create the pull request first, and we'll merge when ready. This a good way to discuss proposed changes.) + +### Other ways to contribute + +Another really great way to help is if you find an interesting, or helpful way in which to use `clap`. You can either add it to the [examples/](../examples) directory, or file an issue and tell me. I'm all about giving credit where credit is due :) + +### Goals + +There are a few goals of `clap` that I'd like to maintain throughout contributions. If your proposed changes break, or go against any of these goals we'll discuss the changes further before merging (but will *not* be ignored, all contributes are welcome!). These are by no means hard-and-fast rules, as I'm no expert and break them myself from time to time (even if by mistake or ignorance :P). + +* Remain backwards compatible when possible + - If backwards compatibility *must* be broken, use deprecation warnings if at all possible before removing legacy code + - This does not apply for security concerns +* Parse arguments quickly + - Parsing of arguments shouldn't slow down usage of the main program + - This is also true of generating help and usage information (although *slightly* less stringent, as the program is about to exit) +* Try to be cognizant of memory usage + - Once parsing is complete, the memory footprint of `clap` should be low since the main program is the star of the show +* `panic!` on *developer* error, exit gracefully on *end-user* error diff --git a/clap/.github/ISSUE_TEMPLATE.md b/clap/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..5f94a2c --- /dev/null +++ b/clap/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,46 @@ + + +### Rust Version + +* Use the output of `rustc -V` + +### Affected Version of clap + +* Can be found in Cargo.lock of your project (i.e. `grep clap Cargo.lock`) + +### Bug or Feature Request Summary + + +### Expected Behavior Summary + + +### Actual Behavior Summary + + +### Steps to Reproduce the issue + + +### Sample Code or Link to Sample Code + + +### Debug output + +Compile clap with cargo features `"debug"` such as: + +```toml +[dependencies] +clap = { version = "2", features = ["debug"] } +``` + +
+ Debug Output +
+
+
+Paste Debug Output Here
+
+
+
+
diff --git a/clap/.gitignore b/clap/.gitignore new file mode 100644 index 0000000..34253e9 --- /dev/null +++ b/clap/.gitignore @@ -0,0 +1,27 @@ +# Compiled files +*.o +*.so +*.rlib +*.dll + +# Executables +*.exe + +# Generated by Cargo +/target/ +/clap-test/target/ + +# Cargo files +Cargo.lock + +# Temp files +.*~ + +# Backup files +*.bak +*.bk +*.orig + +# Project files +.vscode/* +.idea/* diff --git a/clap/.mention-bot b/clap/.mention-bot new file mode 100644 index 0000000..f339f59 --- /dev/null +++ b/clap/.mention-bot @@ -0,0 +1,9 @@ +{ + "findPotentialReviewers": false, + "alwaysNotifyForPaths": [ + { + "name": "kbknapp", + "files": ["**/*.rs", "**/*.md", "*"] + } + ] +} diff --git a/clap/.travis.yml b/clap/.travis.yml new file mode 100644 index 0000000..9eb4044 --- /dev/null +++ b/clap/.travis.yml @@ -0,0 +1,59 @@ +sudo: true +language: rust +cache: cargo +rust: + - nightly + - beta + - stable + - 1.31.0 +matrix: + allow_failures: + - rust: nightly +before_script: + - | + pip install git+git://github.com/kbknapp/travis-cargo.git --user && + export PATH=$HOME/.local/bin:$PATH + - | + if [[ "$TRAVIS_RUST_VERSION" == "1.13.0" ]]; then + echo "Old Rust detected, removing version-sync dependency" + sed -i "/^version-sync =/d" Cargo.toml + rm "tests/version-numbers.rs" + fi +script: + - | + travis-cargo --only stable test -- --verbose --no-default-features && + travis-cargo --skip nightly test -- --verbose --features "yaml unstable" && + travis-cargo --only nightly test -- --verbose --features "yaml unstable nightly" && + travis-cargo --only nightly bench -- --no-run +addons: + apt: + packages: + - binutils-dev + - libcurl4-openssl-dev + - libelf-dev + - libdw-dev + - libiberty-dev + - cmake + - gcc + - zlib1g-dev +after_success: + - | + wget https://github.com/SimonKagstrom/kcov/archive/master.tar.gz && + tar xzf master.tar.gz && + cd kcov-master && + mkdir build && + cd build && + cmake .. && + make && + sudo make install && + cd ../.. && + rm -rf kcov-master && + cargo clean && + cargo test --no-run --features "yaml unstable" && + for file in target/debug/*-*; do mkdir -p "target/cov/$(basename $file)"; kcov --exclude-pattern=/.cargo --verify "target/cov/$(basename $file)" "$file"; done && + kcov --coveralls-id=$TRAVIS_JOB_ID --merge target/cov target/cov/* && + echo "Uploaded code coverage" +env: + global: + - TRAVIS_CARGO_NIGHTLY_FEATURE=lints + - secure: JLBlgHY6OEmhJ8woewNJHmuBokTNUv7/WvLkJGV8xk0t6bXBwSU0jNloXwlH7FiQTc4TccX0PumPDD4MrMgxIAVFPmmmlQOCmdpYP4tqZJ8xo189E5zk8lKF5OyaVYCs5SMmFC3cxCsKjfwGIexNu3ck5Uhwe9jI0tqgkgM3URA= diff --git a/clap/CHANGELOG.md b/clap/CHANGELOG.md new file mode 100644 index 0000000..d1fdd40 --- /dev/null +++ b/clap/CHANGELOG.md @@ -0,0 +1,2855 @@ + +## v2.33.0 (2019-04-06) + +#### New Sponsor + +* Stephen Oats is now a sponsor \o/ ([823457c0](https://github.com/kbknapp/clap-rs/commit/823457c0ef5e994ed7080cf62addbfe1aa3b1833)) +* **SPONSORS.md:** fixes Josh Triplett's info in the sponsor document ([24cb5740](https://github.com/kbknapp/clap-rs/commit/24cb574090a11159b48bba105d5ec2dfb0a20e4e)) + +#### Features + +* **Completions:** adds completion support for Elvish. ([e9d0562a](https://github.com/kbknapp/clap-rs/commit/e9d0562a1dc5dfe731ed7c767e6cee0af08f0cf9)) +* There is a new setting to disable automatic building of `--help` and `-h` flags (`AppSettings::DisableAutoHelp`) + +#### Improvements + +* **arg_matches.rs:** add Debug implementations ([47192b7a](https://github.com/kbknapp/clap-rs/commit/47192b7a2d84ec716b81ae4af621e008a8762dc9)) +* **macros:** Support shorthand syntax for ArgGroups ([df9095e7](https://github.com/kbknapp/clap-rs/commit/df9095e75bb1e7896415251d0d4ffd8a0ebcd559)) + +#### Documentation + +* Refer to macOS rather than OSX. ([ab0d767f](https://github.com/kbknapp/clap-rs/commit/ab0d767f3a5a57e2bbb97d0183c2ef63c8c77a6c)) +* **README.md:** use https for all links ([96a7639a](https://github.com/kbknapp/clap-rs/commit/96a7639a36bcb184c3f45348986883115ef1ab3a)) + +#### Bug Fixes + +* add debug assertion for missing args in subcommand ArgGroup ([2699d9e5](https://github.com/kbknapp/clap-rs/commit/2699d9e51e7eadc258ba64c4e347c5d1fef61343)) +* Restore compat with Rust 1.21 ([6b263de1](https://github.com/kbknapp/clap-rs/commit/6b263de1d42ede692ec5ee55019ad2fc6386f92e)) +* Dont mention unused subcommands ([ef92e2b6](https://github.com/kbknapp/clap-rs/commit/ef92e2b639ed305bdade4741f60fa85cb0101c5a)) +* **OsValues:** Add `ExactSizeIterator` implementation ([356c69e5](https://github.com/kbknapp/clap-rs/commit/356c69e508fd25a9f0ea2d27bf80ae1d9a8d88f4)) +* **arg_enum!:** + * Fix comma position for valid values. ([1f1f9ff3](https://github.com/kbknapp/clap-rs/commit/1f1f9ff3fa38a43231ef8be9cfea89a32e53f518)) + * Invalid expansions of some trailing-comma patterns ([7023184f](https://github.com/kbknapp/clap-rs/commit/7023184fca04e852c270341548d6a16207d13862)) +* **completions:** improve correctness of completions when whitespace is involved ([5a08ff29](https://github.com/kbknapp/clap-rs/commit/5a08ff295b2aa6ce29420df6252a0e3ff4441bdc)) +* **help message:** Unconditionally uses long description for subcommands ([6acc8b6a](https://github.com/kbknapp/clap-rs/commit/6acc8b6a621a765cbf513450188000d943676a30), closes [#897](https://github.com/kbknapp/clap-rs/issues/897)) +* **macros:** fixes broken pattern which prevented calling multi-argument Arg methods ([9e7a352e](https://github.com/kbknapp/clap-rs/commit/9e7a352e13aaf8025d80f2bac5c47fb32528672b)) +* **parser:** Better interaction between AllowExternalSubcommands and SubcommandRequired ([9601c95a](https://github.com/kbknapp/clap-rs/commit/9601c95a03d2b82bf265c328b4769238f1b79002)) + +#### Minimum Required Rust + +* As of this release, `clap` requires `rustc 1.31.0` or greater. + + +## v2.32.0 (2018-06-26) + +#### Minimum Required Rust + +* As of this release, `clap` requires `rustc 1.21.0` or greater. + + +#### Features + +* **Completions:** adds completion support for Elvish. ([e9d0562a](https://github.com/kbknapp/clap-rs/commit/e9d0562a1dc5dfe731ed7c767e6cee0af08f0cf9)) + +#### Improvements + +* **macros:** Support shorthand syntax for ArgGroups ([df9095e7](https://github.com/kbknapp/clap-rs/commit/df9095e75bb1e7896415251d0d4ffd8a0ebcd559)) + +#### Bug Fixes + +* **OsValues:** Add `ExactSizeIterator` implementation ([356c69e5](https://github.com/kbknapp/clap-rs/commit/356c69e508fd25a9f0ea2d27bf80ae1d9a8d88f4)) +* **arg_enum!:** Invalid expansions of some trailing-comma patterns ([7023184f](https://github.com/kbknapp/clap-rs/commit/7023184fca04e852c270341548d6a16207d13862)) +* **help message:** Unconditionally uses long description for subcommands ([6acc8b6a](https://github.com/kbknapp/clap-rs/commit/6acc8b6a621a765cbf513450188000d943676a30), closes [#897](https://github.com/kbknapp/clap-rs/issues/897)) + +#### Documentation + +* Refer to macOS rather than OSX. ([ab0d767f](https://github.com/kbknapp/clap-rs/commit/ab0d767f3a5a57e2bbb97d0183c2ef63c8c77a6c)) + + + + +### v2.31.2 (2018-03-19) + +#### Bug Fixes + +* **Fish Completions:** fixes a bug that only allowed a single completion in in Fish Shell ([e8774a8](https://github.com/kbknapp/clap-rs/pull/1214/commits/e8774a84ee4a319c888036e7c595ab46451d8e48), closes [#1212](https://github.com/kbknapp/clap-rs/issues/1212)) +* **AllowExternalSubcommands**: fixes a bug where external subcommands would be blocked by a similarly named subcommand (suggestions were getting in the way). ([a410e85](https://github.com/kbknapp/clap-rs/pull/1215/commits/a410e855bcd82b05f9efa73fa8b9774dc8842c6b)) + +#### Documentation + +* Fixes some typos in the `README.md` ([c8e685d7](https://github.com/kbknapp/clap-rs/commit/c8e685d76adee2a3cc06cac6952ffcf6f9548089)) + + +### v2.31.1 (2018-03-06) + + +#### Improvements + +* **AllowMissingPositional:** improves the ability of AllowMissingPositional to allow 'skipping' to the last positional arg with '--' ([df20e6e2](https://github.com/kbknapp/clap-rs/commit/df20e6e24b4e782be0b423b484b9798e3e2efe2f)) + + + +## v2.31.0 (2018-03-04) + + +#### Features + +* **Arg Indices:** adds the ability to query argument value indices ([f58d0576](https://github.com/kbknapp/clap-rs/commit/f58d05767ec8133c8eb2de117cb642b9ae29ccbc)) +* **Indices:** implements an Indices iterator ([1e67be44](https://github.com/kbknapp/clap-rs/commit/1e67be44f0ccf161cc84c4e6082382072e89c302)) +* **Raw Args** adds a convenience function to `Arg` that allows implying all of `Arg::last` `Arg::allow_hyphen_values` and `Arg::multiple(true)` ([66a78f29](https://github.com/kbknapp/clap-rs/commit/66a78f2972786f5fe7c07937a1ac23da2542afd2)) + +#### Documentation + +* Fix some typos and markdown issues. ([935ba0dd](https://github.com/kbknapp/clap-rs/commit/935ba0dd547a69c3f636c5486795012019408794)) +* **Arg Indices:** adds the documentation for the arg index querying methods ([50bc0047](https://github.com/kbknapp/clap-rs/commit/50bc00477afa64dc6cdc5de161d3de3ba1d105a7)) +* **CONTRIBUTING.md:** fix url to clippy upstream repo to point to https://github.com/rust-lang-nursery/rust-clippy instead of https://github.com/Manishearth/rust-clippy ([42407d7f](https://github.com/kbknapp/clap-rs/commit/42407d7f21d794103cda61f49d2615aae0a4bcd9)) +* **Values:** improves the docs example of the Values iterator ([74075d65](https://github.com/kbknapp/clap-rs/commit/74075d65e8db1ddb5e2a4558009a5729d749d1b6)) +* Updates readme to hint that the `wrap_help` feature is a thing ([fc7ab227](https://github.com/kbknapp/clap-rs/commit/66a78f2972786f5fe7c07937a1ac23da2542afd2)) + +### Improvements + +* Cargo.toml: use codegen-units = 1 in release and bench profiles ([19f425ea](https://github.com/kbknapp/clap-rs/commit/66a78f2972786f5fe7c07937a1ac23da2542afd2)) +* Adds WASM support (clap now compiles on WASM!) ([689949e5](https://github.com/kbknapp/clap-rs/commit/689949e57d390bb61bc69f3ed91f60a2105738d0)) +* Uses the short help tool-tip for PowerShell completion scripts ([ecda22ce](https://github.com/kbknapp/clap-rs/commit/ecda22ce7210ce56d7b2d1a5445dd1b8a2959656)) + + + +## v2.30.0 (2018-02-13) + +#### Bug Fixes + +* **YAML:** Adds a missing conversion from `Arg::last` when instantiating from a YAML file ([aab77c81a5](https://github.com/kbknapp/clap-rs/pull/1175/commits/aab77c81a519b045f95946ae0dd3e850f9b93070), closes [#1160](https://github.com/kbknapp/clap-rs/issues/1173)) + +#### Improvements + +* **Bash Completions:** instead of completing a generic option name, all bash completions fall back to file completions UNLESS `Arg::possible_values` was used ([872f02ae](https://github.com/kbknapp/clap-rs/commit/872f02aea900ffa376850a279eb164645e1234fa)) +* **Deps:** No longer needlessly compiles `ansi_term` on Windows since its not used ([b57ee946](https://github.com/kbknapp/clap-rs/commit/b57ee94609da3ddc897286cfba968f26ff961491), closes [#1155](https://github.com/kbknapp/clap-rs/issues/1155)) +* **Help Message:** changes the `[values: foo bar baz]` array to `[possible values: foo bar baz]` for consistency with the API ([414707e4e97](https://github.com/kbknapp/clap-rs/pull/1176/commits/414707e4e979d07bfe555247e5d130c546673708), closes [#1160](https://github.com/kbknapp/clap-rs/issues/1160)) + + + +### v2.29.4 (2018-02-06) + + +#### Bug Fixes + +* **Overrides Self:** fixes a bug where options with multiple values couldnt ever have multiple values ([d95907cf](https://github.com/kbknapp/clap-rs/commit/d95907cff6d011a901fe35fa00b0f4e18547a1fb)) + + + + +### v2.29.3 (2018-02-05) + + +#### Improvements + +* **Overrides:** clap now supports arguments which override with themselves ([6c7a0010](https://github.com/kbknapp/clap-rs/commit/6c7a001023ca1eac1cc6ffe6c936b4c4a2aa3c45), closes [#976](https://github.com/kbknapp/clap-rs/issues/976)) + +#### Bug Fixes + +* **Requirements:** fixes an issue where conflicting args would still show up as required ([e06cefac](https://github.com/kbknapp/clap-rs/commit/e06cefac97083838c0a4e1444dcad02a5c3f911e), closes [#1158](https://github.com/kbknapp/clap-rs/issues/1158)) +* Fixes a bug which disallows proper nesting of `--` ([73993fe](https://github.com/kbknapp/clap-rs/commit/73993fe30d135f682e763ec93dcb0814ed518011), closes [#1161](https://github.com/kbknapp/clap-rs/issues/1161)) + +#### New Settings + +* **AllArgsOverrideSelf:** adds a new convenience setting to allow all args to override themselves ([4670325d](https://github.com/kbknapp/clap-rs/commit/4670325d1bf0369addec2ae2bcb56f1be054c924)) + + + + +### v2.29.2 (2018-01-16) + + +#### Features + +* **completions/zsh.rs:** + * Escape possible values for options ([25561dec](https://github.com/kbknapp/clap-rs/commit/25561decf147d329b64634a14d9695673c2fc78f)) + * Implement postional argument possible values completion ([f3b0afd2](https://github.com/kbknapp/clap-rs/commit/f3b0afd2bef8b7be97162f8a7802ddf7603dff36)) + * Complete positional arguments properly ([e39aeab8](https://github.com/kbknapp/clap-rs/commit/e39aeab8487596046fbdbc6a226e5c8820585245)) + +#### Bug Fixes + +* **completions/zsh.rs:** + * Add missing autoload for is-at-least ([a6522607](https://github.com/kbknapp/clap-rs/commit/a652260795d1519f6ec2a7a09ccc1258499cad7b)) + * Don't pass -S to _arguments if Zsh is too old ([16b4f143](https://github.com/kbknapp/clap-rs/commit/16b4f143ff466b7ef18a267bc44ade0f9639109b)) + * Maybe fix completions with mixed positionals and subcommands ([1146f0da](https://github.com/kbknapp/clap-rs/commit/1146f0da154d6796fbfcb09db8efa3593cb0d898)) +* **completions/zsh.zsh:** Remove redundant code from output ([0e185b92](https://github.com/kbknapp/clap-rs/commit/0e185b922ed1e0fd653de00b4cd8d567d72ff68e), closes [#1142](https://github.com/kbknapp/clap-rs/issues/1142)) + + + + +### 2.29.1 (2018-01-09) + + +#### Documentation + +* fixes broken links. ([56e734b8](https://github.com/kbknapp/clap-rs/commit/56e734b839303d733d2e5baf7dac39bd7b97b8e4)) +* updates contributors list ([e1313a5a](https://github.com/kbknapp/clap-rs/commit/e1313a5a0f69d8f4016f73b860a63af8318a6676)) + +#### Performance + +* further debloating by removing generics from error cases ([eb8d919e](https://github.com/kbknapp/clap-rs/commit/eb8d919e6f3443db279ba0c902f15d76676c02dc)) +* debloats clap by deduplicating logic and refactors ([03e413d7](https://github.com/kbknapp/clap-rs/commit/03e413d7175d35827cd7d8908d47dbae15a849a3)) + +#### Bug Fixes + +* fixes the ripgrep benchmark by adding a value to a flag that expects it ([d26ab2b9](https://github.com/kbknapp/clap-rs/commit/d26ab2b97cf9c0ea675b440b7b0eaf6ac3ad01f4)) +* **bash completion:** Change the bash completion script code generation to support hyphens. ([ba7f1d18](https://github.com/kbknapp/clap-rs/commit/ba7f1d18eba7a07ce7f57e0981986f66c994b639)) +* **completions/zsh.rs:** Fix completion of long option values ([46365cf8](https://github.com/kbknapp/clap-rs/commit/46365cf8be5331ba04c895eb183e2f230b5aad51)) + + + +## 2.29.0 (2017-12-02) + + +#### API Additions + +* **Arg:** adds Arg::hide_env_values(bool) which allows one to hide any current env values and display only the key in help messages ([fb41d062](https://github.com/kbknapp/clap-rs/commit/fb41d062eedf37cb4f805c90adca29909bd197d7)) + + + + +## 2.28.0 (2017-11-28) + +The minimum required Rust is now 1.20. This was done to start using bitflags 1.0 and having >1.0 deps is a *very good* thing! + +#### Documentation + +* changes the demo version to 2.28 to stay in sync ([ce6ca492](https://github.com/kbknapp/clap-rs/commit/ce6ca492c7510ab6474075806360b96081b021a9)) +* Fix URL path to github hosted files ([ce72aada](https://github.com/kbknapp/clap-rs/commit/ce72aada56a9581d4a6cb4bf9bdb861c3906f8df), closes [#1106](https://github.com/kbknapp/clap-rs/issues/1106)) +* fix typo ([002b07fc](https://github.com/kbknapp/clap-rs/commit/002b07fc98a1c85acb66296b1eec0b2aba906125)) +* **README.md:** updates the readme and pulls out some redundant sections ([db6caf86](https://github.com/kbknapp/clap-rs/commit/db6caf8663747e679d2f4ed3bd127f33476754aa)) + +#### Improvements + +* adds '[SUBCOMMAND]' to usage strings with only AppSettings::AllowExternalSubcommands is used with no other subcommands ([e78bb757](https://github.com/kbknapp/clap-rs/commit/e78bb757a3df16e82d539e450c06767a6bfcf859), closes [#1093](https://github.com/kbknapp/clap-rs/issues/1093)) + +#### API Additions + +* Adds Arg::case_insensitive(bool) which allows matching Arg::possible_values without worrying about ASCII case ([1fec268e](https://github.com/kbknapp/clap-rs/commit/1fec268e51736602e38e67c76266f439e2e0ef12), closes [#1118](https://github.com/kbknapp/clap-rs/issues/1118)) +* Adds the traits to be used with the clap-derive crate to be able to use Custom Derive ([6f4c3412](https://github.com/kbknapp/clap-rs/commit/6f4c3412415e882f5ca2cc3fbd6d4dce79440828)) + +#### Bug Fixes + +* Fixes a regression where --help couldn't be overridden ([a283d69f](https://github.com/kbknapp/clap-rs/commit/a283d69fc08aa016ae1bf9ba010012abecc7ba69), closes [#1112](https://github.com/kbknapp/clap-rs/issues/1112)) +* fixes a bug that allowed options to pass parsing when no value was provided ([2fb75821](https://github.com/kbknapp/clap-rs/commit/2fb758219c7a60d639da67692e100b855a8165ac), closes [#1105](https://github.com/kbknapp/clap-rs/issues/1105)) +* ignore PropagateGlobalValuesDown deprecation warning ([f61ce3f5](https://github.com/kbknapp/clap-rs/commit/f61ce3f55fe65e16b3db0bd4facdc4575de22767), closes [#1086](https://github.com/kbknapp/clap-rs/issues/1086)) + +#### Deps + +* Updates `bitflags` to 1.0 + + + + +## v2.27.1 (2017-10-24) + + +#### Bug Fixes + +* Adds `term_size` as an optional dependency (with feature `wrap_help`) to fix compile bug + + +## v2.27.0 (2017-10-24) + +** This release raises the minimum required version of Rust to 1.18 ** + +** This release also contains a very minor breaking change to fix a bug ** + +The only CLIs affected will be those using unrestrained multiple values and subcommands where the +subcommand name can coincide with one of the multiple values. + +See the commit [0c223f54](https://github.com/kbknapp/clap-rs/commit/0c223f54ed46da406bc8b43a5806e0b227863b31) for full details. + + +#### Bug Fixes + +* Values from global args are now propagated UP and DOWN! +* fixes a bug where using AppSettings::AllowHyphenValues would allow invalid arguments even when there is no way for them to be valid ([77ed4684](https://github.com/kbknapp/clap-rs/commit/77ed46841fc0263d7aa32fcc5cc49ef703b37c04), closes [#1066](https://github.com/kbknapp/clap-rs/issues/1066)) +* when an argument requires a value and that value happens to match a subcommand name, its parsed as a value ([0c223f54](https://github.com/kbknapp/clap-rs/commit/0c223f54ed46da406bc8b43a5806e0b227863b31), closes [#1031](https://github.com/kbknapp/clap-rs/issues/1031), breaks [#](https://github.com/kbknapp/clap-rs/issues/), [#](https://github.com/kbknapp/clap-rs/issues/)) +* fixes a bug that prevented number_of_values and default_values to be used together ([5eb342a9](https://github.com/kbknapp/clap-rs/commit/5eb342a99dde07b0f011048efde3e283bc1110fc), closes [#1050](https://github.com/kbknapp/clap-rs/issues/1050), [#1056](https://github.com/kbknapp/clap-rs/issues/1056)) +* fixes a bug that didn't allow args with default values to have conflicts ([58b5b4be](https://github.com/kbknapp/clap-rs/commit/58b5b4be315280888d50d9b15119b91a9028f050), closes [#1071](https://github.com/kbknapp/clap-rs/issues/1071)) +* fixes a panic when using global args and calling App::get_matches_from_safe_borrow multiple times ([d86ec797](https://github.com/kbknapp/clap-rs/commit/d86ec79742c77eb3f663fb30e225954515cf25bb), closes [#1076](https://github.com/kbknapp/clap-rs/issues/1076)) +* fixes issues and potential regressions with global args values not being propagated properly or at all ([a43f9dd4](https://github.com/kbknapp/clap-rs/commit/a43f9dd4aaf1864dd14a3c28dec89ccdd70c61e5), closes [#1010](https://github.com/kbknapp/clap-rs/issues/1010), [#1061](https://github.com/kbknapp/clap-rs/issues/1061), [#978](https://github.com/kbknapp/clap-rs/issues/978)) +* fixes a bug where default values are not applied if the option supports zero values ([9c248cbf](https://github.com/kbknapp/clap-rs/commit/9c248cbf7d8a825119bc387c23e9a1d1989682b0), closes [#1047](https://github.com/kbknapp/clap-rs/issues/1047)) + +#### Documentation + +* adds addtional blurbs about using multiples with subcommands ([03455b77](https://github.com/kbknapp/clap-rs/commit/03455b7751a757e7b2f6ffaf2d16168539c99661)) +* updates the docs to reflect changes to global args and that global args values can now be propagated back up the stack ([ead076f0](https://github.com/kbknapp/clap-rs/commit/ead076f03ada4c322bf3e34203925561ec496d87)) +* add html_root_url attribute ([e67a061b](https://github.com/kbknapp/clap-rs/commit/e67a061bcf567c6518d6c2f58852e01f02764b22)) +* sync README version numbers with crate version ([5536361b](https://github.com/kbknapp/clap-rs/commit/5536361bcda29887ed86bb68e43d0b603cbc423f)) + +#### Improvements + +* args that have require_delimiter(true) is now reflected in help and usage strings ([dce61699](https://github.com/kbknapp/clap-rs/commit/dce616998ed9bd95e8ed3bec1f09a4883da47b85), closes [#1052](https://github.com/kbknapp/clap-rs/issues/1052)) +* if all subcommands are hidden, the subcommands section of the help message is no longer displayed ([4ae7b046](https://github.com/kbknapp/clap-rs/commit/4ae7b0464750bc07ec80ece38e43f003fdd1b8ae), closes [#1046](https://github.com/kbknapp/clap-rs/issues/1046)) + +#### Breaking Changes + +* when an argument requires a value and that value happens to match a subcommand name, its parsed as a value ([0c223f54](https://github.com/kbknapp/clap-rs/commit/0c223f54ed46da406bc8b43a5806e0b227863b31), closes [#1031](https://github.com/kbknapp/clap-rs/issues/1031), breaks [#](https://github.com/kbknapp/clap-rs/issues/), [#](https://github.com/kbknapp/clap-rs/issues/)) + +#### Deprecations + +* **AppSettings::PropagateGlobalValuesDown:** this setting is no longer required to propagate values down or up ([2bb5ddce](https://github.com/kbknapp/clap-rs/commit/2bb5ddcee61c791ca1aaca494fbeb4bd5e277488)) + + + + +### v2.26.2 (2017-09-14) + + +#### Improvements + +* if all subcommands are hidden, the subcommands section of the help message is no longer displayed ([4ae7b046](https://github.com/kbknapp/clap-rs/commit/4ae7b0464750bc07ec80ece38e43f003fdd1b8ae), closes [#1046](https://github.com/kbknapp/clap-rs/issues/1046)) + +#### Bug Fixes + +* fixes a bug where default values are not applied if the option supports zero values ([9c248cbf](https://github.com/kbknapp/clap-rs/commit/9c248cbf7d8a825119bc387c23e9a1d1989682b0), closes [#1047](https://github.com/kbknapp/clap-rs/issues/1047)) + + + + +### v2.26.1 (2017-09-14) + + +#### Bug Fixes + +* fixes using require_equals(true) and min_values(0) together ([10ae208f](https://github.com/kbknapp/clap-rs/commit/10ae208f68518eff6e98166724065745f4083174), closes [#1044](https://github.com/kbknapp/clap-rs/issues/1044)) +* escape special characters in zsh and fish completions ([87e019fc](https://github.com/kbknapp/clap-rs/commit/87e019fc84ba6193a8c4ddc26c61eb99efffcd25)) +* avoid panic generating default help msg if term width set to 0 due to bug in textwrap 0.7.0 ([b3eadb0d](https://github.com/kbknapp/clap-rs/commit/b3eadb0de516106db4e08f078ad32e8f6d6e7a57)) +* Change `who's` -> `whose` ([53c1ffe8](https://github.com/kbknapp/clap-rs/commit/53c1ffe87f38b05d8804a0f7832412a952845349)) +* adds a debug assertion to ensure all args added to groups actually exist ([7ad123e2](https://github.com/kbknapp/clap-rs/commit/7ad123e2c02577e3ca30f7e205181e896b157d11), closes [#917](https://github.com/kbknapp/clap-rs/issues/917)) +* fixes a bug where args that allow values to start with a hyphen couldnt contain a double hyphen -- as a value ([ab2f4c9e](https://github.com/kbknapp/clap-rs/commit/ab2f4c9e563e36ec739a4b55d5a5b76fdb9e9fa4), closes [#960](https://github.com/kbknapp/clap-rs/issues/960)) +* fixes a bug where positional argument help text is misaligned ([54c16836](https://github.com/kbknapp/clap-rs/commit/54c16836dea4651806a2cfad53146a83fa3abf21)) +* **Help Message:** fixes long_about not being usable ([a8257ea0](https://github.com/kbknapp/clap-rs/commit/a8257ea0ffb812e552aca256c4a3d2aebfd8065b), closes [#1043](https://github.com/kbknapp/clap-rs/issues/1043)) +* **Suggestions:** output for flag after subcommand ([434ea5ba](https://github.com/kbknapp/clap-rs/commit/434ea5ba71395d8c1afcf88e69f0b0d8339b01a1)) + + + + +## v2.26.0 (2017-07-29) + +Minimum version of Rust is now v1.13.0 (Stable) + + +#### Improvements + +* bumps unicode-segmentation to v1.2 ([cd7b40a2](https://github.com/kbknapp/clap-rs/commit/cd7b40a21c77bae17ba453c5512cb82b7d1ce474)) + + +#### Performance + +* update textwrap to version 0.7.0 ([c2d4e637](https://github.com/kbknapp/clap-rs/commit/c2d4e63756a6f070e38c16dff846e9b0a53d6f93)) + + + + + +### v2.25.1 (2017-07-21) + +#### Improvements + +* impl Default for Values + OsValues for any lifetime. ([fb7d6231f1](https://github.com/kbknapp/clap-rs/commit/fb7d6231f13a2f79f411e62dca210b7dc9994c18)) + +#### Documentation + +* Various documentation typos and grammar fixes + + +### v2.25.0 (2017-06-20) + + +#### Features + +* use textwrap crate for wrapping help texts ([b93870c1](https://github.com/kbknapp/clap-rs/commit/b93870c10ae3bd90d233c586a33e086803117285)) + +#### Improvements + +* **Suggestions:** suggests to use flag after subcommand when applicable ([2671ca72](https://github.com/kbknapp/clap-rs/commit/2671ca7260119d4311d21c4075466aafdd9da734)) +* Bumps bitflags crate to v0.9 + +#### Documentation + +* Change `who's` -> `whose` ([53c1ffe8](https://github.com/kbknapp/clap-rs/commit/53c1ffe87f38b05d8804a0f7832412a952845349)) + +#### Documentation + +* **App::template:** adds details about the necessity to use AppSettings::UnifiedHelpMessage when using {unified} tags in the help template ([cbea3d5a](https://github.com/kbknapp/clap-rs/commit/cbea3d5acf3271a7a734498c4d99c709941c331e), closes [#949](https://github.com/kbknapp/clap-rs/issues/949)) +* **Arg::allow_hyphen_values:** updates the docs to include warnings for allow_hyphen_values and multiple(true) used together ([f9b0d657](https://github.com/kbknapp/clap-rs/commit/f9b0d657835d3f517f313d70962177dc30acf4a7)) +* **README.md:** + * added a warning about using ~ deps ([821929b5](https://github.com/kbknapp/clap-rs/commit/821929b51bd60213955705900a436c9a64fcb79f), closes [#964](https://github.com/kbknapp/clap-rs/issues/964)) +* **clap_app!:** adds using the @group specifier to the macro docs ([826048cb](https://github.com/kbknapp/clap-rs/commit/826048cb3cbc0280169303f1498ff0a2b7395883), closes [#932](https://github.com/kbknapp/clap-rs/issues/932)) + + + + +### v2.24.2 (2017-05-15) + + +#### Bug Fixes + +* adds a debug assertion to ensure all args added to groups actually exist ([14f6b8f3](https://github.com/kbknapp/clap-rs/commit/14f6b8f3a2f6df73aeeec9c54a54909b1acfc158), closes [#917](https://github.com/kbknapp/clap-rs/issues/917)) +* fixes a bug where args that allow values to start with a hyphen couldnt contain a double hyphen -- as a value ([ebf73a09](https://github.com/kbknapp/clap-rs/commit/ebf73a09db6f3c03c19cdd76b1ba6113930e1643), closes [#960](https://github.com/kbknapp/clap-rs/issues/960)) +* fixes a bug where positional argument help text is misaligned ([54c16836](https://github.com/kbknapp/clap-rs/commit/54c16836dea4651806a2cfad53146a83fa3abf21)) + +#### Documentation + +* **App::template:** adds details about the necessity to use AppSettings::UnifiedHelpMessage when using {unified} tags in the help template ([cf569438](https://github.com/kbknapp/clap-rs/commit/cf569438f309c199800bb8e46c9f140187de69d7), closes [#949](https://github.com/kbknapp/clap-rs/issues/949)) +* **Arg::allow_hyphen_values:** updates the docs to include warnings for allow_hyphen_values and multiple(true) used together ([ded5a2f1](https://github.com/kbknapp/clap-rs/commit/ded5a2f15474d4a5bd46a67b130ccb8b6781bd01)) +* **clap_app!:** adds using the @group specifier to the macro docs ([fe85fcb1](https://github.com/kbknapp/clap-rs/commit/fe85fcb1772b61f13b20b7ea5290e2437a76190c), closes [#932](https://github.com/kbknapp/clap-rs/issues/932)) + + + + +### v2.24.0 (2017-05-07) + + +#### Bug Fixes + +* fixes a bug where args with last(true) and required(true) set were not being printed in the usage string ([3ac533fe](https://github.com/kbknapp/clap-rs/commit/3ac533fedabf713943eedf006f830a5a486bbe80), closes [#944](https://github.com/kbknapp/clap-rs/issues/944)) +* fixes a bug that was printing the arg name, instead of value name when Arg::last(true) was used ([e1fe8ac3](https://github.com/kbknapp/clap-rs/commit/e1fe8ac3bc1f9cf4e36df0d881f8419755f1787b), closes [#940](https://github.com/kbknapp/clap-rs/issues/940)) +* fixes a bug where flags were parsed as flags AND positional values when specific combinations of settings were used ([20f83292](https://github.com/kbknapp/clap-rs/commit/20f83292d070038b8cee2a6b47e91f6b0a2f7871), closes [#946](https://github.com/kbknapp/clap-rs/issues/946)) + + + + +## v2.24.0 (2017-05-05) + + +#### Documentation + +* **README.md:** fix some typos ([fa34deac](https://github.com/kbknapp/clap-rs/commit/fa34deac079f334c3af97bb7fb151880ba8887f8)) + +#### API Additions + +* **Arg:** add `default_value_os` ([d5ef8955](https://github.com/kbknapp/clap-rs/commit/d5ef8955414b1587060f7218385256105b639c88)) +* **arg_matches.rs:** Added a Default implementation for Values and OsValues iterators. ([0a4384e3](https://github.com/kbknapp/clap-rs/commit/0a4384e350eed74c2a4dc8964c203f21ac64897f)) + + + +### v2.23.2 (2017-04-19) + + +#### Bug Fixes + +* **PowerShell Completions:** fixes a bug where powershells completions cant be used if no subcommands are defined ([a8bce558](https://github.com/kbknapp/clap-rs/commit/a8bce55837dc4e0fb187dc93180884a40ae09c6f), closes [#931](https://github.com/kbknapp/clap-rs/issues/931)) + +#### Improvements + +* bumps term_size to take advantage of better terminal dimension handling ([e05100b7](https://github.com/kbknapp/clap-rs/commit/e05100b73d74066a90876bf38f952adf5e8ee422)) +* **PowerShell Completions:** massively dedups subcommand names in the generate script to make smaller scripts that are still functionally equiv ([85b0e1cc](https://github.com/kbknapp/clap-rs/commit/85b0e1cc4b9755dda75a93d898d79bc38631552b)) + +#### Documentation + +* Fix a typo the minimum rust version required ([71dabba3](https://github.com/kbknapp/clap-rs/commit/71dabba3ea0a17c88b0e2199c9d99f0acbf3bc17)) + + +### v2.23.1 (2017-04-05) + + +#### Bug Fixes + +* fixes a missing newline character in the autogenerated help and version messages in some instances ([5ae9007d](https://github.com/kbknapp/clap-rs/commit/5ae9007d984ae94ae2752df51bcbaeb0ec89bc15)) + + + +## v2.23.0 (2017-04-05) + + +#### API Additions + +* `App::long_about` +* `App::long_version` +* `App::print_long_help` +* `App::write_long_help` +* `App::print_long_version` +* `App::write_long_version` +* `Arg::long_help` + +#### Features + +* allows distinguishing between short and long version messages (-V/short or --version/long) ([59272b06](https://github.com/kbknapp/clap-rs/commit/59272b06cc213289dc604dbc694cb95d383a5d68)) +* allows distinguishing between short and long help with subcommands in the same manner as args ([6b371891](https://github.com/kbknapp/clap-rs/commit/6b371891a1702173a849d1e95f9fecb168bf6fc4)) +* allows specifying a short help vs a long help (i.e. varying levels of detail depending on if -h or --help was used) ([ef1b24c3](https://github.com/kbknapp/clap-rs/commit/ef1b24c3a0dff2f58c5e2e90880fbc2b69df20ee)) +* **clap_app!:** adds support for arg names with hyphens similar to longs with hyphens ([f7a88779](https://github.com/kbknapp/clap-rs/commit/f7a8877978c8f90e6543d4f0d9600c086cf92cd7), closes [#869](https://github.com/kbknapp/clap-rs/issues/869)) + +#### Bug Fixes + +* fixes a bug that wasn't allowing help and version to be properly overridden ([8b2ceb83](https://github.com/kbknapp/clap-rs/commit/8b2ceb8368bcb70689fadf1c7f4b9549184926c1), closes [#922](https://github.com/kbknapp/clap-rs/issues/922)) + +#### Documentation + +* **clap_app!:** documents the `--("some-arg")` method for using args with hyphens inside them ([bc08ef3e](https://github.com/kbknapp/clap-rs/commit/bc08ef3e185393073d969d301989b6319c616c1f), closes [#919](https://github.com/kbknapp/clap-rs/issues/919)) + + + + +### v2.22.2 (2017-03-30) + + +#### Bug Fixes + +* **Custom Usage Strings:** fixes the usage string regression when using help templates ([0e4fd96d](https://github.com/kbknapp/clap-rs/commit/0e4fd96d74280d306d09e60ac44f938a82321769)) + + + + +### v2.22.1 (2017-03-24) + + +#### Bug Fixes + +* **usage:** fixes a big regression with custom usage strings ([2c41caba](https://github.com/kbknapp/clap-rs/commit/2c41caba3c7d723a2894e315d04da796b0e97759)) + + +## v2.22.0 (2017-03-23) + +#### API Additions + +* **App::name:** adds the ability to change the name of the App instance after creation ([d49e8292](https://github.com/kbknapp/clap-rs/commit/d49e8292b026b06e2b70447cd9f08299f4fcba76), closes [#908](https://github.com/kbknapp/clap-rs/issues/908)) +* **Arg::hide_default_value:** adds ability to hide the default value of an argument from the help string ([89e6ea86](https://github.com/kbknapp/clap-rs/commit/89e6ea861e16a1ad56757ca12f6b32d02253e44a), closes [#902](https://github.com/kbknapp/clap-rs/issues/902)) + + + +### v2.21.3 (2017-03-23) + +#### Bug Fixes + +* **yaml:** adds support for loading author info from yaml ([e04c390c](https://github.com/kbknapp/clap-rs/commit/e04c390c597a55fa27e724050342f16c42f1c5c9)) + + + +### v2.21.2 (2017-03-17) + + +#### Improvements + +* add fish subcommand help support ([f8f68cf8](https://github.com/kbknapp/clap-rs/commit/f8f68cf8251669aef4539a25a7c1166f0ac81ea6)) +* options that use `require_equals(true)` now display the equals sign in help messages, usage strings, and errors" ([c8eb0384](https://github.com/kbknapp/clap-rs/commit/c8eb0384d394d2900ccdc1593099c97808a3fa05), closes [#903](https://github.com/kbknapp/clap-rs/issues/903)) + + +#### Bug Fixes + +* setting the max term width now correctly propagates down through child subcommands + + + + +### v2.21.1 (2017-03-12) + + +#### Bug Fixes + +* **ArgRequiredElseHelp:** fixes the precedence of this error to prioritize over other error messages ([74b751ff](https://github.com/kbknapp/clap-rs/commit/74b751ff2e3631e337b7946347c1119829a41c53), closes [#895](https://github.com/kbknapp/clap-rs/issues/895)) +* **Positionals:** fixes some regression bugs resulting from old asserts in debug mode. ([9a3bc98e](https://github.com/kbknapp/clap-rs/commit/9a3bc98e9b55e7514b74b73374c5ac8b6e5e0508), closes [#896](https://github.com/kbknapp/clap-rs/issues/896)) + + + + +## v2.21.0 (2017-03-09) + +#### Performance + +* doesn't run `arg_post_processing` on multiple values anymore ([ec516182](https://github.com/kbknapp/clap-rs/commit/ec5161828729f6a53f0fccec8648f71697f01f78)) +* changes internal use of `VecMap` to `Vec` for matched values of `Arg`s ([22bf137a](https://github.com/kbknapp/clap-rs/commit/22bf137ac581684c6ed460d2c3c640c503d62621)) +* vastly reduces the amount of cloning when adding non-global args minus when they're added from `App::args` which is forced to clone ([8da0303b](https://github.com/kbknapp/clap-rs/commit/8da0303bc02db5fe047cfc0631a9da41d9dc60f7)) +* refactor to remove unneeded vectors and allocations and checks for significant performance increases ([0efa4119](https://github.com/kbknapp/clap-rs/commit/0efa4119632f134fc5b8b9695b007dd94b76735d)) + +#### Documentation + +* Fix examples link in CONTRIBUTING.md ([60cf875d](https://github.com/kbknapp/clap-rs/commit/60cf875d67a252e19bb85054be57696fac2c57a1)) + +#### Improvements + +* when `AppSettings::SubcommandsNegateReqs` and `ArgsNegateSubcommands` are used, a new more accurate double line usage string is shown ([50f02300](https://github.com/kbknapp/clap-rs/commit/50f02300d81788817acefef0697e157e01b6ca32), closes [#871](https://github.com/kbknapp/clap-rs/issues/871)) + +#### API Additions + +* **Arg::last:** adds the ability to mark a positional argument as 'last' which means it should be used with `--` syntax and can be accessed early ([6a7aea90](https://github.com/kbknapp/clap-rs/commit/6a7aea9043b83badd9ab038b4ecc4c787716147e), closes [#888](https://github.com/kbknapp/clap-rs/issues/888)) +* provides `default_value_os` and `default_value_if[s]_os` ([0f2a3782](https://github.com/kbknapp/clap-rs/commit/0f2a378219a6930748d178ba350fe5925be5dad5), closes [#849](https://github.com/kbknapp/clap-rs/issues/849)) +* provides `App::help_message` and `App::version_message` which allows one to override the auto-generated help/version flag associated help ([389c413](https://github.com/kbknapp/clap-rs/commit/389c413b7023dccab8c76aa00577ea1d048e7a99), closes [#889](https://github.com/kbknapp/clap-rs/issues/889)) + +#### New Settings + +* **InferSubcommands:** adds a setting to allow one to infer shortened subcommands or aliases (i.e. for subcommmand "test", "t", "te", or "tes" would be allowed assuming no other ambiguities) ([11602032](https://github.com/kbknapp/clap-rs/commit/11602032f6ff05881e3adf130356e37d5e66e8f9), closes [#863](https://github.com/kbknapp/clap-rs/issues/863)) + +#### Bug Fixes + +* doesn't print the argument sections in the help message if all args in that section are hidden ([ce5ee5f5](https://github.com/kbknapp/clap-rs/commit/ce5ee5f5a76f838104aeddd01c8ec956dd347f50)) +* doesn't include the various [ARGS] [FLAGS] or [OPTIONS] if the only ones available are hidden ([7b4000af](https://github.com/kbknapp/clap-rs/commit/7b4000af97637703645c5fb2ac8bb65bd546b95b), closes [#882](https://github.com/kbknapp/clap-rs/issues/882)) +* now correctly shows subcommand as required in the usage string when AppSettings::SubcommandRequiredElseHelp is used ([8f0884c1](https://github.com/kbknapp/clap-rs/commit/8f0884c1764983a49b45de52a1eddf8d721564d8)) +* fixes some memory leaks when an error is detected and clap exits ([8c2dd287](https://github.com/kbknapp/clap-rs/commit/8c2dd28718262ace4ae0db98563809548e02a86b)) +* fixes a trait that's marked private accidentlly, but should be crate internal public ([1ae21108](https://github.com/kbknapp/clap-rs/commit/1ae21108015cea87e5360402e1747025116c7878)) +* **Completions:** fixes a bug that tried to propogate global args multiple times when generating multiple completion scripts ([5e9b9cf4](https://github.com/kbknapp/clap-rs/commit/5e9b9cf4dd80fa66a624374fd04e6545635c1f94), closes [#846](https://github.com/kbknapp/clap-rs/issues/846)) + +#### Features + +* **Options:** adds the ability to require the equals syntax with options --opt=val ([f002693d](https://github.com/kbknapp/clap-rs/commit/f002693dec6a6959c4e9590cb7b7bfffd6d6e5bc), closes [#833](https://github.com/kbknapp/clap-rs/issues/833)) + + + + +### v2.20.5 (2017-02-18) + + +#### Bug Fixes + +* **clap_app!:** fixes a critical bug of a missing fragment specifier when using `!property` style tags. ([5635c1f94](https://github.com/kbknapp/clap-rs/commit/5e9b9cf4dd80fa66a624374fd04e6545635c1f94)) + + + +### v2.20.4 (2017-02-15) + + +#### Bug Fixes + +* **Completions:** fixes a bug that tried to propogate global args multiple times when generating multiple completion scripts ([5e9b9cf4](https://github.com/kbknapp/clap-rs/commit/5e9b9cf4dd80fa66a624374fd04e6545635c1f94), closes [#846](https://github.com/kbknapp/clap-rs/issues/846)) + +#### Documentation + +* Fix examples link in CONTRIBUTING.md ([60cf875d](https://github.com/kbknapp/clap-rs/commit/60cf875d67a252e19bb85054be57696fac2c57a1)) + + + +### v2.20.3 (2017-02-03) + + +#### Documentation + +* **Macros:** adds a warning about changing values in Cargo.toml not triggering a rebuild automatically ([112aea3e](https://github.com/kbknapp/clap-rs/commit/112aea3e42ae9e0c0a2d33ebad89496dbdd95e5d), closes [#838](https://github.com/kbknapp/clap-rs/issues/838)) + +#### Bug Fixes + +* fixes a println->debugln typo ([279aa62e](https://github.com/kbknapp/clap-rs/commit/279aa62eaf08f56ce090ba16b937bc763cbb45be)) +* fixes bash completions for commands that have an underscore in the name ([7f5cfa72](https://github.com/kbknapp/clap-rs/commit/7f5cfa724f0ac4e098f5fe466c903febddb2d994), closes [#581](https://github.com/kbknapp/clap-rs/issues/581)) +* fixes a bug where ZSH completions would panic if the binary name had an underscore in it ([891a2a00](https://github.com/kbknapp/clap-rs/commit/891a2a006f775e92c556dda48bb32fac9807c4fb), closes [#581](https://github.com/kbknapp/clap-rs/issues/581)) +* allow final word to be wrapped in wrap_help ([564c5f0f](https://github.com/kbknapp/clap-rs/commit/564c5f0f1730f4a2c1cdd128664f1a981c31dcd4), closes [#828](https://github.com/kbknapp/clap-rs/issues/828)) +* fixes a bug where global args weren't included in the generated completion scripts ([9a1e006e](https://github.com/kbknapp/clap-rs/commit/9a1e006eb75ad5a6057ebd119aa90f7e06c0ace8), closes [#841](https://github.com/kbknapp/clap-rs/issues/841)) + + + + +### v2.20.2 (2017-02-03) + +#### Bug Fixes + +* fixes a critical bug where subcommand settings were being propogated too far ([74648c94](https://github.com/kbknapp/clap-rs/commit/74648c94b893df542bfa5bb595e68c7bb8167e36), closes [#832](https://github.com/kbknapp/clap-rs/issues/832)) + + +#### Improvements + +* adds ArgGroup::multiple to the supported YAML fields for building ArgGroups from YAML ([d8590037](https://github.com/kbknapp/clap-rs/commit/d8590037ce07dafd8cd5b26928aa4a9fd3018288), closes [#840](https://github.com/kbknapp/clap-rs/issues/840)) + + +### v2.20.1 (2017-01-31) + +#### Bug Fixes + +* allow final word to be wrapped in wrap_help ([564c5f0f](https://github.com/kbknapp/clap-rs/commit/564c5f0f1730f4a2c1cdd128664f1a981c31dcd4), closes [#828](https://github.com/kbknapp/clap-rs/issues/828)) +* actually show character in debug output ([84d8c547](https://github.com/kbknapp/clap-rs/commit/84d8c5476de95b7f37d61888bc4f13688b712434)) +* include final character in line lenght ([aff4ba18](https://github.com/kbknapp/clap-rs/commit/aff4ba18da8147e1259b04b0bfbc1fcb5c78a3c0)) + +#### Improvements + +* updates libc and term_size deps for the libc version conflict ([6802ac4a](https://github.com/kbknapp/clap-rs/commit/6802ac4a59c142cda9ec55ca0c45ae5cb9a6ab55)) + +#### Documentation + +* fix link from app_from_crate! to crate_authors! (#822) ([5b29be9b](https://github.com/kbknapp/clap-rs/commit/5b29be9b073330ab1f7227cdd19fe4aab39d5dcb)) +* fix spelling of "guaranteed" ([4f30a65b](https://github.com/kbknapp/clap-rs/commit/4f30a65b9c03eb09607eb91a929a6396637dc105)) + + + +#### New Settings + +* **ArgsNegateSubcommands:** disables args being allowed between subcommands ([5e2af8c9](https://github.com/kbknapp/clap-rs/commit/5e2af8c96adb5ab75fa2d1536237ebcb41869494), closes [#793](https://github.com/kbknapp/clap-rs/issues/793)) +* **DontCollapseArgsInUsage:** disables the collapsing of positional args into `[ARGS]` in the usage string ([c2978afc](https://github.com/kbknapp/clap-rs/commit/c2978afc61fb46d5263ab3b2d87ecde1c9ce1553), closes [#769](https://github.com/kbknapp/clap-rs/issues/769)) +* **DisableHelpSubcommand:** disables building the `help` subcommand ([a10fc859](https://github.com/kbknapp/clap-rs/commit/a10fc859ee20159fbd9ff4337be59b76467a64f2)) +* **AllowMissingPositional:** allows one to implement `$ prog [optional] ` style CLIs where the second postional argument is required, but the first is optional ([1110fdc7](https://github.com/kbknapp/clap-rs/commit/1110fdc7a345c108820dc45783a9bf893fa4c214), closes [#636](https://github.com/kbknapp/clap-rs/issues/636)) +* **PropagateGlobalValuesDown:** automatically propagats global arg's values down through *used* subcommands ([985536c8](https://github.com/kbknapp/clap-rs/commit/985536c8ebcc09af98aac835f42a8072ad58c262), closes [#694](https://github.com/kbknapp/clap-rs/issues/694)) + +#### API Additions + +##### Arg + +* **Arg::value_terminator:** adds the ability to terminate multiple values with a given string or char ([be64ce0c](https://github.com/kbknapp/clap-rs/commit/be64ce0c373efc106384baca3f487ea99fe7b8cf), closes [#782](https://github.com/kbknapp/clap-rs/issues/782)) +* **Arg::default_value_if[s]:** adds new methods for *conditional* default values (such as a particular value from another argument was used) ([eb4010e7](https://github.com/kbknapp/clap-rs/commit/eb4010e7b21724447ef837db11ac441915728f22)) +* **Arg::requires_if[s]:** adds the ability to *conditionally* require additional args (such as if a particular value was used) ([198449d6](https://github.com/kbknapp/clap-rs/commit/198449d64393c265f0bc327aaeac23ec4bb97226)) +* **Arg::required_if[s]:** adds the ability for an arg to be *conditionally* required (i.e. "arg X is only required if arg Y was used with value Z") ([ee9cfddf](https://github.com/kbknapp/clap-rs/commit/ee9cfddf345a6b5ae2af42ba72aa5c89e2ca7f59)) +* **Arg::validator_os:** adds ability to validate values which may contain invalid UTF-8 ([47232498](https://github.com/kbknapp/clap-rs/commit/47232498a813db4f3366ccd3e9faf0bff56433a4)) + +##### Macros + +* **crate_description!:** Uses the `Cargo.toml` description field to fill in the `App::about` method at compile time ([4d9a82db](https://github.com/kbknapp/clap-rs/commit/4d9a82db8e875e9b64a9c2a5c6e22c25afc1279d), closes [#778](https://github.com/kbknapp/clap-rs/issues/778)) +* **crate_name!:** Uses the `Cargo.toml` name field to fill in the `App::new` method at compile time ([4d9a82db](https://github.com/kbknapp/clap-rs/commit/4d9a82db8e875e9b64a9c2a5c6e22c25afc1279d), closes [#778](https://github.com/kbknapp/clap-rs/issues/778)) +* **app_from_crate!:** Combines `crate_version!`, `crate_name!`, `crate_description!`, and `crate_authors!` into a single macro call to build a default `App` instance from the `Cargo.toml` fields ([4d9a82db](https://github.com/kbknapp/clap-rs/commit/4d9a82db8e875e9b64a9c2a5c6e22c25afc1279d), closes [#778](https://github.com/kbknapp/clap-rs/issues/778)) + + +#### Features + +* **no_cargo:** adds a `no_cargo` feature to disable Cargo-env-var-dependent macros for those *not* using `cargo` to build their crates (#786) ([6fdd2f9d](https://github.com/kbknapp/clap-rs/commit/6fdd2f9d693aaf1118fc61bd362273950703f43d)) + +#### Bug Fixes + +* **Options:** fixes a critical bug where options weren't forced to have a value ([5a5f2b1e](https://github.com/kbknapp/clap-rs/commit/5a5f2b1e9f598a0d0280ef3e98abbbba2bc41132), closes [#665](https://github.com/kbknapp/clap-rs/issues/665)) +* fixes a bug where calling the help of a subcommand wasn't ignoring required args of parent commands ([d3d34a2b](https://github.com/kbknapp/clap-rs/commit/d3d34a2b51ef31004055b0ab574f766d801c3adf), closes [#789](https://github.com/kbknapp/clap-rs/issues/789)) +* **Help Subcommand:** fixes a bug where the help subcommand couldn't be overriden ([d34ec3e0](https://github.com/kbknapp/clap-rs/commit/d34ec3e032d03e402d8e87af9b2942fe2819b2da), closes [#787](https://github.com/kbknapp/clap-rs/issues/787)) +* **Low Index Multiples:** fixes a bug which caused combinations of LowIndexMultiples and `Arg::allow_hyphen_values` to fail parsing ([26c670ca](https://github.com/kbknapp/clap-rs/commit/26c670ca16d2c80dc26d5c1ce83380ace6357318)) + +#### Improvements + +* **Default Values:** improves the error message when default values are involved ([1f33de54](https://github.com/kbknapp/clap-rs/commit/1f33de545036e7fd2f80faba251fca009bd519b8), closes [#774](https://github.com/kbknapp/clap-rs/issues/774)) +* **YAML:** adds conditional requirements and conditional default values to YAML ([9a4df327](https://github.com/kbknapp/clap-rs/commit/9a4df327893486adb5558ffefba790c634ccdc6e), closes [#764](https://github.com/kbknapp/clap-rs/issues/764)) +* Support `--("some-arg-name")` syntax for defining long arg names when using `clap_app!` macro ([f41ec962](https://github.com/kbknapp/clap-rs/commit/f41ec962c243a5ffff8b1be1ae2ad63970d3d1d4)) +* Support `("some app name")` syntax for defining app names when using `clap_app!` macro ([9895b671](https://github.com/kbknapp/clap-rs/commit/9895b671cff784f35cf56abcd8270f7c2ba09699), closes [#759](https://github.com/kbknapp/clap-rs/issues/759)) +* **Help Wrapping:** long app names (with spaces), authors, and descriptions are now wrapped appropriately ([ad4691b7](https://github.com/kbknapp/clap-rs/commit/ad4691b71a63e951ace346318238d8834e04ad8a), closes [#777](https://github.com/kbknapp/clap-rs/issues/777)) + + +#### Documentation + +* **Conditional Default Values:** fixes the failing doc tests of Arg::default_value_ifs ([4ef09101](https://github.com/kbknapp/clap-rs/commit/4ef091019c083b4db1a0c13f1c1e95ac363259f2)) +* **Conditional Requirements:** adds docs for Arg::requires_ifs ([7f296e29](https://github.com/kbknapp/clap-rs/commit/7f296e29db7d9036e76e5dbcc9c8b20dfe7b25bd)) +* **README.md:** fix some typos ([f22c21b4](https://github.com/kbknapp/clap-rs/commit/f22c21b422d5b287d1a1ac183a379ee02eebf54f)) +* **src/app/mod.rs:** fix some typos ([5c9b0d47](https://github.com/kbknapp/clap-rs/commit/5c9b0d47ca78dea285c5b9dec79063d24c3e451a)) + + +### v2.19.3 (2016-12-28) + + +#### Bug Fixes + +* fixes a bug where calling the help of a subcommand wasn't ignoring required args of parent commands ([a0ee4993](https://github.com/kbknapp/clap-rs/commit/a0ee4993015ea97b06b5bc9f378d8bcb18f1c51c), closes [#789](https://github.com/kbknapp/clap-rs/issues/789)) + + + + +### v2.19.2 (2016-12-08) + +#### Bug Fixes + +* **ZSH Completions:** escapes square brackets in ZSH completions ([7e17d5a3](https://github.com/kbknapp/clap-rs/commit/7e17d5a36b2cc2cc77e7b15796b14d639ed3cbf7), closes [#771](https://github.com/kbknapp/clap-rs/issues/771)) + +#### Documentation + +* **Examples:** adds subcommand examples ([0e0f3354](https://github.com/kbknapp/clap-rs/commit/0e0f33547a6901425afc1d9fbe19f7ae3832d9a4), closes [#766](https://github.com/kbknapp/clap-rs/issues/766)) +* **README.md:** adds guidance on when to use ~ in version pinning, and clarifies breaking change policy ([591eaefc](https://github.com/kbknapp/clap-rs/commit/591eaefc7319142ba921130e502bb0729feed907), closes [#765](https://github.com/kbknapp/clap-rs/issues/765)) + + + + +### v2.19.1 (2016-12-01) + + +#### Bug Fixes + +* **Help Messages:** fixes help message alignment when specific settings are used on options ([cd94b318](https://github.com/kbknapp/clap-rs/commit/cd94b3188d63b63295a319e90e826bca46befcd2), closes [#760](https://github.com/kbknapp/clap-rs/issues/760)) + +#### Improvements + +* **Bash Completion:** allows bash completion to fall back to traidtional bash completion upon no matching completing function ([b1b16d56](https://github.com/kbknapp/clap-rs/commit/b1b16d56d8fddf819bdbe24b3724bb6a9f3fa613))) + + + +## v2.19.0 (2016-11-21) + +#### Features + +* allows specifying AllowLeadingHyphen style values, but only for specific args vice command wide ([c0d70feb](https://github.com/kbknapp/clap-rs/commit/c0d70febad9996a77a54107054daf1914c50d4ef), closes [#742](https://github.com/kbknapp/clap-rs/issues/742)) + +#### Bug Fixes + +* **Required Unless:** fixes a bug where having required_unless set doesn't work when conflicts are also set ([d20331b6](https://github.com/kbknapp/clap-rs/commit/d20331b6f7940ac3a4e919999f8bb4780875125d), closes [#753](https://github.com/kbknapp/clap-rs/issues/753)) +* **ZSH Completions:** fixes an issue where zsh completions caused panics if there were no subcommands ([49e7cdab](https://github.com/kbknapp/clap-rs/commit/49e7cdab76dd1ccc07221e360f07808ec62648aa), closes [#754](https://github.com/kbknapp/clap-rs/issues/754)) + +#### Improvements + +* **Validators:** improves the error messages for validators ([65eb3385](https://github.com/kbknapp/clap-rs/commit/65eb33859d3ff53e7d3277f02a9d3fd9038a9dfb), closes [#744](https://github.com/kbknapp/clap-rs/issues/744)) + +#### Documentation + +* updates the docs landing page ([01e1e33f](https://github.com/kbknapp/clap-rs/commit/01e1e33f377934099a4a725fab5cd6c5ff50eaa2)) +* adds the macro version back to the readme ([45eb9bf1](https://github.com/kbknapp/clap-rs/commit/45eb9bf130329c3f3853aba0342c2fe3c64ff80f)) +* fix broken docs links ([808e7cee](https://github.com/kbknapp/clap-rs/commit/808e7ceeb86d4a319bdc270f51c23a64621dbfb3)) +* **Compatibility Policy:** adds an official compatibility policy to ([760d66dc](https://github.com/kbknapp/clap-rs/commit/760d66dc17310b357f257776624151da933cd25d), closes [#740](https://github.com/kbknapp/clap-rs/issues/740)) +* **Contributing:** updates the readme to improve the readability and contributing sections ([eb51316c](https://github.com/kbknapp/clap-rs/commit/eb51316cdfdc7258d287ba13b67ef2f42bd2b8f6)) + + +## v2.18.0 (2016-11-05) + + +#### Features + +* **Completions:** adds completion support for PowerShell. ([cff82c88](https://github.com/kbknapp/clap-rs/commit/cff82c880e21064fca63351507b80350df6caadf), closes [#729](https://github.com/kbknapp/clap-rs/issues/729)) + + + + +### v2.17.1 (2016-11-02) + + +#### Bug Fixes + +* **Low Index Multiples:** fixes a bug where using low index multiples was propagated to subcommands ([33924e88](https://github.com/kbknapp/clap-rs/commit/33924e884461983c4e6b5ea1330fecc769a4ade7), closes [#725](https://github.com/kbknapp/clap-rs/issues/725)) + + + + +## v2.17.0 (2016-11-01) + + +#### Features + +* **Positional Args:** allows specifying the second to last positional argument as multiple(true) ([1ced2a74](https://github.com/kbknapp/clap-rs/commit/1ced2a7433ea8937a1b260ea65d708f32ca7c95e), closes [#725](https://github.com/kbknapp/clap-rs/issues/725)) + + + + +### v2.16.4 (2016-10-31) + + +#### Improvements + +* **Error Output:** conflicting errors are now symetrical, meaning more consistent and less confusing ([3d37001d](https://github.com/kbknapp/clap-rs/commit/3d37001d1dc647d73cc597ff172f1072d4beb80d), closes [#718](https://github.com/kbknapp/clap-rs/issues/718)) + +#### Documentation + +* Fix typo in example `13a_enum_values_automatic` ([c22fbc07](https://github.com/kbknapp/clap-rs/commit/c22fbc07356e556ffb5d1a79ec04597d149b915e)) +* **README.md:** fixes failing yaml example (#715) ([21fba9e6](https://github.com/kbknapp/clap-rs/commit/21fba9e6cd8c163012999cd0ce271ec8780c5695)) + +#### Bug Fixes + +* **ZSH Completions:** fixes bug that caused panic on subcommands with aliases ([5c70e1a0](https://github.com/kbknapp/clap-rs/commit/5c70e1a01bc977e44c10015d18bb8e215c32dfc8), closes [#714](https://github.com/kbknapp/clap-rs/issues/714)) +* **debug:** fixes the debug feature (#716) ([6c11ccf4](https://github.com/kbknapp/clap-rs/commit/6c11ccf443d46258d51f7cda33fbcc81e7fe8e90)) + + + + +### v2.16.3 (2016-10-28) + + +#### Bug Fixes + +* Derive display order after propagation ([9cb6facf](https://github.com/kbknapp/clap-rs/commit/9cb6facf507aff7cddd124b8c29714d2b0e7bd13), closes [#706](https://github.com/kbknapp/clap-rs/issues/706)) +* **yaml-example:** inconsistent args ([847f7199](https://github.com/kbknapp/clap-rs/commit/847f7199219ead5065561d91d64780d99ae4b587)) + + + + +### v2.16.2 (2016-10-25) + + +#### Bug Fixes + +* **Fish Completions:** fixes a bug where single quotes are not escaped ([780b4a18](https://github.com/kbknapp/clap-rs/commit/780b4a18281b6f7f7071e1b9db2290fae653c406), closes [#704](https://github.com/kbknapp/clap-rs/issues/704)) + + + +### v2.16.1 (2016-10-24) + + +#### Bug Fixes + +* **Help Message:** fixes a regression bug where args with multiple(true) threw off alignment ([ebddac79](https://github.com/kbknapp/clap-rs/commit/ebddac791f3ceac193d5ad833b4b734b9643a7af), closes [#702](https://github.com/kbknapp/clap-rs/issues/702)) + + + + +## v2.16.0 (2016-10-23) + + +#### Features + +* **Completions:** adds ZSH completion support ([3e36b0ba](https://github.com/kbknapp/clap-rs/commit/3e36b0bac491d3f6194aee14604caf7be26b3d56), closes [#699](https://github.com/kbknapp/clap-rs/issues/699)) + + + + +## v2.15.0 (2016-10-21) + + +#### Features + +* **AppSettings:** adds new setting `AppSettings::AllowNegativeNumbers` ([ab064546](https://github.com/kbknapp/clap-rs/commit/ab06454677fb6aa9b9f804644fcca2168b1eaee3), closes [#696](https://github.com/kbknapp/clap-rs/issues/696)) + +#### Documentation + +* **app/settings.rs:** moves variants to roughly alphabetical order ([9ed4d4d7](https://github.com/kbknapp/clap-rs/commit/9ed4d4d7957a23357aef60081e45639ab9e3905f)) + + + +### v2.14.1 (2016-10-20) + + +#### Documentation + +* Improve documentation around features ([4ee85b95](https://github.com/kbknapp/clap-rs/commit/4ee85b95d2d16708a016a3ba4e6e2c93b89b7fad)) +* reword docs for ErrorKind and app::Settings ([3ccde7a4](https://github.com/kbknapp/clap-rs/commit/3ccde7a4b8f7a2ea8b916a5415c04a8ff4b5cb7a)) +* fix tests that fail when the "suggestions" feature is disabled ([996fc381](https://github.com/kbknapp/clap-rs/commit/996fc381763a48d125c7ea8a58fed057fd0b4ac6)) +* fix the OsString-using doc-tests ([af9e1a39](https://github.com/kbknapp/clap-rs/commit/af9e1a393ce6cdda46a03c8a4f48df222b015a24)) +* tag non-rust code blocks as such instead of ignoring them ([0ba9f4b1](https://github.com/kbknapp/clap-rs/commit/0ba9f4b123f281952581b6dec948f7e51dd22890)) +* **ErrorKind:** improve some errors about subcommands ([9f6217a4](https://github.com/kbknapp/clap-rs/commit/9f6217a424da823343d7b801b9c350dee3cd1906)) +* **yaml:** make sure the doc-tests don't fail before "missing file" ([8c0f5551](https://github.com/kbknapp/clap-rs/commit/8c0f55516f4910c78c9f8a2bdbd822729574f95b)) + +#### Improvements + +* Stabilize clap_app! ([cd516006](https://github.com/kbknapp/clap-rs/commit/cd516006e35c37b005f329338560a0a53d1f3e00)) +* **with_defaults:** Deprecate App::with_defaults() ([26085409](https://github.com/kbknapp/clap-rs/commit/2608540940c8bb66e517b65706bc7dea55510682), closes [#638](https://github.com/kbknapp/clap-rs/issues/638)) + +#### Bug Fixes + +* fixes a bug that made determining when to auto-wrap long help messages inconsistent ([468baadb](https://github.com/kbknapp/clap-rs/commit/468baadb8398fc1d37897b0c49374aef4cf97dca), closes [#688](https://github.com/kbknapp/clap-rs/issues/688)) +* **Completions:** fish completions for nested subcommands ([a61eaf8a](https://github.com/kbknapp/clap-rs/commit/a61eaf8aade76cfe90ccc0f7125751ebf60e3254)) +* **features:** Make lints not enable other nightly-requiring features ([835f75e3](https://github.com/kbknapp/clap-rs/commit/835f75e3ba20999117363ed9f916464d777f36ef)) + + + + +## v2.14.0 (2016-10-05) + + +#### Features + +* **arg_aliases:** Ability to alias arguments ([33b5f6ef](https://github.com/kbknapp/clap-rs/commit/33b5f6ef2c9612ecabb31f96b824793e46bfd3dd), closes [#669](https://github.com/kbknapp/clap-rs/issues/669)) +* **flag_aliases:** Ability to alias flags ([40d6dac9](https://github.com/kbknapp/clap-rs/commit/40d6dac973927dded6ab423481634ef47ee7bfd7)) + +#### Bug Fixes + +* **UsageParser:** Handle non-ascii names / options. ([1d6a7c6e](https://github.com/kbknapp/clap-rs/commit/1d6a7c6e7e6aadc527346aa822f19d8587f714f3), closes [#664](https://github.com/kbknapp/clap-rs/issues/664)) + +#### Documentation + +* typo ([bac417fa](https://github.com/kbknapp/clap-rs/commit/bac417fa1cea3d32308334c7cccfcf54546cd9d8)) + + + +## v2.13.0 (2016-09-18) + + +#### Documentation + +* updates README.md with new website information and updated video tutorials info ([0c19c580](https://github.com/kbknapp/clap-rs/commit/0c19c580cf50f1b82ff32f70b36708ae2bcac132)) +* updates the docs about removing implicit value_delimiter(true) ([c81bc722](https://github.com/kbknapp/clap-rs/commit/c81bc722ebb8a86d22be89b5aec98df9fe222a08)) +* **Default Values:** adds better examples on using default values ([57a8d9ab](https://github.com/kbknapp/clap-rs/commit/57a8d9abb2f973c235a8a14f8fc031673d7a7460), closes [#418](https://github.com/kbknapp/clap-rs/issues/418)) + +#### Bug Fixes + +* **Value Delimiters:** fixes the confusion around implicitly setting value delimiters. (default is now `false`) ([09d4d0a9](https://github.com/kbknapp/clap-rs/commit/09d4d0a9038d7ce2df55c2aec95e16f36189fcee), closes [#666](https://github.com/kbknapp/clap-rs/issues/666)) + + + + +### v2.12.1 (2016-09-13) + + +#### Bug Fixes + +* **Help Wrapping:** fixes a regression-bug where the old {n} newline char stopped working ([92ac353b](https://github.com/kbknapp/clap-rs/commit/92ac353b48b7caa2511ad2a046d94da93c236cf6), closes [#661](https://github.com/kbknapp/clap-rs/issues/661)) + + + + +## v2.12.0 (2016-09-13) + + +#### Features + +* **Help:** adds ability to hide the possible values on a per argument basis ([9151ef73](https://github.com/kbknapp/clap-rs/commit/9151ef739871f2e74910c342299c0de196b95dec), closes [#640](https://github.com/kbknapp/clap-rs/issues/640)) +* **help:** allow for limiting detected terminal width ([a43e28af](https://github.com/kbknapp/clap-rs/commit/a43e28af85c9a9deaedd5ef735f4f13008daab29), closes [#653](https://github.com/kbknapp/clap-rs/issues/653)) + +#### Documentation + +* **Help Wrapping:** removes the verbiage about using `'{n}'` to insert newlines in help text ([c5a2b352](https://github.com/kbknapp/clap-rs/commit/c5a2b352ca600f5b802290ad945731066cd53611)) +* **Value Delimiters:** updates the docs for the Arg::multiple method WRT value delimiters and default settings ([f9d17a06](https://github.com/kbknapp/clap-rs/commit/f9d17a060aa53f10d0a6e1a7eed5d989d1a59533)) +* **appsettings:** Document AppSetting::DisableVersion ([94501965](https://github.com/kbknapp/clap-rs/commit/945019654d2ca67eb2b1d6014fdf80b84d528d30), closes [#589](https://github.com/kbknapp/clap-rs/issues/589)) + +#### Bug Fixes + +* **AllowLeadingHyphen:** fixes a bug where valid args aren't recognized with this setting ([a9699e4d](https://github.com/kbknapp/clap-rs/commit/a9699e4d7cdc9a06e73b845933ff1fe6d76f016a), closes [#588](https://github.com/kbknapp/clap-rs/issues/588)) + +#### Improvements + +* **Help Wrapping:** + * clap now ignores hard newlines in help messages and properly re-aligns text, but still wraps if the term width is too small ([c7678523](https://github.com/kbknapp/clap-rs/commit/c76785239fd42adc8ca04f9202b6fec615aa9f14), closes [#617](https://github.com/kbknapp/clap-rs/issues/617)) + * makes some minor changes to when next line help is automatically used ([01cae799](https://github.com/kbknapp/clap-rs/commit/01cae7990a33167ac35103fb36c811b4fe6eb98f)) +* **Value Delimiters:** changes the default value delimiter rules ([f9e69254](https://github.com/kbknapp/clap-rs/commit/f9e692548e8c94de15f909432de301407d6bb834), closes [#655](https://github.com/kbknapp/clap-rs/issues/655)) +* **YAML:** supports setting Arg::require_delimiter from YAML ([b9b55a39](https://github.com/kbknapp/clap-rs/commit/b9b55a39dfebcdbdc05dca2692927e503db50816)) + +#### Performance + +* **help:** fix redundant contains() checks ([a8afed74](https://github.com/kbknapp/clap-rs/commit/a8afed7428bf0733f8e93bb11ad6c00d9e970fcc)) + + + + +### v2.11.3 (2016-09-07) + + +#### Documentation + +* **Help Wrapping:** removes the verbiage about using `'{n}'` to insert newlines in help text ([c5a2b352](https://github.com/kbknapp/clap-rs/commit/c5a2b352ca600f5b802290ad945731066cd53611)) + +#### Improvements + +* **Help Wrapping:** + * clap now ignores hard newlines in help messages and properly re-aligns text, but still wraps if the term width is too small ([c7678523](https://github.com/kbknapp/clap-rs/commit/c76785239fd42adc8ca04f9202b6fec615aa9f14), closes [#617](https://github.com/kbknapp/clap-rs/issues/617)) + * makes some minor changes to when next line help is automatically used ([01cae799](https://github.com/kbknapp/clap-rs/commit/01cae7990a33167ac35103fb36c811b4fe6eb98f)) +* **YAML:** supports setting Arg::require_delimiter from YAML ([b9b55a39](https://github.com/kbknapp/clap-rs/commit/b9b55a39dfebcdbdc05dca2692927e503db50816)) + + + + + +### v2.11.2 (2016-09-06) + +#### Improvements + +* **Help Wrapping:** makes some minor changes to when next line help is automatically used ([5658b117](https://github.com/kbknapp/clap-rs/commit/5658b117aec3e03adff9c8c52a4c4bc1fcb4e1ff)) + + + +### v2.11.1 (2016-09-05) + + +#### Bug Fixes + +* **Settings:** fixes an issue where settings weren't propogated down through grand-child subcommands ([b3efc107](https://github.com/kbknapp/clap-rs/commit/b3efc107515d78517b20798ff3890b8a2b04498e), closes [#638](https://github.com/kbknapp/clap-rs/issues/638)) + +#### Features + +* **Errors:** Errors with custom description ([58512f2f](https://github.com/kbknapp/clap-rs/commit/58512f2fcb430745f1ee6ee8f1c67f62dc216c73)) + +#### Improvements + +* **help:** use term_size instead of home-grown solution ([fc7327e9](https://github.com/kbknapp/clap-rs/commit/fc7327e9dcf4258ef2baebf0a8714d9c0622855b)) + + + + +### v2.11.0 (2016-08-28) + + +#### Bug Fixes + +* **Groups:** fixes some usage strings that contain both args in groups and ones that conflict with each other ([3d782def](https://github.com/kbknapp/clap-rs/commit/3d782def57725e2de26ca5a5bc5cc2e40ddebefb), closes [#616](https://github.com/kbknapp/clap-rs/issues/616)) + +#### Documentation + +* moves docs to docs.rs ([03209d5e](https://github.com/kbknapp/clap-rs/commit/03209d5e1300906f00bafec1869c2047a92e5071), closes [#634](https://github.com/kbknapp/clap-rs/issues/634)) + +#### Improvements + +* **Completions:** uses standard conventions for bash completion files, namely '{bin}.bash-completion' ([27f5bbfb](https://github.com/kbknapp/clap-rs/commit/27f5bbfbcc9474c2f57c2b92b1feb898ae46ee70), closes [#567](https://github.com/kbknapp/clap-rs/issues/567)) +* **Help:** automatically moves help text to the next line and wraps when term width is determined to be too small, or help text is too long ([150964c4](https://github.com/kbknapp/clap-rs/commit/150964c4e7124d54476c9d9b4b3f2406f0fd00e5), closes [#597](https://github.com/kbknapp/clap-rs/issues/597)) +* **YAML Errors:** vastly improves error messages when using YAML ([f43b7c65](https://github.com/kbknapp/clap-rs/commit/f43b7c65941c53adc0616b8646a21dc255862eb2), closes [#574](https://github.com/kbknapp/clap-rs/issues/574)) + +#### Features + +* adds App::with_defaults to automatically use crate_authors! and crate_version! macros ([5520bb01](https://github.com/kbknapp/clap-rs/commit/5520bb012c127dfd299fd55699443c744d8dcd5b), closes [#600](https://github.com/kbknapp/clap-rs/issues/600)) + + + + +### v2.10.4 (2016-08-25) + + +#### Bug Fixes + +* **Help Wrapping:** fixes a bug where help is wrapped incorrectly and causing a panic with some non-English characters ([d0b442c7](https://github.com/kbknapp/clap-rs/commit/d0b442c7beeecac9764406bc3bd171ced0b8825e), closes [#626](https://github.com/kbknapp/clap-rs/issues/626)) + + + + +### v2.10.3 (2016-08-25) + +#### Features + +* **Help:** adds new short hand way to use source formatting and ignore term width in help messages ([7dfdaf20](https://github.com/kbknapp/clap-rs/commit/7dfdaf200ebb5c431351a045b48f5e0f0d3f31db), closes [#625](https://github.com/kbknapp/clap-rs/issues/625)) + +#### Documentation + +* **Term Width:** adds details about set_term_width(0) ([00b8205d](https://github.com/kbknapp/clap-rs/commit/00b8205d22639d1b54b9c453c55c785aace52cb2)) + +#### Bug Fixes + +* **Unicode:** fixes two bugs where non-English characters were stripped or caused a panic with help wrapping ([763a5c92](https://github.com/kbknapp/clap-rs/commit/763a5c920e23efc74d190af0cb8b5dd714b2d67a), closes [#626](https://github.com/kbknapp/clap-rs/issues/626)) + + + + +### v2.10.2 (2016-08-22) + + +#### Bug Fixes + +* fixes a bug where the help is printed twice ([a643fb28](https://github.com/kbknapp/clap-rs/commit/a643fb283acd9905dc727c4579c5c9fa2ceaa7e7), closes [#623](https://github.com/kbknapp/clap-rs/issues/623)) + + + + +### v2.10.1 (2016-08-21) + + +#### Bug Fixes + +* **Help Subcommand:** fixes misleading usage string when using multi-level subcommmands ([e203515e](https://github.com/kbknapp/clap-rs/commit/e203515e3ac495b405dbba4f78fb6af148fd282e), closes [#618](https://github.com/kbknapp/clap-rs/issues/618)) + +#### Features + +* **YAML:** allows using lists or single values with arg declarations ([9ade2cd4](https://github.com/kbknapp/clap-rs/commit/9ade2cd4b268d6d7fe828319ce6a523c641b9c38), closes [#614](https://github.com/kbknapp/clap-rs/issues/614), [#613](https://github.com/kbknapp/clap-rs/issues/613)) + + + + +## v2.10.0 (2016-07-29) + + +#### Features + +* **Completions:** one can generate a basic fish completions script at compile time ([1979d2f2](https://github.com/kbknapp/clap-rs/commit/1979d2f2f3216e57d02a97e624a8a8f6cf867ed9)) + +#### Bug Fixes + +* **parser:** preserve external subcommand name ([875df243](https://github.com/kbknapp/clap-rs/commit/875df24316c266920a073c13bbefbf546bc1f635)) + +#### Breaking Changes + +* **parser:** preserve external subcommand name ([875df243](https://github.com/kbknapp/clap-rs/commit/875df24316c266920a073c13bbefbf546bc1f635)) + +#### Documentation + +* **YAML:** fixes example 17's incorrect reference to arg_groups instead of groups ([b6c99e13](https://github.com/kbknapp/clap-rs/commit/b6c99e1377f918e78c16c8faced70a71607da931), closes [#601](https://github.com/kbknapp/clap-rs/issues/601)) + + + + +### 2.9.3 (2016-07-24) + + +#### Bug Fixes + +* fixes bug where only first arg in list of required_unless_one is recognized ([1fc3b55b](https://github.com/kbknapp/clap-rs/commit/1fc3b55bd6c8653b02e7c4253749c6b77737d2ac), closes [#575](https://github.com/kbknapp/clap-rs/issues/575)) +* **Settings:** fixes typo subcommandsrequired->subcommandrequired ([fc72cdf5](https://github.com/kbknapp/clap-rs/commit/fc72cdf591d30f5d9375d0b5cc2a2ff3e812f9f6), closes [#593](https://github.com/kbknapp/clap-rs/issues/593)) + +#### Features + +* **Completions:** adds the ability to generate completions to io::Write object ([9f62cf73](https://github.com/kbknapp/clap-rs/commit/9f62cf7378ba5acb5ce8c5bac89b4aa60c30755f)) +* **Settings:** Add unset_setting and unset_settings fns to App (#598) ([0ceba231](https://github.com/kbknapp/clap-rs/commit/0ceba231c6767cd6d88fdb1feeeea41deadf77ff), closes [#590](https://github.com/kbknapp/clap-rs/issues/590)) + + + +### 2.9.2 (2016-07-03) + + +#### Documentation + +* **Completions:** fixes the formatting of the Cargo.toml excerpt in the completions example ([722f2607](https://github.com/kbknapp/clap-rs/commit/722f2607beaef56b6a0e433db5fd09492d9f028c)) + +#### Bug Fixes + +* **Completions:** fixes bug where --help and --version short weren't added to the completion list ([e9f2438e](https://github.com/kbknapp/clap-rs/commit/e9f2438e2ce99af0ae570a2eaf541fc7f55b771b), closes [#536](https://github.com/kbknapp/clap-rs/issues/536)) + + + + +### 2.9.1 (2016-07-02) + + +#### Improvements + +* **Completions:** allows multiple completions to be built by namespacing with bin name ([57484b2d](https://github.com/kbknapp/clap-rs/commit/57484b2daeaac01c1026e8c84efc8bf099e0eb31)) + + + +## v2.9.0 (2016-07-01) + + +#### Documentation + +* **Completions:** + * fixes some errors in the completion docs ([9b359bf0](https://github.com/kbknapp/clap-rs/commit/9b359bf06255d3dad8f489308044b60a9d1e6a87)) + * adds documentation for completion scripts ([c6c519e4](https://github.com/kbknapp/clap-rs/commit/c6c519e40efd6c4533a9ef5efe8e74fd150391b7)) + +#### Features + +* **Completions:** + * one can now generate a bash completions script at compile time! ([e75b6c7b](https://github.com/kbknapp/clap-rs/commit/e75b6c7b75f729afb9eb1d2a2faf61dca7674634), closes [#376](https://github.com/kbknapp/clap-rs/issues/376)) + * completions now include aliases to subcommands, including all subcommand options ([0ab9f840](https://github.com/kbknapp/clap-rs/commit/0ab9f84052a8cf65b5551657f46c0c270841e634), closes [#556](https://github.com/kbknapp/clap-rs/issues/556)) + * completions now continue completing even after first completion ([18fc2e5b](https://github.com/kbknapp/clap-rs/commit/18fc2e5b5af63bf54a94b72cec5e1223d49f4806)) + * allows matching on possible values in options ([89cc2026](https://github.com/kbknapp/clap-rs/commit/89cc2026ba9ac69cf44c5254360bbf99236d4f89), closes [#557](https://github.com/kbknapp/clap-rs/issues/557)) + +#### Bug Fixes + +* **AllowLeadingHyphen:** fixes an issue where isn't ignored like it should be with this setting ([96c24c9a](https://github.com/kbknapp/clap-rs/commit/96c24c9a8fa1f85e06138d3cdd133e51659e19d2), closes [#558](https://github.com/kbknapp/clap-rs/issues/558)) + + +## v2.8.0 (2016-06-30) + + +#### Features + +* **Arg:** adds new setting `Arg::require_delimiter` which requires val delimiter to parse multiple values ([920b5595](https://github.com/kbknapp/clap-rs/commit/920b5595ed72abfb501ce054ab536067d8df2a66)) + +#### Bug Fixes + +* Declare term::Winsize as repr(C) ([5d663d90](https://github.com/kbknapp/clap-rs/commit/5d663d905c9829ce6e7a164f1f0896cdd70236dd)) + +#### Documentation + +* **Arg:** adds docs for ([49af4e38](https://github.com/kbknapp/clap-rs/commit/49af4e38a5dae2ab0a7fc3b4147e2c053d532484)) + + + + +### v2.7.1 (2016-06-29) + + +#### Bug Fixes + +* **Options:** + * options with multiple values and using delimiters no longer parse additional values after a trailing space ([cdc500bd](https://github.com/kbknapp/clap-rs/commit/cdc500bdde6abe238c36ade406ddafc2bafff583)) + * using options with multiple values and with an = no longer parse args after the trailing space as values ([290f61d0](https://github.com/kbknapp/clap-rs/commit/290f61d07177413cf082ada55526d83405f6d011)) + + + + +## v2.7.0 (2016-06-28) + + +#### Documentation + +* fix typos ([43b3d40b](https://github.com/kbknapp/clap-rs/commit/43b3d40b8c38b1571da75af86b5088be96cccec2)) +* **ArgGroup:** vastly improves ArgGroup docs by adding better examples ([9e5f4f5d](https://github.com/kbknapp/clap-rs/commit/9e5f4f5d734d630bca5535c3a0aa4fd4f9db3e39), closes [#534](https://github.com/kbknapp/clap-rs/issues/534)) + +#### Features + +* **ArgGroup:** one can now specify groups which require AT LEAST one of the args ([33689acc](https://github.com/kbknapp/clap-rs/commit/33689acc689b217a8c0ee439f1b1225590c38355), closes [#533](https://github.com/kbknapp/clap-rs/issues/533)) + +#### Bug Fixes + +* **App:** using `App::print_help` now prints the same as would have been printed by `--help` or the like ([e84cc018](https://github.com/kbknapp/clap-rs/commit/e84cc01836bbe0527e97de6db9889bd9e0fd6ba1), closes [#536](https://github.com/kbknapp/clap-rs/issues/536)) +* **Help:** + * prevents invoking help help and displaying incorrect help message ([e3d2893f](https://github.com/kbknapp/clap-rs/commit/e3d2893f377942a2d4cf3c6ff04524d0346e6fdb), closes [#538](https://github.com/kbknapp/clap-rs/issues/538)) + * subcommand help messages requested via help now correctly match --help ([08ad1cff](https://github.com/kbknapp/clap-rs/commit/08ad1cff4fec57224ea957a2891a057b323c01bc), closes [#539](https://github.com/kbknapp/clap-rs/issues/539)) + +#### Improvements + +* **ArgGroup:** Add multiple ArgGroups per Arg ([902e182f](https://github.com/kbknapp/clap-rs/commit/902e182f7a58aff11ff01e0a452abcdbdb2262aa), closes [#426](https://github.com/kbknapp/clap-rs/issues/426)) +* **Usage Strings:** `[FLAGS]` and `[ARGS]` are no longer blindly added to usage strings ([9b2e45b1](https://github.com/kbknapp/clap-rs/commit/9b2e45b170aff567b038d8b3368880b6046c10c6), closes [#537](https://github.com/kbknapp/clap-rs/issues/537)) +* **arg_enum!:** allows using meta items like repr(C) with arg_enum!s ([edf9b233](https://github.com/kbknapp/clap-rs/commit/edf9b2331c17a2cbcc13f961add4c55c2778e773), closes [#543](https://github.com/kbknapp/clap-rs/issues/543)) + + + + +## v2.6.0 (2016-06-14) + + +#### Improvements + +* removes extra newline from help output ([86e61d19](https://github.com/kbknapp/clap-rs/commit/86e61d19a748fb9870fcf1175308984e51ca1115)) +* allows printing version to any io::Write object ([921f5f79](https://github.com/kbknapp/clap-rs/commit/921f5f7916597f1d028cd4a65bfe76a01c801724)) +* removes extra newline when printing version ([7e2e2cbb](https://github.com/kbknapp/clap-rs/commit/7e2e2cbb4a8a0f050bb8072a376f742fc54b8589)) +* **Aliases:** improves readability of asliases in help messages ([ca511de7](https://github.com/kbknapp/clap-rs/commit/ca511de71f5b8c2ac419f1b188658e8c63b67846), closes [#526](https://github.com/kbknapp/clap-rs/issues/526), [#529](https://github.com/kbknapp/clap-rs/issues/529)) +* **Usage Strings:** improves the default usage string when only a single positional arg is present ([ec86f2da](https://github.com/kbknapp/clap-rs/commit/ec86f2dada1545a63fc72355e22fcdc4c466c215), closes [#518](https://github.com/kbknapp/clap-rs/issues/518)) + +#### Features + +* **Help:** allows wrapping at specified term width (Even on Windows!) ([1761dc0d](https://github.com/kbknapp/clap-rs/commit/1761dc0d27d0d621229d792be40c36fbf65c3014), closes [#451](https://github.com/kbknapp/clap-rs/issues/451)) +* **Settings:** + * adds new setting to stop delimiting values with -- or TrailingVarArg ([fc3e0f5a](https://github.com/kbknapp/clap-rs/commit/fc3e0f5afda6d24cdb3c4676614beebe13e1e870), closes [#511](https://github.com/kbknapp/clap-rs/issues/511)) + * one can now set an AppSetting which is propogated down through child subcommands ([e2341835](https://github.com/kbknapp/clap-rs/commit/e23418351a3b98bf08dfd7744bc14377c70d59ee), closes [#519](https://github.com/kbknapp/clap-rs/issues/519)) +* **Subcommands:** adds support for visible aliases ([7b10e7f8](https://github.com/kbknapp/clap-rs/commit/7b10e7f8937a07fdb8d16a6d8df79ce78d080cd3), closes [#522](https://github.com/kbknapp/clap-rs/issues/522)) + +#### Bug Fixes + +* fixes bug where args are printed out of order with templates ([05abb534](https://github.com/kbknapp/clap-rs/commit/05abb534864764102031a0d402e64ac65867aa87)) +* fixes bug where one can't override version or help flags ([90d7d6a2](https://github.com/kbknapp/clap-rs/commit/90d7d6a2ea8240122dd9bf8d82d3c4f5ebb5c703), closes [#514](https://github.com/kbknapp/clap-rs/issues/514)) +* fixes issue where before_help wasn't printed ([b3faff60](https://github.com/kbknapp/clap-rs/commit/b3faff6030f76a23f26afcfa6a90169002ed7106)) +* **Help:** `App::before_help` and `App::after_help` now correctly wrap ([1f4da767](https://github.com/kbknapp/clap-rs/commit/1f4da7676e6e71aa8dda799f3eeefad105a47819), closes [#516](https://github.com/kbknapp/clap-rs/issues/516)) +* **Settings:** fixes bug where new color settings couldn't be converted from strs ([706a7c11](https://github.com/kbknapp/clap-rs/commit/706a7c11b0900be594de6d5a3121938eff197602)) +* **Subcommands:** subcommands with aliases now display help of the aliased subcommand ([5354d14b](https://github.com/kbknapp/clap-rs/commit/5354d14b51f189885ba110e01e6b76cca3752992), closes [#521](https://github.com/kbknapp/clap-rs/issues/521)) +* **Windows:** fixes a failing windows build ([01e7dfd6](https://github.com/kbknapp/clap-rs/commit/01e7dfd6c07228c0be6695b3c7bf9370d82860d4)) +* **YAML:** adds missing YAML methods for App and Arg ([e468faf3](https://github.com/kbknapp/clap-rs/commit/e468faf3f05950fd9f72d84b69aa2061e91c6c64), closes [#528](https://github.com/kbknapp/clap-rs/issues/528)) + + + + +### v2.5.2 (2016-05-31) + + +#### Improvements + +* removes extra newline from help output ([86e61d19](https://github.com/kbknapp/clap-rs/commit/86e61d19a748fb9870fcf1175308984e51ca1115)) +* allows printing version to any io::Write object ([921f5f79](https://github.com/kbknapp/clap-rs/commit/921f5f7916597f1d028cd4a65bfe76a01c801724)) +* removes extra newline when printing version ([7e2e2cbb](https://github.com/kbknapp/clap-rs/commit/7e2e2cbb4a8a0f050bb8072a376f742fc54b8589)) + +#### Bug Fixes + +* fixes bug where args are printed out of order with templates ([3935431d](https://github.com/kbknapp/clap-rs/commit/3935431d5633f577c0826ae2142794b301f4b8ca)) +* fixes bug where one can't override version or help flags ([90d7d6a2](https://github.com/kbknapp/clap-rs/commit/90d7d6a2ea8240122dd9bf8d82d3c4f5ebb5c703), closes [#514](https://github.com/kbknapp/clap-rs/issues/514)) +* fixes issue where before_help wasn't printed ([b3faff60](https://github.com/kbknapp/clap-rs/commit/b3faff6030f76a23f26afcfa6a90169002ed7106)) + +#### Documentation + +* inter-links all types and pages ([3312893d](https://github.com/kbknapp/clap-rs/commit/3312893ddaef3f44d68d8d26ed3d08010be50d97), closes [#505](https://github.com/kbknapp/clap-rs/issues/505)) +* makes all publicly available types viewable in docs ([52ca6505](https://github.com/kbknapp/clap-rs/commit/52ca6505b4fec7b5c2d53d160c072d395eb21da6)) + + +### v2.5.1 (2016-05-11) + + +#### Bug Fixes + +* **Subcommand Aliases**: fixes lifetime issue when setting multiple aliases at once ([ac42f6cf0](https://github.com/kbknapp/clap-rs/commit/ac42f6cf0de6c4920f703807d63061803930b18d)) + + +## v2.5.0 (2016-05-10) + + +#### Improvements + +* **SubCommand Aliases:** adds feature to yaml configs too ([69592195](https://github.com/kbknapp/clap-rs/commit/695921954dde46dfd483399dcdef482c9dd7f34a)) + +#### Features + +* **SubCommands:** adds support for subcommand aliases ([66b4dea6](https://github.com/kbknapp/clap-rs/commit/66b4dea65c44d8f77ff522238a9237aed1bcab6d), closes [#469](https://github.com/kbknapp/clap-rs/issues/469)) + + + +### v2.4.3 (2016-05-10) + + +#### Bug Fixes + +* **Usage Strings:** + * now properly dedups args that are also in groups ([3ca0947c](https://github.com/kbknapp/clap-rs/commit/3ca0947c166b4f8525752255e3a4fa6565eb9689), closes [#498](https://github.com/kbknapp/clap-rs/issues/498)) + * removes duplicate groups from usage strings ([f574fb8a](https://github.com/kbknapp/clap-rs/commit/f574fb8a7cde4d4a2fa4c4481d59be2d0f135427)) + +#### Improvements + +* **Groups:** formats positional args in groups in a better way ([fef11154](https://github.com/kbknapp/clap-rs/commit/fef11154fb7430d1cbf04a672aabb366e456a368)) +* **Help:** + * moves positionals to standard <> formatting ([03dfe5ce](https://github.com/kbknapp/clap-rs/commit/03dfe5ceff1d63f172788ff688567ddad9fe119b)) + * default help subcommand string has been shortened ([5b7fe8e4](https://github.com/kbknapp/clap-rs/commit/5b7fe8e4161e43ab19e2e5fcf55fbe46791134e9), closes [#494](https://github.com/kbknapp/clap-rs/issues/494)) + + +### v2.4.3 (2016-05-10) + +* Ghost Release + + +### v2.4.3 (2016-05-10) + +* Ghost Release + + +## v2.4.0 (2016-05-02) + + +#### Features + +* **Help:** adds support for displaying info before help message ([29fbfa3b](https://github.com/kbknapp/clap-rs/commit/29fbfa3b963f2f3ca7704bf5d3e1201531baa373)) +* **Required:** adds allowing args that are required unless certain args are present ([af1f7916](https://github.com/kbknapp/clap-rs/commit/af1f79168390ea7da4074d0d9777de458ea64971)) + +#### Documentation + +* hides formatting from docs ([cb708093](https://github.com/kbknapp/clap-rs/commit/cb708093a7cd057f08c98b7bd1ed54c2db86ae7e)) +* **required_unless:** adds docs and examples for required_unless ([ca727b52](https://github.com/kbknapp/clap-rs/commit/ca727b52423b9883acd88b2f227b2711bc144573)) + +#### Bug Fixes + +* **Required Args:** fixes issue where missing required args are sometimes duplicatd in error messages ([3beebd81](https://github.com/kbknapp/clap-rs/commit/3beebd81e7bc2faa4115ac109cf570e512c5477f), closes [#492](https://github.com/kbknapp/clap-rs/issues/492)) + + + +## v2.3.0 (2016-04-18) + + +#### Improvements + +* **macros.rs:** Added write_nspaces macro (a new version of write_spaces) ([9d757e86](https://github.com/kbknapp/clap-rs/commit/9d757e8678e334e5a740ac750c76a9ed4e785cba)) +* **parser.rs:** + * Provide a way to create a usage string without the USAGE: title ([a91d378b](https://github.com/kbknapp/clap-rs/commit/a91d378ba0c91b5796457f8c6e881b13226ab735)) + * Make Parser's create_usage public allowing to have function outside the parser to generate the help ([d51945f8](https://github.com/kbknapp/clap-rs/commit/d51945f8b82ebb0963f4f40b384a9e8335783091)) + * Expose Parser's flags, opts and positionals argument as iterators ([9b23e7ee](https://github.com/kbknapp/clap-rs/commit/9b23e7ee40e51f7a823644c4496be955dc6c9d3a)) +* **src/args:** Exposes argument display order by introducing a new Trait ([1321630e](https://github.com/kbknapp/clap-rs/commit/1321630ef56955f152c73376d4d85cceb0bb4a12)) +* **srs/args:** Added longest_filter to AnyArg trait ([65b3f667](https://github.com/kbknapp/clap-rs/commit/65b3f667532685f854c699ddd264d326599cf7e5)) + +#### Features + +* **Authors Macro:** adds a crate_authors macro ([38fb59ab](https://github.com/kbknapp/clap-rs/commit/38fb59abf480eb2b6feca269097412f8b00b5b54), closes [#447](https://github.com/kbknapp/clap-rs/issues/447)) +* **HELP:** + * implements optional colored help messages ([abc8f669](https://github.com/kbknapp/clap-rs/commit/abc8f669c3c8193ffc3a3b0ac6c3ac2198794d4f), closes [#483](https://github.com/kbknapp/clap-rs/issues/483)) + * Add a Templated Help system. ([81e121ed](https://github.com/kbknapp/clap-rs/commit/81e121edd616f7285593f11120c63bcccae0d23e)) + +#### Bug Fixes + +* **HELP:** Adjust Help to semantic changes introduced in 6933b84 ([8d23806b](https://github.com/kbknapp/clap-rs/commit/8d23806bd67530ad412c34a1dcdcb1435555573d)) + + +### v2.2.6 (2016-04-11) + +#### Bug Fixes + +* **Arg Groups**: fixes bug where arg name isn't printed properly ([3019a685](https://github.com/kbknapp/clap-rs/commit/3019a685eee747ccbe6be09ad5dddce0b1d1d4db), closes [#476](https://github.com/kbknapp/clap-rs/issues/476)) + + + +### v2.2.5 (2016-04-03) + + +#### Bug Fixes + +* **Empty Values:** fixes bug where empty values weren't stored ([885d166f](https://github.com/kbknapp/clap-rs/commit/885d166f04eb3fb581898ae5818c6c8032e5a686), closes [#470](https://github.com/kbknapp/clap-rs/issues/470)) +* **Help Message:** fixes bug where arg name is printed twice ([71acf1d5](https://github.com/kbknapp/clap-rs/commit/71acf1d576946658b8bbdb5ae79e6716c43a030f), closes [#472](https://github.com/kbknapp/clap-rs/issues/472)) + + + +### v2.2.4 (2016-03-30) + + +#### Bug Fixes + +* fixes compiling with debug cargo feature ([d4b55450](https://github.com/kbknapp/clap-rs/commit/d4b554509928031ac0808076178075bb21f8c1da)) +* **Empty Values:** fixes bug where empty values weren't stored ([885d166f](https://github.com/kbknapp/clap-rs/commit/885d166f04eb3fb581898ae5818c6c8032e5a686), closes [#470](https://github.com/kbknapp/clap-rs/issues/470)) + + + + +### v2.2.3 (2016-03-28) + + +#### Bug Fixes + +* **Help Subcommand:** fixes issue where help and version flags weren't properly displayed ([205b07bf](https://github.com/kbknapp/clap-rs/commit/205b07bf2e6547851f1290f8cd6b169145e144f1), closes [#466](https://github.com/kbknapp/clap-rs/issues/466)) + + +### v2.2.2 (2016-03-27) + + +#### Bug Fixes + +* **Help Message:** fixes bug with wrapping in the middle of a unicode sequence ([05365ddc](https://github.com/kbknapp/clap-rs/commit/05365ddcc252e4b49e7a75e199d6001a430bd84d), closes [#456](https://github.com/kbknapp/clap-rs/issues/456)) +* **Usage Strings:** fixes small bug where -- would appear needlessly in usage strings ([6933b849](https://github.com/kbknapp/clap-rs/commit/6933b8491c2a7e28cdb61b47dcf10caf33c2f78a), closes [#461](https://github.com/kbknapp/clap-rs/issues/461)) + + + +### 2.2.1 (2016-03-16) + + +#### Features + +* **Help Message:** wraps and aligns the help message of subcommands ([813d75d0](https://github.com/kbknapp/clap-rs/commit/813d75d06fbf077c65762608c0fa5e941cfc393c), closes [#452](https://github.com/kbknapp/clap-rs/issues/452)) + +#### Bug Fixes + +* **Help Message:** fixes a bug where small terminal sizes causing a loop ([1d73b035](https://github.com/kbknapp/clap-rs/commit/1d73b0355236923aeaf6799abc759762ded7e1d0), closes [#453](https://github.com/kbknapp/clap-rs/issues/453)) + + + +## v2.2.0 (2016-03-15) + + +#### Features + +* **Help Message:** can auto wrap and aligning help text to term width ([e36af026](https://github.com/kbknapp/clap-rs/commit/e36af0266635f23e85e951b9088d561e9a5d1bf6), closes [#428](https://github.com/kbknapp/clap-rs/issues/428)) +* **Help Subcommand:** adds support passing additional subcommands to help subcommand ([2c12757b](https://github.com/kbknapp/clap-rs/commit/2c12757bbdf34ce481f3446c074e24c09c2e60fd), closes [#416](https://github.com/kbknapp/clap-rs/issues/416)) +* **Opts and Flags:** adds support for custom ordering in help messages ([9803b51e](https://github.com/kbknapp/clap-rs/commit/9803b51e799904c0befaac457418ee766ccc1ab9)) +* **Settings:** adds support for automatically deriving custom display order of args ([ad86e433](https://github.com/kbknapp/clap-rs/commit/ad86e43334c4f70e86909689a088fb87e26ff95a), closes [#444](https://github.com/kbknapp/clap-rs/issues/444)) +* **Subcommands:** adds support for custom ordering in help messages ([7d2a2ed4](https://github.com/kbknapp/clap-rs/commit/7d2a2ed413f5517d45988eef0765cdcd663b6372), closes [#442](https://github.com/kbknapp/clap-rs/issues/442)) + +#### Bug Fixes + +* **From Usage:** fixes a bug where adding empty lines werent ignored ([c5c58c86](https://github.com/kbknapp/clap-rs/commit/c5c58c86b9c503d8de19da356a5a5cffb59fbe84)) + +#### Documentation + +* **Groups:** explains required ArgGroups better ([4ff0205b](https://github.com/kbknapp/clap-rs/commit/4ff0205b85a45151b59bbaf090a89df13438380f), closes [#439](https://github.com/kbknapp/clap-rs/issues/439)) + + +### v2.1.2 (2016-02-24) + +#### Bug Fixes + +* **Nightly:** fixes failing nightly build ([d752c170](https://github.com/kbknapp/clap-rs/commit/d752c17029598b19037710f204b7943f0830ae75), closes [#434](https://github.com/kbknapp/clap-rs/issues/434)) + + + +### v2.1.1 (2016-02-19) + + +#### Documentation + +* **AppSettings:** clarifies that AppSettings do not propagate ([3c8db0e9](https://github.com/kbknapp/clap-rs/commit/3c8db0e9be1d24edaad364359513cbb02abb4186), closes [#429](https://github.com/kbknapp/clap-rs/issues/429)) +* **Arg Examples:** adds better examples ([1e79cccc](https://github.com/kbknapp/clap-rs/commit/1e79cccc12937bc0e7cd2aad8e404410798e9fff)) + +#### Improvements + +* **Help:** adds setting for next line help by arg ([066df748](https://github.com/kbknapp/clap-rs/commit/066df7486e684cf50a8479a356a12ba972c34ce1), closes [#427](https://github.com/kbknapp/clap-rs/issues/427)) + + + +## v2.1.0 (2016-02-10) + + +#### Features + +* **Defult Values:** adds support for default values in args ([73211952](https://github.com/kbknapp/clap-rs/commit/73211952964a79d97b434dd567e6d7d34be7feb5), closes [#418](https://github.com/kbknapp/clap-rs/issues/418)) + +#### Documentation + +* **Default Values:** adds better examples and notes for default values ([9facd74f](https://github.com/kbknapp/clap-rs/commit/9facd74f843ef3807c5d35259558a344e6c25905)) + + + +### v2.0.6 (2016-02-09) + + +#### Improvements + +* **Positional Arguments:** now displays value name if appropriate ([f0a99916](https://github.com/kbknapp/clap-rs/commit/f0a99916c59ce675515c6dcdfe9a40b130510908), closes [#420](https://github.com/kbknapp/clap-rs/issues/420)) + + + +### v2.0.5 (2016-02-05) + + +#### Bug Fixes + +* **Multiple Values:** fixes bug where number_of_values wasnt respected ([72c387da](https://github.com/kbknapp/clap-rs/commit/72c387da0bb8a6f526f863770f08bb8ca0d3de03)) + + + +### v2.0.4 (2016-02-04) + + +#### Bug Fixes + +* adds support for building ArgGroups from standalone YAML ([fcbc7e12](https://github.com/kbknapp/clap-rs/commit/fcbc7e12f5d7b023b8f30cba8cad28a01cf6cd26)) +* Stop lonely hyphens from causing panic ([85b11468](https://github.com/kbknapp/clap-rs/commit/85b11468b0189d5cc15f1cfac5db40d17a0077dc), closes [#410](https://github.com/kbknapp/clap-rs/issues/410)) +* **AppSettings:** fixes bug where subcmds didn't receive parent ver ([a62e4527](https://github.com/kbknapp/clap-rs/commit/a62e452754b3b0e3ac9a15aa8b5330636229ead1)) + + +### v2.0.3 (2016-02-02) + + +#### Improvements + +* **values:** adds support for up to u64::max values per arg ([c7abf7d7](https://github.com/kbknapp/clap-rs/commit/c7abf7d7611e317b0d31d97632e3d2e13570947c)) +* **occurrences:** Allow for more than 256 occurrences of an argument. ([3731ddb3](https://github.com/kbknapp/clap-rs/commit/3731ddb361163f3d6b86844362871e48c80fa530)) + +#### Features + +* **AppSettings:** adds HidePossibleValuesInHelp to skip writing those values ([cdee7a0e](https://github.com/kbknapp/clap-rs/commit/cdee7a0eb2beeec723cb98acfacf03bf629c1da3)) + +#### Bug Fixes + +* **value_t_or_exit:** fixes typo which causes value_t_or_exit to return a Result ([ee96baff](https://github.com/kbknapp/clap-rs/commit/ee96baffd306cb8d20ddc5575cf739bb1a6354e8)) + + + +### v2.0.2 (2016-01-31) + + +#### Improvements + +* **arg_enum:** enum declared with arg_enum returns [&'static str; #] instead of Vec ([9c4b8a1a](https://github.com/kbknapp/clap-rs/commit/9c4b8a1a6b12949222f17d1074578ad7676b9c0d)) + +#### Bug Fixes + +* clap_app! should be gated by unstable, not nightly feature ([0c8b84af](https://github.com/kbknapp/clap-rs/commit/0c8b84af6161d5baf683688eafc00874846f83fa)) +* **SubCommands:** fixed where subcmds weren't recognized after mult args ([c19c17a8](https://github.com/kbknapp/clap-rs/commit/c19c17a8850602990e24347aeb4427cf43316223), closes [#405](https://github.com/kbknapp/clap-rs/issues/405)) +* **Usage Parser:** fixes a bug where literal single quotes weren't allowed in help strings ([0bcc7120](https://github.com/kbknapp/clap-rs/commit/0bcc71206478074769e311479b34a9f74fe80f5c), closes [#406](https://github.com/kbknapp/clap-rs/issues/406)) + + + +### v2.0.1 (2016-01-30) + + +#### Bug Fixes + +* fixes cargo features to NOT require nightly with unstable features ([dcbcc60c](https://github.com/kbknapp/clap-rs/commit/dcbcc60c9ba17894be636472ea4b07a82d86a9db), closes [#402](https://github.com/kbknapp/clap-rs/issues/402)) + + + +## v2.0.0 (2016-01-28) + + +#### Improvements + +* **From Usage:** vastly improves the usage parser ([fa3a2f86](https://github.com/kbknapp/clap-rs/commit/fa3a2f86bd674c5eb07128c95098fab7d1437247), closes [#350](https://github.com/kbknapp/clap-rs/issues/350)) + +#### Features + +* adds support for external subcommands ([177fe5cc](https://github.com/kbknapp/clap-rs/commit/177fe5cce745c2164a8e38c23be4c4460d2d7211), closes [#372](https://github.com/kbknapp/clap-rs/issues/372)) +* adds support values with a leading hyphen ([e4d429b9](https://github.com/kbknapp/clap-rs/commit/e4d429b9d52e95197bd0b572d59efacecf305a59), closes [#385](https://github.com/kbknapp/clap-rs/issues/385)) +* adds support for turning off the value delimiter ([508db850](https://github.com/kbknapp/clap-rs/commit/508db850a87c2e251cf6b6ddead9ad56b29f9e57), closes [#352](https://github.com/kbknapp/clap-rs/issues/352)) +* adds support changing the value delimiter ([dafeae8a](https://github.com/kbknapp/clap-rs/commit/dafeae8a526162640f6a68da434370c64d190889), closes [#353](https://github.com/kbknapp/clap-rs/issues/353)) +* adds support for comma separated values ([e69da6af](https://github.com/kbknapp/clap-rs/commit/e69da6afcd2fe48a3c458ca031db40997f860eda), closes [#348](https://github.com/kbknapp/clap-rs/issues/348)) +* adds support with options with optional values ([4555736c](https://github.com/kbknapp/clap-rs/commit/4555736cad01441dcde4ea84a285227e0844c16e), closes [#367](https://github.com/kbknapp/clap-rs/issues/367)) +* **UTF-8:** adds support for invalid utf8 in values ([c5c59dec](https://github.com/kbknapp/clap-rs/commit/c5c59dec0bc33b86b2e99d30741336f17ec84282), closes [#269](https://github.com/kbknapp/clap-rs/issues/269)) +* **v2:** implementing the base of 2.x ([a3536054](https://github.com/kbknapp/clap-rs/commit/a3536054512ba833533dc56615ce3663d884381c)) + +#### Bug Fixes + +* fixes nightly build with new lints ([17599195](https://github.com/kbknapp/clap-rs/commit/175991956c37dc83ba9c49396e927a1cb65c5b11)) +* fixes Windows build for 2x release ([674c9b48](https://github.com/kbknapp/clap-rs/commit/674c9b48c7c92079cb180cc650a9e39f34781c32), closes [#392](https://github.com/kbknapp/clap-rs/issues/392)) +* fixes yaml build for 2x base ([adceae64](https://github.com/kbknapp/clap-rs/commit/adceae64c8556d00ab715677377b216f9f468ad7)) + +#### Documentation + +* updates examples for 2x release ([1303b360](https://github.com/kbknapp/clap-rs/commit/1303b3607468f362ab1b452d5614c1a064dc69b4), closes [#394](https://github.com/kbknapp/clap-rs/issues/394)) +* updates examples for 2x release ([0a011f31](https://github.com/kbknapp/clap-rs/commit/0a011f3142aec338d388a6c8bfe22fa7036021bb), closes [#394](https://github.com/kbknapp/clap-rs/issues/394)) +* updates documentation for v2 release ([8d51724e](https://github.com/kbknapp/clap-rs/commit/8d51724ef73dfde5bb94fb9466bc5463a1cc1502)) +* updating docs for 2x release ([576d0e0e](https://github.com/kbknapp/clap-rs/commit/576d0e0e2c7b8f386589179bbf7419b93abacf1c)) +* **README.md:** + * updates readme for v2 release ([acaba01a](https://github.com/kbknapp/clap-rs/commit/acaba01a353c12144b9cd9a3ce447400691849b0), closes [#393](https://github.com/kbknapp/clap-rs/issues/393)) + * fix typo and make documentation conspicuous ([07b9f614](https://github.com/kbknapp/clap-rs/commit/07b9f61495d927f69f7abe6c0d85253f0f4e6107)) + +#### BREAKING CHANGES + +* **Fewer liftimes! Yay!** + * `App<'a, 'b, 'c, 'd, 'e, 'f>` => `App<'a, 'b>` + * `Arg<'a, 'b, 'c, 'd, 'e, 'f>` => `Arg<'a, 'b>` + * `ArgMatches<'a, 'b>` => `ArgMatches<'a>` +* **Simply Renamed** + * `App::arg_group` => `App::group` + * `App::arg_groups` => `App::groups` + * `ArgGroup::add` => `ArgGroup::arg` + * `ArgGroup::add_all` => `ArgGroup::args` + * `ClapError` => `Error` + * struct field `ClapError::error_type` => `Error::kind` + * `ClapResult` => `Result` + * `ClapErrorType` => `ErrorKind` +* **Removed Deprecated Functions and Methods** + * `App::subcommands_negate_reqs` + * `App::subcommand_required` + * `App::arg_required_else_help` + * `App::global_version(bool)` + * `App::versionless_subcommands` + * `App::unified_help_messages` + * `App::wait_on_error` + * `App::subcommand_required_else_help` + * `SubCommand::new` + * `App::error_on_no_subcommand` + * `Arg::new` + * `Arg::mutually_excludes` + * `Arg::mutually_excludes_all` + * `Arg::mutually_overrides_with` + * `simple_enum!` +* **Renamed Error Variants** + * `InvalidUnicode` => `InvalidUtf8` + * `InvalidArgument` => `UnknownArgument` +* **Usage Parser** + * Value names can now be specified inline, i.e. `-o, --option 'some option which takes two files'` + * **There is now a priority of order to determine the name** - This is perhaps the biggest breaking change. See the documentation for full details. Prior to this change, the value name took precedence. **Ensure your args are using the proper names (i.e. typically the long or short and NOT the value name) throughout the code** +* `ArgMatches::values_of` returns an `Values` now which implements `Iterator` (should not break any code) +* `crate_version!` returns `&'static str` instead of `String` +* Using the `clap_app!` macro requires compiling with the `unstable` feature because the syntax could change slightly in the future + + + +### v1.5.5 (2016-01-04) + + +#### Bug Fixes + +* fixes an issue where invalid short args didn't cause an error ([c9bf7e44](https://github.com/kbknapp/clap-rs/commit/c9bf7e4440bd2f9b524ea955311d433c40a7d1e0)) +* prints the name in version and help instead of binary name ([8f3817f6](https://github.com/kbknapp/clap-rs/commit/8f3817f665c0cab6726bc16c56a53b6a61e44448), closes [#368](https://github.com/kbknapp/clap-rs/issues/368)) +* fixes an intentional panic issue discovered via clippy ([ea83a3d4](https://github.com/kbknapp/clap-rs/commit/ea83a3d421ea8856d4cac763942834d108b71406)) + + + +### v1.5.4 (2015-12-18) + + +#### Examples + +* **17_yaml:** conditinonally compile 17_yaml example ([575de089](https://github.com/kbknapp/clap-rs/commit/575de089a3e240c398cb10e6cf5a5c6b68662c01)) + +#### Improvements + +* clippy improvements ([99cdebc2](https://github.com/kbknapp/clap-rs/commit/99cdebc23da3a45a165f14b27bebeb2ed828a2ce)) + +#### Bug Fixes + + +* **errors:** return correct error type in WrongNumValues error builder ([5ba8ba9d](https://github.com/kbknapp/clap-rs/commit/5ba8ba9dcccdfa74dd1c44260e64b359bbb36be6)) +* ArgRequiredElseHelp setting now takes precedence over missing required args ([faad83fb](https://github.com/kbknapp/clap-rs/commit/faad83fbef6752f3093b6e98fca09a9449b830f4), closes [#362](https://github.com/kbknapp/clap-rs/issues/362)) + + + +### v1.5.3 (2015-11-20) + + +#### Bug Fixes + +* **Errors:** fixes some instances when errors are missing a final newline ([c4d2b171](https://github.com/kbknapp/clap-rs/commit/c4d2b1711994479ad64ee52b6b49d2ceccbf2118)) + + + + + +### v1.5.2 (2015-11-14) + + +#### Bug Fixes + +* **Errors:** fixes a compiling bug when built on Windows or without the color feature ([a35f7634](https://github.com/kbknapp/clap-rs/commit/a35f76346fe6ecc88dda6a1eb13627186e7ce185)) + + + + +### v1.5.1 (2015-11-13) + + +#### Bug Fixes + +* **Required Args:** fixes a bug where required args are not correctly accounted for ([f03b88a9](https://github.com/kbknapp/clap-rs/commit/f03b88a9766b331a63879bcd747687f2e5a2661b), closes [#343](https://github.com/kbknapp/clap-rs/issues/343)) + + + + +## v1.5.0 (2015-11-13) + + +#### Bug Fixes + +* fixes a bug with required positional args in usage strings ([c6858f78](https://github.com/kbknapp/clap-rs/commit/c6858f78755f8e860204323c828c8355a066dc83)) + +#### Documentation + +* **FAQ:** updates readme with slight changes to FAQ ([a4ef0fab](https://github.com/kbknapp/clap-rs/commit/a4ef0fab73c8dc68f1b138965d1340459c113398)) + +#### Improvements + +* massive errors overhaul ([cdc29175](https://github.com/kbknapp/clap-rs/commit/cdc29175bc9c53e5b4aec86cbc04c1743154dae6)) +* **ArgMatcher:** huge refactor and deduplication of code ([8988853f](https://github.com/kbknapp/clap-rs/commit/8988853fb8825e8f841fde349834cc12cdbad081)) +* **Errors:** errors have been vastly improved ([e59bc0c1](https://github.com/kbknapp/clap-rs/commit/e59bc0c16046db156a88ba71a037db05028e995c)) +* **Traits:** refactoring some configuration into traits ([5800cdec](https://github.com/kbknapp/clap-rs/commit/5800cdec6dce3def4242b9f7bd136308afb19685)) + +#### Performance + +* **App:** + * more BTreeMap->Vec, Opts and SubCmds ([bc4495b3](https://github.com/kbknapp/clap-rs/commit/bc4495b32ec752b6c4b29719e831c043ef2a26ce)) + * changes flags BTreeMap->Vec ([d357640f](https://github.com/kbknapp/clap-rs/commit/d357640fab55e5964fe83efc3c771e53aa3222fd)) + * removed unneeded BTreeMap ([78971fd6](https://github.com/kbknapp/clap-rs/commit/78971fd68d7dc5c8e6811b4520cdc54e4188f733)) + * changes BTreeMap to VecMap in some instances ([64b921d0](https://github.com/kbknapp/clap-rs/commit/64b921d087fdd03775c95ba0bcf65d3f5d36f812)) + * removed excess clones ([ec0089d4](https://github.com/kbknapp/clap-rs/commit/ec0089d42ed715d293fb668d3a90b0db0aa3ec39)) + + + + +### v1.4.7 (2015-11-03) + + +#### Documentation + +* Clarify behavior of Arg::multiple with options. ([434f497a](https://github.com/kbknapp/clap-rs/commit/434f497ab6d831f8145cf09278c97ca6ee6c6fe7)) +* Fix typos and improve grammar. ([c1f66b5d](https://github.com/kbknapp/clap-rs/commit/c1f66b5de7b5269fbf8760a005ef8c645edd3229)) + +#### Bug Fixes + +* **Error Status:** fixes bug where --help and --version return non-zero exit code ([89b51fdf](https://github.com/kbknapp/clap-rs/commit/89b51fdf8b1ab67607567344e2317ff1a757cb12)) + + + + +### v1.4.6 (2015-10-29) + + +#### Features + +* allows parsing without a binary name for daemons and interactive CLIs ([aff89d57](https://github.com/kbknapp/clap-rs/commit/aff89d579b5b85c3dc81b64f16d5865299ec39a2), closes [#318](https://github.com/kbknapp/clap-rs/issues/318)) + +#### Bug Fixes + +* **Errors:** tones down quoting in some error messages ([34ce59ed](https://github.com/kbknapp/clap-rs/commit/34ce59ede53bfa2eef722c74881cdba7419fd9c7), closes [#309](https://github.com/kbknapp/clap-rs/issues/309)) +* **Help and Version:** only builds help and version once ([e3be87cf](https://github.com/kbknapp/clap-rs/commit/e3be87cfc095fc41c9811adcdc6d2b079f237d5e)) +* **Option Args:** fixes bug with args and multiple values ([c9a9548a](https://github.com/kbknapp/clap-rs/commit/c9a9548a8f96cef8a3dd9a980948325fbbc1b91b), closes [#323](https://github.com/kbknapp/clap-rs/issues/323)) +* **POSIX Overrides:** fixes bug where required args are overridden ([40ed2b50](https://github.com/kbknapp/clap-rs/commit/40ed2b50c3a9fe88bfdbaa43cef9fd6493ecaa8e)) +* **Safe Matches:** using 'safe' forms of the get_matches family no longer exit the process ([c47025dc](https://github.com/kbknapp/clap-rs/commit/c47025dca2b3305dea0a0acfdd741b09af0c0d05), closes [#256](https://github.com/kbknapp/clap-rs/issues/256)) +* **Versionless SubCommands:** fixes a bug where the -V flag was needlessly built ([27df8b9d](https://github.com/kbknapp/clap-rs/commit/27df8b9d98d13709dad3929a009f40ebff089a1a), closes [#329](https://github.com/kbknapp/clap-rs/issues/329)) + +#### Documentation + +* adds comparison in readme ([1a8bf31e](https://github.com/kbknapp/clap-rs/commit/1a8bf31e7a6b87ce48a66af2cde1645b2dd5bc95), closes [#325](https://github.com/kbknapp/clap-rs/issues/325)) + + + + +### v1.4.5 (2015-10-06) + + +#### Bug Fixes + +* fixes crash on invalid arg error ([c78ce128](https://github.com/kbknapp/clap-rs/commit/c78ce128ebbe7b8f730815f8176c29d76f4ade8c)) + + + + +### v1.4.4 (2015-10-06) + + +#### Documentation + +* clean up some formatting ([b7df92d7](https://github.com/kbknapp/clap-rs/commit/b7df92d7ea25835701dd22ddff984b9749f48a00)) +* move the crate-level docs to top of the lib.rs file ([d7233bf1](https://github.com/kbknapp/clap-rs/commit/d7233bf122dbf80ba8fc79e5641be2df8af10e7a)) +* changes doc comments to rustdoc comments ([34b601be](https://github.com/kbknapp/clap-rs/commit/34b601be5fdde76c1a0859385b359b96d66b8732)) +* fixes panic in 14_groups example ([945b00a0](https://github.com/kbknapp/clap-rs/commit/945b00a0c27714b63bdca48d003fe205fcfdc578), closes [#295](https://github.com/kbknapp/clap-rs/issues/295)) +* avoid suggesting star dependencies. ([d33228f4](https://github.com/kbknapp/clap-rs/commit/d33228f40b5fefb84cf3dd51546bfb340dcd9f5a)) +* **Rustdoc:** adds portions of the readme to main rustdoc page ([6f9ee181](https://github.com/kbknapp/clap-rs/commit/6f9ee181e69d90bd4206290e59d6f3f1e8f0cbb2), closes [#293](https://github.com/kbknapp/clap-rs/issues/293)) + +#### Bug Fixes + +* grammar error in some conflicting option errors ([e73b07e1](https://github.com/kbknapp/clap-rs/commit/e73b07e19474323ad2260da66abbf6a6d4ecbd4f)) +* **Unified Help:** sorts both flags and options as a unified category ([2a223dad](https://github.com/kbknapp/clap-rs/commit/2a223dad82901fa2e74baad3bfc4c7b94509300f)) +* **Usage:** fixes a bug where required args aren't filtered properly ([72b453dc](https://github.com/kbknapp/clap-rs/commit/72b453dc170af3050bb123d35364f6da77fc06d7), closes [#277](https://github.com/kbknapp/clap-rs/issues/277)) +* **Usage Strings:** fixes a bug ordering of elements in usage strings ([aaf0d6fe](https://github.com/kbknapp/clap-rs/commit/aaf0d6fe7aa2403e76096c16204d254a9ee61ee2), closes [#298](https://github.com/kbknapp/clap-rs/issues/298)) + +#### Features + +* supports -aValue style options ([0e3733e4](https://github.com/kbknapp/clap-rs/commit/0e3733e4fec2015c2d566a51432dcd92cb69cad3)) +* **Trailing VarArg:** adds opt-in setting for final arg being vararg ([27018b18](https://github.com/kbknapp/clap-rs/commit/27018b1821a4bcd5235cfe92abe71b3c99efc24d), closes [#278](https://github.com/kbknapp/clap-rs/issues/278)) + + + + +### v1.4.3 (2015-09-30) + + +#### Features + +* allows accessing arg values by group name ([c92a4b9e](https://github.com/kbknapp/clap-rs/commit/c92a4b9eff2d679957f61c0c41ff404b40d38a91)) + +#### Documentation + +* use links to examples instead of plain text ([bb4fe237](https://github.com/kbknapp/clap-rs/commit/bb4fe237858535627271465147add537e4556b43)) + +#### Bug Fixes + +* **Help Message:** required args no longer double list in usage ([1412e639](https://github.com/kbknapp/clap-rs/commit/1412e639e0a79df84936d1101a837f90077d1c83), closes [#277](https://github.com/kbknapp/clap-rs/issues/277)) +* **Possible Values:** possible value validation is restored ([f121ae74](https://github.com/kbknapp/clap-rs/commit/f121ae749f8f4bfe754ef2e8a6dfc286504b5b75), closes [#287](https://github.com/kbknapp/clap-rs/issues/287)) + + + + +### v1.4.2 (2015-09-23) + + +#### Bug Fixes + +* **Conflicts:** fixes bug with conflicts not removing required args ([e17fcec5](https://github.com/kbknapp/clap-rs/commit/e17fcec53b3216ad047a13dddc6f740473fad1a1), closes [#271](https://github.com/kbknapp/clap-rs/issues/271)) + + + + +### v1.4.1 (2015-09-22) + + +#### Examples + +* add clap_app quick example ([4ba6249c](https://github.com/kbknapp/clap-rs/commit/4ba6249c3cf4d2e083370d1fe4dcc7025282c28a)) + +#### Features + +* **Unicode:** allows non-panicing on invalid unicode characters ([c5bf7ddc](https://github.com/kbknapp/clap-rs/commit/c5bf7ddc8cfb876ec928a5aaf5591232bbb32e5d)) + +#### Documentation + +* properly names Examples section for rustdoc ([87ba5445](https://github.com/kbknapp/clap-rs/commit/87ba54451d7ec7b1c9b9ef134f90bbe39e6fac69)) +* fixes various typos and spelling ([f85640f9](https://github.com/kbknapp/clap-rs/commit/f85640f9f6d8fd3821a40e9b8b7a34fabb789d02)) +* **Arg:** unhides fields of the Arg struct ([931aea88](https://github.com/kbknapp/clap-rs/commit/931aea88427edf43a3da90d5a500c1ff2b2c3614)) + +#### Bug Fixes + +* flush the buffer in App::print_version() ([cbc42a37](https://github.com/kbknapp/clap-rs/commit/cbc42a37d212d84d22b1777d08e584ff191934e7)) +* Macro benchmarks ([13712da1](https://github.com/kbknapp/clap-rs/commit/13712da1d36dc7614eec3a10ad488257ba615751)) + + + + +## v1.4.0 (2015-09-09) + + +#### Features + +* allows printing help message by library consumers ([56b95f32](https://github.com/kbknapp/clap-rs/commit/56b95f320875c62dda82cb91b29059671e120ed1)) +* allows defining hidden args and subcmds ([2cab4d03](https://github.com/kbknapp/clap-rs/commit/2cab4d0334ea3c2439a1d4bfca5bf9905c7ea9ac), closes [#231](https://github.com/kbknapp/clap-rs/issues/231)) +* Builder macro to assist with App/Arg/Group/SubCommand building ([443841b0](https://github.com/kbknapp/clap-rs/commit/443841b012a8d795cd5c2bd69ae6e23ef9b16477)) +* **Errors:** allows consumers to write to stderr and exit on error ([1e6403b6](https://github.com/kbknapp/clap-rs/commit/1e6403b6a863574fa3cb6946b1fb58f034e8664c)) + + + + +### v1.3.2 (2015-09-08) + + +#### Documentation + +* fixed ErrorKind docs ([dd057843](https://github.com/kbknapp/clap-rs/commit/dd05784327fa070eb6ce5ce89a8507e011d8db94)) +* **ErrorKind:** changed examples content ([b9ca2616](https://github.com/kbknapp/clap-rs/commit/b9ca261634b89613bbf3d98fd74d55cefbb31a8c)) + +#### Bug Fixes + +* fixes a bug where the help subcommand wasn't overridable ([94003db4](https://github.com/kbknapp/clap-rs/commit/94003db4b5eebe552ca337521c1c001295822745)) + +#### Features + +* adds abiltiy not consume self when parsing matches and/or exit on help ([94003db4](https://github.com/kbknapp/clap-rs/commit/94003db4b5eebe552ca337521c1c001295822745)) +* **App:** Added ability for users to handle errors themselves ([934e6fbb](https://github.com/kbknapp/clap-rs/commit/934e6fbb643b2385efc23444fe6fce31494dc288)) + + + + +### v1.3.1 (2015-09-04) + + +#### Examples + +* **17_yaml:** fixed example ([9b848622](https://github.com/kbknapp/clap-rs/commit/9b848622296c8c5c7b9a39b93ddd41f51df790b5)) + +#### Performance + +* changes ArgGroup HashSets to Vec ([3cb4a48e](https://github.com/kbknapp/clap-rs/commit/3cb4a48ebd15c20692f4f3a2a924284dc7fd5e10)) +* changes BTreeSet for Vec in some instances ([baab2e3f](https://github.com/kbknapp/clap-rs/commit/baab2e3f4060e811abee14b1654cbcd5cf3b5fea)) + + + + +## v1.3.0 (2015-09-01) + + +#### Features + +* **YAML:** allows building a CLI from YAML files ([86cf4c45](https://github.com/kbknapp/clap-rs/commit/86cf4c45626a36b8115446952f9069f73c1debc3)) +* **ArgGroups:** adds support for building ArgGroups from yaml ([ecf88665](https://github.com/kbknapp/clap-rs/commit/ecf88665cbff367018b29161a1b75d44a212707d)) +* **Subcommands:** adds support for subcommands from yaml ([e415cf78](https://github.com/kbknapp/clap-rs/commit/e415cf78ba916052d118a8648deba2b9c16b1530)) + +#### Documentation + +* **YAML:** adds examples for using YAML to build a CLI ([ab41d7f3](https://github.com/kbknapp/clap-rs/commit/ab41d7f38219544750e6e1426076dc498073191b)) +* **Args from YAML:** fixes doc examples ([19b348a1](https://github.com/kbknapp/clap-rs/commit/19b348a10050404cd93888dbbbe4f396681b67d0)) +* **Examples:** adds better usage examples instead of having unused variables ([8cbacd88](https://github.com/kbknapp/clap-rs/commit/8cbacd8883004fe71a8ea036ec4391c7dd8efe94)) + +#### Examples + +* Add AppSettings example ([12705079](https://github.com/kbknapp/clap-rs/commit/12705079ca96a709b4dd94f7ddd20a833b26838c)) + +#### Bug Fixes + +* **Unified Help Messages:** fixes a crash from this setting and no opts ([169ffec1](https://github.com/kbknapp/clap-rs/commit/169ffec1003d58d105d7ef2585b3425e57980000), closes [#210](https://github.com/kbknapp/clap-rs/issues/210)) + + + + +### v1.2.5 (2015-08-27) + + +#### Examples + +* add custom validator example ([b9997d1f](https://github.com/kbknapp/clap-rs/commit/b9997d1fca74d4d8f93971f2a01bdf9798f913d5)) +* fix indentation ([d4f1b740](https://github.com/kbknapp/clap-rs/commit/d4f1b740ede410fd2528b9ecd89592c2fd8b1e20)) + +#### Features + +* **Args:** allows opts and args to define a name for help and usage msgs ([ad962ec4](https://github.com/kbknapp/clap-rs/commit/ad962ec478da999c7dba0afdb84c266f4d09b1bd)) + + + + +### v1.2.4 (2015-08-26) + + +#### Bug Fixes + +* **Possible Values:** fixes a bug where suggestions arent made when using --long=value format ([3d5e9a6c](https://github.com/kbknapp/clap-rs/commit/3d5e9a6cedb26668839b481c9978e2fbbab8be6f), closes [#192](https://github.com/kbknapp/clap-rs/issues/192)) + + + + +### v1.2.3 (2015-08-24) + + +#### Bug Fixes + +* **App, Args:** fixed subcommand reqs negation ([b41afa8c](https://github.com/kbknapp/clap-rs/commit/b41afa8c3ded3d1be12f7a2f8ea06cc44afc9458), closes [#188](https://github.com/kbknapp/clap-rs/issues/188)) + + + + +### v1.2.2 (2015-08-23) + + +#### Bug Fixes + +* fixed confusing error message, also added test for it ([fc7a31a7](https://github.com/kbknapp/clap-rs/commit/fc7a31a745efbf1768ee2c62cd3bb72bfe30c708)) +* **App:** fixed requirmets overriding ([9c135eb7](https://github.com/kbknapp/clap-rs/commit/9c135eb790fa16183e5bdb2009ddc3cf9e25f99f)) + + + + +### v1.2.1 (2015-08-20) + + +#### Documentation + +* **README.md:** updates for new features ([16cf9245](https://github.com/kbknapp/clap-rs/commit/16cf9245fb5fc4cf6face898e358368bf9961cbb)) + +#### Features + +* implements posix compatible conflicts for long args ([8c2d48ac](https://github.com/kbknapp/clap-rs/commit/8c2d48acf5473feebd721a9049a9c9b7051e70f9)) +* added overrides to support conflicts in POSIX compatible manner ([0b916a00](https://github.com/kbknapp/clap-rs/commit/0b916a00de26f6941538f6bc5f3365fa302083c1)) +* **Args:** allows defining POSIX compatible argument conflicts ([d715646e](https://github.com/kbknapp/clap-rs/commit/d715646e69759ccd95e01f49b04f489827ecf502)) + +#### Bug Fixes + +* fixed links in cargo and license buttons ([6d9837ad](https://github.com/kbknapp/clap-rs/commit/6d9837ad9a9e006117cd7372fdc60f9a3889c7e2)) + +#### Performance + +* **Args and Apps:** changes HashSet->Vec in some instances for increased performance ([d0c3b379](https://github.com/kbknapp/clap-rs/commit/d0c3b379700757e0a9b0c40af709f8af1f5b4949)) + + + + +### v1.2.0 (2015-08-15) + + +#### Bug Fixes + +* fixed misspell and enum name ([7df170d7](https://github.com/kbknapp/clap-rs/commit/7df170d7f4ecff06608317655d1e0c4298f62076)) +* fixed use for clap crate ([dc3ada73](https://github.com/kbknapp/clap-rs/commit/dc3ada738667d4b689678f79d14251ee82004ece)) + +#### Documentation + +* updates docs for new features ([03496547](https://github.com/kbknapp/clap-rs/commit/034965471782d872ca495045b58d34b31807c5b1)) +* fixed docs for previous changes ([ade36778](https://github.com/kbknapp/clap-rs/commit/ade367780c366425de462506d256e0f554ed3b9c)) + +#### Improvements + +* **AppSettings:** adds ability to add multiple settings at once ([4a00e251](https://github.com/kbknapp/clap-rs/commit/4a00e2510d0ca8d095d5257d51691ba3b61c1374)) + +#### Features + +* Replace application level settings with enum variants ([618dc4e2](https://github.com/kbknapp/clap-rs/commit/618dc4e2c205bf26bc43146164e65eb1f6b920ed)) +* **Args:** allows for custom argument value validations to be defined ([84ae2ddb](https://github.com/kbknapp/clap-rs/commit/84ae2ddbceda34b5cbda98a6959edaa52fde2e1a), closes [#170](https://github.com/kbknapp/clap-rs/issues/170)) + + + + +### v1.1.6 (2015-08-01) + + +#### Bug Fixes + +* fixes two bugs in App when printing newlines in help and subcommands required error ([d63c0136](https://github.com/kbknapp/clap-rs/commit/d63c0136310db9dd2b1c7b4745938311601d8938)) + + + + +### v1.1.5 (2015-07-29) + +#### Performance + +* removes some unneeded allocations ([93e915df](https://github.com/kbknapp/clap-rs/commit/93e915dfe300f7b7d6209ca93323c6a46f89a8c1)) + + +### v1.1.4 (2015-07-20) + + +#### Improvements + +* **Usage Strings** displays a [--] when it may be helpful ([86c3be85](https://github.com/kbknapp/clap-rs/commit/86c3be85fb6f77f83b5a6d2df40ae60937486984)) + +#### Bug Fixes + +* **Macros** fixes a typo in a macro generated error message ([c9195c5f](https://github.com/kbknapp/clap-rs/commit/c9195c5f92abb8cd6a37b4f4fbb2f1fee2a8e368)) +* **Type Errors** fixes formatting of error output when failed type parsing ([fe5d95c6](https://github.com/kbknapp/clap-rs/commit/fe5d95c64f3296e6eddcbec0cb8b86659800145f)) + + + + +### v1.1.3 (2015-07-18) + + +#### Documentation + +* updates README.md to include lack of color support on Windows ([52f81e17](https://github.com/kbknapp/clap-rs/commit/52f81e17377b18d2bd0f34693b642b7f358998ee)) + +#### Bug Fixes + +* fixes formatting bug which prevented compiling on windows ([9cb5dceb](https://github.com/kbknapp/clap-rs/commit/9cb5dceb3e5fe5e0e7b24619ff77e5040672b723), closes [#163](https://github.com/kbknapp/clap-rs/issues/163)) + + + + +### v1.1.2 (2015-07-17) + + +#### Bug Fixes + +* fixes a bug when parsing multiple {n} newlines inside help strings ([6d214b54](https://github.com/kbknapp/clap-rs/commit/6d214b549a9b7e189a94e5fa2b7c92cc333ca637)) + + + + +## v1.1.1 (2015-07-17) + + +#### Bug Fixes + +* fixes a logic bug and allows setting Arg::number_of_values() < 2 ([42b6d1fc](https://github.com/kbknapp/clap-rs/commit/42b6d1fc3c519c92dfb3af15276e7d3b635e6cfe), closes [#161](https://github.com/kbknapp/clap-rs/issues/161)) + + + + +## v1.1.0 (2015-07-16) + + +#### Features + +* allows creating unified help messages, a la docopt or getopts ([52bcd892](https://github.com/kbknapp/clap-rs/commit/52bcd892ea51564ce463bc5865acd64f8fe91cb1), closes [#158](https://github.com/kbknapp/clap-rs/issues/158)) +* allows stating all subcommands should *not* have --version flags ([336c476f](https://github.com/kbknapp/clap-rs/commit/336c476f631d512b54ac56fdca6f29ebdc2c00c5), closes [#156](https://github.com/kbknapp/clap-rs/issues/156)) +* allows setting version number to auto-propagate through subcommands ([bc66d3c6](https://github.com/kbknapp/clap-rs/commit/bc66d3c6deedeca62463fff95369ab1cfcdd366b), closes [#157](https://github.com/kbknapp/clap-rs/issues/157)) + +#### Improvements + +* **Help Strings** properly aligns and handles newlines in long help strings ([f9800a29](https://github.com/kbknapp/clap-rs/commit/f9800a29696dd2cc0b0284bf693b3011831e556f), closes [#145](https://github.com/kbknapp/clap-rs/issues/145)) + + +#### Performance + +* **Help Messages** big performance improvements when printing help messages ([52bcd892](https://github.com/kbknapp/clap-rs/commit/52bcd892ea51564ce463bc5865acd64f8fe91cb1)) + +#### Documentation + +* updates readme with new features ([8232f7bb](https://github.com/kbknapp/clap-rs/commit/8232f7bb52e88862bc13c3d4f99ee4f56cfe4bc0)) +* fix incorrect code example for `App::subcommand_required` ([8889689d](https://github.com/kbknapp/clap-rs/commit/8889689dc6336ccc45b2c9f2cf8e2e483a639e93)) + + + +### v1.0.3 (2015-07-11) + + +#### Improvements + +* **Errors** writes errors to stderr ([cc76ab8c](https://github.com/kbknapp/clap-rs/commit/cc76ab8c2b77c67b42f4717ded530df7806142cf), closes [#154](https://github.com/kbknapp/clap-rs/issues/154)) + +#### Documentation + +* **README.md** updates example help message to new format ([0aca29bd](https://github.com/kbknapp/clap-rs/commit/0aca29bd5d6d1a4e9971bdc88d946ffa58606efa)) + + + + +### v1.0.2 (2015-07-09) + + +#### Improvements + +* **Usage** re-orders optional arguments and required to natural standard ([dc7e1fce](https://github.com/kbknapp/clap-rs/commit/dc7e1fcea5c85d317018fb201d2a9262249131b4), closes [#147](https://github.com/kbknapp/clap-rs/issues/147)) + + + + +### v1.0.1 (2015-07-08) + + +#### Bug Fixes + +* allows empty values when using --long='' syntax ([083f82d3](https://github.com/kbknapp/clap-rs/commit/083f82d333b69720a6ef30074875310921d964d1), closes [#151](https://github.com/kbknapp/clap-rs/issues/151)) + + + + +## v1.0.0 (2015-07-08) + + +#### Documentation + +* **README.md** adds new features to what's new list ([938f7f01](https://github.com/kbknapp/clap-rs/commit/938f7f01340f521969376cf4e2e3d9436bca21f7)) +* **README.md** use with_name for subcommands ([28b7e316](https://github.com/kbknapp/clap-rs/commit/28b7e3161fb772e5309042648fe8c3a420645bac)) + +#### Features + +* args can now be parsed from arbitrary locations, not just std::env::args() ([75312528](https://github.com/kbknapp/clap-rs/commit/753125282b1b9bfff875f1557ce27610edcc59e1)) + + + + +## v1.0.0-beta (2015-06-30) + + +#### Features + +* allows waiting for user input on error ([d0da3bdd](https://github.com/kbknapp/clap-rs/commit/d0da3bdd9d1871541907ea9c645322a74d260e07), closes [#140](https://github.com/kbknapp/clap-rs/issues/140)) +* **Help** allows one to fully override the auto-generated help message ([26d5ae3e](https://github.com/kbknapp/clap-rs/commit/26d5ae3e330d1e150811d5b60b2b01a8f8df854e), closes [#141](https://github.com/kbknapp/clap-rs/issues/141)) + +#### Documentation + +* adds "whats new" section to readme ([ff149a29](https://github.com/kbknapp/clap-rs/commit/ff149a29dd9e179865e6d577cd7dc87c54f8f95c)) + +#### Improvements + +* removes deprecated functions in prep for 1.0 ([274484df](https://github.com/kbknapp/clap-rs/commit/274484dfd08fff4859cefd7e9bef3b73d3a9cb5f)) + + + + +## v0.11.0 (2015-06-17) - BREAKING CHANGE + + +#### Documentation + +* updates docs to new version flag defaults ([ebf442eb](https://github.com/kbknapp/clap-rs/commit/ebf442ebebbcd2ec6bfe2c06566c9d362bccb112)) + +#### Features + +* **Help and Version** default short for version is now `-V` but can be overridden (only breaks manual documentation) (**BREAKING CHANGE** [eb1d9320](https://github.com/kbknapp/clap-rs/commit/eb1d9320c509c1e4e57d7c7959da82bcfe06ada0)) + + + + +### v0.10.5 (2015-06-06) + + +#### Bug Fixes + +* **Global Args** global arguments propogate fully now ([1f377960](https://github.com/kbknapp/clap-rs/commit/1f377960a48c82f54ca5f39eb56bcb393140b046), closes [#137](https://github.com/kbknapp/clap-rs/issues/137)) + + + + +### v0.10.4 (2015-06-06) + + +#### Bug Fixes + +* **Global Args** global arguments propogate fully now ([8f2c0160](https://github.com/kbknapp/clap-rs/commit/8f2c0160c8d844daef375a33dbaec7d89de00a00), closes [#137](https://github.com/kbknapp/clap-rs/issues/137)) + + + + +### v0.10.3 (2015-05-31) + + +#### Bug Fixes + +* **Global Args** fixes a bug where globals only transfer to one subcommand ([a37842ee](https://github.com/kbknapp/clap-rs/commit/a37842eec1ee3162b86fdbda23420b221cdb1e3b), closes [#135](https://github.com/kbknapp/clap-rs/issues/135)) + + + + +### v0.10.2 (2015-05-30) + + +#### Improvements + +* **Binary Names** allows users to override the system determined bin name ([2191fe94](https://github.com/kbknapp/clap-rs/commit/2191fe94bda35771383b52872fb7f5421b178be1), closes [#134](https://github.com/kbknapp/clap-rs/issues/134)) + +#### Documentation + +* adds contributing guidelines ([6f76bd0a](https://github.com/kbknapp/clap-rs/commit/6f76bd0a07e8b7419b391243ab2d6687cd8a9c5f)) + + + + +### v0.10.1 (2015-05-26) + + +#### Features + +* can now specify that an app or subcommand should display help on no args or subcommands ([29ca7b2f](https://github.com/kbknapp/clap-rs/commit/29ca7b2f74376ca0cdb9d8ee3bfa99f7640cc404), closes [#133](https://github.com/kbknapp/clap-rs/issues/133)) + + + + +## v0.10.0 (2015-05-23) + + +#### Features + +* **Global Args** allows args that propagate down to child commands ([2bcc6137](https://github.com/kbknapp/clap-rs/commit/2bcc6137a83cb07757771a0afea953e68e692f0b), closes [#131](https://github.com/kbknapp/clap-rs/issues/131)) + +#### Improvements + +* **Colors** implements more structured colored output ([d6c3ed54](https://github.com/kbknapp/clap-rs/commit/d6c3ed54d21cf7b40d9f130d4280ff5448522fc5), closes [#129](https://github.com/kbknapp/clap-rs/issues/129)) + +#### Deprecations + +* **SubCommand/App** several methods and functions for stable release ([28b73855](https://github.com/kbknapp/clap-rs/commit/28b73855523ad170544afdb20665db98702fbe70)) + +#### Documentation + +* updates for deprecations and new features ([743eefe8](https://github.com/kbknapp/clap-rs/commit/743eefe8dd40c1260065ce086d572e9e9358bc4c)) + + + + +## v0.9.2 (2015-05-20) + + +#### Bug Fixes + +* **help** allows parent requirements to be ignored with help and version ([52218cc1](https://github.com/kbknapp/clap-rs/commit/52218cc1fdb06a42456c964d98cc2c7ac3432412), closes [#124](https://github.com/kbknapp/clap-rs/issues/124)) + + + + +## v0.9.1 (2015-05-18) + + +#### Bug Fixes + +* **help** fixes a bug where requirements are included as program name in help and version ([08ba3f25](https://github.com/kbknapp/clap-rs/commit/08ba3f25cf38b149229ba8b9cb37a5804fe6b789)) + + + + +## v0.9.0 (2015-05-17) + + +#### Improvements + +* **usage** usage strings now include parent command requirements ([dd8f21c7](https://github.com/kbknapp/clap-rs/commit/dd8f21c7c15cde348fdcf44fa7c205f0e98d2e4a), closes [#125](https://github.com/kbknapp/clap-rs/issues/125)) +* **args** allows consumer of clap to decide if empty values are allowed or not ([ab4ec609](https://github.com/kbknapp/clap-rs/commit/ab4ec609ccf692b9b72cccef5c9f74f5577e360d), closes [#122](https://github.com/kbknapp/clap-rs/issues/122)) + +#### Features + +* **subcommands** + * allows optionally specifying that no subcommand is an error ([7554f238](https://github.com/kbknapp/clap-rs/commit/7554f238fd3afdd60b7e4dcf00ff4a9eccf842c1), closes [#126](https://github.com/kbknapp/clap-rs/issues/126)) + * subcommands can optionally negate parent requirements ([4a4229f5](https://github.com/kbknapp/clap-rs/commit/4a4229f500e21c350e1ef78dd09ef27559653288), closes [#123](https://github.com/kbknapp/clap-rs/issues/123)) + + + + +## v0.8.6 (2015-05-17) + + +#### Bug Fixes + +* **args** `-` can now be parsed as a value for an argument ([bc12e78e](https://github.com/kbknapp/clap-rs/commit/bc12e78eadd7eaf9d008a8469fdd2dfd7990cb5d), closes [#121](https://github.com/kbknapp/clap-rs/issues/121)) + + + + +## v0.8.5 (2015-05-15) + + +#### Bug Fixes + +* **macros** makes macro errors consistent with others ([0c264a8c](https://github.com/kbknapp/clap-rs/commit/0c264a8ca57ec1cfdcb74dae79145d766cdc9b97), closes [#118](https://github.com/kbknapp/clap-rs/issues/118)) + +#### Features + +* **macros** + * arg_enum! and simple_enum! provide a Vec<&str> of variant names ([30fa87ba](https://github.com/kbknapp/clap-rs/commit/30fa87ba4e0f3189351d8f4f78b72e616a30d0bd), closes [#119](https://github.com/kbknapp/clap-rs/issues/119)) + * arg_enum! and simple_enum! auto-implement Display ([d1219f0d](https://github.com/kbknapp/clap-rs/commit/d1219f0d1371d872061bd0718057eca4ef47b739), closes [#120](https://github.com/kbknapp/clap-rs/issues/120)) + + + + +## v0.8.4 (2015-05-12) + + +#### Bug Fixes + +* **suggestions** --help and --version now get suggestions ([d2b3b1fa](https://github.com/kbknapp/clap-rs/commit/d2b3b1faa0bdc1c5d2350cc4635aba81e02e9d96), closes [#116](https://github.com/kbknapp/clap-rs/issues/116)) + + + + +## v0.8.3 (2015-05-10) + + +#### Bug Fixes + +* **usage** groups unfold their members in usage strings ([55d15582](https://github.com/kbknapp/clap-rs/commit/55d155827ea4a6b077a83669701e797ce1ad68f4), closes [#114](https://github.com/kbknapp/clap-rs/issues/114)) + +#### Performance + +* **usage** removes unneeded allocations ([fd53cd18](https://github.com/kbknapp/clap-rs/commit/fd53cd188555f5c3dc8bc341c5d7eb04b761a70f)) + + + + +## v0.8.2 (2015-05-08) + + +#### Bug Fixes + +* **usage strings** positional arguments are presented in index order ([eb0e374e](https://github.com/kbknapp/clap-rs/commit/eb0e374ecf952f1eefbc73113f21e0705936e40b), closes [#112](https://github.com/kbknapp/clap-rs/issues/112)) + + + + +## v0.8.1 (2015-05-06) + + +#### Bug Fixes + +* **subcommands** stops parsing multiple values when subcommands are found ([fc79017e](https://github.com/kbknapp/clap-rs/commit/fc79017eced04fd41cc1801331e5054df41fac17), closes [#109](https://github.com/kbknapp/clap-rs/issues/109)) + +#### Improvements + +* **color** reduces color in error messages ([aab44cca](https://github.com/kbknapp/clap-rs/commit/aab44cca6352f47e280c296e50c535f5d752dd46), closes [#110](https://github.com/kbknapp/clap-rs/issues/110)) +* **suggestions** adds suggested arguments to usage strings ([99447414](https://github.com/kbknapp/clap-rs/commit/994474146e9fb8b701af773a52da71553d74d4b7)) + + + + +## v0.8.0 (2015-05-06) + + +#### Bug Fixes + +* **did-you-mean** for review ([0535cfb0](https://github.com/kbknapp/clap-rs/commit/0535cfb0c711331568b4de8080eeef80bd254b68)) +* **Positional** positionals were ignored if they matched a subcmd, even after '--' ([90e7b081](https://github.com/kbknapp/clap-rs/commit/90e7b0818741668b47cbe3becd029bab588e3553)) +* **help** fixes bug where space between arg and help is too long ([632fb115](https://github.com/kbknapp/clap-rs/commit/632fb11514c504999ea86bdce47cdd34f8ebf646)) + +#### Features + +* **from_usage** adds ability to add value names or num of vals in usage string ([3d581976](https://github.com/kbknapp/clap-rs/commit/3d58197674ed7886ca315efb76e411608a327501), closes [#98](https://github.com/kbknapp/clap-rs/issues/98)) +* **did-you-mean** + * gate it behind 'suggestions' ([c0e38351](https://github.com/kbknapp/clap-rs/commit/c0e383515d01bdd5ca459af9c2f7e2cf49e2488b)) + * for possible values ([1cc2deb2](https://github.com/kbknapp/clap-rs/commit/1cc2deb29158e0e4e8b434e4ce26b3d819301a7d)) + * for long flags (i.e. --long) ([52a0b850](https://github.com/kbknapp/clap-rs/commit/52a0b8505c99354bdf5fd1cd256cf41197ac2d81)) + * for subcommands ([06e869b5](https://github.com/kbknapp/clap-rs/commit/06e869b5180258047ed3c60ba099de818dd25fff)) +* **Flags** adds sugestions functionality ([8745071c](https://github.com/kbknapp/clap-rs/commit/8745071c3257dd327c497013516f12a823df9530)) +* **errors** colorizes output red on error ([f8b26b13](https://github.com/kbknapp/clap-rs/commit/f8b26b13da82ba3ba9a932d3d1ab4ea45d1ab036)) + +#### Improvements + +* **arg_enum** allows ascii case insensitivity for enum variants ([b249f965](https://github.com/kbknapp/clap-rs/commit/b249f9657c6921c004764bd80d13ebca81585eec), closes [#104](https://github.com/kbknapp/clap-rs/issues/104)) +* **clap-test** simplified `make test` invocation ([d17dcb29](https://github.com/kbknapp/clap-rs/commit/d17dcb2920637a1f58c61c596b7bd362fd53047c)) + +#### Documentation + +* **README** adds details about optional and new features ([960389de](https://github.com/kbknapp/clap-rs/commit/960389de02c9872aaee9adabe86987f71f986e39)) +* **clap** fix typos caught by codespell ([8891d929](https://github.com/kbknapp/clap-rs/commit/8891d92917aa1a069cca67272be41b99e548356e)) +* **from_usage** explains new usage strings with multiple values ([05476fc6](https://github.com/kbknapp/clap-rs/commit/05476fc61cd1e5f4a4e750d258c878732a3a9c64)) + + + + +## v0.7.6 (2015-05-05) + + +#### Improvements + +* **Options** adds number of values to options in help/usage ([c1c993c4](https://github.com/kbknapp/clap-rs/commit/c1c993c419d18e35c443785053d8de9a2ef88073)) + +#### Features + +* **from_usage** adds ability to add value names or num of vals in usage string ([ad55748c](https://github.com/kbknapp/clap-rs/commit/ad55748c265cf27935c7b210307d2040b6a09125), closes [#98](https://github.com/kbknapp/clap-rs/issues/98)) + +#### Bug Fixes + +* **MultipleValues** properly distinguishes between multiple values and multiple occurrences ([dd2a7564](https://github.com/kbknapp/clap-rs/commit/dd2a75640ca68a91b973faad15f04df891356cef), closes [#99](https://github.com/kbknapp/clap-rs/issues/99)) +* **help** fixes tab alignment with multiple values ([847001ff](https://github.com/kbknapp/clap-rs/commit/847001ff6d8f4d9518e810fefb8edf746dd0f31e)) + +#### Documentation + +* **from_usage** explains new usage strings with multiple values ([5a3a42df](https://github.com/kbknapp/clap-rs/commit/5a3a42dfa3a783537f88dedc0fd5f0edcb8ea372)) + + + + +## v0.7.5 (2015-05-04) + + +#### Bug Fixes + +* **Options** fixes bug where options with no value don't error out ([a1fb94be](https://github.com/kbknapp/clap-rs/commit/a1fb94be53141572ffd97aad037295d4ffec82d0)) + + + + +## v0.7.4 (2015-05-03) + + +#### Bug Fixes + +* **Options** fixes a bug where option arguments in succession get their values skipped ([f66334d0](https://github.com/kbknapp/clap-rs/commit/f66334d0ce984e2b56e5c19abb1dd536fae9342a)) + + + + +## v0.7.3 (2015-05-03) + + +#### Bug Fixes + +* **RequiredValues** fixes a bug where missing values are parsed as missing arguments ([93c4a723](https://github.com/kbknapp/clap-rs/commit/93c4a7231ba1a08152648598f7aa4503ea82e4de)) + +#### Improvements + +* **ErrorMessages** improves error messages and corrections ([a29c3983](https://github.com/kbknapp/clap-rs/commit/a29c3983c4229906655a29146ec15a0e46dd942d)) +* **ArgGroups** improves requirement and confliction support for groups ([c236dc5f](https://github.com/kbknapp/clap-rs/commit/c236dc5ff475110d2a1b80e62903f80296163ad3)) + + + + +## v0.7.2 (2015-05-03) + + +#### Bug Fixes + +* **RequiredArgs** fixes bug where required-by-default arguments are not listed in usage ([12aea961](https://github.com/kbknapp/clap-rs/commit/12aea9612d290845ba86515c240aeeb0a21198db), closes [#96](https://github.com/kbknapp/clap-rs/issues/96)) + + + + +## v0.7.1 (2015-05-01) + + +#### Bug Fixes + +* **MultipleValues** stops evaluating values if the max or exact number of values was reached ([86d92c9f](https://github.com/kbknapp/clap-rs/commit/86d92c9fdbf9f422442e9562977bbaf268dbbae1)) + + + + +## v0.7.0 (2015-04-30) - BREAKING CHANGE + + +#### Bug Fixes + +* **from_usage** removes bug where usage strings have no help text ([ad4e5451](https://github.com/kbknapp/clap-rs/commit/ad4e54510739aeabf75f0da3278fb0952db531b3), closes [#83](https://github.com/kbknapp/clap-rs/issues/83)) + +#### Features + +* **MultipleValues** + * add support for minimum and maximum number of values ([53f6b8c9](https://github.com/kbknapp/clap-rs/commit/53f6b8c9d8dc408b4fa9f833fc3a63683873c42f)) + * adds support limited number and named values ([ae09f05e](https://github.com/kbknapp/clap-rs/commit/ae09f05e92251c1b39a83d372736fcc7b504e432)) + * implement shorthand for options with multiple values ([6669f0a9](https://github.com/kbknapp/clap-rs/commit/6669f0a9687d4f668523145d7bd5c007d1eb59a8)) +* **arg** allow other types besides Vec for multiple value settings (**BREAKING CHANGE** [0cc2f698](https://github.com/kbknapp/clap-rs/commit/0cc2f69839b9b1db5d06330771b494783049a88e), closes [#87](https://github.com/kbknapp/clap-rs/issues/87)) +* **usage** implement smart usage strings on errors ([d77048ef](https://github.com/kbknapp/clap-rs/commit/d77048efb1e595ffe831f1a2bea2f2700db53b9f), closes [#88](https://github.com/kbknapp/clap-rs/issues/88)) + + + + +## v0.6.9 (2015-04-29) + + +#### Bug Fixes + +* **from_usage** removes bug where usage strings have no help text ([ad4e5451](https://github.com/kbknapp/clap-rs/commit/ad4e54510739aeabf75f0da3278fb0952db531b3), closes [#83](https://github.com/kbknapp/clap-rs/issues/83)) + + + + +## 0.6.8 (2015-04-27) + + +#### Bug Fixes + +* **help** change long help --long=long -> --long ([1e25abfc](https://github.com/kbknapp/clap-rs/commit/1e25abfc36679ab89eae71bf98ced4de81992d00)) +* **RequiredArgs** required by default args should no longer be required when their exclusions are present ([4bb4c3cc](https://github.com/kbknapp/clap-rs/commit/4bb4c3cc076b49e86720e882bf8c489877199f2d)) + +#### Features + +* **ArgGroups** add ability to create arg groups ([09eb4d98](https://github.com/kbknapp/clap-rs/commit/09eb4d9893af40c347e50e2b717e1adef552357d)) + + + + +## v0.6.7 (2015-04-22) + + +#### Bug Fixes + +* **from_usage** fix bug causing args to not be required ([b76129e9](https://github.com/kbknapp/clap-rs/commit/b76129e9b71a63365d5c77a7f57b58dbd1e94d49)) + +#### Features + +* **apps** add ability to display additional help info after auto-gen'ed help msg ([65cc259e](https://github.com/kbknapp/clap-rs/commit/65cc259e4559cbe3653c865ec0c4b1e42a389b07)) + + + + +## v0.6.6 (2015-04-19) + + +#### Bug Fixes + +* **from_usage** tabs and spaces should be treated equally ([4fd44181](https://github.com/kbknapp/clap-rs/commit/4fd44181d55d8eb88caab1e625231cfa3129e347)) + +#### Features + +* **macros.rs** add macro to get version from Cargo.toml ([c630969a](https://github.com/kbknapp/clap-rs/commit/c630969aa3bbd386379219cae27ba1305b117f3e)) + + + + +## v0.6.5 (2015-04-19) + + +#### Bug Fixes + +* **macros.rs** fix use statements for trait impls ([86e4075e](https://github.com/kbknapp/clap-rs/commit/86e4075eb111937c8a7bdb344e866e350429f042)) + + + + +## v0.6.4 (2015-04-17) + + +#### Features + +* **macros** add ability to create enums pub or priv with derives ([2c499f80](https://github.com/kbknapp/clap-rs/commit/2c499f8015a199827cdf1fa3ec4f6f171722f8c7)) + + + + +## v0.6.3 (2015-04-16) + + +#### Features + +* **macros** add macro to create custom enums to use as types ([fb672aff](https://github.com/kbknapp/clap-rs/commit/fb672aff561c29db2e343d6c607138f141aca8b6)) + + + + +## v0.6.2 (2015-04-14) + + +#### Features + +* **macros** + * add ability to get multiple typed values or exit ([0b87251f](https://github.com/kbknapp/clap-rs/commit/0b87251fc088234bee51c323c2b652d7254f7a59)) + * add ability to get a typed multiple values ([e243fe38](https://github.com/kbknapp/clap-rs/commit/e243fe38ddbbf845a46c0b9baebaac3778c80927)) + * add convenience macro to get a typed value or exit ([4b7cd3ea](https://github.com/kbknapp/clap-rs/commit/4b7cd3ea4947780d9daa39f3e1ddab53ad4c7fef)) + * add convenience macro to get a typed value ([8752700f](https://github.com/kbknapp/clap-rs/commit/8752700fbb30e89ee68adbce24489ae9a24d33a9)) + + + + +## v0.6.1 (2015-04-13) + + +#### Bug Fixes + +* **from_usage** trim all whitespace before parsing ([91d29045](https://github.com/kbknapp/clap-rs/commit/91d2904599bd602deef2e515dfc65dc2863bdea0)) + + + + +## v0.6.0 (2015-04-13) + + +#### Bug Fixes + +* **tests** fix failing doc tests ([3710cd69](https://github.com/kbknapp/clap-rs/commit/3710cd69162f87221a62464f63437c1ce843ad3c)) + +#### Features + +* **app** add support for building args from usage strings ([d5d48bcf](https://github.com/kbknapp/clap-rs/commit/d5d48bcf463a4e494ef758836bd69a4c220bbbb5)) +* **args** add ability to create basic arguments from a usage string ([ab409a8f](https://github.com/kbknapp/clap-rs/commit/ab409a8f1db9e37cc70200f6f4a84a162692e618)) + + + + +## v0.5.14 (2015-04-10) + + +#### Bug Fixes + +* **usage** + * remove unneeded space ([51372789](https://github.com/kbknapp/clap-rs/commit/5137278942121bc2593ce6e5dc224ec2682549e6)) + * remove warning about unused variables ([ba817b9d](https://github.com/kbknapp/clap-rs/commit/ba817b9d815e37320650973f1bea0e7af3030fd7)) + +#### Features + +* **usage** add ability to get usage string for subcommands too ([3636afc4](https://github.com/kbknapp/clap-rs/commit/3636afc401c2caa966efb5b1869ef4f1ed3384aa)) + + + + +## v0.5.13 (2015-04-09) + + +#### Features + +* **SubCommands** add method to get name and subcommand matches together ([64e53928](https://github.com/kbknapp/clap-rs/commit/64e539280e23e567cf5de393b346eb0ca20e7eb5)) +* **ArgMatches** add method to get default usage string ([02462150](https://github.com/kbknapp/clap-rs/commit/02462150ca750bdc7012627d7e8d96379d494d7f)) + + + + +## v0.5.12 (2015-04-08) + + +#### Features + +* **help** sort arguments by name so as to not display a random order ([f4b2bf57](https://github.com/kbknapp/clap-rs/commit/f4b2bf5767386013069fb74862e6e938dacf44d2)) + + + + +## v0.5.11 (2015-04-08) + + +#### Bug Fixes + +* **flags** fix bug not allowing users to specify -v or -h ([90e72cff](https://github.com/kbknapp/clap-rs/commit/90e72cffdee321b79eea7a2207119533540062b4)) + + + + +## v0.5.10 (2015-04-08) + + +#### Bug Fixes + +* **help** fix spacing when option argument has not long version ([ca17fa49](https://github.com/kbknapp/clap-rs/commit/ca17fa494b68e92da83ee364bf64b0687006824b)) + + + + +## v0.5.9 (2015-04-08) + + +#### Bug Fixes + +* **positional args** all previous positional args become required when a latter one is required ([c14c3f31](https://github.com/kbknapp/clap-rs/commit/c14c3f31fd557c165570b60911d8ee483d89d6eb), closes [#50](https://github.com/kbknapp/clap-rs/issues/50)) +* **clap** remove unstable features for Rust 1.0 ([9abdb438](https://github.com/kbknapp/clap-rs/commit/9abdb438e36e364d41550e7f5d44ebcaa8ee6b10)) +* **args** improve error messages for arguments with mutual exclusions ([18dbcf37](https://github.com/kbknapp/clap-rs/commit/18dbcf37024daf2b76ca099a6f118b53827aa339), closes [#51](https://github.com/kbknapp/clap-rs/issues/51)) + + + + +## v0.5.8 (2015-04-08) + + +#### Bug Fixes + +* **option args** fix bug in getting the wrong number of occurrences for options ([82ad6ad7](https://github.com/kbknapp/clap-rs/commit/82ad6ad77539cf9f9a03b78db466f575ebd972cc)) +* **help** fix formatting for option arguments with no long ([e8691004](https://github.com/kbknapp/clap-rs/commit/e869100423d93fa3acff03c4620cbcc0d0e790a1)) +* **flags** add assertion to catch flags with specific value sets ([a0a2a40f](https://github.com/kbknapp/clap-rs/commit/a0a2a40fed57f7c5ad9d68970d090e9856306c7d), closes [#52](https://github.com/kbknapp/clap-rs/issues/52)) +* **args** improve error messages for arguments with mutual exclusions ([bff945fc](https://github.com/kbknapp/clap-rs/commit/bff945fc5d03bba4266533340adcffb002508d1b), closes [#51](https://github.com/kbknapp/clap-rs/issues/51)) +* **tests** add missing .takes_value(true) to option2 ([bdb0e88f](https://github.com/kbknapp/clap-rs/commit/bdb0e88f696c8595c3def3bfb0e52d538c7be085)) +* **positional args** all previous positional args become required when a latter one is required ([343d47dc](https://github.com/kbknapp/clap-rs/commit/343d47dcbf83786a45c0d0f01b27fd9dd76725de), closes [#50](https://github.com/kbknapp/clap-rs/issues/50)) + + + + +## v0.5.7 (2015-04-08) + + +#### Bug Fixes + +* **args** fix bug in arguments who are required and mutually exclusive ([6ceb88a5](https://github.com/kbknapp/clap-rs/commit/6ceb88a594caae825605abc1cdad95204996bf29)) + + + + +## v0.5.6 (2015-04-08) + + +#### Bug Fixes + +* **help** fix formatting of help and usage ([28691b52](https://github.com/kbknapp/clap-rs/commit/28691b52f67e65c599e10e4ea2a0f6f9765a06b8)) + + + + +## v0.5.5 (2015-04-08) + + +#### Bug Fixes + +* **help** fix formatting of help for flags and options ([6ec10115](https://github.com/kbknapp/clap-rs/commit/6ec1011563a746f0578a93b76d45e63878e0f9a8)) + + + + +## v0.5.4 (2015-04-08) + + +#### Features + +* **help** add '...' to indicate multiple values supported ([297ddba7](https://github.com/kbknapp/clap-rs/commit/297ddba77000e2228762ab0eca50b480f7467386)) + + + + +## v0.5.3 (2015-04-08) + + +#### Features + +* **positionals** + * add assertions for positional args with multiple vals ([b7fa72d4](https://github.com/kbknapp/clap-rs/commit/b7fa72d40f18806ec2042dd67a518401c2cf5681)) + * add support for multiple values ([80784009](https://github.com/kbknapp/clap-rs/commit/807840094109fbf90b348039ae22669ef27889ba)) + + + + +## v0.5.2 (2015-04-08) + + +#### Bug Fixes + +* **apps** allow use of hyphens in application and subcommand names ([da549dcb](https://github.com/kbknapp/clap-rs/commit/da549dcb6c7e0d773044ab17829744483a8b0f7f)) + + + + +## v0.5.1 (2015-04-08) + + +#### Bug Fixes + +* **args** determine if the only arguments allowed are also required ([0a09eb36](https://github.com/kbknapp/clap-rs/commit/0a09eb365ced9a03faf8ed24f083ef730acc90e8)) + + + + +## v0.5.0 (2015-04-08) + + +#### Features + +* **args** add support for a specific set of allowed values on options or positional arguments ([270eb889](https://github.com/kbknapp/clap-rs/commit/270eb88925b6dc2881bff1f31ee344f085d31809)) + + + + +## v0.4.18 (2015-04-08) + + +#### Bug Fixes + +* **usage** display required args in usage, even if only required by others ([1b7316d4](https://github.com/kbknapp/clap-rs/commit/1b7316d4a8df70b0aa584ccbfd33f68966ad2a54)) + +#### Features + +* **subcommands** properly list subcommands in help and usage ([4ee02344](https://github.com/kbknapp/clap-rs/commit/4ee023442abc3dba54b68138006a52b714adf331)) + + + + +## v0.4.17 (2015-04-08) + + +#### Bug Fixes + +* **tests** remove cargo test from claptests makefile ([1cf73817](https://github.com/kbknapp/clap-rs/commit/1cf73817d6fb1dccb5b6a23b46c2efa8b567ad62)) + + + + +## v0.4.16 (2015-04-08) + + +#### Bug Fixes + +* **option** fix bug with option occurrence values ([9af52e93](https://github.com/kbknapp/clap-rs/commit/9af52e93cef9e17ac9974963f132013d0b97b946)) +* **tests** fix testing script bug and formatting ([d8f03a55](https://github.com/kbknapp/clap-rs/commit/d8f03a55c4f74d126710ee06aad5a667246a8001)) + +#### Features + +* **arg** allow lifetimes other than 'static in arguments ([9e8c1fb9](https://github.com/kbknapp/clap-rs/commit/9e8c1fb9406f8448873ca58bab07fe905f1551e5)) diff --git a/clap/CONTRIBUTORS.md b/clap/CONTRIBUTORS.md new file mode 100644 index 0000000..f0fd777 --- /dev/null +++ b/clap/CONTRIBUTORS.md @@ -0,0 +1,91 @@ +the following is a list of contributors: + + +[kbknapp](https://github.com/kbknapp) |[homu](https://github.com/homu) |[Vinatorul](https://github.com/Vinatorul) |[tormol](https://github.com/tormol) |[willmurphyscode](https://github.com/willmurphyscode) |[little-dude](https://github.com/little-dude) | +:---: |:---: |:---: |:---: |:---: |:---: | +[kbknapp](https://github.com/kbknapp) |[homu](https://github.com/homu) |[Vinatorul](https://github.com/Vinatorul) |[tormol](https://github.com/tormol) |[willmurphyscode](https://github.com/willmurphyscode) |[little-dude](https://github.com/little-dude) | + +[sru](https://github.com/sru) |[mgeisler](https://github.com/mgeisler) |[nabijaczleweli](https://github.com/nabijaczleweli) |[Byron](https://github.com/Byron) |[hgrecco](https://github.com/hgrecco) |[bluejekyll](https://github.com/bluejekyll) | +:---: |:---: |:---: |:---: |:---: |:---: | +[sru](https://github.com/sru) |[mgeisler](https://github.com/mgeisler) |[nabijaczleweli](https://github.com/nabijaczleweli) |[Byron](https://github.com/Byron) |[hgrecco](https://github.com/hgrecco) |[bluejekyll](https://github.com/bluejekyll) | + +[segevfiner](https://github.com/segevfiner) |[ignatenkobrain](https://github.com/ignatenkobrain) |[james-darkfox](https://github.com/james-darkfox) |[H2CO3](https://github.com/H2CO3) |[nateozem](https://github.com/nateozem) |[glowing-chemist](https://github.com/glowing-chemist) | +:---: |:---: |:---: |:---: |:---: |:---: | +[segevfiner](https://github.com/segevfiner) |[ignatenkobrain](https://github.com/ignatenkobrain) |[james-darkfox](https://github.com/james-darkfox) |[H2CO3](https://github.com/H2CO3) |[nateozem](https://github.com/nateozem) |[glowing-chemist](https://github.com/glowing-chemist) | + +[discosultan](https://github.com/discosultan) |[rtaycher](https://github.com/rtaycher) |[Arnavion](https://github.com/Arnavion) |[japaric](https://github.com/japaric) |[untitaker](https://github.com/untitaker) |[afiune](https://github.com/afiune) | +:---: |:---: |:---: |:---: |:---: |:---: | +[discosultan](https://github.com/discosultan) |[rtaycher](https://github.com/rtaycher) |[Arnavion](https://github.com/Arnavion) |[japaric](https://github.com/japaric) |[untitaker](https://github.com/untitaker) |[afiune](https://github.com/afiune) | + +[crazymerlyn](https://github.com/crazymerlyn) |[SuperFluffy](https://github.com/SuperFluffy) |[matthiasbeyer](https://github.com/matthiasbeyer) |[malbarbo](https://github.com/malbarbo) |[tshepang](https://github.com/tshepang) |[golem131](https://github.com/golem131) | +:---: |:---: |:---: |:---: |:---: |:---: | +[crazymerlyn](https://github.com/crazymerlyn) |[SuperFluffy](https://github.com/SuperFluffy) |[matthiasbeyer](https://github.com/matthiasbeyer) |[malbarbo](https://github.com/malbarbo) |[tshepang](https://github.com/tshepang) |[golem131](https://github.com/golem131) | + +[jimmycuadra](https://github.com/jimmycuadra) |[Nemo157](https://github.com/Nemo157) |[severen](https://github.com/severen) |[Eijebong](https://github.com/Eijebong) |[cstorey](https://github.com/cstorey) |[wdv4758h](https://github.com/wdv4758h) | +:---: |:---: |:---: |:---: |:---: |:---: | +[jimmycuadra](https://github.com/jimmycuadra) |[Nemo157](https://github.com/Nemo157) |[severen](https://github.com/severen) |[Eijebong](https://github.com/Eijebong) |[cstorey](https://github.com/cstorey) |[wdv4758h](https://github.com/wdv4758h) | + +[frewsxcv](https://github.com/frewsxcv) |[hoodie](https://github.com/hoodie) |[huonw](https://github.com/huonw) |[GrappigPanda](https://github.com/GrappigPanda) |[shepmaster](https://github.com/shepmaster) |[starkat99](https://github.com/starkat99) | +:---: |:---: |:---: |:---: |:---: |:---: | +[frewsxcv](https://github.com/frewsxcv) |[hoodie](https://github.com/hoodie) |[huonw](https://github.com/huonw) |[GrappigPanda](https://github.com/GrappigPanda) |[shepmaster](https://github.com/shepmaster) |[starkat99](https://github.com/starkat99) | + +[porglezomp](https://github.com/porglezomp) |[kraai](https://github.com/kraai) |[musoke](https://github.com/musoke) |[nelsonjchen](https://github.com/nelsonjchen) |[pkgw](https://github.com/pkgw) |[Deedasmi](https://github.com/Deedasmi) | +:---: |:---: |:---: |:---: |:---: |:---: | +[porglezomp](https://github.com/porglezomp) |[kraai](https://github.com/kraai) |[musoke](https://github.com/musoke) |[nelsonjchen](https://github.com/nelsonjchen) |[pkgw](https://github.com/pkgw) |[Deedasmi](https://github.com/Deedasmi) | + +[vmchale](https://github.com/vmchale) |[etopiei](https://github.com/etopiei) |[messense](https://github.com/messense) |[Keats](https://github.com/Keats) |[kieraneglin](https://github.com/kieraneglin) |[durka](https://github.com/durka) | +:---: |:---: |:---: |:---: |:---: |:---: | +[vmchale](https://github.com/vmchale) |[etopiei](https://github.com/etopiei) |[messense](https://github.com/messense) |[Keats](https://github.com/Keats) |[kieraneglin](https://github.com/kieraneglin) |[durka](https://github.com/durka) | + +[alex-gulyas](https://github.com/alex-gulyas) |[cite-reader](https://github.com/cite-reader) |[alexbool](https://github.com/alexbool) |[AluisioASG](https://github.com/AluisioASG) |[BurntSushi](https://github.com/BurntSushi) |[AndrewGaspar](https://github.com/AndrewGaspar) | +:---: |:---: |:---: |:---: |:---: |:---: | +[alex-gulyas](https://github.com/alex-gulyas) |[cite-reader](https://github.com/cite-reader) |[alexbool](https://github.com/alexbool) |[AluisioASG](https://github.com/AluisioASG) |[BurntSushi](https://github.com/BurntSushi) |[AndrewGaspar](https://github.com/AndrewGaspar) | + +[nox](https://github.com/nox) |[mitsuhiko](https://github.com/mitsuhiko) |[pixelistik](https://github.com/pixelistik) |[ogham](https://github.com/ogham) |[Bilalh](https://github.com/Bilalh) |[dotdash](https://github.com/dotdash) | +:---: |:---: |:---: |:---: |:---: |:---: | +[nox](https://github.com/nox) |[mitsuhiko](https://github.com/mitsuhiko) |[pixelistik](https://github.com/pixelistik) |[ogham](https://github.com/ogham) |[Bilalh](https://github.com/Bilalh) |[dotdash](https://github.com/dotdash) | + +[bradurani](https://github.com/bradurani) |[Seeker14491](https://github.com/Seeker14491) |[brianp](https://github.com/brianp) |[cldershem](https://github.com/cldershem) |[casey](https://github.com/casey) |[volks73](https://github.com/volks73) | +:---: |:---: |:---: |:---: |:---: |:---: | +[bradurani](https://github.com/bradurani) |[Seeker14491](https://github.com/Seeker14491) |[brianp](https://github.com/brianp) |[cldershem](https://github.com/cldershem) |[casey](https://github.com/casey) |[volks73](https://github.com/volks73) | + +[daboross](https://github.com/daboross) |[da-x](https://github.com/da-x) |[mernen](https://github.com/mernen) |[dguo](https://github.com/dguo) |[davidszotten](https://github.com/davidszotten) |[drusellers](https://github.com/drusellers) | +:---: |:---: |:---: |:---: |:---: |:---: | +[daboross](https://github.com/daboross) |[da-x](https://github.com/da-x) |[mernen](https://github.com/mernen) |[dguo](https://github.com/dguo) |[davidszotten](https://github.com/davidszotten) |[drusellers](https://github.com/drusellers) | + +[eddyb](https://github.com/eddyb) |[Enet4](https://github.com/Enet4) |[Fraser999](https://github.com/Fraser999) |[birkenfeld](https://github.com/birkenfeld) |[guanqun](https://github.com/guanqun) |[tanakh](https://github.com/tanakh) | +:---: |:---: |:---: |:---: |:---: |:---: | +[eddyb](https://github.com/eddyb) |[Enet4](https://github.com/Enet4) |[Fraser999](https://github.com/Fraser999) |[birkenfeld](https://github.com/birkenfeld) |[guanqun](https://github.com/guanqun) |[tanakh](https://github.com/tanakh) | + +[SirVer](https://github.com/SirVer) |[idmit](https://github.com/idmit) |[archer884](https://github.com/archer884) |[jacobmischka](https://github.com/jacobmischka) |[jespino](https://github.com/jespino) |[jfrankenau](https://github.com/jfrankenau) | +:---: |:---: |:---: |:---: |:---: |:---: | +[SirVer](https://github.com/SirVer) |[idmit](https://github.com/idmit) |[archer884](https://github.com/archer884) |[jacobmischka](https://github.com/jacobmischka) |[jespino](https://github.com/jespino) |[jfrankenau](https://github.com/jfrankenau) | + +[jtdowney](https://github.com/jtdowney) |[andete](https://github.com/andete) |[joshtriplett](https://github.com/joshtriplett) |[Kalwyn](https://github.com/Kalwyn) |[manuel-rhdt](https://github.com/manuel-rhdt) |[Marwes](https://github.com/Marwes) | +:---: |:---: |:---: |:---: |:---: |:---: | +[jtdowney](https://github.com/jtdowney) |[andete](https://github.com/andete) |[joshtriplett](https://github.com/joshtriplett) |[Kalwyn](https://github.com/Kalwyn) |[manuel-rhdt](https://github.com/manuel-rhdt) |[Marwes](https://github.com/Marwes) | + +[mdaffin](https://github.com/mdaffin) |[iliekturtles](https://github.com/iliekturtles) |[nicompte](https://github.com/nicompte) |[NickeZ](https://github.com/NickeZ) |[nvzqz](https://github.com/nvzqz) |[nuew](https://github.com/nuew) | +:---: |:---: |:---: |:---: |:---: |:---: | +[mdaffin](https://github.com/mdaffin) |[iliekturtles](https://github.com/iliekturtles) |[nicompte](https://github.com/nicompte) |[NickeZ](https://github.com/NickeZ) |[nvzqz](https://github.com/nvzqz) |[nuew](https://github.com/nuew) | + +[Geogi](https://github.com/Geogi) |[focusaurus](https://github.com/focusaurus) |[flying-sheep](https://github.com/flying-sheep) |[Phlosioneer](https://github.com/Phlosioneer) |[peppsac](https://github.com/peppsac) |[golddranks](https://github.com/golddranks) | +:---: |:---: |:---: |:---: |:---: |:---: | +[Geogi](https://github.com/Geogi) |[focusaurus](https://github.com/focusaurus) |[flying-sheep](https://github.com/flying-sheep) |[Phlosioneer](https://github.com/Phlosioneer) |[peppsac](https://github.com/peppsac) |[golddranks](https://github.com/golddranks) | + +[hexjelly](https://github.com/hexjelly) |[rom1v](https://github.com/rom1v) |[rnelson](https://github.com/rnelson) |[swatteau](https://github.com/swatteau) |[tchajed](https://github.com/tchajed) |[tspiteri](https://github.com/tspiteri) | +:---: |:---: |:---: |:---: |:---: |:---: | +[hexjelly](https://github.com/hexjelly) |[rom1v](https://github.com/rom1v) |[rnelson](https://github.com/rnelson) |[swatteau](https://github.com/swatteau) |[tchajed](https://github.com/tchajed) |[tspiteri](https://github.com/tspiteri) | + +[siiptuo](https://github.com/siiptuo) |[vks](https://github.com/vks) |[vsupalov](https://github.com/vsupalov) |[mineo](https://github.com/mineo) |[wabain](https://github.com/wabain) |[grossws](https://github.com/grossws) | +:---: |:---: |:---: |:---: |:---: |:---: | +[siiptuo](https://github.com/siiptuo) |[vks](https://github.com/vks) |[vsupalov](https://github.com/vsupalov) |[mineo](https://github.com/mineo) |[wabain](https://github.com/wabain) |[grossws](https://github.com/grossws) | + +[kennytm](https://github.com/kennytm) |[king6cong](https://github.com/king6cong) |[mvaude](https://github.com/mvaude) |[panicbit](https://github.com/panicbit) |[brennie](https://github.com/brennie) | +:---: |:---: |:---: |:---: |:---: | +[kennytm](https://github.com/kennytm) |[king6cong](https://github.com/king6cong) |[mvaude](https://github.com/mvaude) |[panicbit](https://github.com/panicbit) |[brennie](https://github.com/brennie) | + + + + +This list was generated by [mgechev/github-contributors-list](https://github.com/mgechev/github-contributors-list) diff --git a/clap/Cargo.toml b/clap/Cargo.toml new file mode 100644 index 0000000..47498c1 --- /dev/null +++ b/clap/Cargo.toml @@ -0,0 +1,91 @@ +[package] + +name = "clap" +version = "2.33.0" +authors = ["Kevin K. "] +exclude = ["examples/*", "clap-test/*", "tests/*", "benches/*", "*.png", "clap-perf/*", "*.dot"] +repository = "https://github.com/clap-rs/clap" +documentation = "https://docs.rs/clap/" +homepage = "https://clap.rs/" +readme = "README.md" +license = "MIT" +keywords = ["argument", "cli", "arg", "parser", "parse"] +categories = ["command-line-interface"] +description = """ +A simple to use, efficient, and full-featured Command Line Argument Parser +""" + +[badges] +travis-ci = { repository = "clap-rs/clap" } +appveyor = { repository = "clap-rs/clap" } +coveralls = { repository = "clap-rs/clap", branch = "master" } +is-it-maintained-issue-resolution = { repository = "clap-rs/clap" } +is-it-maintained-open-issues = { repository = "clap-rs/clap" } +maintenance = {status = "actively-developed"} + +[dependencies] +bitflags = "1.0" +unicode-width = "0.1.4" +textwrap = "0.11.0" +strsim = { version = "0.8", optional = true } +yaml-rust = { version = "0.3.5", optional = true } +clippy = { version = "~0.0.166", optional = true } +atty = { version = "0.2.2", optional = true } +vec_map = { version = "0.8", optional = true } +term_size = { version = "0.3.0", optional = true } + +[target.'cfg(not(windows))'.dependencies] +ansi_term = { version = "0.11", optional = true } + +[dev-dependencies] +regex = "1" +lazy_static = "1.3" +version-sync = "0.8" + +[features] +default = ["suggestions", "color", "vec_map"] +suggestions = ["strsim"] +color = ["ansi_term", "atty"] +wrap_help = ["term_size", "textwrap/term_size"] +yaml = ["yaml-rust"] +unstable = [] # for building with unstable clap features (doesn't require nightly Rust) (currently none) +nightly = [] # for building with unstable Rust features (currently none) +lints = ["clippy"] # Requires nightly Rust +debug = [] # Enables debug messages +no_cargo = [] # Enable if you're not using Cargo, disables Cargo-env-var-dependent macros +doc = ["yaml"] # All the features which add to documentation + +[profile.release] +opt-level = 3 +debug = false +rpath = false +lto = true +debug-assertions = false +codegen-units = 1 + +[profile.dev] +opt-level = 0 +debug = true +rpath = false +lto = false +debug-assertions = true +codegen-units = 4 + +[profile.test] +opt-level = 1 +debug = true +rpath = false +lto = false +debug-assertions = true +codegen-units = 4 + +[profile.bench] +opt-level = 3 +debug = false +rpath = false +lto = true +debug-assertions = false +codegen-units = 1 + +[package.metadata.docs.rs] +features = ["doc"] diff --git a/clap/LICENSE-MIT b/clap/LICENSE-MIT new file mode 100644 index 0000000..5acedf0 --- /dev/null +++ b/clap/LICENSE-MIT @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015-2016 Kevin B. Knapp + +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/clap/README.md b/clap/README.md new file mode 100644 index 0000000..c95f494 --- /dev/null +++ b/clap/README.md @@ -0,0 +1,542 @@ +clap +==== + +[![Crates.io](https://img.shields.io/crates/v/clap.svg)](https://crates.io/crates/clap) [![Crates.io](https://img.shields.io/crates/d/clap.svg)](https://crates.io/crates/clap) [![license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/clap-rs/clap/blob/master/LICENSE-MIT) [![Coverage Status](https://coveralls.io/repos/kbknapp/clap-rs/badge.svg?branch=master&service=github)](https://coveralls.io/github/kbknapp/clap-rs?branch=master) [![Join the chat at https://gitter.im/kbknapp/clap-rs](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/kbknapp/clap-rs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + +Linux: [![Build Status](https://travis-ci.org/clap-rs/clap.svg?branch=master)](https://travis-ci.org/clap-rs/clap) +Windows: [![Build status](https://ci.appveyor.com/api/projects/status/ejg8c33dn31nhv36/branch/master?svg=true)](https://ci.appveyor.com/project/kbknapp/clap-rs/branch/master) + +Command Line Argument Parser for Rust + +It is a simple-to-use, efficient, and full-featured library for parsing command line arguments and subcommands when writing console/terminal applications. + +* [documentation](https://docs.rs/clap/) +* [website](https://clap.rs/) +* [video tutorials](https://www.youtube.com/playlist?list=PLza5oFLQGTl2Z5T8g1pRkIynR3E0_pc7U) + +Table of Contents +================= + +* [About](#about) +* [FAQ](#faq) +* [Features](#features) +* [Quick Example](#quick-example) +* [Try it!](#try-it) + * [Pre-Built Test](#pre-built-test) + * [BYOB (Build Your Own Binary)](#byob-build-your-own-binary) +* [Usage](#usage) + * [Optional Dependencies / Features](#optional-dependencies--features) + * [Dependencies Tree](#dependencies-tree) + * [More Information](#more-information) + * [Video Tutorials](#video-tutorials) +* [How to Contribute](#how-to-contribute) + * [Compatibility Policy](#compatibility-policy) + * [Minimum Version of Rust](#minimum-version-of-rust) +* [Related Crates](#related-crates) +* [License](#license) +* [Recent Breaking Changes](#recent-breaking-changes) + * [Deprecations](#deprecations) + +Created by [gh-md-toc](https://github.com/ekalinin/github-markdown-toc) + +## About + +`clap` is used to parse *and validate* the string of command line arguments provided by a user at runtime. You provide the list of valid possibilities, and `clap` handles the rest. This means you focus on your *applications* functionality, and less on the parsing and validating of arguments. + +`clap` provides many things 'for free' (with no configuration) including the traditional version and help switches (or flags) along with associated messages. If you are using subcommands, `clap` will also auto-generate a `help` subcommand and separate associated help messages. + +Once `clap` parses the user provided string of arguments, it returns the matches along with any applicable values. If the user made an error or typo, `clap` informs them with a friendly message and exits gracefully (or returns a `Result` type and allows you to perform any clean up prior to exit). Because of this, you can make reasonable assumptions in your code about the validity of the arguments prior to your applications main execution. + +## FAQ + +For a full FAQ and more in depth details, see [the wiki page](https://github.com/clap-rs/clap/wiki/FAQ) + +### Comparisons + +First, let me say that these comparisons are highly subjective, and not meant in a critical or harsh manner. All the argument parsing libraries out there (to include `clap`) have their own strengths and weaknesses. Sometimes it just comes down to personal taste when all other factors are equal. When in doubt, try them all and pick one that you enjoy :) There's plenty of room in the Rust community for multiple implementations! + +#### How does `clap` compare to [getopts](https://github.com/rust-lang-nursery/getopts)? + +`getopts` is a very basic, fairly minimalist argument parsing library. This isn't a bad thing, sometimes you don't need tons of features, you just want to parse some simple arguments, and have some help text generated for you based on valid arguments you specify. The downside to this approach is that you must manually implement most of the common features (such as checking to display help messages, usage strings, etc.). If you want a highly custom argument parser, and don't mind writing the majority of the functionality yourself, `getopts` is an excellent base. + +`getopts` also doesn't allocate much, or at all. This gives it a very small performance boost. Although, as you start implementing additional features, that boost quickly disappears. + +Personally, I find many, many uses of `getopts` are manually implementing features that `clap` provides by default. Using `clap` simplifies your codebase allowing you to focus on your application, and not argument parsing. + +#### How does `clap` compare to [docopt.rs](https://github.com/docopt/docopt.rs)? + +I first want to say I'm a big a fan of BurntSushi's work, the creator of `Docopt.rs`. I aspire to produce the quality of libraries that this man does! When it comes to comparing these two libraries they are very different. `docopt` tasks you with writing a help message, and then it parsers that message for you to determine all valid arguments and their use. Some people LOVE this approach, others do not. If you're willing to write a detailed help message, it's nice that you can stick that in your program and have `docopt` do the rest. On the downside, it's far less flexible. + +`docopt` is also excellent at translating arguments into Rust types automatically. There is even a syntax extension which will do all this for you, if you're willing to use a nightly compiler (use of a stable compiler requires you to somewhat manually translate from arguments to Rust types). To use BurntSushi's words, `docopt` is also a sort of black box. You get what you get, and it's hard to tweak implementation or customize the experience for your use case. + +Because `docopt` is doing a ton of work to parse your help messages and determine what you were trying to communicate as valid arguments, it's also one of the more heavy weight parsers performance-wise. For most applications this isn't a concern and this isn't to say `docopt` is slow, in fact far from it. This is just something to keep in mind while comparing. + +#### All else being equal, what are some reasons to use `clap`? (The Pitch) + +`clap` is as fast, and as lightweight as possible while still giving all the features you'd expect from a modern argument parser. In fact, for the amount and type of features `clap` offers it remains about as fast as `getopts`. If you use `clap` when just need some simple arguments parsed, you'll find it's a walk in the park. `clap` also makes it possible to represent extremely complex, and advanced requirements, without too much thought. `clap` aims to be intuitive, easy to use, and fully capable for wide variety use cases and needs. + +#### All else being equal, what are some reasons *not* to use `clap`? (The Anti Pitch) + +Depending on the style in which you choose to define the valid arguments, `clap` can be very verbose. `clap` also offers so many fine-tuning knobs and dials, that learning everything can seem overwhelming. I strive to keep the simple cases simple, but when turning all those custom dials it can get complex. `clap` is also opinionated about parsing. Even though so much can be tweaked and tuned with `clap` (and I'm adding more all the time), there are still certain features which `clap` implements in specific ways which may be contrary to some users use-cases. Finally, `clap` is "stringly typed" when referring to arguments which can cause typos in code. This particular paper-cut is being actively worked on, and should be gone in v3.x. + +## Features + +Below are a few of the features which `clap` supports, full descriptions and usage can be found in the [documentation](https://docs.rs/clap/) and [examples/](examples) directory + +* **Auto-generated Help, Version, and Usage information** + - Can optionally be fully, or partially overridden if you want a custom help, version, or usage statements +* **Auto-generated completion scripts at compile time (Bash, Zsh, Fish, and PowerShell)** + - Even works through many multiple levels of subcommands + - Works with options which only accept certain values + - Works with subcommand aliases +* **Flags / Switches** (i.e. bool fields) + - Both short and long versions supported (i.e. `-f` and `--flag` respectively) + - Supports combining short versions (i.e. `-fBgoZ` is the same as `-f -B -g -o -Z`) + - Supports multiple occurrences (i.e. `-vvv` or `-v -v -v`) +* **Positional Arguments** (i.e. those which are based off an index from the program name) + - Supports multiple values (i.e. `myprog ...` such as `myprog file1.txt file2.txt` being two values for the same "file" argument) + - Supports Specific Value Sets (See below) + - Can set value parameters (such as the minimum number of values, the maximum number of values, or the exact number of values) + - Can set custom validations on values to extend the argument parsing capability to truly custom domains +* **Option Arguments** (i.e. those that take values) + - Both short and long versions supported (i.e. `-o value`, `-ovalue`, `-o=value` and `--option value` or `--option=value` respectively) + - Supports multiple values (i.e. `-o -o ` or `-o `) + - Supports delimited values (i.e. `-o=val1,val2,val3`, can also change the delimiter) + - Supports Specific Value Sets (See below) + - Supports named values so that the usage/help info appears as `-o ` etc. for when you require specific multiple values + - Can set value parameters (such as the minimum number of values, the maximum number of values, or the exact number of values) + - Can set custom validations on values to extend the argument parsing capability to truly custom domains +* **Sub-Commands** (i.e. `git add ` where `add` is a sub-command of `git`) + - Support their own sub-arguments, and sub-sub-commands independent of the parent + - Get their own auto-generated Help, Version, and Usage independent of parent +* **Support for building CLIs from YAML** - This keeps your Rust source nice and tidy and makes supporting localized translation very simple! +* **Requirement Rules**: Arguments can define the following types of requirement rules + - Can be required by default + - Can be required only if certain arguments are present + - Can require other arguments to be present + - Can be required only if certain values of other arguments are used +* **Confliction Rules**: Arguments can optionally define the following types of exclusion rules + - Can be disallowed when certain arguments are present + - Can disallow use of other arguments when present +* **Groups**: Arguments can be made part of a group + - Fully compatible with other relational rules (requirements, conflicts, and overrides) which allows things like requiring the use of any arg in a group, or denying the use of an entire group conditionally +* **Specific Value Sets**: Positional or Option Arguments can define a specific set of allowed values (i.e. imagine a `--mode` option which may *only* have one of two values `fast` or `slow` such as `--mode fast` or `--mode slow`) +* **Default Values** + - Also supports conditional default values (i.e. a default which only applies if specific arguments are used, or specific values of those arguments) +* **Automatic Version from Cargo.toml**: `clap` is fully compatible with Rust's `env!()` macro for automatically setting the version of your application to the version in your Cargo.toml. See [09_auto_version example](examples/09_auto_version.rs) for how to do this (Thanks to [jhelwig](https://github.com/jhelwig) for pointing this out) +* **Typed Values**: You can use several convenience macros provided by `clap` to get typed values (i.e. `i32`, `u8`, etc.) from positional or option arguments so long as the type you request implements `std::str::FromStr` See the [12_typed_values example](examples/12_typed_values.rs). You can also use `clap`s `arg_enum!` macro to create an enum with variants that automatically implement `std::str::FromStr`. See [13a_enum_values_automatic example](examples/13a_enum_values_automatic.rs) for details +* **Suggestions**: Suggests corrections when the user enters a typo. For example, if you defined a `--myoption` argument, and the user mistakenly typed `--moyption` (notice `y` and `o` transposed), they would receive a `Did you mean '--myoption'?` error and exit gracefully. This also works for subcommands and flags. (Thanks to [Byron](https://github.com/Byron) for the implementation) (This feature can optionally be disabled, see 'Optional Dependencies / Features') +* **Colorized Errors (Non Windows OS only)**: Error message are printed in in colored text (this feature can optionally be disabled, see 'Optional Dependencies / Features'). +* **Global Arguments**: Arguments can optionally be defined once, and be available to all child subcommands. There values will also be propagated up/down throughout all subcommands. +* **Custom Validations**: You can define a function to use as a validator of argument values. Imagine defining a function to validate IP addresses, or fail parsing upon error. This means your application logic can be solely focused on *using* values. +* **POSIX Compatible Conflicts/Overrides** - In POSIX args can be conflicting, but not fail parsing because whichever arg comes *last* "wins" so to speak. This allows things such as aliases (i.e. `alias ls='ls -l'` but then using `ls -C` in your terminal which ends up passing `ls -l -C` as the final arguments. Since `-l` and `-C` aren't compatible, this effectively runs `ls -C` in `clap` if you choose...`clap` also supports hard conflicts that fail parsing). (Thanks to [Vinatorul](https://github.com/Vinatorul)!) +* Supports the Unix `--` meaning, only positional arguments follow + +## Quick Example + +The following examples show a quick example of some of the very basic functionality of `clap`. For more advanced usage, such as requirements, conflicts, groups, multiple values and occurrences see the [documentation](https://docs.rs/clap/), [examples/](examples) directory of this repository or the [video tutorials](https://www.youtube.com/playlist?list=PLza5oFLQGTl2Z5T8g1pRkIynR3E0_pc7U). + + **NOTE:** All of these examples are functionally the same, but show different styles in which to use `clap`. These different styles are purely a matter of personal preference. + +The first example shows a method using the 'Builder Pattern' which allows more advanced configuration options (not shown in this small example), or even dynamically generating arguments when desired. + +```rust +// (Full example with detailed comments in examples/01b_quick_example.rs) +// +// This example demonstrates clap's full 'builder pattern' style of creating arguments which is +// more verbose, but allows easier editing, and at times more advanced options, or the possibility +// to generate arguments dynamically. +extern crate clap; +use clap::{Arg, App, SubCommand}; + +fn main() { + let matches = App::new("My Super Program") + .version("1.0") + .author("Kevin K. ") + .about("Does awesome things") + .arg(Arg::with_name("config") + .short("c") + .long("config") + .value_name("FILE") + .help("Sets a custom config file") + .takes_value(true)) + .arg(Arg::with_name("INPUT") + .help("Sets the input file to use") + .required(true) + .index(1)) + .arg(Arg::with_name("v") + .short("v") + .multiple(true) + .help("Sets the level of verbosity")) + .subcommand(SubCommand::with_name("test") + .about("controls testing features") + .version("1.3") + .author("Someone E. ") + .arg(Arg::with_name("debug") + .short("d") + .help("print debug information verbosely"))) + .get_matches(); + + // Gets a value for config if supplied by user, or defaults to "default.conf" + let config = matches.value_of("config").unwrap_or("default.conf"); + println!("Value for config: {}", config); + + // Calling .unwrap() is safe here because "INPUT" is required (if "INPUT" wasn't + // required we could have used an 'if let' to conditionally get the value) + println!("Using input file: {}", matches.value_of("INPUT").unwrap()); + + // Vary the output based on how many times the user used the "verbose" flag + // (i.e. 'myprog -v -v -v' or 'myprog -vvv' vs 'myprog -v' + match matches.occurrences_of("v") { + 0 => println!("No verbose info"), + 1 => println!("Some verbose info"), + 2 => println!("Tons of verbose info"), + 3 | _ => println!("Don't be crazy"), + } + + // You can handle information about subcommands by requesting their matches by name + // (as below), requesting just the name used, or both at the same time + if let Some(matches) = matches.subcommand_matches("test") { + if matches.is_present("debug") { + println!("Printing debug info..."); + } else { + println!("Printing normally..."); + } + } + + // more program logic goes here... +} +``` + +One could also optionally declare their CLI in YAML format and keep your Rust source tidy +or support multiple localized translations by having different YAML files for each localization. + +First, create the `cli.yml` file to hold your CLI options, but it could be called anything we like: + +```yaml +name: myapp +version: "1.0" +author: Kevin K. +about: Does awesome things +args: + - config: + short: c + long: config + value_name: FILE + help: Sets a custom config file + takes_value: true + - INPUT: + help: Sets the input file to use + required: true + index: 1 + - verbose: + short: v + multiple: true + help: Sets the level of verbosity +subcommands: + - test: + about: controls testing features + version: "1.3" + author: Someone E. + args: + - debug: + short: d + help: print debug information +``` + +Since this feature requires additional dependencies that not everyone may want, it is *not* compiled in by default and we need to enable a feature flag in Cargo.toml: + +Simply change your `clap = "2.33"` to `clap = {version = "2.33", features = ["yaml"]}`. + +Finally we create our `main.rs` file just like we would have with the previous two examples: + +```rust +// (Full example with detailed comments in examples/17_yaml.rs) +// +// This example demonstrates clap's building from YAML style of creating arguments which is far +// more clean, but takes a very small performance hit compared to the other two methods. +#[macro_use] +extern crate clap; +use clap::App; + +fn main() { + // The YAML file is found relative to the current file, similar to how modules are found + let yaml = load_yaml!("cli.yml"); + let matches = App::from_yaml(yaml).get_matches(); + + // Same as previous examples... +} +``` + +If you were to compile any of the above programs and run them with the flag `--help` or `-h` (or `help` subcommand, since we defined `test` as a subcommand) the following would be output + +```sh +$ myprog --help +My Super Program 1.0 +Kevin K. +Does awesome things + +USAGE: + MyApp [FLAGS] [OPTIONS] [SUBCOMMAND] + +FLAGS: + -h, --help Prints help information + -v Sets the level of verbosity + -V, --version Prints version information + +OPTIONS: + -c, --config Sets a custom config file + +ARGS: + INPUT The input file to use + +SUBCOMMANDS: + help Prints this message or the help of the given subcommand(s) + test Controls testing features +``` + +**NOTE:** You could also run `myapp test --help` or `myapp help test` to see the help message for the `test` subcommand. + +There are also two other methods to create CLIs. Which style you choose is largely a matter of personal preference. The two other methods are: + +* Using [usage strings (examples/01a_quick_example.rs)](examples/01a_quick_example.rs) similar to (but not exact) docopt style usage statements. This is far less verbose than the above methods, but incurs a slight runtime penalty. +* Using [a macro (examples/01c_quick_example.rs)](examples/01c_quick_example.rs) which is like a hybrid of the builder and usage string style. It's less verbose, but doesn't incur the runtime penalty of the usage string style. The downside is that it's harder to debug, and more opaque. + +Examples of each method can be found in the [examples/](examples) directory of this repository. + +## Try it! + +### Pre-Built Test + +To try out the pre-built examples, use the following steps: + +* Clone the repository `$ git clone https://github.com/clap-rs/clap && cd clap-rs/` +* Compile the example `$ cargo build --example ` +* Run the help info `$ ./target/debug/examples/ --help` +* Play with the arguments! +* You can also do a onetime run via `$ cargo run --example -- [args to example]` + +### BYOB (Build Your Own Binary) + +To test out `clap`'s default auto-generated help/version follow these steps: +* Create a new cargo project `$ cargo new fake --bin && cd fake` +* Add `clap` to your `Cargo.toml` + +```toml +[dependencies] +clap = "2" +``` + +* Add the following to your `src/main.rs` + +```rust +extern crate clap; +use clap::App; + +fn main() { + App::new("fake").version("v1.0-beta").get_matches(); +} +``` + +* Build your program `$ cargo build --release` +* Run with help or version `$ ./target/release/fake --help` or `$ ./target/release/fake --version` + +## Usage + +For full usage, add `clap` as a dependency in your `Cargo.toml` () to use from crates.io: + +```toml +[dependencies] +clap = "~2.33" +``` + +(**note**: If you are concerned with supporting a minimum version of Rust that is *older* than the current stable Rust minus 2 stable releases, it's recommended to use the `~major.minor.patch` style versions in your `Cargo.toml` which will only update the patch version automatically. For more information see the [Compatibility Policy](#compatibility-policy)) + +Then add `extern crate clap;` to your crate root. + +Define a list of valid arguments for your program (see the [documentation](https://docs.rs/clap/) or [examples/](examples) directory of this repo) + +Then run `cargo build` or `cargo update && cargo build` for your project. + +### Optional Dependencies / Features + +#### Features enabled by default + +* **"suggestions"**: Turns on the `Did you mean '--myoption'?` feature for when users make typos. (builds dependency `strsim`) +* **"color"**: Turns on colored error messages. This feature only works on non-Windows OSs. (builds dependency `ansi-term` only on non-Windows targets) +* **"vec_map"**: Use [`VecMap`](https://crates.io/crates/vec_map) internally instead of a [`BTreeMap`](https://doc.rust-lang.org/stable/std/collections/struct.BTreeMap.html). This feature provides a _slight_ performance improvement. (builds dependency `vec_map`) + +To disable these, add this to your `Cargo.toml`: + +```toml +[dependencies.clap] +version = "2.33" +default-features = false +``` + +You can also selectively enable only the features you'd like to include, by adding: + +```toml +[dependencies.clap] +version = "2.33" +default-features = false + +# Cherry-pick the features you'd like to use +features = [ "suggestions", "color" ] +``` + +#### Opt-in features + +* **"yaml"**: Enables building CLIs from YAML documents. (builds dependency `yaml-rust`) +* **"unstable"**: Enables unstable `clap` features that may change from release to release +* **"wrap_help"**: Turns on the help text wrapping feature, based on the terminal size. (builds dependency `term-size`) + +### Dependencies Tree + +The following graphic depicts `clap`s dependency graph (generated using [cargo-graph](https://github.com/kbknapp/cargo-graph)). + + * **Dashed** Line: Optional dependency + * **Red** Color: **NOT** included by default (must use cargo `features` to enable) + * **Blue** Color: Dev dependency, only used while developing. + +![clap dependencies](clap_dep_graph.png) + +### More Information + +You can find complete documentation on the [docs.rs](https://docs.rs/clap/) for this project. + +You can also find usage examples in the [examples/](examples) directory of this repo. + +#### Video Tutorials + +There's also the video tutorial series [Argument Parsing with Rust v2](https://www.youtube.com/playlist?list=PLza5oFLQGTl2Z5T8g1pRkIynR3E0_pc7U). + +These videos slowly trickle out as I finish them and currently a work in progress. + +## How to Contribute + +Details on how to contribute can be found in the [CONTRIBUTING.md](.github/CONTRIBUTING.md) file. + +### Compatibility Policy + +Because `clap` takes SemVer and compatibility seriously, this is the official policy regarding breaking changes and minimum required versions of Rust. + +`clap` will pin the minimum required version of Rust to the CI builds. Bumping the minimum version of Rust is considered a minor breaking change, meaning *at a minimum* the minor version of `clap` will be bumped. + +In order to keep from being surprised of breaking changes, it is **highly** recommended to use the `~major.minor.patch` style in your `Cargo.toml` only if you wish to target a version of Rust that is *older* than current stable minus two releases: + +```toml +[dependencies] +clap = "~2.33" +``` + +This will cause *only* the patch version to be updated upon a `cargo update` call, and therefore cannot break due to new features, or bumped minimum versions of Rust. + +#### Warning about '~' Dependencies + +Using `~` can cause issues in certain circumstances. + +From @alexcrichton: + +Right now Cargo's version resolution is pretty naive, it's just a brute-force search of the solution space, returning the first resolvable graph. This also means that it currently won't terminate until it proves there is not possible resolvable graph. This leads to situations where workspaces with multiple binaries, for example, have two different dependencies such as: + +```toml,no_sync + +# In one Cargo.toml +[dependencies] +clap = "~2.33.0" + +# In another Cargo.toml +[dependencies] +clap = "2.33.0" +``` + +This is inherently an unresolvable crate graph in Cargo right now. Cargo requires there's only one major version of a crate, and being in the same workspace these two crates must share a version. This is impossible in this location, though, as these version constraints cannot be met. + +#### Minimum Version of Rust + +`clap` will officially support current stable Rust, minus two releases, but may work with prior releases as well. For example, current stable Rust at the time of this writing is 1.32.0, meaning `clap` is guaranteed to compile with 1.30.0 and beyond. + +At the 1.33.0 stable release, `clap` will be guaranteed to compile with 1.31.0 and beyond, etc. + +Upon bumping the minimum version of Rust (assuming it's within the stable-2 range), it *must* be clearly annotated in the `CHANGELOG.md` + +#### Breaking Changes + +`clap` takes a similar policy to Rust and will bump the major version number upon breaking changes with only the following exceptions: + + * The breaking change is to fix a security concern + * The breaking change is to be fixing a bug (i.e. relying on a bug as a feature) + * The breaking change is a feature isn't used in the wild, or all users of said feature have given approval *prior* to the change + +#### Compatibility with Wasm + +A best effort is made to ensure that `clap` will work on projects targeting `wasm32-unknown-unknown`. However there is no dedicated CI build +covering this specific target. + +## License + +`clap` is licensed under the MIT license. Please read the [LICENSE-MIT](LICENSE-MIT) file in this repository for more information. + +## Related Crates + +There are several excellent crates which can be used with `clap`, I recommend checking them all out! If you've got a crate that would be a good fit to be used with `clap` open an issue and let me know, I'd love to add it! + +* [`structopt`](https://github.com/TeXitoi/structopt) - This crate allows you to define a struct, and build a CLI from it! No more "stringly typed" and it uses `clap` behind the scenes! (*Note*: There is work underway to pull this crate into mainline `clap`). +* [`assert_cli`](https://github.com/assert-rs/assert_cli) - This crate allows you test your CLIs in a very intuitive and functional way! + +## Recent Breaking Changes + +`clap` follows semantic versioning, so breaking changes should only happen upon major version bumps. The only exception to this rule is breaking changes that happen due to implementation that was deemed to be a bug, security concerns, or it can be reasonably proved to affect no code. For the full details, see [CHANGELOG.md](./CHANGELOG.md). + +As of 2.27.0: + +* Argument values now take precedence over subcommand names. This only arises by using unrestrained multiple values and subcommands together where the subcommand name can coincide with one of the multiple values. Such as `$ prog ... `. The fix is to place restraints on number of values, or disallow the use of `$ prog ` structure. + +As of 2.0.0 (From 1.x) + +* **Fewer lifetimes! Yay!** + * `App<'a, 'b, 'c, 'd, 'e, 'f>` => `App<'a, 'b>` + * `Arg<'a, 'b, 'c, 'd, 'e, 'f>` => `Arg<'a, 'b>` + * `ArgMatches<'a, 'b>` => `ArgMatches<'a>` +* **Simply Renamed** + * `App::arg_group` => `App::group` + * `App::arg_groups` => `App::groups` + * `ArgGroup::add` => `ArgGroup::arg` + * `ArgGroup::add_all` => `ArgGroup::args` + * `ClapError` => `Error` + * struct field `ClapError::error_type` => `Error::kind` + * `ClapResult` => `Result` + * `ClapErrorType` => `ErrorKind` +* **Removed Deprecated Functions and Methods** + * `App::subcommands_negate_reqs` + * `App::subcommand_required` + * `App::arg_required_else_help` + * `App::global_version(bool)` + * `App::versionless_subcommands` + * `App::unified_help_messages` + * `App::wait_on_error` + * `App::subcommand_required_else_help` + * `SubCommand::new` + * `App::error_on_no_subcommand` + * `Arg::new` + * `Arg::mutually_excludes` + * `Arg::mutually_excludes_all` + * `Arg::mutually_overrides_with` + * `simple_enum!` +* **Renamed Error Variants** + * `InvalidUnicode` => `InvalidUtf8` + * `InvalidArgument` => `UnknownArgument` +* **Usage Parser** + * Value names can now be specified inline, i.e. `-o, --option 'some option which takes two files'` + * **There is now a priority of order to determine the name** - This is perhaps the biggest breaking change. See the documentation for full details. Prior to this change, the value name took precedence. **Ensure your args are using the proper names (i.e. typically the long or short and NOT the value name) throughout the code** +* `ArgMatches::values_of` returns an `Values` now which implements `Iterator` (should not break any code) +* `crate_version!` returns `&'static str` instead of `String` + +### Deprecations + +Old method names will be left around for several minor version bumps, or one major version bump. + +As of 2.27.0: + +* **AppSettings::PropagateGlobalValuesDown:** this setting deprecated and is no longer required to propagate values down or up diff --git a/clap/SPONSORS.md b/clap/SPONSORS.md new file mode 100644 index 0000000..67f5544 --- /dev/null +++ b/clap/SPONSORS.md @@ -0,0 +1,17 @@ +Below is a list of sponsors for the clap-rs project + +If you are interested in becoming a sponsor for this project please our [sponsorship page](https://clap.rs/sponsorship/). + +## Recurring Sponsors: + +| [Noelia Seva-Gonzalez](https://noeliasg.com/about/) | [messense](https://github.com/messense) | [Josh](https://joshtriplett.org) | Stephen Oats | +|:-:|:-:|:-:|:-:| +|Noelia Seva-Gonzalez | Messense | Josh Triplett | Stephen Oats | + + +## Single-Donation and Former Sponsors: + +| [Rob Tsuk](https://github.com/rtsuk)| | | +|:-:|:-:|:-:| +|Rob Tsuk| | | + diff --git a/clap/benches/01_default.rs b/clap/benches/01_default.rs new file mode 100644 index 0000000..24e619e --- /dev/null +++ b/clap/benches/01_default.rs @@ -0,0 +1,18 @@ +#![feature(test)] + +extern crate clap; +extern crate test; + +use clap::App; + +use test::Bencher; + +#[bench] +fn build_app(b: &mut Bencher) { + b.iter(|| App::new("claptests")); +} + +#[bench] +fn parse_clean(b: &mut Bencher) { + b.iter(|| App::new("claptests").get_matches_from(vec![""])); +} diff --git a/clap/benches/02_simple.rs b/clap/benches/02_simple.rs new file mode 100644 index 0000000..010fa86 --- /dev/null +++ b/clap/benches/02_simple.rs @@ -0,0 +1,114 @@ +#![feature(test)] + +extern crate clap; +extern crate test; + +use clap::{App, Arg}; + +use test::Bencher; + +macro_rules! create_app { + () => ({ + App::new("claptests") + .version("0.1") + .about("tests clap library") + .author("Kevin K. ") + .args_from_usage("-f --flag 'tests flags' + -o --option=[opt] 'tests options' + [positional] 'tests positional'") + }) +} + +#[bench] +fn build_app(b: &mut Bencher) { + + b.iter(|| create_app!()); +} + +#[bench] +fn add_flag(b: &mut Bencher) { + fn build_app() -> App<'static, 'static> { + App::new("claptests") + } + + b.iter(|| build_app().arg(Arg::from_usage("-s, --some 'something'"))); +} + +#[bench] +fn add_flag_ref(b: &mut Bencher) { + fn build_app() -> App<'static, 'static> { + App::new("claptests") + } + + b.iter(|| { + let arg = Arg::from_usage("-s, --some 'something'"); + build_app().arg(&arg) + }); +} + +#[bench] +fn add_opt(b: &mut Bencher) { + fn build_app() -> App<'static, 'static> { + App::new("claptests") + } + + b.iter(|| build_app().arg(Arg::from_usage("-s, --some 'something'"))); +} + +#[bench] +fn add_opt_ref(b: &mut Bencher) { + fn build_app() -> App<'static, 'static> { + App::new("claptests") + } + + b.iter(|| { + let arg = Arg::from_usage("-s, --some 'something'"); + build_app().arg(&arg) + }); +} + +#[bench] +fn add_pos(b: &mut Bencher) { + fn build_app() -> App<'static, 'static> { + App::new("claptests") + } + + b.iter(|| build_app().arg(Arg::with_name("some"))); +} + +#[bench] +fn add_pos_ref(b: &mut Bencher) { + fn build_app() -> App<'static, 'static> { + App::new("claptests") + } + + b.iter(|| { + let arg = Arg::with_name("some"); + build_app().arg(&arg) + }); +} + +#[bench] +fn parse_clean(b: &mut Bencher) { + b.iter(|| create_app!().get_matches_from(vec![""])); +} + +#[bench] +fn parse_flag(b: &mut Bencher) { + b.iter(|| create_app!().get_matches_from(vec!["myprog", "-f"])); +} + +#[bench] +fn parse_option(b: &mut Bencher) { + b.iter(|| create_app!().get_matches_from(vec!["myprog", "-o", "option1"])); +} + +#[bench] +fn parse_positional(b: &mut Bencher) { + b.iter(|| create_app!().get_matches_from(vec!["myprog", "arg1"])); +} + +#[bench] +fn parse_complex(b: &mut Bencher) { + b.iter(|| create_app!().get_matches_from(vec!["myprog", "-o", "option1", "-f", "arg1"])); +} diff --git a/clap/benches/03_complex.rs b/clap/benches/03_complex.rs new file mode 100644 index 0000000..b00fc4e --- /dev/null +++ b/clap/benches/03_complex.rs @@ -0,0 +1,230 @@ +#![feature(test)] + +#[macro_use] +extern crate clap; +extern crate test; + +use clap::{App, Arg, SubCommand, AppSettings}; + +use test::Bencher; + +static ARGS: &'static str = "-o --option=[opt]... 'tests options' + [positional] 'tests positionals'"; +static OPT3_VALS: [&'static str; 2] = ["fast", "slow"]; +static POS3_VALS: [&'static str; 2] = ["vi", "emacs"]; + +macro_rules! create_app { + () => ({ + App::new("claptests") + .version("0.1") + .about("tests clap library") + .author("Kevin K. ") + .args_from_usage(ARGS) + .arg(Arg::from_usage("-f --flag... 'tests flags'") + .global(true)) + .args(&[ + Arg::from_usage("[flag2] -F 'tests flags with exclusions'").conflicts_with("flag").requires("option2"), + Arg::from_usage("--long-option-2 [option2] 'tests long options with exclusions'").conflicts_with("option").requires("positional2"), + Arg::from_usage("[positional2] 'tests positionals with exclusions'"), + Arg::from_usage("-O --Option [option3] 'tests options with specific value sets'").possible_values(&OPT3_VALS), + Arg::from_usage("[positional3]... 'tests positionals with specific values'").possible_values(&POS3_VALS), + Arg::from_usage("--multvals [one] [two] 'Tests multiple values, not mult occs'"), + Arg::from_usage("--multvalsmo... [one] [two] 'Tests multiple values, not mult occs'"), + Arg::from_usage("--minvals2 [minvals]... 'Tests 2 min vals'").min_values(2), + Arg::from_usage("--maxvals3 [maxvals]... 'Tests 3 max vals'").max_values(3) + ]) + .subcommand(SubCommand::with_name("subcmd") + .about("tests subcommands") + .version("0.1") + .author("Kevin K. ") + .arg_from_usage("-o --option [scoption]... 'tests options'") + .arg_from_usage("[scpositional] 'tests positionals'")) + }) +} + +#[bench] +fn create_app_from_usage(b: &mut Bencher) { + + b.iter(|| create_app!()); +} + +#[bench] +fn create_app_builder(b: &mut Bencher) { + b.iter(|| { + App::new("claptests") + .version("0.1") + .about("tests clap library") + .author("Kevin K. ") + .arg(Arg::with_name("opt") + .help("tests options") + .short("o") + .long("option") + .takes_value(true) + .multiple(true)) + .arg(Arg::with_name("positional") + .help("tests positionals") + .index(1)) + .arg(Arg::with_name("flag") + .short("f") + .help("tests flags") + .long("flag") + .multiple(true) + .global(true)) + .arg(Arg::with_name("flag2") + .short("F") + .help("tests flags with exclusions") + .conflicts_with("flag") + .requires("option2")) + .arg(Arg::with_name("option2") + .help("tests long options with exclusions") + .conflicts_with("option") + .requires("positional2") + .takes_value(true) + .long("long-option-2")) + .arg(Arg::with_name("positional2") + .index(3) + .help("tests positionals with exclusions")) + .arg(Arg::with_name("option3") + .short("O") + .long("Option") + .takes_value(true) + .help("tests options with specific value sets") + .possible_values(&OPT3_VALS)) + .arg(Arg::with_name("positional3") + .multiple(true) + .help("tests positionals with specific values") + .index(4) + .possible_values(&POS3_VALS)) + .arg(Arg::with_name("multvals") + .long("multvals") + .takes_value(true) + .help("Tests multiple values, not mult occs") + .value_names(&["one", "two"])) + .arg(Arg::with_name("multvalsmo") + .long("multvalsmo") + .takes_value(true) + .multiple(true) + .help("Tests multiple values, not mult occs") + .value_names(&["one", "two"])) + .arg(Arg::with_name("minvals") + .long("minvals2") + .multiple(true) + .takes_value(true) + .help("Tests 2 min vals") + .min_values(2)) + .arg(Arg::with_name("maxvals") + .long("maxvals3") + .takes_value(true) + .multiple(true) + .help("Tests 3 max vals") + .max_values(3)) + .subcommand(SubCommand::with_name("subcmd") + .about("tests subcommands") + .version("0.1") + .author("Kevin K. ") + .arg(Arg::with_name("scoption") + .short("o") + .long("option") + .multiple(true) + .takes_value(true) + .help("tests options")) + .arg(Arg::with_name("scpositional") + .index(1) + .help("tests positionals"))); + }); +} + +#[bench] +fn create_app_macros(b: &mut Bencher) { + b.iter(|| { + clap_app!(claptests => + (version: "0.1") + (about: "tests clap library") + (author: "Kevin K. ") + (@arg opt: -o --option +takes_value ... "tests options") + (@arg positional: index(1) "tests positionals") + (@arg flag: -f --flag ... +global "tests flags") + (@arg flag2: -F conflicts_with[flag] requires[option2] + "tests flags with exclusions") + (@arg option2: --long_option_2 conflicts_with[option] requires[positional2] + "tests long options with exclusions") + (@arg positional2: index(2) "tests positionals with exclusions") + (@arg option3: -O --Option +takes_value possible_value[fast slow] + "tests options with specific value sets") + (@arg positional3: index(3) ... possible_value[vi emacs] + "tests positionals with specific values") + (@arg multvals: --multvals +takes_value value_name[one two] + "Tests multiple values, not mult occs") + (@arg multvalsmo: --multvalsmo ... +takes_value value_name[one two] + "Tests multiple values, not mult occs") + (@arg minvals: --minvals2 min_values(1) ... +takes_value "Tests 2 min vals") + (@arg maxvals: --maxvals3 ... +takes_value max_values(3) "Tests 3 max vals") + (@subcommand subcmd => + (about: "tests subcommands") + (version: "0.1") + (author: "Kevin K. ") + (@arg scoption: -o --option ... +takes_value "tests options") + (@arg scpositional: index(1) "tests positionals")) + ); + }); +} + +#[bench] +fn parse_clean(b: &mut Bencher) { + b.iter(|| create_app!().get_matches_from(vec![""])); +} + +#[bench] +fn parse_flag(b: &mut Bencher) { + b.iter(|| create_app!().get_matches_from(vec!["myprog", "-f"])); +} + +#[bench] +fn parse_option(b: &mut Bencher) { + b.iter(|| create_app!().get_matches_from(vec!["myprog", "-o", "option1"])); +} + +#[bench] +fn parse_positional(b: &mut Bencher) { + b.iter(|| create_app!().get_matches_from(vec!["myprog", "arg1"])); +} + +#[bench] +fn parse_sc_clean(b: &mut Bencher) { + b.iter(|| create_app!().get_matches_from(vec!["myprog", "subcmd"])); +} + +#[bench] +fn parse_sc_flag(b: &mut Bencher) { + b.iter(|| create_app!().get_matches_from(vec!["myprog", "subcmd", "-f"])); +} + +#[bench] +fn parse_sc_option(b: &mut Bencher) { + b.iter(|| create_app!().get_matches_from(vec!["myprog", "subcmd", "-o", "option1"])); +} + +#[bench] +fn parse_sc_positional(b: &mut Bencher) { + b.iter(|| create_app!().get_matches_from(vec!["myprog", "subcmd", "arg1"])); +} + +#[bench] +fn parse_complex1(b: &mut Bencher) { + b.iter(|| create_app!().get_matches_from(vec!["myprog", "-ff", "-o", "option1", "arg1", "-O", "fast", "arg2", "--multvals", "one", "two", "emacs"])); +} + +#[bench] +fn parse_complex2(b: &mut Bencher) { + b.iter(|| create_app!().get_matches_from(vec!["myprog", "arg1", "-f", "arg2", "--long-option-2", "some", "-O", "slow", "--multvalsmo", "one", "two", "--minvals2", "3", "2", "1"])); +} + +#[bench] +fn parse_complex2_with_args_negate_scs(b: &mut Bencher) { + b.iter(|| create_app!().setting(AppSettings::ArgsNegateSubcommands).get_matches_from(vec!["myprog", "arg1", "-f", "arg2", "--long-option-2", "some", "-O", "slow", "--multvalsmo", "one", "two", "--minvals2", "3", "2", "1"])); +} + +#[bench] +fn parse_sc_complex(b: &mut Bencher) { + b.iter(|| create_app!().get_matches_from(vec!["myprog", "subcmd", "-f", "-o", "option1", "arg1"])); +} diff --git a/clap/benches/04_new_help.rs b/clap/benches/04_new_help.rs new file mode 100644 index 0000000..f033efb --- /dev/null +++ b/clap/benches/04_new_help.rs @@ -0,0 +1,198 @@ +#![feature(test)] + +extern crate clap; +extern crate test; + +use test::Bencher; + +use std::io::Cursor; + +use clap::App; +use clap::{Arg, SubCommand}; + +fn build_help(app: &App) -> String { + let mut buf = Cursor::new(Vec::with_capacity(50)); + app.write_help(&mut buf).unwrap(); + let content = buf.into_inner(); + String::from_utf8(content).unwrap() +} + +fn app_example1<'b, 'c>() -> App<'b, 'c> { + App::new("MyApp") + .version("1.0") + .author("Kevin K. ") + .about("Does awesome things") + .args_from_usage("-c, --config=[FILE] 'Sets a custom config file' + 'Sets an optional output file' + -d... 'Turn debugging information on'") + .subcommand(SubCommand::with_name("test") + .about("does testing things") + .arg_from_usage("-l, --list 'lists test values'")) +} + +fn app_example2<'b, 'c>() -> App<'b, 'c> { + App::new("MyApp") + .version("1.0") + .author("Kevin K. ") + .about("Does awesome things") +} + +fn app_example3<'b, 'c>() -> App<'b, 'c> { + App::new("MyApp") + .arg(Arg::with_name("debug") + .help("turn on debugging information") + .short("d")) + .args(&[Arg::with_name("config") + .help("sets the config file to use") + .takes_value(true) + .short("c") + .long("config"), + Arg::with_name("input") + .help("the input file to use") + .index(1) + .required(true)]) + .arg_from_usage("--license 'display the license file'") + .args_from_usage("[output] 'Supply an output file to use' + -i, --int=[IFACE] 'Set an interface to use'") +} + +fn app_example4<'b, 'c>() -> App<'b, 'c> { + App::new("MyApp") + .about("Parses an input file to do awesome things") + .version("1.0") + .author("Kevin K. ") + .arg(Arg::with_name("debug") + .help("turn on debugging information") + .short("d") + .long("debug")) + .arg(Arg::with_name("config") + .help("sets the config file to use") + .short("c") + .long("config")) + .arg(Arg::with_name("input") + .help("the input file to use") + .index(1) + .required(true)) +} + +fn app_example5<'b, 'c>() -> App<'b, 'c> { + App::new("MyApp").arg(Arg::with_name("awesome") + .help("turns up the awesome") + .short("a") + .long("awesome") + .multiple(true) + .requires("config") + .conflicts_with("output")) +} + +fn app_example6<'b, 'c>() -> App<'b, 'c> { + App::new("MyApp") + .arg(Arg::with_name("input") + .help("the input file to use") + .index(1) + .requires("config") + .conflicts_with("output") + .required(true)) + .arg(Arg::with_name("config") + .help("the config file to use") + .index(2)) +} + +fn app_example7<'b, 'c>() -> App<'b, 'c> { + App::new("MyApp") + .arg(Arg::with_name("config")) + .arg(Arg::with_name("output")) + .arg(Arg::with_name("input") + .help("the input file to use") + .takes_value(true) + .short("i") + .long("input") + .multiple(true) + .required(true) + .requires("config") + .conflicts_with("output")) +} + +fn app_example8<'b, 'c>() -> App<'b, 'c> { + App::new("MyApp") + .arg(Arg::with_name("config")) + .arg(Arg::with_name("output")) + .arg(Arg::with_name("input") + .help("the input file to use") + .takes_value(true) + .short("i") + .long("input") + .multiple(true) + .required(true) + .requires("config") + .conflicts_with("output")) +} + +fn app_example10<'b, 'c>() -> App<'b, 'c> { + App::new("myapp") + .about("does awesome things") + .arg(Arg::with_name("CONFIG") + .help("The config file to use (default is \"config.json\")") + .short("c") + .takes_value(true)) +} + +#[bench] +fn example1(b: &mut Bencher) { + let app = app_example1(); + b.iter(|| build_help(&app)); +} + +#[bench] +fn example2(b: &mut Bencher) { + let app = app_example2(); + b.iter(|| build_help(&app)); +} + +#[bench] +fn example3(b: &mut Bencher) { + let app = app_example3(); + b.iter(|| build_help(&app)); +} + +#[bench] +fn example4(b: &mut Bencher) { + let app = app_example4(); + b.iter(|| build_help(&app)); +} + +#[bench] +fn example5(b: &mut Bencher) { + let app = app_example5(); + b.iter(|| build_help(&app)); +} + +#[bench] +fn example6(b: &mut Bencher) { + let app = app_example6(); + b.iter(|| build_help(&app)); +} + +#[bench] +fn example7(b: &mut Bencher) { + let app = app_example7(); + b.iter(|| build_help(&app)); +} + +#[bench] +fn example8(b: &mut Bencher) { + let app = app_example8(); + b.iter(|| build_help(&app)); +} + +#[bench] +fn example10(b: &mut Bencher) { + let app = app_example10(); + b.iter(|| build_help(&app)); +} + +#[bench] +fn example4_template(b: &mut Bencher) { + let app = app_example4().template("{bin} {version}\n{author}\n{about}\n\nUSAGE:\n {usage}\n\nFLAGS:\n{flags}\n\nARGS:\n{args}\n"); + b.iter(|| build_help(&app)); +} diff --git a/clap/benches/05_ripgrep.rs b/clap/benches/05_ripgrep.rs new file mode 100644 index 0000000..7db1552 --- /dev/null +++ b/clap/benches/05_ripgrep.rs @@ -0,0 +1,777 @@ +// Used to simulate a fairly large number of options/flags and parsing with thousands of positional +// args +// +// CLI used is adapted from ripgrep 48a8a3a691220f9e5b2b08f4051abe8655ea7e8a + +#![feature(test)] + +extern crate clap; +extern crate test; +#[macro_use] +extern crate lazy_static; + +use clap::{App, AppSettings, Arg, ArgSettings}; + +use test::Bencher; +use std::collections::HashMap; +use std::io::Cursor; + +#[bench] +fn build_app_short(b: &mut Bencher) { b.iter(|| app_short()); } + +#[bench] +fn build_app_long(b: &mut Bencher) { b.iter(|| app_long()); } + +#[bench] +fn build_help_short(b: &mut Bencher) { + let app = app_short(); + b.iter(|| build_help(&app)); +} + +#[bench] +fn build_help_long(b: &mut Bencher) { + let app = app_long(); + b.iter(|| build_help(&app)); +} + +#[bench] +fn parse_clean(b: &mut Bencher) { b.iter(|| app_short().get_matches_from(vec!["rg", "pat"])); } + +#[bench] +fn parse_complex(b: &mut Bencher) { + b.iter(|| { + app_short().get_matches_from(vec!["rg", + "pat", + "-cFlN", + "-pqr=some", + "--null", + "--no-filename", + "--no-messages", + "-SH", + "-C5", + "--follow", + "-e some"]) + }); +} + +#[bench] +fn parse_lots(b: &mut Bencher) { + b.iter(|| { + app_short() + .get_matches_from(vec!["rg", "pat", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some", "some", "some", "some", "some", + "some", "some", "some", "some"]) + }); +} + +const ABOUT: &'static str = " +ripgrep (rg) recursively searches your current directory for a regex pattern. + +ripgrep's regex engine uses finite automata and guarantees linear time +searching. Because of this, features like backreferences and arbitrary +lookaround are not supported. + +Project home page: https://github.com/BurntSushi/ripgrep + +Use -h for short descriptions and --help for more details."; + +const USAGE: &'static str = " + rg [OPTIONS] [ ...] + rg [OPTIONS] [-e PATTERN | -f FILE ]... [ ...] + rg [OPTIONS] --files [ ...] + rg [OPTIONS] --type-list"; + +const TEMPLATE: &'static str = "\ +{bin} {version} +{author} +{about} + +USAGE:{usage} + +ARGS: +{positionals} + +OPTIONS: +{unified}"; + +/// Build a clap application with short help strings. +pub fn app_short() -> App<'static, 'static> { app(false, |k| USAGES[k].short) } + +/// Build a clap application with long help strings. +pub fn app_long() -> App<'static, 'static> { app(true, |k| USAGES[k].long) } + + +/// Build the help text of an application. +fn build_help(app: &App) -> String { + let mut buf = Cursor::new(Vec::with_capacity(50)); + app.write_help(&mut buf).unwrap(); + let content = buf.into_inner(); + String::from_utf8(content).unwrap() +} + + +/// Build a clap application parameterized by usage strings. +/// +/// The function given should take a clap argument name and return a help +/// string. `app` will panic if a usage string is not defined. +/// +/// This is an intentionally stand-alone module so that it can be used easily +/// in a `build.rs` script to build shell completion files. +fn app(next_line_help: bool, doc: F) -> App<'static, 'static> + where F: Fn(&'static str) -> &'static str +{ + let arg = |name| Arg::with_name(name).help(doc(name)).next_line_help(next_line_help); + let flag = |name| arg(name).long(name); + + App::new("ripgrep") + .author("BurntSushi") // simulating since it's only a bench + .version("0.4.0") // Simulating + .about(ABOUT) + .max_term_width(100) + .setting(AppSettings::UnifiedHelpMessage) + .usage(USAGE) + .template(TEMPLATE) + // Handle help/version manually to make their output formatting + // consistent with short/long views. + .arg(arg("help-short").short("h")) + .arg(flag("help")) + .arg(flag("version").short("V")) + // First, set up primary positional/flag arguments. + .arg(arg("pattern") + .required_unless_one(&[ + "file", "files", "help-short", "help", "regexp", "type-list", + "version", + ])) + .arg(arg("path").multiple(true)) + .arg(flag("regexp").short("e") + .takes_value(true).multiple(true).number_of_values(1) + .set(ArgSettings::AllowLeadingHyphen) + .value_name("pattern")) + .arg(flag("files") + // This should also conflict with `pattern`, but the first file + // path will actually be in `pattern`. + .conflicts_with_all(&["file", "regexp", "type-list"])) + .arg(flag("type-list") + .conflicts_with_all(&["file", "files", "pattern", "regexp"])) + // Second, set up common flags. + .arg(flag("text").short("a")) + .arg(flag("count").short("c")) + .arg(flag("color") + .value_name("WHEN") + .takes_value(true) + .hide_possible_values(true) + .possible_values(&["never", "auto", "always", "ansi"])) + .arg(flag("colors").value_name("SPEC") + .takes_value(true).multiple(true).number_of_values(1)) + .arg(flag("fixed-strings").short("F")) + .arg(flag("glob").short("g") + .takes_value(true).multiple(true).number_of_values(1) + .value_name("GLOB")) + .arg(flag("ignore-case").short("i")) + .arg(flag("line-number").short("n")) + .arg(flag("no-line-number").short("N")) + .arg(flag("quiet").short("q")) + .arg(flag("type").short("t") + .takes_value(true).multiple(true).number_of_values(1) + .value_name("TYPE")) + .arg(flag("type-not").short("T") + .takes_value(true).multiple(true).number_of_values(1) + .value_name("TYPE")) + .arg(flag("unrestricted").short("u") + .multiple(true)) + .arg(flag("invert-match").short("v")) + .arg(flag("word-regexp").short("w")) + // Third, set up less common flags. + .arg(flag("after-context").short("A") + .value_name("NUM").takes_value(true) + .validator(validate_number)) + .arg(flag("before-context").short("B") + .value_name("NUM").takes_value(true) + .validator(validate_number)) + .arg(flag("context").short("C") + .value_name("NUM").takes_value(true) + .validator(validate_number)) + .arg(flag("column")) + .arg(flag("context-separator") + .value_name("SEPARATOR").takes_value(true)) + .arg(flag("debug")) + .arg(flag("file").short("f") + .value_name("FILE").takes_value(true) + .multiple(true).number_of_values(1)) + .arg(flag("files-with-matches").short("l")) + .arg(flag("files-without-match")) + .arg(flag("with-filename").short("H")) + .arg(flag("no-filename")) + .arg(flag("heading").overrides_with("no-heading")) + .arg(flag("no-heading").overrides_with("heading")) + .arg(flag("hidden")) + .arg(flag("ignore-file") + .value_name("FILE").takes_value(true) + .multiple(true).number_of_values(1)) + .arg(flag("follow").short("L")) + .arg(flag("max-count") + .short("m").value_name("NUM").takes_value(true) + .validator(validate_number)) + .arg(flag("maxdepth") + .value_name("NUM").takes_value(true) + .validator(validate_number)) + .arg(flag("mmap")) + .arg(flag("no-messages")) + .arg(flag("no-mmap")) + .arg(flag("no-ignore")) + .arg(flag("no-ignore-parent")) + .arg(flag("no-ignore-vcs")) + .arg(flag("null")) + .arg(flag("path-separator").value_name("SEPARATOR").takes_value(true)) + .arg(flag("pretty").short("p")) + .arg(flag("replace").short("r").value_name("ARG").takes_value(true)) + .arg(flag("case-sensitive").short("s")) + .arg(flag("smart-case").short("S")) + .arg(flag("sort-files")) + .arg(flag("threads") + .short("j").value_name("ARG").takes_value(true) + .validator(validate_number)) + .arg(flag("vimgrep")) + .arg(flag("type-add") + .value_name("TYPE").takes_value(true) + .multiple(true).number_of_values(1)) + .arg(flag("type-clear") + .value_name("TYPE").takes_value(true) + .multiple(true).number_of_values(1)) +} + +struct Usage { + short: &'static str, + long: &'static str, +} + +macro_rules! doc { + ($map:expr, $name:expr, $short:expr) => { + doc!($map, $name, $short, $short) + }; + ($map:expr, $name:expr, $short:expr, $long:expr) => { + $map.insert($name, Usage { + short: $short, + long: concat!($long, "\n "), + }); + }; +} + +lazy_static! { + static ref USAGES: HashMap<&'static str, Usage> = { + let mut h = HashMap::new(); + doc!(h, "help-short", + "Show short help output.", + "Show short help output. Use --help to show more details."); + doc!(h, "help", + "Show verbose help output.", + "When given, more details about flags are provided."); + doc!(h, "version", + "Prints version information."); + + doc!(h, "pattern", + "A regular expression used for searching.", + "A regular expression used for searching. Multiple patterns \ + may be given. To match a pattern beginning with a -, use [-]."); + doc!(h, "regexp", + "A regular expression used for searching.", + "A regular expression used for searching. Multiple patterns \ + may be given. To match a pattern beginning with a -, use [-]."); + doc!(h, "path", + "A file or directory to search.", + "A file or directory to search. Directories are searched \ + recursively."); + doc!(h, "files", + "Print each file that would be searched.", + "Print each file that would be searched without actually \ + performing the search. This is useful to determine whether a \ + particular file is being searched or not."); + doc!(h, "type-list", + "Show all supported file types.", + "Show all supported file types and their corresponding globs."); + + doc!(h, "text", + "Search binary files as if they were text."); + doc!(h, "count", + "Only show count of matches for each file."); + doc!(h, "color", + "When to use color. [default: auto]", + "When to use color in the output. The possible values are \ + never, auto, always or ansi. The default is auto. When always \ + is used, coloring is attempted based on your environment. When \ + ansi used, coloring is forcefully done using ANSI escape color \ + codes."); + doc!(h, "colors", + "Configure color settings and styles.", + "This flag specifies color settings for use in the output. \ + This flag may be provided multiple times. Settings are applied \ + iteratively. Colors are limited to one of eight choices: \ + red, blue, green, cyan, magenta, yellow, white and black. \ + Styles are limited to nobold, bold, nointense or intense.\n\n\ + The format of the flag is {type}:{attribute}:{value}. {type} \ + should be one of path, line or match. {attribute} can be fg, bg \ + or style. {value} is either a color (for fg and bg) or a text \ + style. A special format, {type}:none, will clear all color \ + settings for {type}.\n\nFor example, the following command will \ + change the match color to magenta and the background color for \ + line numbers to yellow:\n\n\ + rg --colors 'match:fg:magenta' --colors 'line:bg:yellow' foo."); + doc!(h, "fixed-strings", + "Treat the pattern as a literal string.", + "Treat the pattern as a literal string instead of a regular \ + expression. When this flag is used, special regular expression \ + meta characters such as (){}*+. do not need to be escaped."); + doc!(h, "glob", + "Include or exclude files/directories.", + "Include or exclude files/directories for searching that \ + match the given glob. This always overrides any other \ + ignore logic. Multiple glob flags may be used. Globbing \ + rules match .gitignore globs. Precede a glob with a ! \ + to exclude it."); + doc!(h, "ignore-case", + "Case insensitive search.", + "Case insensitive search. This is overridden by \ + --case-sensitive."); + doc!(h, "line-number", + "Show line numbers.", + "Show line numbers (1-based). This is enabled by default when \ + searching in a tty."); + doc!(h, "no-line-number", + "Suppress line numbers.", + "Suppress line numbers. This is enabled by default when NOT \ + searching in a tty."); + doc!(h, "quiet", + "Do not print anything to stdout.", + "Do not print anything to stdout. If a match is found in a file, \ + stop searching. This is useful when ripgrep is used only for \ + its exit code."); + doc!(h, "type", + "Only search files matching TYPE.", + "Only search files matching TYPE. Multiple type flags may be \ + provided. Use the --type-list flag to list all available \ + types."); + doc!(h, "type-not", + "Do not search files matching TYPE.", + "Do not search files matching TYPE. Multiple type-not flags may \ + be provided. Use the --type-list flag to list all available \ + types."); + doc!(h, "unrestricted", + "Reduce the level of \"smart\" searching.", + "Reduce the level of \"smart\" searching. A single -u \ + won't respect .gitignore (etc.) files. Two -u flags will \ + additionally search hidden files and directories. Three \ + -u flags will additionally search binary files. -uu is \ + roughly equivalent to grep -r and -uuu is roughly \ + equivalent to grep -a -r."); + doc!(h, "invert-match", + "Invert matching.", + "Invert matching. Show lines that don't match given patterns."); + doc!(h, "word-regexp", + "Only show matches surrounded by word boundaries.", + "Only show matches surrounded by word boundaries. This is \ + equivalent to putting \\b before and after all of the search \ + patterns."); + + doc!(h, "after-context", + "Show NUM lines after each match."); + doc!(h, "before-context", + "Show NUM lines before each match."); + doc!(h, "context", + "Show NUM lines before and after each match."); + doc!(h, "column", + "Show column numbers", + "Show column numbers (1-based). This only shows the column \ + numbers for the first match on each line. This does not try \ + to account for Unicode. One byte is equal to one column. This \ + implies --line-number."); + doc!(h, "context-separator", + "Set the context separator string. [default: --]", + "The string used to separate non-contiguous context lines in the \ + output. Escape sequences like \\x7F or \\t may be used. The \ + default value is --."); + doc!(h, "debug", + "Show debug messages.", + "Show debug messages. Please use this when filing a bug report."); + doc!(h, "file", + "Search for patterns from the given file.", + "Search for patterns from the given file, with one pattern per \ + line. When this flag is used or multiple times or in \ + combination with the -e/--regexp flag, then all patterns \ + provided are searched. Empty pattern lines will match all input \ + lines, and the newline is not counted as part of the pattern."); + doc!(h, "files-with-matches", + "Only show the path of each file with at least one match."); + doc!(h, "files-without-match", + "Only show the path of each file that contains zero matches."); + doc!(h, "with-filename", + "Show file name for each match.", + "Prefix each match with the file name that contains it. This is \ + the default when more than one file is searched."); + doc!(h, "no-filename", + "Never show the file name for a match.", + "Never show the file name for a match. This is the default when \ + one file is searched."); + doc!(h, "heading", + "Show matches grouped by each file.", + "This shows the file name above clusters of matches from each \ + file instead of showing the file name for every match. This is \ + the default mode at a tty."); + doc!(h, "no-heading", + "Don't group matches by each file.", + "Don't group matches by each file. If -H/--with-filename is \ + enabled, then file names will be shown for every line matched. \ + This is the default mode when not at a tty."); + doc!(h, "hidden", + "Search hidden files and directories.", + "Search hidden files and directories. By default, hidden files \ + and directories are skipped."); + doc!(h, "ignore-file", + "Specify additional ignore files.", + "Specify additional ignore files for filtering file paths. \ + Ignore files should be in the gitignore format and are matched \ + relative to the current working directory. These ignore files \ + have lower precedence than all other ignore files. When \ + specifying multiple ignore files, earlier files have lower \ + precedence than later files."); + doc!(h, "follow", + "Follow symbolic links."); + doc!(h, "max-count", + "Limit the number of matches.", + "Limit the number of matching lines per file searched to NUM."); + doc!(h, "maxdepth", + "Descend at most NUM directories.", + "Limit the depth of directory traversal to NUM levels beyond \ + the paths given. A value of zero only searches the \ + starting-points themselves.\n\nFor example, \ + 'rg --maxdepth 0 dir/' is a no-op because dir/ will not be \ + descended into. 'rg --maxdepth 1 dir/' will search only the \ + direct children of dir/."); + doc!(h, "mmap", + "Searching using memory maps when possible.", + "Search using memory maps when possible. This is enabled by \ + default when ripgrep thinks it will be faster. Note that memory \ + map searching doesn't currently support all options, so if an \ + incompatible option (e.g., --context) is given with --mmap, \ + then memory maps will not be used."); + doc!(h, "no-messages", + "Suppress all error messages.", + "Suppress all error messages. This is equivalent to redirecting \ + stderr to /dev/null."); + doc!(h, "no-mmap", + "Never use memory maps.", + "Never use memory maps, even when they might be faster."); + doc!(h, "no-ignore", + "Don't respect ignore files.", + "Don't respect ignore files (.gitignore, .ignore, etc.). This \ + implies --no-ignore-parent and --no-ignore-vcs."); + doc!(h, "no-ignore-parent", + "Don't respect ignore files in parent directories.", + "Don't respect ignore files (.gitignore, .ignore, etc.) in \ + parent directories."); + doc!(h, "no-ignore-vcs", + "Don't respect VCS ignore files", + "Don't respect version control ignore files (.gitignore, etc.). \ + This implies --no-ignore-parent. Note that .ignore files will \ + continue to be respected."); + doc!(h, "null", + "Print NUL byte after file names", + "Whenever a file name is printed, follow it with a NUL byte. \ + This includes printing file names before matches, and when \ + printing a list of matching files such as with --count, \ + --files-with-matches and --files. This option is useful for use \ + with xargs."); + doc!(h, "path-separator", + "Path separator to use when printing file paths.", + "The path separator to use when printing file paths. This \ + defaults to your platform's path separator, which is / on Unix \ + and \\ on Windows. This flag is intended for overriding the \ + default when the environment demands it (e.g., cygwin). A path \ + separator is limited to a single byte."); + doc!(h, "pretty", + "Alias for --color always --heading -n."); + doc!(h, "replace", + "Replace matches with string given.", + "Replace every match with the string given when printing \ + results. Neither this flag nor any other flag will modify your \ + files.\n\nCapture group indices (e.g., $5) and names \ + (e.g., $foo) are supported in the replacement string.\n\n\ + Note that the replacement by default replaces each match, and \ + NOT the entire line. To replace the entire line, you should \ + match the entire line."); + doc!(h, "case-sensitive", + "Search case sensitively.", + "Search case sensitively. This overrides -i/--ignore-case and \ + -S/--smart-case."); + doc!(h, "smart-case", + "Smart case search.", + "Searches case insensitively if the pattern is all lowercase. \ + Search case sensitively otherwise. This is overridden by \ + either -s/--case-sensitive or -i/--ignore-case."); + doc!(h, "sort-files", + "Sort results by file path. Implies --threads=1.", + "Sort results by file path. Note that this currently \ + disables all parallelism and runs search in a single thread."); + doc!(h, "threads", + "The approximate number of threads to use.", + "The approximate number of threads to use. A value of 0 (which \ + is the default) causes ripgrep to choose the thread count \ + using heuristics."); + doc!(h, "vimgrep", + "Show results in vim compatible format.", + "Show results with every match on its own line, including \ + line numbers and column numbers. With this option, a line with \ + more than one match will be printed more than once."); + + doc!(h, "type-add", + "Add a new glob for a file type.", + "Add a new glob for a particular file type. Only one glob can be \ + added at a time. Multiple --type-add flags can be provided. \ + Unless --type-clear is used, globs are added to any existing \ + globs defined inside of ripgrep.\n\nNote that this MUST be \ + passed to every invocation of ripgrep. Type settings are NOT \ + persisted.\n\nExample: \ + rg --type-add 'foo:*.foo' -tfoo PATTERN.\n\n\ + --type-add can also be used to include rules from other types \ + with the special include directive. The include directive \ + permits specifying one or more other type names (separated by a \ + comma) that have been defined and its rules will automatically \ + be imported into the type specified. For example, to create a \ + type called src that matches C++, Python and Markdown files, one \ + can use:\n\n\ + --type-add 'src:include:cpp,py,md'\n\n\ + Additional glob rules can still be added to the src type by \ + using the --type-add flag again:\n\n\ + --type-add 'src:include:cpp,py,md' --type-add 'src:*.foo'\n\n\ + Note that type names must consist only of Unicode letters or \ + numbers. Punctuation characters are not allowed."); + doc!(h, "type-clear", + "Clear globs for given file type.", + "Clear the file type globs previously defined for TYPE. This \ + only clears the default type definitions that are found inside \ + of ripgrep.\n\nNote that this MUST be passed to every \ + invocation of ripgrep. Type settings are NOT persisted."); + + h + }; +} + +fn validate_number(s: String) -> Result<(), String> { + s.parse::().map(|_| ()).map_err(|err| err.to_string()) +} diff --git a/clap/benches/06_rustup.rs b/clap/benches/06_rustup.rs new file mode 100644 index 0000000..f9ba477 --- /dev/null +++ b/clap/benches/06_rustup.rs @@ -0,0 +1,458 @@ +// Used to simulate a fairly large number of subcommands +// +// CLI used is from rustup 408ed84f0e50511ed44a405dd91365e5da588790 + +#![feature(test)] + +extern crate clap; +extern crate test; + +use clap::{App, AppSettings, Arg, Shell, SubCommand, ArgGroup}; + +use test::Bencher; + +#[bench] +fn build_app(b: &mut Bencher) { b.iter(|| build_cli()); } + +#[bench] +fn parse_clean(b: &mut Bencher) { b.iter(|| build_cli().get_matches_from(vec![""])); } + +#[bench] +fn parse_subcommands(b: &mut Bencher) { + b.iter(|| build_cli().get_matches_from(vec!["rustup override add stable"])); +} + +pub fn build_cli() -> App<'static, 'static> { + App::new("rustup") + .version("0.9.0") // Simulating + .about("The Rust toolchain installer") + .after_help(RUSTUP_HELP) + .setting(AppSettings::VersionlessSubcommands) + .setting(AppSettings::DeriveDisplayOrder) + // .setting(AppSettings::SubcommandRequiredElseHelp) + .arg(Arg::with_name("verbose") + .help("Enable verbose output") + .short("v") + .long("verbose")) + .subcommand(SubCommand::with_name("show") + .about("Show the active and installed toolchains") + .after_help(SHOW_HELP)) + .subcommand(SubCommand::with_name("install") + .about("Update Rust toolchains") + .after_help(TOOLCHAIN_INSTALL_HELP) + .setting(AppSettings::Hidden) // synonym for 'toolchain install' + .arg(Arg::with_name("toolchain") + .required(true))) + .subcommand(SubCommand::with_name("update") + .about("Update Rust toolchains") + .after_help(UPDATE_HELP) + .arg(Arg::with_name("toolchain").required(false)) + .arg(Arg::with_name("no-self-update") + .help("Don't perform self update when running the `rustup` command") + .long("no-self-update") + .takes_value(false) + .hidden(true))) + .subcommand(SubCommand::with_name("default") + .about("Set the default toolchain") + .after_help(DEFAULT_HELP) + .arg(Arg::with_name("toolchain").required(true))) + .subcommand(SubCommand::with_name("toolchain") + .about("Modify or query the installed toolchains") + .after_help(TOOLCHAIN_HELP) + .setting(AppSettings::VersionlessSubcommands) + .setting(AppSettings::DeriveDisplayOrder) + // .setting(AppSettings::SubcommandRequiredElseHelp) + .subcommand(SubCommand::with_name("list").about("List installed toolchains")) + .subcommand(SubCommand::with_name("install") + .about("Install or update a given toolchain") + .arg(Arg::with_name("toolchain").required(true))) + .subcommand(SubCommand::with_name("uninstall") + .about("Uninstall a toolchain") + .arg(Arg::with_name("toolchain").required(true))) + .subcommand(SubCommand::with_name("link") + .about("Create a custom toolchain by symlinking to a directory") + .arg(Arg::with_name("toolchain").required(true)) + .arg(Arg::with_name("path").required(true))) + .subcommand(SubCommand::with_name("update") + .setting(AppSettings::Hidden) // synonym for 'install' + .arg(Arg::with_name("toolchain") + .required(true))) + .subcommand(SubCommand::with_name("add") + .setting(AppSettings::Hidden) // synonym for 'install' + .arg(Arg::with_name("toolchain") + .required(true))) + .subcommand(SubCommand::with_name("remove") + .setting(AppSettings::Hidden) // synonym for 'uninstall' + .arg(Arg::with_name("toolchain") + .required(true)))) + .subcommand(SubCommand::with_name("target") + .about("Modify a toolchain's supported targets") + .setting(AppSettings::VersionlessSubcommands) + .setting(AppSettings::DeriveDisplayOrder) + // .setting(AppSettings::SubcommandRequiredElseHelp) + .subcommand(SubCommand::with_name("list") + .about("List installed and available targets") + .arg(Arg::with_name("toolchain") + .long("toolchain") + .takes_value(true))) + .subcommand(SubCommand::with_name("add") + .about("Add a target to a Rust toolchain") + .arg(Arg::with_name("target").required(true)) + .arg(Arg::with_name("toolchain") + .long("toolchain") + .takes_value(true))) + .subcommand(SubCommand::with_name("remove") + .about("Remove a target from a Rust toolchain") + .arg(Arg::with_name("target").required(true)) + .arg(Arg::with_name("toolchain") + .long("toolchain") + .takes_value(true))) + .subcommand(SubCommand::with_name("install") + .setting(AppSettings::Hidden) // synonym for 'add' + .arg(Arg::with_name("target") + .required(true)) + .arg(Arg::with_name("toolchain") + .long("toolchain") + .takes_value(true))) + .subcommand(SubCommand::with_name("uninstall") + .setting(AppSettings::Hidden) // synonym for 'remove' + .arg(Arg::with_name("target") + .required(true)) + .arg(Arg::with_name("toolchain") + .long("toolchain") + .takes_value(true)))) + .subcommand(SubCommand::with_name("component") + .about("Modify a toolchain's installed components") + .setting(AppSettings::VersionlessSubcommands) + .setting(AppSettings::DeriveDisplayOrder) + // .setting(AppSettings::SubcommandRequiredElseHelp) + .subcommand(SubCommand::with_name("list") + .about("List installed and available components") + .arg(Arg::with_name("toolchain") + .long("toolchain") + .takes_value(true))) + .subcommand(SubCommand::with_name("add") + .about("Add a component to a Rust toolchain") + .arg(Arg::with_name("component").required(true)) + .arg(Arg::with_name("toolchain") + .long("toolchain") + .takes_value(true)) + .arg(Arg::with_name("target") + .long("target") + .takes_value(true))) + .subcommand(SubCommand::with_name("remove") + .about("Remove a component from a Rust toolchain") + .arg(Arg::with_name("component").required(true)) + .arg(Arg::with_name("toolchain") + .long("toolchain") + .takes_value(true)) + .arg(Arg::with_name("target") + .long("target") + .takes_value(true)))) + .subcommand(SubCommand::with_name("override") + .about("Modify directory toolchain overrides") + .after_help(OVERRIDE_HELP) + .setting(AppSettings::VersionlessSubcommands) + .setting(AppSettings::DeriveDisplayOrder) + // .setting(AppSettings::SubcommandRequiredElseHelp) + .subcommand(SubCommand::with_name("list").about("List directory toolchain overrides")) + .subcommand(SubCommand::with_name("set") + .about("Set the override toolchain for a directory") + .arg(Arg::with_name("toolchain").required(true))) + .subcommand(SubCommand::with_name("unset") + .about("Remove the override toolchain for a directory") + .after_help(OVERRIDE_UNSET_HELP) + .arg(Arg::with_name("path") + .long("path") + .takes_value(true) + .help("Path to the directory")) + .arg(Arg::with_name("nonexistent") + .long("nonexistent") + .takes_value(false) + .help("Remove override toolchain for all nonexistent directories"))) + .subcommand(SubCommand::with_name("add") + .setting(AppSettings::Hidden) // synonym for 'set' + .arg(Arg::with_name("toolchain") + .required(true))) + .subcommand(SubCommand::with_name("remove") + .setting(AppSettings::Hidden) // synonym for 'unset' + .about("Remove the override toolchain for a directory") + .arg(Arg::with_name("path") + .long("path") + .takes_value(true)) + .arg(Arg::with_name("nonexistent") + .long("nonexistent") + .takes_value(false) + .help("Remove override toolchain for all nonexistent directories")))) + .subcommand(SubCommand::with_name("run") + .about("Run a command with an environment configured for a given toolchain") + .after_help(RUN_HELP) + .setting(AppSettings::TrailingVarArg) + .arg(Arg::with_name("toolchain").required(true)) + .arg(Arg::with_name("command") + .required(true) + .multiple(true) + .use_delimiter(false))) + .subcommand(SubCommand::with_name("which") + .about("Display which binary will be run for a given command") + .arg(Arg::with_name("command").required(true))) + .subcommand(SubCommand::with_name("doc") + .about("Open the documentation for the current toolchain") + .after_help(DOC_HELP) + .arg(Arg::with_name("book") + .long("book") + .help("The Rust Programming Language book")) + .arg(Arg::with_name("std") + .long("std") + .help("Standard library API documentation")) + .group(ArgGroup::with_name("page").args(&["book", "std"]))) + .subcommand(SubCommand::with_name("man") + .about("View the man page for a given command") + .arg(Arg::with_name("command").required(true)) + .arg(Arg::with_name("toolchain") + .long("toolchain") + .takes_value(true))) + .subcommand(SubCommand::with_name("self") + .about("Modify the rustup installation") + .setting(AppSettings::VersionlessSubcommands) + .setting(AppSettings::DeriveDisplayOrder) + // .setting(AppSettings::SubcommandRequiredElseHelp) + .subcommand(SubCommand::with_name("update") + .about("Download and install updates to rustup")) + .subcommand(SubCommand::with_name("uninstall") + .about("Uninstall rustup.") + .arg(Arg::with_name("no-prompt").short("y"))) + .subcommand(SubCommand::with_name("upgrade-data") + .about("Upgrade the internal data format."))) + .subcommand(SubCommand::with_name("telemetry") + .about("rustup telemetry commands") + .setting(AppSettings::Hidden) + .setting(AppSettings::VersionlessSubcommands) + .setting(AppSettings::DeriveDisplayOrder) + // .setting(AppSettings::SubcommandRequiredElseHelp) + .subcommand(SubCommand::with_name("enable").about("Enable rustup telemetry")) + .subcommand(SubCommand::with_name("disable").about("Disable rustup telemetry")) + .subcommand(SubCommand::with_name("analyze").about("Analyze stored telemetry"))) + .subcommand(SubCommand::with_name("set") + .about("Alter rustup settings") + // .setting(AppSettings::SubcommandRequiredElseHelp) + .subcommand(SubCommand::with_name("default-host") + .about("The triple used to identify toolchains when not specified") + .arg(Arg::with_name("host_triple").required(true)))) + .subcommand(SubCommand::with_name("completions") + .about("Generate completion scripts for your shell") + .after_help(COMPLETIONS_HELP) + .setting(AppSettings::ArgRequiredElseHelp) + .arg(Arg::with_name("shell").possible_values(&Shell::variants()))) +} + +static RUSTUP_HELP: &'static str = r" +rustup installs The Rust Programming Language from the official +release channels, enabling you to easily switch between stable, beta, +and nightly compilers and keep them updated. It makes cross-compiling +simpler with binary builds of the standard library for common platforms. + +If you are new to Rust consider running `rustup doc --book` +to learn Rust."; + +static SHOW_HELP: &'static str = r" +Shows the name of the active toolchain and the version of `rustc`. + +If the active toolchain has installed support for additional +compilation targets, then they are listed as well. + +If there are multiple toolchains installed then all installed +toolchains are listed as well."; + +static UPDATE_HELP: &'static str = r" +With no toolchain specified, the `update` command updates each of the +installed toolchains from the official release channels, then updates +rustup itself. + +If given a toolchain argument then `update` updates that toolchain, +the same as `rustup toolchain install`. + +'toolchain' specifies a toolchain name, such as 'stable', 'nightly', +or '1.8.0'. For more information see `rustup help toolchain`."; + +static TOOLCHAIN_INSTALL_HELP: &'static str = r" +Installs a specific rust toolchain. + +The 'install' command is an alias for 'rustup update '. + +'toolchain' specifies a toolchain name, such as 'stable', 'nightly', +or '1.8.0'. For more information see `rustup help toolchain`."; + +static DEFAULT_HELP: &'static str = r" +Sets the default toolchain to the one specified. If the toolchain is +not already installed then it is installed first."; + +static TOOLCHAIN_HELP: &'static str = r" +Many `rustup` commands deal with *toolchains*, a single installation +of the Rust compiler. `rustup` supports multiple types of +toolchains. The most basic track the official release channels: +'stable', 'beta' and 'nightly'; but `rustup` can also install +toolchains from the official archives, for alternate host platforms, +and from local builds. + +Standard release channel toolchain names have the following form: + + [-][-] + + = stable|beta|nightly| + = YYYY-MM-DD + = + +'channel' is either a named release channel or an explicit version +number, such as '1.8.0'. Channel names can be optionally appended with +an archive date, as in 'nightly-2014-12-18', in which case the +toolchain is downloaded from the archive for that date. + +Finally, the host may be specified as a target triple. This is most +useful for installing a 32-bit compiler on a 64-bit platform, or for +installing the [MSVC-based toolchain] on Windows. For example: + + rustup toolchain install stable-x86_64-pc-windows-msvc + +For convenience, elements of the target triple that are omitted will be +inferred, so the above could be written: + + $ rustup default stable-msvc + +Toolchain names that don't name a channel instead can be used to name +custom toolchains with the `rustup toolchain link` command."; + +static OVERRIDE_HELP: &'static str = r" +Overrides configure rustup to use a specific toolchain when +running in a specific directory. + +Directories can be assigned their own Rust toolchain with +`rustup override`. When a directory has an override then +any time `rustc` or `cargo` is run inside that directory, +or one of its child directories, the override toolchain +will be invoked. + +To pin to a specific nightly: + + rustup override set nightly-2014-12-18 + +Or a specific stable release: + + rustup override set 1.0.0 + +To see the active toolchain use `rustup show`. To remove the override +and use the default toolchain again, `rustup override unset`."; + +static OVERRIDE_UNSET_HELP: &'static str = r" +If `--path` argument is present, removes the override toolchain for +the specified directory. If `--nonexistent` argument is present, removes +the override toolchain for all nonexistent directories. Otherwise, +removes the override toolchain for the current directory."; + +static RUN_HELP: &'static str = r" +Configures an environment to use the given toolchain and then runs +the specified program. The command may be any program, not just +rustc or cargo. This can be used for testing arbitrary toolchains +without setting an override. + +Commands explicitly proxied by `rustup` (such as `rustc` and `cargo`) +also have a shorthand for this available. The toolchain can be set by +using `+toolchain` as the first argument. These are equivalent: + + cargo +nightly build + + rustup run nightly cargo build"; + +static DOC_HELP: &'static str = r" +Opens the documentation for the currently active toolchain with the +default browser. + +By default, it opens the documentation index. Use the various flags to +open specific pieces of documentation."; + +static COMPLETIONS_HELP: &'static str = r" +One can generate a completion script for `rustup` that is compatible with +a given shell. The script is output on `stdout` allowing one to re-direct +the output to the file of their choosing. Where you place the file will +depend on which shell, and which operating system you are using. Your +particular configuration may also determine where these scripts need +to be placed. + +Here are some common set ups for the three supported shells under +Unix and similar operating systems (such as GNU/Linux). + +BASH: + +Completion files are commonly stored in `/usr/share/bash-completion/completions` + +Run the command: + +`rustup completions bash > /usr/share/bash-completion/completions/rustup.bash` + +This installs the completion script. You may have to log out and log +back in to your shell session for the changes to take affect. + +FISH: + +Fish completion files are commonly stored in +`$HOME/.config/fish/completions` + +Run the command: +`rustup completions fish > ~/.config/fish/completions/rustup.fish` + +This installs the completion script. You may have to log out and log +back in to your shell session for the changes to take affect. + +ZSH: + +ZSH completions are commonly stored in any directory listed in your +`$fpath` variable. To use these completions, you must either add the +generated script to one of those directories, or add your own +to this list. + +Adding a custom directory is often the safest best if you're unsure +of which directory to use. First create the directory, for this +example we'll create a hidden directory inside our `$HOME` directory + +`mkdir ~/.zfunc` + +Then add the following lines to your `.zshrc` just before `compinit` + +`fpath+=~/.zfunc` + +Now you can install the completions script using the following command + +`rustup completions zsh > ~/.zfunc/_rustup` + +You must then either log out and log back in, or simply run + +`exec zsh` + +For the new completions to take affect. + +CUSTOM LOCATIONS: + +Alternatively, you could save these files to the place of your choosing, +such as a custom directory inside your $HOME. Doing so will require you +to add the proper directives, such as `source`ing inside your login +script. Consult your shells documentation for how to add such directives. + +POWERSHELL: + +The powershell completion scripts require PowerShell v5.0+ (which comes +Windows 10, but can be downloaded separately for windows 7 or 8.1). + +First, check if a profile has already been set + +`PS C:\> Test-Path $profile` + +If the above command returns `False` run the following + +`PS C:\> New-Item -path $profile -type file --force` + +Now open the file provided by `$profile` (if you used the `New-Item` command +it will be `%USERPROFILE%\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1` + +Next, we either save the completions file into our profile, or into a separate file +and source it inside our profile. To save the completions into our profile simply +use"; diff --git a/clap/clap-perf/clap_perf.dat b/clap/clap-perf/clap_perf.dat new file mode 100644 index 0000000..6a8592c --- /dev/null +++ b/clap/clap-perf/clap_perf.dat @@ -0,0 +1,8 @@ +#Version Date Builder Err Usage Err Parse1 Err Parse2 Err +1.0 2015-07-07 12408 840 16830 229 21235 725 27387 910 +1.1 2015-07-16 11885 191 16670 595 20919 252 26868 457 +1.2 2015-08-14 12563 587 17190 311 22421 233 28232 624 +1.3 2015-09-01 10534 131 14648 874 18213 1070 24101 361 +1.4 2015-09-09 10223 852 13203 749 18924 1216 23492 944 +1.5 2015-11-12 5422 416 7974 680 9723 792 13389 1151 +2.0 2016-01-27 4783 376 5263 481 9651 1015 10577 191 diff --git a/clap/clap-perf/clap_perf.png b/clap/clap-perf/clap_perf.png new file mode 100644 index 0000000..caaefb0 Binary files /dev/null and b/clap/clap-perf/clap_perf.png differ diff --git a/clap/clap-perf/plot_perf.gp b/clap/clap-perf/plot_perf.gp new file mode 100755 index 0000000..4db6a87 --- /dev/null +++ b/clap/clap-perf/plot_perf.gp @@ -0,0 +1,26 @@ +#!/usr/bin/gnuplot +reset +set terminal png +set output "clap_perf.png" + +set xlabel "Version" +set xrange [0.9:2.1] +set ylabel "Time (ns)" +set yrange [0:35000] + +set title "clap-rs Performance (Parse Time - Lower is Better) by Version" +set key inside right top +set grid +set style line 1 lc rgb '#0060ad' lt 1 lw 1 pt 7 ps .5 # --- blue +set style line 2 lc rgb '#dd181f' lt 1 lw 1 pt 5 ps .5 # --- red +set style line 3 lc rgb '#18dd00' lt 1 lw 1 pt 7 ps .5 # --- green +set style line 4 lc rgb '#000000' lt 1 lw 1 pt 5 ps .5 # --- black + +plot "clap_perf.dat" u 1:3:4 notitle w yerrorbars ls 1, \ + "" u 1:3 t "Create Parser Using Builder" w lines ls 1, \ + "" u 1:5:6 notitle w yerrorbars ls 2, \ + "" u 1:5 t "Create Parser Usage String" w lines ls 2, \ + "" u 1:7:8 notitle "Parse Complex Args" w yerrorbars ls 3, \ + "" u 1:7 t "Parse Complex Args" w lines ls 3, \ + "" u 1:9:10 notitle w yerrorbars ls 4, \ + "" u 1:9 t "Parse Very Complex Args" w lines ls 4 diff --git a/clap/clap-test.rs b/clap/clap-test.rs new file mode 100644 index 0000000..7d57ac4 --- /dev/null +++ b/clap/clap-test.rs @@ -0,0 +1,86 @@ +#[allow(unused_imports, dead_code)] +mod test { + use std::str; + use std::io::{Cursor, Write}; + + use regex::Regex; + + use clap::{App, Arg, SubCommand, ArgGroup}; + + fn compare(l: S, r: S2) -> bool + where S: AsRef, + S2: AsRef + { + let re = Regex::new("\x1b[^m]*m").unwrap(); + // Strip out any mismatching \r character on windows that might sneak in on either side + let ls = l.as_ref().trim().replace("\r", ""); + let rs = r.as_ref().trim().replace("\r", ""); + let left = re.replace_all(&*ls, ""); + let right = re.replace_all(&*rs, ""); + let b = left == right; + if !b { + println!(); + println!("--> left"); + println!("{}", left); + println!("--> right"); + println!("{}", right); + println!("--") + } + b + } + + pub fn compare_output(l: App, args: &str, right: &str, stderr: bool) -> bool { + let mut buf = Cursor::new(Vec::with_capacity(50)); + let res = l.get_matches_from_safe(args.split(' ').collect::>()); + let err = res.unwrap_err(); + err.write_to(&mut buf).unwrap(); + let content = buf.into_inner(); + let left = String::from_utf8(content).unwrap(); + assert_eq!(stderr, err.use_stderr()); + compare(left, right) + } + pub fn compare_output2(l: App, args: &str, right1: &str, right2: &str, stderr: bool) -> bool { + let mut buf = Cursor::new(Vec::with_capacity(50)); + let res = l.get_matches_from_safe(args.split(' ').collect::>()); + let err = res.unwrap_err(); + err.write_to(&mut buf).unwrap(); + let content = buf.into_inner(); + let left = String::from_utf8(content).unwrap(); + assert_eq!(stderr, err.use_stderr()); + compare(&*left, right1) || compare(&*left, right2) + } + + // Legacy tests from the Python script days + + pub fn complex_app() -> App<'static, 'static> { + let args = "-o --option=[opt]... 'tests options' + [positional] 'tests positionals'"; + let opt3_vals = ["fast", "slow"]; + let pos3_vals = ["vi", "emacs"]; + App::new("clap-test") + .version("v1.4.8") + .about("tests clap library") + .author("Kevin K. ") + .args_from_usage(args) + .arg(Arg::from_usage("-f --flag... 'tests flags'") + .global(true)) + .args(&[ + Arg::from_usage("[flag2] -F 'tests flags with exclusions'").conflicts_with("flag").requires("long-option-2"), + Arg::from_usage("--long-option-2 [option2] 'tests long options with exclusions'").conflicts_with("option").requires("positional2"), + Arg::from_usage("[positional2] 'tests positionals with exclusions'"), + Arg::from_usage("-O --Option [option3] 'specific vals'").possible_values(&opt3_vals), + Arg::from_usage("[positional3]... 'tests specific values'").possible_values(&pos3_vals), + Arg::from_usage("--multvals [one] [two] 'Tests multiple values, not mult occs'"), + Arg::from_usage("--multvalsmo... [one] [two] 'Tests multiple values, and mult occs'"), + Arg::from_usage("--minvals2 [minvals]... 'Tests 2 min vals'").min_values(2), + Arg::from_usage("--maxvals3 [maxvals]... 'Tests 3 max vals'").max_values(3) + ]) + .subcommand(SubCommand::with_name("subcmd") + .about("tests subcommands") + .version("0.1") + .author("Kevin K. ") + .arg_from_usage("-o --option [scoption]... 'tests options'") + .arg_from_usage("-s --subcmdarg [subcmdarg] 'tests other args'") + .arg_from_usage("[scpositional] 'tests positionals'")) + } +} diff --git a/clap/clap_dep_graph.dot b/clap/clap_dep_graph.dot new file mode 100644 index 0000000..9d7e5c6 --- /dev/null +++ b/clap/clap_dep_graph.dot @@ -0,0 +1,41 @@ +digraph dependencies { + N0[label="clap"]; + N1[label="ansi_term"]; + N2[label="bitflags"]; + N3[label="clippy",color=blue]; + N4[label="strsim"]; + N5[label="term_size"]; + N6[label="unicode-segmentation"]; + N7[label="unicode-width"]; + N8[label="vec_map"]; + N9[label="yaml-rust",color=red]; + N10[label="clippy_lints",color=blue]; + N11[label="matches",color=blue]; + N12[label="quine-mc_cluskey",color=blue]; + N13[label="regex-syntax",color=red]; + N14[label="rustc-serialize",color=blue]; + N15[label="semver",color=blue]; + N16[label="toml",color=blue]; + N17[label="unicode-normalization",color=blue]; + N0 -> N1[label="",style=dashed]; + N0 -> N2[label=""]; + N0 -> N3[label="",style=dashed,color=blue]; + N0 -> N4[label="",style=dashed]; + N0 -> N5[label="",style=dashed]; + N0 -> N6[label=""]; + N0 -> N7[label=""]; + N0 -> N8[label=""]; + N0 -> N9[label="",style=dashed,color=red]; + N3 -> N10[label="",style=dashed,color=blue]; + N5 -> N3[label="",style=dashed,color=blue]; + N9 -> N3[label="",style=dashed,color=blue]; + N9 -> N13[label="",style=dashed,color=red]; + N10 -> N11[label="",style=dashed,color=blue]; + N10 -> N12[label="",style=dashed,color=blue]; + N10 -> N13[label="",style=dashed,color=blue]; + N10 -> N14[label="",style=dashed,color=blue]; + N10 -> N15[label="",style=dashed,color=blue]; + N10 -> N16[label="",style=dashed,color=blue]; + N10 -> N17[label="",style=dashed,color=blue]; + N16 -> N14[label="",style=dashed,color=blue]; +} diff --git a/clap/clap_dep_graph.png b/clap/clap_dep_graph.png new file mode 100644 index 0000000..8290d6f Binary files /dev/null and b/clap/clap_dep_graph.png differ diff --git a/clap/examples/01a_quick_example.rs b/clap/examples/01a_quick_example.rs new file mode 100644 index 0000000..c7fa20f --- /dev/null +++ b/clap/examples/01a_quick_example.rs @@ -0,0 +1,79 @@ +extern crate clap; + +use clap::{App, SubCommand}; + +fn main() { + + // This example shows how to create an application with several arguments using usage strings, which can be + // far less verbose that shown in 01b_QuickExample.rs, but is more readable. The downside is you cannot set + // the more advanced configuration options using this method (well...actually you can, you'll see ;) ) + // + // The example below is functionally identical to the 01b_quick_example.rs and 01c_quick_example.rs + // + // Create an application with 5 possible arguments (2 auto generated) and 2 subcommands (1 auto generated) + // - A config file + // + Uses "-c filename" or "--config filename" + // - An output file + // + A positional argument (i.e. "$ myapp output_filename") + // - A debug flag + // + Uses "-d" or "--debug" + // + Allows multiple occurrences of such as "-dd" (for vary levels of debugging, as an example) + // - A help flag (automatically generated by clap) + // + Uses "-h" or "--help" (Only autogenerated if you do NOT specify your own "-h" or "--help") + // - A version flag (automatically generated by clap) + // + Uses "-V" or "--version" (Only autogenerated if you do NOT specify your own "-V" or "--version") + // - A subcommand "test" (subcommands behave like their own apps, with their own arguments + // + Used by "$ myapp test" with the following arguments + // > A list flag + // = Uses "-l" (usage is "$ myapp test -l" + // > A help flag (automatically generated by clap + // = Uses "-h" or "--help" (full usage "$ myapp test -h" or "$ myapp test --help") + // > A version flag (automatically generated by clap + // = Uses "-V" or "--version" (full usage "$ myapp test -V" or "$ myapp test --version") + // - A subcommand "help" (automatically generated by clap because we specified a subcommand of our own) + // + Used by "$ myapp help" (same functionality as "-h" or "--help") + let matches = App::new("MyApp") + .version("1.0") + .author("Kevin K. ") + .about("Does awesome things") + .args_from_usage("-c, --config=[FILE] 'Sets a custom config file' + 'Sets an optional output file' + -d... 'Turn debugging information on'") + .subcommand(SubCommand::with_name("test") + .about("does testing things") + .arg_from_usage("-l, --list 'lists test values'")) + .get_matches(); + + // You can check the value provided by positional arguments, or option arguments + if let Some(o) = matches.value_of("output") { + println!("Value for output: {}", o); + } + + if let Some(c) = matches.value_of("config") { + println!("Value for config: {}", c); + } + + // You can see how many times a particular flag or argument occurred + // Note, only flags can have multiple occurrences + match matches.occurrences_of("d") { + 0 => println!("Debug mode is off"), + 1 => println!("Debug mode is kind of on"), + 2 => println!("Debug mode is on"), + 3 | _ => println!("Don't be crazy"), + } + + // You can check for the existence of subcommands, and if found use their + // matches just as you would the top level app + if let Some(matches) = matches.subcommand_matches("test") { + // "$ myapp test" was run + if matches.is_present("list") { + // "$ myapp test -l" was run + println!("Printing testing lists..."); + } else { + println!("Not printing testing lists..."); + } + } + + + // Continued program logic goes here... +} diff --git a/clap/examples/01b_quick_example.rs b/clap/examples/01b_quick_example.rs new file mode 100644 index 0000000..7f455a8 --- /dev/null +++ b/clap/examples/01b_quick_example.rs @@ -0,0 +1,93 @@ +extern crate clap; + +use clap::{App, Arg, SubCommand}; + +fn main() { + + // This method shows the traditional, and slightly more configurable way to set up arguments. This method is + // more verbose, but allows setting more configuration options, and even supports easier dynamic generation. + // + // The example below is functionally identical to the 01a_quick_example.rs and 01c_quick_example.rs + // + // *NOTE:* You can actually achieve the best of both worlds by using Arg::from_usage() (instead of Arg::with_name()) + // and *then* setting any additional properties. + // + // Create an application with 5 possible arguments (2 auto generated) and 2 subcommands (1 auto generated) + // - A config file + // + Uses "-c filename" or "--config filename" + // - An output file + // + A positional argument (i.e. "$ myapp output_filename") + // - A debug flag + // + Uses "-d" or "--debug" + // + Allows multiple occurrences of such as "-dd" (for vary levels of debugging, as an example) + // - A help flag (automatically generated by clap) + // + Uses "-h" or "--help" (Only autogenerated if you do NOT specify your own "-h" or "--help") + // - A version flag (automatically generated by clap) + // + Uses "-V" or "--version" (Only autogenerated if you do NOT specify your own "-V" or "--version") + // - A subcommand "test" (subcommands behave like their own apps, with their own arguments + // + Used by "$ myapp test" with the following arguments + // > A list flag + // = Uses "-l" (usage is "$ myapp test -l" + // > A help flag (automatically generated by clap + // = Uses "-h" or "--help" (full usage "$ myapp test -h" or "$ myapp test --help") + // > A version flag (automatically generated by clap + // = Uses "-V" or "--version" (full usage "$ myapp test -V" or "$ myapp test --version") + // - A subcommand "help" (automatically generated by clap because we specified a subcommand of our own) + // + Used by "$ myapp help" (same functionality as "-h" or "--help") + let matches = App::new("MyApp") + .version("1.0") + .author("Kevin K. ") + .about("Does awesome things") + .arg(Arg::with_name("config") + .short("c") + .long("config") + .value_name("FILE") + .help("Sets a custom config file") + .takes_value(true)) + .arg(Arg::with_name("output") + .help("Sets an optional output file") + .index(1)) + .arg(Arg::with_name("debug") + .short("d") + .multiple(true) + .help("Turn debugging information on")) + .subcommand(SubCommand::with_name("test") + .about("does testing things") + .arg(Arg::with_name("list") + .short("l") + .help("lists test values"))) + .get_matches(); + + // You can check the value provided by positional arguments, or option arguments + if let Some(o) = matches.value_of("output") { + println!("Value for output: {}", o); + } + + if let Some(c) = matches.value_of("config") { + println!("Value for config: {}", c); + } + + // You can see how many times a particular flag or argument occurred + // Note, only flags can have multiple occurrences + match matches.occurrences_of("debug") { + 0 => println!("Debug mode is off"), + 1 => println!("Debug mode is kind of on"), + 2 => println!("Debug mode is on"), + 3 | _ => println!("Don't be crazy"), + } + + // You can check for the existence of subcommands, and if found use their + // matches just as you would the top level app + if let Some(matches) = matches.subcommand_matches("test") { + // "$ myapp test" was run + if matches.is_present("list") { + // "$ myapp test -l" was run + println!("Printing testing lists..."); + } else { + println!("Not printing testing lists..."); + } + } + + + // Continued program logic goes here... +} diff --git a/clap/examples/01c_quick_example.rs b/clap/examples/01c_quick_example.rs new file mode 100644 index 0000000..071bdc0 --- /dev/null +++ b/clap/examples/01c_quick_example.rs @@ -0,0 +1,75 @@ +#[macro_use] +extern crate clap; + +fn main() { + // This example shows how to create an application with several arguments using macro builder. + // It combines the simplicity of the from_usage methods and the performance of the Builder Pattern. + // + // The example below is functionally identical to the one in 01a_quick_example.rs and 01b_quick_example.rs + // + // Create an application with 5 possible arguments (2 auto generated) and 2 subcommands (1 auto generated) + // - A config file + // + Uses "-c filename" or "--config filename" + // - An output file + // + A positional argument (i.e. "$ myapp output_filename") + // - A debug flag + // + Uses "-d" or "--debug" + // + Allows multiple occurrences of such as "-dd" (for vary levels of debugging, as an example) + // - A help flag (automatically generated by clap) + // + Uses "-h" or "--help" (Only autogenerated if you do NOT specify your own "-h" or "--help") + // - A version flag (automatically generated by clap) + // + Uses "-V" or "--version" (Only autogenerated if you do NOT specify your own "-V" or "--version") + // - A subcommand "test" (subcommands behave like their own apps, with their own arguments + // + Used by "$ myapp test" with the following arguments + // > A list flag + // = Uses "-l" (usage is "$ myapp test -l" + // > A help flag (automatically generated by clap + // = Uses "-h" or "--help" (full usage "$ myapp test -h" or "$ myapp test --help") + // > A version flag (automatically generated by clap + // = Uses "-V" or "--version" (full usage "$ myapp test -V" or "$ myapp test --version") + // - A subcommand "help" (automatically generated by clap because we specified a subcommand of our own) + // + Used by "$ myapp help" (same functionality as "-h" or "--help") + let matches = clap_app!(myapp => + (version: "1.0") + (author: "Kevin K. ") + (about: "Does awesome things") + (@arg CONFIG: -c --config +takes_value "Sets a custom config file") + (@arg INPUT: +required "Sets the input file to use") + (@arg debug: -d ... "Sets the level of debugging information") + (@subcommand test => + (about: "controls testing features") + (version: "1.3") + (author: "Someone E. ") + (@arg verbose: -v --verbose "Print test information verbosely") + ) + ).get_matches(); + + // Calling .unwrap() is safe here because "INPUT" is required (if "INPUT" wasn't + // required we could have used an 'if let' to conditionally get the value) + println!("Using input file: {}", matches.value_of("INPUT").unwrap()); + + // Gets a value for config if supplied by user, or defaults to "default.conf" + let config = matches.value_of("CONFIG").unwrap_or("default.conf"); + println!("Value for config: {}", config); + + // Vary the output based on how many times the user used the "debug" flag + // (i.e. 'myapp -d -d -d' or 'myapp -ddd' vs 'myapp -d' + match matches.occurrences_of("debug") { + 0 => println!("Debug mode is off"), + 1 => println!("Debug mode is kind of on"), + 2 => println!("Debug mode is on"), + 3 | _ => println!("Don't be crazy"), + } + + // You can information about subcommands by requesting their matches by name + // (as below), requesting just the name used, or both at the same time + if let Some(matches) = matches.subcommand_matches("test") { + if matches.is_present("verbose") { + println!("Printing verbosely..."); + } else { + println!("Printing normally..."); + } + } + + // more program logic goes here... +} diff --git a/clap/examples/02_apps.rs b/clap/examples/02_apps.rs new file mode 100644 index 0000000..8e45640 --- /dev/null +++ b/clap/examples/02_apps.rs @@ -0,0 +1,30 @@ +extern crate clap; + +use clap::App; + +fn main() { + // Apps describe the top level application + // + // You create an App and set various options on that App using the "builder pattern" + // + // The options (version(), author(), about()) aren't mandatory, but recommended. There is + // another option, usage(), which is an exception to the rule. This should only be used when + // the default usage string automatically generated by clap doesn't suffice. + // + // You also set all the valid arguments your App should accept via the arg(), args(), arg_from_usage() + // and args_from_usage() (as well as subcommands via the subcommand() and subcommands() methods) which + // will be covered later. + // + // Once all options have been set, call one of the .get_matches* family of methods in order to + // start the parsing and find all valid command line arguments that supplied by the user at + // runtime. The name given to new() will be displayed when the version or help flags are used. + App::new("MyApp") + .version("1.0") + .author("Kevin K. ") + .about("Does awesome things") + .get_matches(); + + // This example doesn't do much, but it *does* give automatic -h, --help, -V, and --version functionality ;) + + // Continued program logic goes here... +} diff --git a/clap/examples/03_args.rs b/clap/examples/03_args.rs new file mode 100644 index 0000000..c62d576 --- /dev/null +++ b/clap/examples/03_args.rs @@ -0,0 +1,84 @@ +extern crate clap; + +use clap::{App, Arg}; + +fn main() { + // Args describe a possible valid argument which may be supplied by the user at runtime. There + // are three different types of arguments (flags, options, and positional) as well as a fourth + // special type of argument, called SubCommands (which will be discussed separately). + // + // Args are described in the same manner as Apps using the "builder pattern" with multiple + // methods describing various settings for the individual arguments. Or by supplying a "usage" + // string. Both methods have their pros and cons. + // + // Arguments can be added to applications in two manners, one at a time with the arg(), and + // arg_from_usage() method, or multiple arguments at once via a Vec inside the args() method, + // or a single &str describing multiple Args (one per line) supplied to args_from_usage(). + // + // There are various options which can be set for a given argument, some apply to any of the + // three types of arguments, some only apply one or two of the types. *NOTE* if you set + // incompatible options on a single argument, clap will panic! at runtime. This is by design, + // so that you know right away an error was made by the developer, not the end user. + // + // # Help and Version + // clap automatically generates a help and version flag for you, unless you specify your + // own. By default help uses "-h" and "--help", and version uses "-V" and "--version". You can + // safely override "-V" and "-h" to your own arguments, and "--help" and "--version" will still + // be automatically generated for you. + let matches = App::new("MyApp") + // All application settings go here... + + // A simple "Flag" argument example (i.e. "-d") using the builder pattern + .arg(Arg::with_name("debug") + .help("turn on debugging information") + .short("d")) + + // Two arguments, one "Option" argument (i.e. one that takes a value) such + // as "-c some", and one positional argument (i.e. "myapp some_file") + .args(&[ + Arg::with_name("config") + .help("sets the config file to use") + .takes_value(true) + .short("c") + .long("config"), + Arg::with_name("input") + .help("the input file to use") + .index(1) + .required(true) + ]) + + // *Note* the following two examples are convenience methods, if you wish + // to still get the full configurability of Arg::with_name() and the readability + // of arg_from_usage(), you can instantiate a new Arg with Arg::from_usage() and + // still be able to set all the additional properties, just like Arg::with_name() + // + // + // One "Flag" using a usage string + .arg_from_usage("--license 'display the license file'") + + // Two args, one "Positional", and one "Option" using a usage string + .args_from_usage("[output] 'Supply an output file to use' + -i, --int=[IFACE] 'Set an interface to use'") + .get_matches(); + + // Here are some examples of using the arguments defined above. Keep in mind that this is only + // an example, and may be somewhat contrived + // + // First we check if debugging should be on or not + println!("Debugging mode is: {}", if matches.is_present("debug") { "ON" } else { "OFF" }); + + // Next we print the config file we're using, if any was defined with either -c or + // --config + if let Some(config) = matches.value_of("config") { + println!("A config file was passed in: {}", config); + } + + // Let's print the file the user passed in. We can use .unwrap() here becase the arg is + // required, and parsing would have failed if the user forgot it + println!("Using input file: {}", matches.value_of("input").unwrap()); + + // We could continue checking for and using arguments in this manner, such as "license", + // "output", and "interface". Keep in mind that "output" and "interface" are optional, so you + // shouldn't call .unwrap(). Instead, prefer using an 'if let' expression as we did with + // "config" +} diff --git a/clap/examples/04_using_matches.rs b/clap/examples/04_using_matches.rs new file mode 100644 index 0000000..a0a986f --- /dev/null +++ b/clap/examples/04_using_matches.rs @@ -0,0 +1,54 @@ +extern crate clap; + +use clap::{App, Arg}; + +fn main() { + + // Once all App settings (including all arguments) have been set, you call get_matches() which + // parses the string provided by the user, and returns all the valid matches to the ones you + // specified. + // + // You can then query the matches struct to get information about how the user ran the program + // at startup. + // + // For this example, let's assume you created an App which accepts three arguments (plus two + // generated by clap), a flag to display debugging information triggered with "-d" or + // "--debug" as well as an option argument which specifies a custom configuration file to use + // triggered with "-c file" or "--config file" or "--config=file" and finally a positional + // argument which is the input file we want to work with, this will be the only required + // argument. + let matches = App::new("MyApp") + .about("Parses an input file to do awesome things") + .version("1.0") + .author("Kevin K. ") + .arg(Arg::with_name("debug") + .help("turn on debugging information") + .short("d") + .long("debug")) + .arg(Arg::with_name("config") + .help("sets the config file to use") + .short("c") + .long("config")) + .arg(Arg::with_name("input") + .help("the input file to use") + .index(1) + .required(true)) + .get_matches(); + + // We can find out whether or not debugging was turned on + if matches.is_present("debug") { + println!("Debugging is turned on"); + } + + // If we wanted to do some custom initialization based off some configuration file + // provided by the user, we could get the file (A string of the file) + if let Some(file) = matches.value_of("config") { + println!("Using config file: {}", file); + } + + // Because "input" is required we can safely call unwrap() because had the user NOT + // specified a value, clap would have explained the error the user, and exited. + println!("Doing real work with file: {}", matches.value_of("input").unwrap() ); + + // Continued program logic goes here... +} diff --git a/clap/examples/05_flag_args.rs b/clap/examples/05_flag_args.rs new file mode 100644 index 0000000..a6b8945 --- /dev/null +++ b/clap/examples/05_flag_args.rs @@ -0,0 +1,56 @@ +extern crate clap; + +use clap::{App, Arg}; + +fn main() { + + // Of the three argument types, flags are the most simple. Flags are simple switches which can + // be either "on" or "off" + // + // clap also supports multiple occurrences of flags, the common example is "verbosity" where a + // user could want a little information with "-v" or tons of information with "-v -v" or "-vv" + let matches = App::new("MyApp") + // Regular App configuration goes here... + + // We'll add a flag that represents an awesome meter... + // + // I'll explain each possible setting that "flags" accept. Keep in mind + // that you DO NOT need to set each of these for every flag, only the ones + // you want for your individual case. + .arg(Arg::with_name("awesome") + .help("turns up the awesome") // Displayed when showing help info + .short("a") // Trigger this arg with "-a" + .long("awesome") // Trigger this arg with "--awesome" + .multiple(true) // This flag should allow multiple + // occurrences such as "-aaa" or "-a -a" + .requires("config") // Says, "If the user uses -a, they MUST + // also use this other 'config' arg too" + // Can also specify a list using + // requires_all(Vec<&str>) + .conflicts_with("output") // Opposite of requires(), says "if the + // user uses -a, they CANNOT use 'output'" + // also has a conflicts_with_all(Vec<&str>) + ) + // NOTE: In order to compile this example, comment out requires() and + // conflicts_with() because we have not defined an "output" or "config" + // argument. + .get_matches(); + + // We can find out whether or not awesome was used + if matches.is_present("awesome") { + println!("Awesomeness is turned on"); + } + + // If we set the multiple() option of a flag we can check how many times the user specified + // + // Note: if we did not specify the multiple() option, and the user used "awesome" we would get + // a 1 (no matter how many times they actually used it), or a 0 if they didn't use it at all + match matches.occurrences_of("awesome") { + 0 => println!("Nothing is awesome"), + 1 => println!("Some things are awesome"), + 2 => println!("Lots of things are awesome"), + 3 | _ => println!("EVERYTHING is awesome!"), + } + + // Continued program logic goes here... +} diff --git a/clap/examples/06_positional_args.rs b/clap/examples/06_positional_args.rs new file mode 100644 index 0000000..1f29612 --- /dev/null +++ b/clap/examples/06_positional_args.rs @@ -0,0 +1,56 @@ +extern crate clap; + +use clap::{App, Arg}; + +fn main() { + + // Positional arguments are those values after the program name which are not preceded by any + // identifier (such as "myapp some_file"). Positionals support many of the same options as + // flags, as well as a few additional ones. + let matches = App::new("MyApp") + // Regular App configuration goes here... + + // We'll add two positional arguments, a input file, and a config file. + // + // I'll explain each possible setting that "positionals" accept. Keep in + // mind that you DO NOT need to set each of these for every flag, only the + // ones that apply to your individual case. + .arg(Arg::with_name("input") + .help("the input file to use") // Displayed when showing help info + .index(1) // Set the order in which the user must + // specify this argument (Starts at 1) + .requires("config") // Says, "If the user uses "input", they MUST + // also use this other 'config' arg too" + // Can also specify a list using + // requires_all(Vec<&str>) + .conflicts_with("output") // Opposite of requires(), says "if the + // user uses -a, they CANNOT use 'output'" + // also has a conflicts_with_all(Vec<&str>) + .required(true) // By default this argument MUST be present + // NOTE: mutual exclusions take precedence over + // required arguments + ) + .arg(Arg::with_name("config") + .help("the config file to use") + .index(2)) // Note, we do not need to specify required(true) + // if we don't want to, because "input" already + // requires "config" + // Note, we also do not need to specify requires("input") + // because requires lists are automatically two-way + + // NOTE: In order to compile this example, comment out conflicts_with() + // because we have not defined an "output" argument. + .get_matches(); + + // We can find out whether or not "input" or "config" were used + if matches.is_present("input") { + println!("An input file was specified"); + } + + // We can also get the values for those arguments + if let Some(in_file) = matches.value_of("input") { + // It's safe to call unwrap() because of the required options we set above + println!("Doing work with {} and {}", in_file, matches.value_of("config").unwrap()); + } + // Continued program logic goes here... +} diff --git a/clap/examples/07_option_args.rs b/clap/examples/07_option_args.rs new file mode 100644 index 0000000..85ff0e5 --- /dev/null +++ b/clap/examples/07_option_args.rs @@ -0,0 +1,71 @@ +extern crate clap; + +use clap::{App, Arg}; + +fn main() { + + // Option arguments are those that take an additional value, such as "-c value". In clap they + // support three types of specification, those with short() as "-o some", or those with long() + // as "--option value" or "--option=value" + // + // Options also support a multiple setting, which is discussed in the example below. + let matches = App::new("MyApp") + // Regular App configuration goes here... + + // Assume we have an application that accepts an input file via the "-i file" + // or the "--input file" (as well as "--input=file"). + // Below every setting supported by option arguments is discussed. + // NOTE: You DO NOT need to specify each setting, only those which apply + // to your particular case. + .arg(Arg::with_name("input") + .help("the input file to use") // Displayed when showing help info + .takes_value(true) // MUST be set to true in order to be an "option" argument + .short("i") // This argument is triggered with "-i" + .long("input") // This argument is triggered with "--input" + .multiple(true) // Set to true if you wish to allow multiple occurrences + // such as "-i file -i other_file -i third_file" + .required(true) // By default this argument MUST be present + // NOTE: mutual exclusions take precedence over + // required arguments + .requires("config") // Says, "If the user uses "input", they MUST + // also use this other 'config' arg too" + // Can also specify a list using + // requires_all(Vec<&str>) + .conflicts_with("output") // Opposite of requires(), says "if the + // user uses -a, they CANNOT use 'output'" + // also has a conflicts_with_all(Vec<&str>) + ) + // NOTE: In order to compile this example, comment out conflicts_with() + // and requires() because we have not defined an "output" or "config" + // argument. + .get_matches(); + + // We can find out whether or not "input" was used + if matches.is_present("input") { + println!("An input file was specified"); + } + + // We can also get the value for "input" + // + // NOTE: If we specified multiple(), this will only return the _FIRST_ + // occurrence + if let Some(in_file) = matches.value_of("input") { + println!("An input file: {}", in_file); + } + + // If we specified the multiple() setting we can get all the values + if let Some(in_v) = matches.values_of("input") { + for in_file in in_v { + println!("An input file: {}", in_file); + } + } + + // We can see how many times the option was used with the occurrences_of() method + // + // NOTE: Just like with flags, if we did not specify the multiple() setting this will only + // return 1 no matter how many times the argument was used (unless it wasn't used at all, in + // in which case 0 is returned) + println!("The \"input\" argument was used {} times", matches.occurrences_of("input")); + + // Continued program logic goes here... +} diff --git a/clap/examples/08_subcommands.rs b/clap/examples/08_subcommands.rs new file mode 100644 index 0000000..73bd098 --- /dev/null +++ b/clap/examples/08_subcommands.rs @@ -0,0 +1,57 @@ +extern crate clap; + +use clap::{App, Arg, SubCommand}; + +fn main() { + + // SubCommands function exactly like sub-Apps, because that's exactly what they are. Each + // instance of a SubCommand can have it's own version, author(s), Args, and even it's own + // subcommands. + // + // # Help and Version + // Just like Apps, each subcommand will get it's own "help" and "version" flags automatically + // generated. Also, like Apps, you can override "-V" or "-h" safely and still get "--help" and + // "--version" auto generated. + // + // NOTE: If you specify a subcommand for your App, clap will also autogenerate a "help" + // subcommand along with "-h" and "--help" (applies to sub-subcommands as well). + // + // Just like arg() and args(), subcommands can be specified one at a time via subcommand() or + // multiple ones at once with a Vec provided to subcommands(). + let matches = App::new("MyApp") + // Normal App and Arg configuration goes here... + + // In the following example assume we wanted an application which + // supported an "add" subcommand, this "add" subcommand also took + // one positional argument of a file to add: + .subcommand(SubCommand::with_name("add") // The name we call argument with + .about("Adds files to myapp") // The message displayed in "myapp -h" + // or "myapp help" + .version("0.1") // Subcommands can have independent version + .author("Kevin K.") // And authors + .arg(Arg::with_name("input") // And their own arguments + .help("the file to add") + .index(1) + .required(true))) + .get_matches(); + + // You can check if a subcommand was used like normal + if matches.is_present("add") { + println!("'myapp add' was run."); + } + + // You can get the independent subcommand matches (which function exactly like App matches) + if let Some(matches) = matches.subcommand_matches("add") { + // Safe to use unwrap() because of the required() option + println!("Adding file: {}", matches.value_of("input").unwrap()); + } + + // You can also match on a subcommand's name + match matches.subcommand_name() { + Some("add") => println!("'myapp add' was used"), + None => println!("No subcommand was used"), + _ => println!("Some other subcommand was used"), + } + + // Continued program logic goes here... +} diff --git a/clap/examples/09_auto_version.rs b/clap/examples/09_auto_version.rs new file mode 100644 index 0000000..dfd221f --- /dev/null +++ b/clap/examples/09_auto_version.rs @@ -0,0 +1,29 @@ +#[macro_use] +extern crate clap; + +use clap::App; + +fn main() { + // You can have clap pull the application version directly from your Cargo.toml starting with + // clap v0.4.14 on crates.io (or master#a81f915 on github). Using Rust's env! macro like this: + // + // let version = format!("{}.{}.{}{}", + // env!("CARGO_PKG_VERSION_MAJOR"), + // env!("CARGO_PKG_VERSION_MINOR"), + // env!("CARGO_PKG_VERSION_PATCH"), + // option_env!("CARGO_PKG_VERSION_PRE").unwrap_or("")); + // + // Starting from v0.6.6 on crates.io you can also use the crate_version!() macro instead of + // manually using the env!() macros. Under the hood, the macro uses this exact method to get + // the version. + // + // Thanks to https://github.com/jhelwig for pointing this out + App::new("myapp") + .about("does awesome things") + // use crate_version! to pull the version number + .version(crate_version!()) + .get_matches(); + + // running this app with the -V or --version will display whatever version is in your + // Cargo.toml, the default being: myapp 0.0.1 +} diff --git a/clap/examples/10_default_values.rs b/clap/examples/10_default_values.rs new file mode 100644 index 0000000..ca50981 --- /dev/null +++ b/clap/examples/10_default_values.rs @@ -0,0 +1,40 @@ +extern crate clap; + +use clap::{App, Arg}; + +fn main() { + // There are two ways in which to get a default value, one is to use claps Arg::default_value + // method, and the other is to use Rust's built in Option::unwrap_or method. + // + // I'll demo both here. + // + // First, we'll use clap's Arg::default_value with an "INPUT" file. + let matches = App::new("myapp").about("does awesome things") + .arg(Arg::with_name("INPUT") + .help("The input file to use") // Note, we don't need to specify + // anything like, "Defaults to..." + // because clap will automatically + // generate that for us, and place + // it in the help text + .default_value("input.txt") + .index(1)) + + // Next we'll use the Option::unwrap_or method on this "CONFIG" option + .arg(Arg::with_name("CONFIG") + // Note that we have to manually include some verbiage to the user + // telling them what the default will be. + .help("The config file to use (default is \"config.json\")") + .short("c") + .takes_value(true)) + .get_matches(); + + // It's safe to call unwrap because the value with either be what the user input at runtime + // or "input.txt" + let input = matches.value_of("INPUT").unwrap(); + + // Using Option::unwrap_or we get the same affect, but without the added help text injection + let config_file = matches.value_of("CONFIG").unwrap_or("config.json"); + + println!("The input file is: {}", input); + println!("The config file is: {}", config_file); +} diff --git a/clap/examples/11_only_specific_values.rs b/clap/examples/11_only_specific_values.rs new file mode 100644 index 0000000..3445218 --- /dev/null +++ b/clap/examples/11_only_specific_values.rs @@ -0,0 +1,33 @@ +extern crate clap; + +use clap::{App, Arg}; + +fn main() { + // If you have arguments of specific values you want to test for, you can use the + // .possible_values() method of Arg + // + // This allows you specify the valid values for that argument. If the user does not use one of + // those specific values, they will receive a graceful exit with error message informing them + // of the mistake, and what the possible valid values are + // + // For this example, assume you want one positional argument of either "fast" or "slow" + // i.e. the only possible ways to run the program are "myprog fast" or "myprog slow" + let matches = App::new("myapp").about("does awesome things") + .arg(Arg::with_name("MODE") + .help("What mode to run the program in") + .index(1) + .possible_values(&["fast", "slow"]) + .required(true)) + .get_matches(); + + // Note, it's safe to call unwrap() because the arg is required + match matches.value_of("MODE").unwrap() { + "fast" => { + // Do fast things... + }, + "slow" => { + // Do slow things... + }, + _ => unreachable!() + } +} diff --git a/clap/examples/12_typed_values.rs b/clap/examples/12_typed_values.rs new file mode 100644 index 0000000..3d03e4f --- /dev/null +++ b/clap/examples/12_typed_values.rs @@ -0,0 +1,50 @@ +#[macro_use] +extern crate clap; + +use clap::App; + +fn main() { + // You can use some convenience macros provided by clap to get typed values, so long as the + // type you specify implements std::str::FromStr + // + // This works for both single, and multiple values (multiple values returns a Vec) + // + // There are also two ways in which to get types, those where failures cause the program to exit + // with an error and usage string, and those which return a Result or Result,String> + // respectively. Both methods support single and multiple values. + // + // The macro which returns a Result allows you decide what to do upon a failure, exit, provide a + // default value, etc. You have control. But it also means you have to write the code or boiler plate + // to handle those instances. + // + // That is why the second method exists, so you can simply get a T or Vec back, or be sure the + // program will exit gracefully. The catch is, the second method should *only* be used on required + // arguments, because if the argument isn't found, it exits. Just FYI ;) + // + // The following example shows both methods. + // + // **NOTE:** to use the macros, you must include #[macro_use] just above the 'extern crate clap;' + // declaration in your crate root. + let matches = App::new("myapp") + // Create two arguments, a required positional which accepts multiple values + // and an optional '-l value' + .args_from_usage( + "... 'A sequence of whole positive numbers, i.e. 20 25 30' + -l [len] 'A length to use, defaults to 10 when omitted'") + .get_matches(); + + // Here we get a value of type u32 from our optional -l argument. + // If the value provided to len fails to parse, we default to 10 + // + // Using other methods such as unwrap_or_else(|e| println!("{}",e)) + // are possible too. + let len = value_t!(matches, "len", u32).unwrap_or(10); + + println!("len ({}) + 2 = {}", len, len + 2); + + // This code loops through all the values provided to "seq" and adds 2 + // If seq fails to parse, the program exits, you don't have an option + for v in values_t!(matches, "seq", u32).unwrap_or_else(|e| e.exit()) { + println!("Sequence part {} + 2: {}", v, v + 2); + } +} diff --git a/clap/examples/13a_enum_values_automatic.rs b/clap/examples/13a_enum_values_automatic.rs new file mode 100644 index 0000000..1abe5cb --- /dev/null +++ b/clap/examples/13a_enum_values_automatic.rs @@ -0,0 +1,68 @@ +// You can use clap's value_t! macro with a custom enum by implementing the std::str::FromStr +// trait which is very straight forward. There are three ways to do this, for simple enums +// meaning those that don't require 'pub' or any '#[derive()]' directives you can use clap's +// simple_enum! macro. For those that require 'pub' or any '#[derive()]'s you can use clap's +// arg_enum! macro. The third way is to implement std::str::FromStr manually. +// +// In most circumstances using either simple_enum! or arg_enum! is fine. +// +// In the following example we will create two enums using macros, assign a positional argument +// that accepts only one of those values, and use clap to parse the argument. + +// Add clap like normal +#[macro_use] +extern crate clap; + +use clap::{App, Arg}; + +// Using arg_enum! is more like traditional enum declarations +// +// **NOTE:** Only bare variants are supported +arg_enum!{ + #[derive(Debug)] + pub enum Oof { + Rab, + Zab, + Xuq + } +} + +arg_enum!{ + #[derive(Debug)] + enum Foo { + Bar, + Baz, + Qux + } +} + +fn main() { + // Create the application like normal + let enum_vals = ["fast", "slow"]; + let m = App::new("myapp") + // Use a single positional argument that is required + .arg(Arg::from_usage(" 'The Foo to use'") + .possible_values(&Foo::variants())) + .arg(Arg::from_usage(" 'The speed to use'") + // You can define a list of possible values if you want the values to be + // displayed in the help information. Whether you use possible_values() or + // not, the valid values will ALWAYS be displayed on a failed parse. + .possible_values(&enum_vals)) + // For the second positional, lets not use possible_values() just to show the difference + .arg_from_usage(" 'The Oof to use'") + .get_matches(); + + let t = value_t!(m.value_of("foo"), Foo).unwrap_or_else(|e| e.exit()); + let t2 = value_t!(m.value_of("oof"), Oof).unwrap_or_else(|e| e.exit()); + + + // Now we can use our enum like normal. + match t { + Foo::Bar => println!("Found a Bar"), + Foo::Baz => println!("Found a Baz"), + Foo::Qux => println!("Found a Qux") + } + + // Since our Oof derives Debug, we can do this: + println!("Oof: {:?}", t2); +} diff --git a/clap/examples/13b_enum_values_manual.rs b/clap/examples/13b_enum_values_manual.rs new file mode 100644 index 0000000..81ffe5e --- /dev/null +++ b/clap/examples/13b_enum_values_manual.rs @@ -0,0 +1,54 @@ +// In the following example we will create an enum with 4 values, assign a positional argument +// that accepts only one of those values, and use clap to parse the argument. +// +// Start with bringing the trait into scope. +use std::str::FromStr; + +// Add clap like normal +#[macro_use] +extern crate clap; + +use clap::{App, Arg}; + +// Define your enum +enum Vals { + Foo, + Bar, + Baz, + Qux +} + +// Implement the trait +impl FromStr for Vals { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + match s { + "Foo" => Ok(Vals::Foo), + "Bar" => Ok(Vals::Bar), + "Baz" => Ok(Vals::Baz), + "Qux" => Ok(Vals::Qux), + _ => Err("no match") + } + } +} + +fn main() { + // Create the application like normal + let m = App::new("myapp") + // Use a single positional argument that is required + .arg(Arg::from_usage(" 'The type to use'") + // Define the list of possible values + .possible_values(&["Foo", "Bar", "Baz", "Qux"])) + .get_matches(); + + let t = value_t!(m, "type", Vals).unwrap_or_else(|e| e.exit()); + + // Now we can use our enum like normal. + match t { + Vals::Foo => println!("Found a Foo"), + Vals::Bar => println!("Found a Bar"), + Vals::Baz => println!("Found a Baz"), + Vals::Qux => println!("Found a Qux") + } +} diff --git a/clap/examples/14_groups.rs b/clap/examples/14_groups.rs new file mode 100644 index 0000000..e160464 --- /dev/null +++ b/clap/examples/14_groups.rs @@ -0,0 +1,87 @@ +/// `ArgGroup`s are a family of related arguments and way for you to say, "Any of these arguments". +/// By placing arguments in a logical group, you can make easier requirement and exclusion rules +/// instead of having to list each individually, or when you want a rule to apply "any but not all" +/// arguments. +/// +/// For instance, you can make an entire `ArgGroup` required, this means that one (and *only* one) +/// argument from that group must be present. Using more than one argument from an `ArgGroup` causes +/// a failure (graceful exit). +/// +/// You can also do things such as name an `ArgGroup` as a confliction or requirement, meaning any +/// of the arguments that belong to that group will cause a failure if present, or must present +/// respectively. +/// +/// Perhaps the most common use of `ArgGroup`s is to require one and *only* one argument to be +/// present out of a given set. Imagine that you had multiple arguments, and you want one of them to +/// be required, but making all of them required isn't feasible because perhaps they conflict with +/// each other. For example, lets say that you were building an application where one could set a +/// given version number by supplying a string with an option argument, i.e. `--set-ver v1.2.3`, you +/// also wanted to support automatically using a previous version number and simply incrementing one +/// of the three numbers. So you create three flags `--major`, `--minor`, and `--patch`. All of +/// these arguments shouldn't be used at one time but you want to specify that *at least one* of +/// them is used. For this, you can create a group. + +extern crate clap; + +use clap::{App, Arg, ArgGroup}; + +fn main() { + // Create application like normal + let matches = App::new("myapp") + // Add the version arguments + .args_from_usage("--set-ver [ver] 'set version manually' + --major 'auto inc major' + --minor 'auto inc minor' + --patch 'auto inc patch'") + // Create a group, make it required, and add the above arguments + .group(ArgGroup::with_name("vers") + .required(true) + .args(&["ver", "major", "minor", "patch"])) + // Arguments can also be added to a group individually, these two arguments + // are part of the "input" group which is not required + .arg(Arg::from_usage("[INPUT_FILE] 'some regular input'") + .group("input")) + .arg(Arg::from_usage("--spec-in [SPEC_IN] 'some special input argument'") + .group("input")) + // Now let's assume we have a -c [config] argument which requires one of + // (but **not** both) the "input" arguments + .arg(Arg::with_name("config") + .short("c") + .takes_value(true) + .requires("input")) + .get_matches(); + + // Let's assume the old version 1.2.3 + let mut major = 1; + let mut minor = 2; + let mut patch = 3; + + // See if --set-ver was used to set the version manually + let version = if let Some(ver) = matches.value_of("ver") { + format!("{}", ver) + } else { + // Increment the one requested (in a real program, we'd reset the lower numbers) + let (maj, min, pat) = (matches.is_present("major"), + matches.is_present("minor"), + matches.is_present("patch")); + match (maj, min, pat) { + (true, _, _) => major += 1, + (_, true, _) => minor += 1, + (_, _, true) => patch += 1, + _ => unreachable!(), + }; + format!("{}.{}.{}", major, minor, patch) + }; + + println!("Version: {}", version); + + // Check for usage of -c + if matches.is_present("config") { + let input = matches.value_of("INPUT_FILE").unwrap_or(matches.value_of("SPEC_IN").unwrap()); + println!("Doing work using input {} and config {}", + input, + matches.value_of("config").unwrap()); + } + + +} diff --git a/clap/examples/15_custom_validator.rs b/clap/examples/15_custom_validator.rs new file mode 100644 index 0000000..a5c0d42 --- /dev/null +++ b/clap/examples/15_custom_validator.rs @@ -0,0 +1,37 @@ +extern crate clap; + +use clap::{App, Arg}; + +fn main() { + // You can define a function (or a closure) to use as a validator to argument values. The + // function must accept a String and return Result<(), String> where Err(String) is the message + // displayed to the user. + + let matches = App::new("myapp") + // Application logic goes here... + .arg(Arg::with_name("input") + .help("the input file to use") + .index(1) + .required(true) + // You can pass in a closure, or a function + .validator(is_png)) + .get_matches(); + + // Here we can call .unwrap() because the argument is required. + println!("The .PNG file is: {}", matches.value_of("input").unwrap()); +} + +fn is_png(val: String) -> Result<(), String> { + // val is the argument value passed in by the user + // val has type of String. + if val.ends_with(".png") { + Ok(()) + } else { + // clap automatically adds "error: " to the beginning + // of the message. + Err(String::from("the file format must be png.")) + } + // Of course, you can do more complicated validation as + // well, but for the simplicity, this example only checks + // if the value passed in ends with ".png" or not. +} diff --git a/clap/examples/16_app_settings.rs b/clap/examples/16_app_settings.rs new file mode 100644 index 0000000..ab1d185 --- /dev/null +++ b/clap/examples/16_app_settings.rs @@ -0,0 +1,41 @@ +extern crate clap; + +use clap::{App, AppSettings, SubCommand}; + +fn main() { + // You can use AppSettings to change the application level behavior of clap. .setting() function + // of App struct takes AppSettings enum as argument. There is also .settings() function which + // takes slice of AppSettings enum. You can learn more about AppSettings in the documentation, + // which also has examples on each setting. + // + // This example will only show usage of one AppSettings setting. See documentation for more + // information. + + let matches = App::new("myapp") + .setting(AppSettings::SubcommandsNegateReqs) + // Negates requirement of parent command. + + .arg_from_usage(" 'input file to use'") + // Required positional argument called input. This + // will be only required if subcommand is not present. + + .subcommand(SubCommand::with_name("test") + .about("does some testing")) + // if program is invoked with subcommand, you do not + // need to specify the argument anymore due to + // the AppSettings::SubcommandsNegateReqs setting. + + .get_matches(); + + // Calling unwrap() on "input" would not be advised here, because although it's required, + // if the user uses a subcommand, those requirements are no longer required. Hence, we should + // use some sort of 'if let' construct + if let Some(inp) = matches.value_of("input") { + println!("The input file is: {}", inp); + } + + match matches.subcommand() { + ("test", _) => println!("The 'test' subcommand was used"), + _ => unreachable!() + } +} diff --git a/clap/examples/17_yaml.rs b/clap/examples/17_yaml.rs new file mode 100644 index 0000000..3353d73 --- /dev/null +++ b/clap/examples/17_yaml.rs @@ -0,0 +1,53 @@ +// In order to use YAML to define your CLI you must compile clap with the "yaml" feature because +// it's **not** included by default. +// +// In order to do this, ensure your Cargo.toml looks like one of the following: +// +// [dependencies.clap] +// features = ["yaml"] +// +// __OR__ +// +// [dependencies] +// clap = { features = ["yaml"] } + + +// Using yaml requires calling a clap macro `load_yaml!()` so we must use the '#[macro_use]' +// directive +#[macro_use] +extern crate clap; + +#[cfg(feature = "yaml")] +fn main() { + use clap::App; + + // To load a yaml file containing our CLI definition such as the example '17_yaml.yml' we can + // use the convenience macro which loads the file at compile relative to the current file + // similar to how modules are found. + // + // Then we pass that yaml object to App to build the CLI. + // + // Finally we call get_matches() to start the parsing process. We use the matches just as we + // normally would + let yml = load_yaml!("17_yaml.yml"); + let m = App::from_yaml(yml).get_matches(); + + // Because the example 17_yaml.yml is rather large we'll just look a single arg so you can + // see that it works... + if let Some(mode) = m.value_of("mode") { + match mode { + "vi" => println!("You are using vi"), + "emacs" => println!("You are using emacs..."), + _ => unreachable!() + } + } else { + println!("--mode wasn't used..."); + } +} + +#[cfg(not(feature = "yaml"))] +fn main() { + // As stated above, if clap is not compiled with the YAML feature, it is disabled. + println!("YAML feature is disabled."); + println!("Pass --features yaml to cargo when trying this example."); +} diff --git a/clap/examples/17_yaml.yml b/clap/examples/17_yaml.yml new file mode 100644 index 0000000..b0d58b3 --- /dev/null +++ b/clap/examples/17_yaml.yml @@ -0,0 +1,97 @@ +name: yml_app +version: "1.0" +about: an example using a .yml file to build a CLI +author: Kevin K. + +# AppSettings can be defined as a list and are **not** ascii case sensitive +settings: + - ArgRequiredElseHelp + +# All Args must be defined in the 'args:' list where the name of the arg, is the +# key to a Hash object +args: + # The name of this argument, is 'opt' which will be used to access the value + # later in your Rust code + - opt: + help: example option argument from yaml + short: o + long: option + multiple: true + takes_value: true + - pos: + help: example positional argument from yaml + index: 1 + # A list of possible values can be defined as a list + possible_values: + - fast + - slow + - flag: + help: demo flag argument + short: F + multiple: true + global: true + # Conflicts, mutual overrides, and requirements can all be defined as a + # list, where the key is the name of the other argument + conflicts_with: + - opt + requires: + - pos + - mode: + long: mode + help: shows an option with specific values + # possible_values can also be defined in this list format + possible_values: [ vi, emacs ] + takes_value: true + - mvals: + long: mult-vals + help: demos an option which has two named values + # value names can be described in a list, where the help will be shown + # --mult-vals + value_names: + - one + - two + - minvals: + long: min-vals + multiple: true + help: you must supply at least two values to satisfy me + min_values: 2 + - maxvals: + long: max-vals + multiple: true + help: you can only supply a max of 3 values for me! + max_values: 3 + +# All subcommands must be listed in the 'subcommand:' object, where the key to +# the list is the name of the subcommand, and all settings for that command are +# are part of a Hash object +subcommands: + # The name of this subcommand will be 'subcmd' which can be accessed in your + # Rust code later + - subcmd: + about: demos subcommands from yaml + version: "0.1" + author: Kevin K. + # Subcommand args are exactly like App args + args: + - scopt: + short: B + multiple: true + help: example subcommand option + takes_value: true + - scpos1: + help: example subcommand positional + index: 1 + +# ArgGroups are supported as well, and must be sepcified in the 'groups:' +# object of this file +groups: + # the name of the ArgGoup is specified here + - min-max-vals: + # All args and groups that are a part of this group are set here + args: + - minvals + - maxvals + # setting conflicts is done the same manner as setting 'args:' + # + # to make this group required, you could set 'required: true' but for + # this example we won't do that. diff --git a/clap/examples/18_builder_macro.rs b/clap/examples/18_builder_macro.rs new file mode 100644 index 0000000..6bdce47 --- /dev/null +++ b/clap/examples/18_builder_macro.rs @@ -0,0 +1,84 @@ +#[macro_use] +extern crate clap; + +// Note, there isn't a need for "use clap::{ ... };" Because the clap_app! macro uses +// $crate:: internally + +fn main() { + + // Validation example testing that a file exists + let file_exists = |path| { + if std::fs::metadata(path).is_ok() { + Ok(()) + } else { + Err(String::from("File doesn't exist")) + } + }; + + // External module may contain this subcommand. If this exists in another module, a function is + // required to access it. Recommend `fn clap() -> Clap::SubCommand`. + let external_sub_command = clap_app!( @subcommand foo => + (@arg bar: -b "Bar") + ); + + let matches = clap_app!(MyApp => + (@setting SubcommandRequiredElseHelp) + (version: "1.0") + (author: "Alice") + (about: "Does awesome things") + (@arg config: -c --config #{1, 2} {file_exists} "Sets a custom config file") + (@arg proxyHostname: --("proxy-hostname") +takes_value "Sets the hostname of the proxy to use") + (@arg input: * "Input file") + (@group test => + (@attributes +required) + (@arg output: "Sets an optional output file") + (@arg debug: -d ... "Turn debugging information on") + ) + (subcommand: external_sub_command) + (@subcommand test => + (about: "does testing things") + (version: "2.5") + (@arg list: -l "Lists test values") + (@arg test_req: -r requires[list] "Tests requirement for listing") + (@arg aaaa: --aaaa +takes_value { + |a| if a.contains('a') { + Ok(()) + } else { + Err(String::from("string does not contain at least one a")) + } + } "Test if the argument contains an a") + ) + ).get_matches(); + + // You can check the value provided by positional arguments, or option arguments + if let Some(o) = matches.value_of("output") { + println!("Value for output: {}", o); + } + + if let Some(c) = matches.value_of("config") { + println!("Value for config: {}", c); + } + + // You can see how many times a particular flag or argument occurred + // Note, only flags can have multiple occurrences + match matches.occurrences_of("debug") { + 0 => println!("Debug mode is off"), + 1 => println!("Debug mode is kind of on"), + 2 => println!("Debug mode is on"), + 3 | _ => println!("Don't be crazy"), + } + + // You can check for the existence of subcommands, and if found use their + // matches just as you would the top level app + if let Some(matches) = matches.subcommand_matches("test") { + // "$ myapp test" was run + if matches.is_present("list") { + // "$ myapp test -l" was run + println!("Printing testing lists..."); + } else { + println!("Not printing testing lists..."); + } + } + + // Continued program logic goes here... +} diff --git a/clap/examples/19_auto_authors.rs b/clap/examples/19_auto_authors.rs new file mode 100644 index 0000000..afbb985 --- /dev/null +++ b/clap/examples/19_auto_authors.rs @@ -0,0 +1,15 @@ +#[macro_use] +extern crate clap; + +use clap::App; + +fn main() { + App::new("myapp") + .about("does awesome things") + // use crate_authors! to pull the author(s) names from the Cargo.toml + .author(crate_authors!()) + .get_matches(); + + // running this app with -h will display whatever author(s) are in your + // Cargo.toml +} diff --git a/clap/examples/20_subcommands.rs b/clap/examples/20_subcommands.rs new file mode 100644 index 0000000..f80f46d --- /dev/null +++ b/clap/examples/20_subcommands.rs @@ -0,0 +1,143 @@ +// Working with subcommands is simple. There are a few key points to remember when working with +// subcommands in clap. First, SubCommands are really just Apps. This means they can have their own +// settings, version, authors, args, and even their own subcommands. The next thing to remember is +// that subcommands are set up in a tree like hierarchy. +// +// An ASCII art depiction may help explain this better. Using a fictional version of git as the demo +// subject. Imagine the following are all subcommands of git (note, the author is aware these aren't +// actually all subcommands in the real git interface, but it makes explanation easier) +// +// Top Level App (git) TOP +// | +// ----------------------------------------- +// / | \ \ +// clone push add commit LEVEL 1 +// | / \ / \ | +// url origin remote ref name message LEVEL 2 +// / /\ +// path remote local LEVEL 3 +// +// Given the above fictional subcommand hierarchy, valid runtime uses would be (not an all inclusive +// list): +// +// $ git clone url +// $ git push origin path +// $ git add ref local +// $ git commit message +// +// Notice only one command per "level" may be used. You could not, for example, do: +// +// $ git clone url push origin path +// +// It's also important to know that subcommands each have their own set of matches and may have args +// with the same name as other subcommands in a different part of the tree hierarchy (i.e. the arg +// names aren't in a flat namespace). +// +// In order to use subcommands in clap, you only need to know which subcommand you're at in your +// tree, and which args are defined on that subcommand. +// +// Let's make a quick program to illustrate. We'll be using the same example as above but for +// brevity sake we won't implement all of the subcommands, only a few. + +extern crate clap; + +use clap::{App, Arg, SubCommand, AppSettings}; + +fn main() { + + let matches = App::new("git") + .about("A fictional versioning CLI") + .version("1.0") + .author("Me") + .subcommand(SubCommand::with_name("clone") + .about("clones repos") + .arg(Arg::with_name("repo") + .help("The repo to clone") + .required(true))) + .subcommand(SubCommand::with_name("push") + .about("pushes things") + .setting(AppSettings::SubcommandRequiredElseHelp) + .subcommand(SubCommand::with_name("remote") // Subcommands can have their own subcommands, + // which in turn have their own subcommands + .about("pushes remote things") + .arg(Arg::with_name("repo") + .required(true) + .help("The remote repo to push things to"))) + .subcommand(SubCommand::with_name("local") + .about("pushes local things"))) + .subcommand(SubCommand::with_name("add") + .about("adds things") + .author("Someone Else") // Subcommands can list different authors + .version("v2.0 (I'm versioned differently") // or different version from their parents + .setting(AppSettings::ArgRequiredElseHelp) // They can even have different settings + .arg(Arg::with_name("stuff") + .long("stuff") + .help("Stuff to add") + .takes_value(true) + .multiple(true))) + .get_matches(); + + // At this point, the matches we have point to git. Keep this in mind... + + // You can check if one of git's subcommands was used + if matches.is_present("clone") { + println!("'git clone' was run."); + } + + // You can see which subcommand was used + if let Some(subcommand) = matches.subcommand_name() { + println!("'git {}' was used", subcommand); + + // It's important to note, this *only* check's git's DIRECT children, **NOT** it's + // grandchildren, great grandchildren, etc. + // + // i.e. if the command `git push remove --stuff foo` was run, the above will only print out, + // `git push` was used. We'd need to get push's matches to see further into the tree + } + + // An alternative to checking the name is matching on known names. Again notice that only the + // direct children are matched here. + match matches.subcommand_name() { + Some("clone") => println!("'git clone' was used"), + Some("push") => println!("'git push' was used"), + Some("add") => println!("'git add' was used"), + None => println!("No subcommand was used"), + _ => unreachable!(), // Assuming you've listed all direct children above, this is unreachable + } + + // You could get the independent subcommand matches, although this is less common + if let Some(clone_matches) = matches.subcommand_matches("clone") { + // Now we have a reference to clone's matches + println!("Cloning repo: {}", clone_matches.value_of("repo").unwrap()); + } + + // The most common way to handle subcommands is via a combined approach using + // `ArgMatches::subcommand` which returns a tuple of both the name and matches + match matches.subcommand() { + ("clone", Some(clone_matches)) =>{ + // Now we have a reference to clone's matches + println!("Cloning {}", clone_matches.value_of("repo").unwrap()); + }, + ("push", Some(push_matches)) =>{ + // Now we have a reference to push's matches + match push_matches.subcommand() { + ("remote", Some(remote_matches)) =>{ + // Now we have a reference to remote's matches + println!("Pushing to {}", remote_matches.value_of("repo").unwrap()); + }, + ("local", Some(_)) =>{ + println!("'git push local' was used"); + }, + _ => unreachable!(), + } + }, + ("add", Some(add_matches)) =>{ + // Now we have a reference to add's matches + println!("Adding {}", add_matches.values_of("stuff").unwrap().collect::>().join(", ")); + }, + ("", None) => println!("No subcommand was used"), // If no subcommand was used it'll match the tuple ("", None) + _ => unreachable!(), // If all subcommands are defined above, anything else is unreachable!() + } + + // Continued program logic goes here... +} diff --git a/clap/examples/21_aliases.rs b/clap/examples/21_aliases.rs new file mode 100644 index 0000000..3be0445 --- /dev/null +++ b/clap/examples/21_aliases.rs @@ -0,0 +1,39 @@ +extern crate clap; + +use clap::{App, Arg, SubCommand}; + +fn main() { + + let matches = App::new("MyApp") + .subcommand(SubCommand::with_name("ls") + .aliases(&["list", "dir"]) + .about("Adds files to myapp") + .version("0.1") + .author("Kevin K.") + .arg(Arg::with_name("input") + .help("the file to add") + .index(1) + .required(true)) + ) + .get_matches(); + + // You can check if a subcommand was used like normal + if matches.is_present("add") { + println!("'myapp add' was run."); + } + + // You can get the independent subcommand matches (which function exactly like App matches) + if let Some(matches) = matches.subcommand_matches("add") { + // Safe to use unwrap() because of the required() option + println!("Adding file: {}", matches.value_of("input").unwrap()); + } + + // You can also match on a subcommand's name + match matches.subcommand_name() { + Some("add") => println!("'myapp add' was used"), + None => println!("No subcommand was used"), + _ => println!("Some other subcommand was used"), + } + + // Continued program logic goes here... +} diff --git a/clap/examples/22_stop_parsing_with_--.rs b/clap/examples/22_stop_parsing_with_--.rs new file mode 100644 index 0000000..a5ba5b3 --- /dev/null +++ b/clap/examples/22_stop_parsing_with_--.rs @@ -0,0 +1,25 @@ +extern crate clap; + +use clap::{App, Arg}; + +/// myprog -f -p=bob -- sloppy slop slop +fn main() { + + let matches = App::new("myprog") + .arg(Arg::with_name("eff") + .short("f")) + .arg(Arg::with_name("pea") + .short("p") + .takes_value(true)) + .arg(Arg::with_name("slop") + .multiple(true) + .last(true)) + .get_matches(); + + + println!("-f used: {:?}", matches.is_present("eff")); + println!("-p's value: {:?}", matches.value_of("pea")); + println!("'slops' values: {:?}", matches.values_of("slop").map(|vals| vals.collect::>())); + + // Continued program logic goes here... +} diff --git a/clap/justfile b/clap/justfile new file mode 100644 index 0000000..0768764 --- /dev/null +++ b/clap/justfile @@ -0,0 +1,39 @@ +@update-contributors: + echo 'Removing old CONTRIBUTORS.md' + mv CONTRIBUTORS.md CONTRIBUTORS.md.bak + echo 'Downloading a list of new contributors' + echo "the following is a list of contributors:" > CONTRIBUTORS.md + echo "" >> CONTRIBUTORS.md + echo "" >> CONTRIBUTORS.md + githubcontrib --owner clap-rs --repo clap --sha master --cols 6 --format md --showlogin true --sortBy contributions --sortOrder desc >> CONTRIBUTORS.md + echo "" >> CONTRIBUTORS.md + echo "" >> CONTRIBUTORS.md + echo "This list was generated by [mgechev/github-contributors-list](https://github.com/mgechev/github-contributors-list)" >> CONTRIBUTORS.md + rm CONTRIBUTORS.md.bak + +run-test TEST: + cargo test --test {{TEST}} + +debug TEST: + cargo test --test {{TEST}} --features debug + +run-tests: + cargo test --features "yaml unstable" + +@bench: nightly + cargo bench && just remove-nightly + +nightly: + rustup override add nightly + +remove-nightly: + rustup override remove + +@lint: nightly + cargo build --features lints && just remove-nightly + +clean: + cargo clean + find . -type f -name "*.orig" -exec rm {} \; + find . -type f -name "*.bk" -exec rm {} \; + find . -type f -name ".*~" -exec rm {} \; diff --git a/clap/rustfmt.toml b/clap/rustfmt.toml new file mode 100644 index 0000000..0136d86 --- /dev/null +++ b/clap/rustfmt.toml @@ -0,0 +1,4 @@ +format_strings = false +chain_overflow_last = false +same_line_if_else = true +fn_single_line = true diff --git a/clap/src/app/help.rs b/clap/src/app/help.rs new file mode 100644 index 0000000..34f97ac --- /dev/null +++ b/clap/src/app/help.rs @@ -0,0 +1,1028 @@ +// Std +use std::borrow::Cow; +use std::cmp; +use std::collections::BTreeMap; +use std::fmt::Display; +use std::io::{self, Cursor, Read, Write}; +use std::usize; + +// Internal +use app::parser::Parser; +use app::usage; +use app::{App, AppSettings}; +use args::{AnyArg, ArgSettings, DispOrder}; +use errors::{Error, Result as ClapResult}; +use fmt::{Colorizer, ColorizerOption, Format}; +use map::VecMap; +use INTERNAL_ERROR_MSG; + +// Third Party +#[cfg(feature = "wrap_help")] +use term_size; +use textwrap; +use unicode_width::UnicodeWidthStr; + +#[cfg(not(feature = "wrap_help"))] +mod term_size { + pub fn dimensions() -> Option<(usize, usize)> { + None + } +} + +fn str_width(s: &str) -> usize { + UnicodeWidthStr::width(s) +} + +const TAB: &'static str = " "; + +// These are just convenient traits to make the code easier to read. +trait ArgWithDisplay<'b, 'c>: AnyArg<'b, 'c> + Display {} +impl<'b, 'c, T> ArgWithDisplay<'b, 'c> for T +where + T: AnyArg<'b, 'c> + Display, +{ +} + +trait ArgWithOrder<'b, 'c>: ArgWithDisplay<'b, 'c> + DispOrder { + fn as_base(&self) -> &ArgWithDisplay<'b, 'c>; +} +impl<'b, 'c, T> ArgWithOrder<'b, 'c> for T +where + T: ArgWithDisplay<'b, 'c> + DispOrder, +{ + fn as_base(&self) -> &ArgWithDisplay<'b, 'c> { + self + } +} + +fn as_arg_trait<'a, 'b, T: ArgWithOrder<'a, 'b>>(x: &T) -> &ArgWithOrder<'a, 'b> { + x +} + +impl<'b, 'c> DispOrder for App<'b, 'c> { + fn disp_ord(&self) -> usize { + 999 + } +} + +macro_rules! color { + ($_self:ident, $s:expr, $c:ident) => { + if $_self.color { + write!($_self.writer, "{}", $_self.cizer.$c($s)) + } else { + write!($_self.writer, "{}", $s) + } + }; + ($_self:ident, $fmt_s:expr, $v:expr, $c:ident) => { + if $_self.color { + write!($_self.writer, "{}", $_self.cizer.$c(format!($fmt_s, $v))) + } else { + write!($_self.writer, $fmt_s, $v) + } + }; +} + +/// `clap` Help Writer. +/// +/// Wraps a writer stream providing different methods to generate help for `clap` objects. +pub struct Help<'a> { + writer: &'a mut Write, + next_line_help: bool, + hide_pv: bool, + term_w: usize, + color: bool, + cizer: Colorizer, + longest: usize, + force_next_line: bool, + use_long: bool, +} + +// Public Functions +impl<'a> Help<'a> { + /// Create a new `Help` instance. + #[cfg_attr(feature = "cargo-clippy", allow(too_many_arguments))] + pub fn new( + w: &'a mut Write, + next_line_help: bool, + hide_pv: bool, + color: bool, + cizer: Colorizer, + term_w: Option, + max_w: Option, + use_long: bool, + ) -> Self { + debugln!("Help::new;"); + Help { + writer: w, + next_line_help: next_line_help, + hide_pv: hide_pv, + term_w: match term_w { + Some(width) => if width == 0 { + usize::MAX + } else { + width + }, + None => cmp::min( + term_size::dimensions().map_or(120, |(w, _)| w), + match max_w { + None | Some(0) => usize::MAX, + Some(mw) => mw, + }, + ), + }, + color: color, + cizer: cizer, + longest: 0, + force_next_line: false, + use_long: use_long, + } + } + + /// Reads help settings from an App + /// and write its help to the wrapped stream. + pub fn write_app_help(w: &'a mut Write, app: &App, use_long: bool) -> ClapResult<()> { + debugln!("Help::write_app_help;"); + Self::write_parser_help(w, &app.p, use_long) + } + + /// Reads help settings from a Parser + /// and write its help to the wrapped stream. + pub fn write_parser_help(w: &'a mut Write, parser: &Parser, use_long: bool) -> ClapResult<()> { + debugln!("Help::write_parser_help;"); + Self::_write_parser_help(w, parser, false, use_long) + } + + /// Reads help settings from a Parser + /// and write its help to the wrapped stream which will be stderr. This method prevents + /// formatting when required. + pub fn write_parser_help_to_stderr(w: &'a mut Write, parser: &Parser) -> ClapResult<()> { + debugln!("Help::write_parser_help;"); + Self::_write_parser_help(w, parser, true, false) + } + + #[doc(hidden)] + pub fn _write_parser_help( + w: &'a mut Write, + parser: &Parser, + stderr: bool, + use_long: bool, + ) -> ClapResult<()> { + debugln!("Help::write_parser_help;"); + let nlh = parser.is_set(AppSettings::NextLineHelp); + let hide_v = parser.is_set(AppSettings::HidePossibleValuesInHelp); + let color = parser.is_set(AppSettings::ColoredHelp); + let cizer = Colorizer::new(ColorizerOption { + use_stderr: stderr, + when: parser.color(), + }); + Self::new( + w, + nlh, + hide_v, + color, + cizer, + parser.meta.term_w, + parser.meta.max_w, + use_long, + ).write_help(parser) + } + + /// Writes the parser help to the wrapped stream. + pub fn write_help(&mut self, parser: &Parser) -> ClapResult<()> { + debugln!("Help::write_help;"); + if let Some(h) = parser.meta.help_str { + write!(self.writer, "{}", h).map_err(Error::from)?; + } else if let Some(tmpl) = parser.meta.template { + self.write_templated_help(parser, tmpl)?; + } else { + self.write_default_help(parser)?; + } + Ok(()) + } +} + +// Methods to write AnyArg help. +impl<'a> Help<'a> { + /// Writes help for each argument in the order they were declared to the wrapped stream. + fn write_args_unsorted<'b: 'd, 'c: 'd, 'd, I: 'd>(&mut self, args: I) -> io::Result<()> + where + I: Iterator>, + { + debugln!("Help::write_args_unsorted;"); + // The shortest an arg can legally be is 2 (i.e. '-x') + self.longest = 2; + let mut arg_v = Vec::with_capacity(10); + let use_long = self.use_long; + for arg in args.filter(|arg| should_show_arg(use_long, *arg)) { + if arg.longest_filter() { + self.longest = cmp::max(self.longest, str_width(arg.to_string().as_str())); + } + arg_v.push(arg) + } + let mut first = true; + for arg in arg_v { + if first { + first = false; + } else { + self.writer.write_all(b"\n")?; + } + self.write_arg(arg.as_base())?; + } + Ok(()) + } + + /// Sorts arguments by length and display order and write their help to the wrapped stream. + fn write_args<'b: 'd, 'c: 'd, 'd, I: 'd>(&mut self, args: I) -> io::Result<()> + where + I: Iterator>, + { + debugln!("Help::write_args;"); + // The shortest an arg can legally be is 2 (i.e. '-x') + self.longest = 2; + let mut ord_m = VecMap::new(); + let use_long = self.use_long; + // Determine the longest + for arg in args.filter(|arg| { + // If it's NextLineHelp, but we don't care to compute how long because it may be + // NextLineHelp on purpose *because* it's so long and would throw off all other + // args alignment + should_show_arg(use_long, *arg) + }) { + if arg.longest_filter() { + debugln!("Help::write_args: Current Longest...{}", self.longest); + self.longest = cmp::max(self.longest, str_width(arg.to_string().as_str())); + debugln!("Help::write_args: New Longest...{}", self.longest); + } + let btm = ord_m.entry(arg.disp_ord()).or_insert(BTreeMap::new()); + btm.insert(arg.name(), arg); + } + let mut first = true; + for btm in ord_m.values() { + for arg in btm.values() { + if first { + first = false; + } else { + self.writer.write_all(b"\n")?; + } + self.write_arg(arg.as_base())?; + } + } + Ok(()) + } + + /// Writes help for an argument to the wrapped stream. + fn write_arg<'b, 'c>(&mut self, arg: &ArgWithDisplay<'b, 'c>) -> io::Result<()> { + debugln!("Help::write_arg;"); + self.short(arg)?; + self.long(arg)?; + let spec_vals = self.val(arg)?; + self.help(arg, &*spec_vals)?; + Ok(()) + } + + /// Writes argument's short command to the wrapped stream. + fn short<'b, 'c>(&mut self, arg: &ArgWithDisplay<'b, 'c>) -> io::Result<()> { + debugln!("Help::short;"); + write!(self.writer, "{}", TAB)?; + if let Some(s) = arg.short() { + color!(self, "-{}", s, good) + } else if arg.has_switch() { + write!(self.writer, "{}", TAB) + } else { + Ok(()) + } + } + + /// Writes argument's long command to the wrapped stream. + fn long<'b, 'c>(&mut self, arg: &ArgWithDisplay<'b, 'c>) -> io::Result<()> { + debugln!("Help::long;"); + if !arg.has_switch() { + return Ok(()); + } + if arg.takes_value() { + if let Some(l) = arg.long() { + if arg.short().is_some() { + write!(self.writer, ", ")?; + } + color!(self, "--{}", l, good)? + } + + let sep = if arg.is_set(ArgSettings::RequireEquals) { + "=" + } else { + " " + }; + write!(self.writer, "{}", sep)?; + } else if let Some(l) = arg.long() { + if arg.short().is_some() { + write!(self.writer, ", ")?; + } + color!(self, "--{}", l, good)?; + } + Ok(()) + } + + /// Writes argument's possible values to the wrapped stream. + fn val<'b, 'c>(&mut self, arg: &ArgWithDisplay<'b, 'c>) -> Result { + debugln!("Help::val: arg={}", arg); + if arg.takes_value() { + let delim = if arg.is_set(ArgSettings::RequireDelimiter) { + arg.val_delim().expect(INTERNAL_ERROR_MSG) + } else { + ' ' + }; + if let Some(vec) = arg.val_names() { + let mut it = vec.iter().peekable(); + while let Some((_, val)) = it.next() { + color!(self, "<{}>", val, good)?; + if it.peek().is_some() { + write!(self.writer, "{}", delim)?; + } + } + let num = vec.len(); + if arg.is_set(ArgSettings::Multiple) && num == 1 { + color!(self, "...", good)?; + } + } else if let Some(num) = arg.num_vals() { + let mut it = (0..num).peekable(); + while let Some(_) = it.next() { + color!(self, "<{}>", arg.name(), good)?; + if it.peek().is_some() { + write!(self.writer, "{}", delim)?; + } + } + if arg.is_set(ArgSettings::Multiple) && num == 1 { + color!(self, "...", good)?; + } + } else if arg.has_switch() { + color!(self, "<{}>", arg.name(), good)?; + if arg.is_set(ArgSettings::Multiple) { + color!(self, "...", good)?; + } + } else { + color!(self, "{}", arg, good)?; + } + } + + let spec_vals = self.spec_vals(arg); + let h = arg.help().unwrap_or(""); + let h_w = str_width(h) + str_width(&*spec_vals); + let nlh = self.next_line_help || arg.is_set(ArgSettings::NextLineHelp); + let taken = self.longest + 12; + self.force_next_line = !nlh && self.term_w >= taken + && (taken as f32 / self.term_w as f32) > 0.40 + && h_w > (self.term_w - taken); + + debug!("Help::val: Has switch..."); + if arg.has_switch() { + sdebugln!("Yes"); + debugln!("Help::val: force_next_line...{:?}", self.force_next_line); + debugln!("Help::val: nlh...{:?}", nlh); + debugln!("Help::val: taken...{}", taken); + debugln!( + "Help::val: help_width > (width - taken)...{} > ({} - {})", + h_w, + self.term_w, + taken + ); + debugln!("Help::val: longest...{}", self.longest); + debug!("Help::val: next_line..."); + if !(nlh || self.force_next_line) { + sdebugln!("No"); + let self_len = str_width(arg.to_string().as_str()); + // subtract ourself + let mut spcs = self.longest - self_len; + // Since we're writing spaces from the tab point we first need to know if we + // had a long and short, or just short + if arg.long().is_some() { + // Only account 4 after the val + spcs += 4; + } else { + // Only account for ', --' + 4 after the val + spcs += 8; + } + + write_nspaces!(self.writer, spcs); + } else { + sdebugln!("Yes"); + } + } else if !(nlh || self.force_next_line) { + sdebugln!("No, and not next_line"); + write_nspaces!( + self.writer, + self.longest + 4 - (str_width(arg.to_string().as_str())) + ); + } else { + sdebugln!("No"); + } + Ok(spec_vals) + } + + fn write_before_after_help(&mut self, h: &str) -> io::Result<()> { + debugln!("Help::write_before_after_help;"); + let mut help = String::from(h); + // determine if our help fits or needs to wrap + debugln!( + "Help::write_before_after_help: Term width...{}", + self.term_w + ); + let too_long = str_width(h) >= self.term_w; + + debug!("Help::write_before_after_help: Too long..."); + if too_long || h.contains("{n}") { + sdebugln!("Yes"); + debugln!("Help::write_before_after_help: help: {}", help); + debugln!( + "Help::write_before_after_help: help width: {}", + str_width(&*help) + ); + // Determine how many newlines we need to insert + debugln!( + "Help::write_before_after_help: Usable space: {}", + self.term_w + ); + help = wrap_help(&help.replace("{n}", "\n"), self.term_w); + } else { + sdebugln!("No"); + } + write!(self.writer, "{}", help)?; + Ok(()) + } + + /// Writes argument's help to the wrapped stream. + fn help<'b, 'c>(&mut self, arg: &ArgWithDisplay<'b, 'c>, spec_vals: &str) -> io::Result<()> { + debugln!("Help::help;"); + let h = if self.use_long && arg.name() != "" { + arg.long_help().unwrap_or_else(|| arg.help().unwrap_or("")) + } else { + arg.help().unwrap_or_else(|| arg.long_help().unwrap_or("")) + }; + let mut help = String::from(h) + spec_vals; + let nlh = self.next_line_help || arg.is_set(ArgSettings::NextLineHelp) || (self.use_long && arg.name() != ""); + debugln!("Help::help: Next Line...{:?}", nlh); + + let spcs = if nlh || self.force_next_line { + 12 // "tab" * 3 + } else { + self.longest + 12 + }; + + let too_long = spcs + str_width(h) + str_width(&*spec_vals) >= self.term_w; + + // Is help on next line, if so then indent + if nlh || self.force_next_line { + write!(self.writer, "\n{}{}{}", TAB, TAB, TAB)?; + } + + debug!("Help::help: Too long..."); + if too_long && spcs <= self.term_w || h.contains("{n}") { + sdebugln!("Yes"); + debugln!("Help::help: help...{}", help); + debugln!("Help::help: help width...{}", str_width(&*help)); + // Determine how many newlines we need to insert + let avail_chars = self.term_w - spcs; + debugln!("Help::help: Usable space...{}", avail_chars); + help = wrap_help(&help.replace("{n}", "\n"), avail_chars); + } else { + sdebugln!("No"); + } + if let Some(part) = help.lines().next() { + write!(self.writer, "{}", part)?; + } + for part in help.lines().skip(1) { + write!(self.writer, "\n")?; + if nlh || self.force_next_line { + write!(self.writer, "{}{}{}", TAB, TAB, TAB)?; + } else if arg.has_switch() { + write_nspaces!(self.writer, self.longest + 12); + } else { + write_nspaces!(self.writer, self.longest + 8); + } + write!(self.writer, "{}", part)?; + } + if !help.contains('\n') && (nlh || self.force_next_line) { + write!(self.writer, "\n")?; + } + Ok(()) + } + + fn spec_vals(&self, a: &ArgWithDisplay) -> String { + debugln!("Help::spec_vals: a={}", a); + let mut spec_vals = vec![]; + if let Some(ref env) = a.env() { + debugln!( + "Help::spec_vals: Found environment variable...[{:?}:{:?}]", + env.0, + env.1 + ); + let env_val = if !a.is_set(ArgSettings::HideEnvValues) { + format!( + "={}", + env.1.map_or(Cow::Borrowed(""), |val| val.to_string_lossy()) + ) + } else { + String::new() + }; + let env_info = format!(" [env: {}{}]", env.0.to_string_lossy(), env_val); + spec_vals.push(env_info); + } + if !a.is_set(ArgSettings::HideDefaultValue) { + if let Some(pv) = a.default_val() { + debugln!("Help::spec_vals: Found default value...[{:?}]", pv); + spec_vals.push(format!( + " [default: {}]", + if self.color { + self.cizer.good(pv.to_string_lossy()) + } else { + Format::None(pv.to_string_lossy()) + } + )); + } + } + if let Some(ref aliases) = a.aliases() { + debugln!("Help::spec_vals: Found aliases...{:?}", aliases); + spec_vals.push(format!( + " [aliases: {}]", + if self.color { + aliases + .iter() + .map(|v| format!("{}", self.cizer.good(v))) + .collect::>() + .join(", ") + } else { + aliases.join(", ") + } + )); + } + if !self.hide_pv && !a.is_set(ArgSettings::HidePossibleValues) { + if let Some(pv) = a.possible_vals() { + debugln!("Help::spec_vals: Found possible vals...{:?}", pv); + spec_vals.push(if self.color { + format!( + " [possible values: {}]", + pv.iter() + .map(|v| format!("{}", self.cizer.good(v))) + .collect::>() + .join(", ") + ) + } else { + format!(" [possible values: {}]", pv.join(", ")) + }); + } + } + spec_vals.join(" ") + } +} + +fn should_show_arg(use_long: bool, arg: &ArgWithOrder) -> bool { + if arg.is_set(ArgSettings::Hidden) { + return false; + } + + (!arg.is_set(ArgSettings::HiddenLongHelp) && use_long) + || (!arg.is_set(ArgSettings::HiddenShortHelp) && !use_long) + || arg.is_set(ArgSettings::NextLineHelp) +} + +// Methods to write Parser help. +impl<'a> Help<'a> { + /// Writes help for all arguments (options, flags, args, subcommands) + /// including titles of a Parser Object to the wrapped stream. + #[cfg_attr(feature = "lints", allow(useless_let_if_seq))] + #[cfg_attr(feature = "cargo-clippy", allow(useless_let_if_seq))] + pub fn write_all_args(&mut self, parser: &Parser) -> ClapResult<()> { + debugln!("Help::write_all_args;"); + let flags = parser.has_flags(); + let pos = parser + .positionals() + .filter(|arg| !arg.is_set(ArgSettings::Hidden)) + .count() > 0; + let opts = parser.has_opts(); + let subcmds = parser.has_visible_subcommands(); + + let unified_help = parser.is_set(AppSettings::UnifiedHelpMessage); + + let mut first = true; + + if unified_help && (flags || opts) { + let opts_flags = parser + .flags() + .map(as_arg_trait) + .chain(parser.opts().map(as_arg_trait)); + color!(self, "OPTIONS:\n", warning)?; + self.write_args(opts_flags)?; + first = false; + } else { + if flags { + color!(self, "FLAGS:\n", warning)?; + self.write_args(parser.flags().map(as_arg_trait))?; + first = false; + } + if opts { + if !first { + self.writer.write_all(b"\n\n")?; + } + color!(self, "OPTIONS:\n", warning)?; + self.write_args(parser.opts().map(as_arg_trait))?; + first = false; + } + } + + if pos { + if !first { + self.writer.write_all(b"\n\n")?; + } + color!(self, "ARGS:\n", warning)?; + self.write_args_unsorted(parser.positionals().map(as_arg_trait))?; + first = false; + } + + if subcmds { + if !first { + self.writer.write_all(b"\n\n")?; + } + color!(self, "SUBCOMMANDS:\n", warning)?; + self.write_subcommands(parser)?; + } + + Ok(()) + } + + /// Writes help for subcommands of a Parser Object to the wrapped stream. + fn write_subcommands(&mut self, parser: &Parser) -> io::Result<()> { + debugln!("Help::write_subcommands;"); + // The shortest an arg can legally be is 2 (i.e. '-x') + self.longest = 2; + let mut ord_m = VecMap::new(); + for sc in parser + .subcommands + .iter() + .filter(|s| !s.p.is_set(AppSettings::Hidden)) + { + let btm = ord_m.entry(sc.p.meta.disp_ord).or_insert(BTreeMap::new()); + self.longest = cmp::max(self.longest, str_width(sc.p.meta.name.as_str())); + //self.longest = cmp::max(self.longest, sc.p.meta.name.len()); + btm.insert(sc.p.meta.name.clone(), sc.clone()); + } + + let mut first = true; + for btm in ord_m.values() { + for sc in btm.values() { + if first { + first = false; + } else { + self.writer.write_all(b"\n")?; + } + self.write_arg(sc)?; + } + } + Ok(()) + } + + /// Writes version of a Parser Object to the wrapped stream. + fn write_version(&mut self, parser: &Parser) -> io::Result<()> { + debugln!("Help::write_version;"); + write!(self.writer, "{}", parser.meta.version.unwrap_or(""))?; + Ok(()) + } + + /// Writes binary name of a Parser Object to the wrapped stream. + fn write_bin_name(&mut self, parser: &Parser) -> io::Result<()> { + debugln!("Help::write_bin_name;"); + macro_rules! write_name { + () => {{ + let mut name = parser.meta.name.clone(); + name = name.replace("{n}", "\n"); + color!(self, wrap_help(&name, self.term_w), good)?; + }}; + } + if let Some(bn) = parser.meta.bin_name.as_ref() { + if bn.contains(' ') { + // Incase we're dealing with subcommands i.e. git mv is translated to git-mv + color!(self, bn.replace(" ", "-"), good)? + } else { + write_name!(); + } + } else { + write_name!(); + } + Ok(()) + } + + /// Writes default help for a Parser Object to the wrapped stream. + pub fn write_default_help(&mut self, parser: &Parser) -> ClapResult<()> { + debugln!("Help::write_default_help;"); + if let Some(h) = parser.meta.pre_help { + self.write_before_after_help(h)?; + self.writer.write_all(b"\n\n")?; + } + + macro_rules! write_thing { + ($thing:expr) => {{ + let mut owned_thing = $thing.to_owned(); + owned_thing = owned_thing.replace("{n}", "\n"); + write!(self.writer, "{}\n", wrap_help(&owned_thing, self.term_w))? + }}; + } + // Print the version + self.write_bin_name(parser)?; + self.writer.write_all(b" ")?; + self.write_version(parser)?; + self.writer.write_all(b"\n")?; + if let Some(author) = parser.meta.author { + write_thing!(author) + } + // if self.use_long { + // if let Some(about) = parser.meta.long_about { + // debugln!("Help::write_default_help: writing long about"); + // write_thing!(about) + // } else if let Some(about) = parser.meta.about { + // debugln!("Help::write_default_help: writing about"); + // write_thing!(about) + // } + // } else + if let Some(about) = parser.meta.long_about { + debugln!("Help::write_default_help: writing long about"); + write_thing!(about) + } else if let Some(about) = parser.meta.about { + debugln!("Help::write_default_help: writing about"); + write_thing!(about) + } + + color!(self, "\nUSAGE:", warning)?; + write!( + self.writer, + "\n{}{}\n\n", + TAB, + usage::create_usage_no_title(parser, &[]) + )?; + + let flags = parser.has_flags(); + let pos = parser.has_positionals(); + let opts = parser.has_opts(); + let subcmds = parser.has_subcommands(); + + if flags || opts || pos || subcmds { + self.write_all_args(parser)?; + } + + if let Some(h) = parser.meta.more_help { + if flags || opts || pos || subcmds { + self.writer.write_all(b"\n\n")?; + } + self.write_before_after_help(h)?; + } + + self.writer.flush().map_err(Error::from) + } +} + +/// Possible results for a copying function that stops when a given +/// byte was found. +enum CopyUntilResult { + DelimiterFound(usize), + DelimiterNotFound(usize), + ReaderEmpty, + ReadError(io::Error), + WriteError(io::Error), +} + +/// Copies the contents of a reader into a writer until a delimiter byte is found. +/// On success, the total number of bytes that were +/// copied from reader to writer is returned. +fn copy_until(r: &mut R, w: &mut W, delimiter_byte: u8) -> CopyUntilResult { + debugln!("copy_until;"); + + let mut count = 0; + for wb in r.bytes() { + match wb { + Ok(b) => { + if b == delimiter_byte { + return CopyUntilResult::DelimiterFound(count); + } + match w.write(&[b]) { + Ok(c) => count += c, + Err(e) => return CopyUntilResult::WriteError(e), + } + } + Err(e) => return CopyUntilResult::ReadError(e), + } + } + if count > 0 { + CopyUntilResult::DelimiterNotFound(count) + } else { + CopyUntilResult::ReaderEmpty + } +} + +/// Copies the contents of a reader into a writer until a {tag} is found, +/// copying the tag content to a buffer and returning its size. +/// In addition to errors, there are three possible outputs: +/// - `None`: The reader was consumed. +/// - `Some(Ok(0))`: No tag was captured but the reader still contains data. +/// - `Some(Ok(length>0))`: a tag with `length` was captured to the `tag_buffer`. +fn copy_and_capture( + r: &mut R, + w: &mut W, + tag_buffer: &mut Cursor>, +) -> Option> { + use self::CopyUntilResult::*; + debugln!("copy_and_capture;"); + + // Find the opening byte. + match copy_until(r, w, b'{') { + // The end of the reader was reached without finding the opening tag. + // (either with or without having copied data to the writer) + // Return None indicating that we are done. + ReaderEmpty | DelimiterNotFound(_) => None, + + // Something went wrong. + ReadError(e) | WriteError(e) => Some(Err(e)), + + // The opening byte was found. + // (either with or without having copied data to the writer) + DelimiterFound(_) => { + // Lets reset the buffer first and find out how long it is. + tag_buffer.set_position(0); + let buffer_size = tag_buffer.get_ref().len(); + + // Find the closing byte,limiting the reader to the length of the buffer. + let mut rb = r.take(buffer_size as u64); + match copy_until(&mut rb, tag_buffer, b'}') { + // We were already at the end of the reader. + // Return None indicating that we are done. + ReaderEmpty => None, + + // The closing tag was found. + // Return the tag_length. + DelimiterFound(tag_length) => Some(Ok(tag_length)), + + // The end of the reader was found without finding the closing tag. + // Write the opening byte and captured text to the writer. + // Return 0 indicating that nothing was captured but the reader still contains data. + DelimiterNotFound(not_tag_length) => match w.write(b"{") { + Err(e) => Some(Err(e)), + _ => match w.write(&tag_buffer.get_ref()[0..not_tag_length]) { + Err(e) => Some(Err(e)), + _ => Some(Ok(0)), + }, + }, + + ReadError(e) | WriteError(e) => Some(Err(e)), + } + } + } +} + +// Methods to write Parser help using templates. +impl<'a> Help<'a> { + /// Write help to stream for the parser in the format defined by the template. + /// + /// Tags arg given inside curly brackets: + /// Valid tags are: + /// * `{bin}` - Binary name. + /// * `{version}` - Version number. + /// * `{author}` - Author information. + /// * `{usage}` - Automatically generated or given usage string. + /// * `{all-args}` - Help for all arguments (options, flags, positionals arguments, + /// and subcommands) including titles. + /// * `{unified}` - Unified help for options and flags. + /// * `{flags}` - Help for flags. + /// * `{options}` - Help for options. + /// * `{positionals}` - Help for positionals arguments. + /// * `{subcommands}` - Help for subcommands. + /// * `{after-help}` - Info to be displayed after the help message. + /// * `{before-help}` - Info to be displayed before the help message. + /// + /// The template system is, on purpose, very simple. Therefore the tags have to written + /// in the lowercase and without spacing. + fn write_templated_help(&mut self, parser: &Parser, template: &str) -> ClapResult<()> { + debugln!("Help::write_templated_help;"); + let mut tmplr = Cursor::new(&template); + let mut tag_buf = Cursor::new(vec![0u8; 15]); + + // The strategy is to copy the template from the reader to wrapped stream + // until a tag is found. Depending on its value, the appropriate content is copied + // to the wrapped stream. + // The copy from template is then resumed, repeating this sequence until reading + // the complete template. + + loop { + let tag_length = match copy_and_capture(&mut tmplr, &mut self.writer, &mut tag_buf) { + None => return Ok(()), + Some(Err(e)) => return Err(Error::from(e)), + Some(Ok(val)) if val > 0 => val, + _ => continue, + }; + + debugln!("Help::write_template_help:iter: tag_buf={};", unsafe { + String::from_utf8_unchecked( + tag_buf.get_ref()[0..tag_length] + .iter() + .map(|&i| i) + .collect::>(), + ) + }); + match &tag_buf.get_ref()[0..tag_length] { + b"?" => { + self.writer.write_all(b"Could not decode tag name")?; + } + b"bin" => { + self.write_bin_name(parser)?; + } + b"version" => { + write!( + self.writer, + "{}", + parser.meta.version.unwrap_or("unknown version") + )?; + } + b"author" => { + write!( + self.writer, + "{}", + parser.meta.author.unwrap_or("unknown author") + )?; + } + b"about" => { + write!( + self.writer, + "{}", + parser.meta.about.unwrap_or("unknown about") + )?; + } + b"long-about" => { + write!( + self.writer, + "{}", + parser.meta.long_about.unwrap_or("unknown about") + )?; + } + b"usage" => { + write!(self.writer, "{}", usage::create_usage_no_title(parser, &[]))?; + } + b"all-args" => { + self.write_all_args(parser)?; + } + b"unified" => { + let opts_flags = parser + .flags() + .map(as_arg_trait) + .chain(parser.opts().map(as_arg_trait)); + self.write_args(opts_flags)?; + } + b"flags" => { + self.write_args(parser.flags().map(as_arg_trait))?; + } + b"options" => { + self.write_args(parser.opts().map(as_arg_trait))?; + } + b"positionals" => { + self.write_args(parser.positionals().map(as_arg_trait))?; + } + b"subcommands" => { + self.write_subcommands(parser)?; + } + b"after-help" => { + write!( + self.writer, + "{}", + parser.meta.more_help.unwrap_or("unknown after-help") + )?; + } + b"before-help" => { + write!( + self.writer, + "{}", + parser.meta.pre_help.unwrap_or("unknown before-help") + )?; + } + // Unknown tag, write it back. + r => { + self.writer.write_all(b"{")?; + self.writer.write_all(r)?; + self.writer.write_all(b"}")?; + } + } + } + } +} + +fn wrap_help(help: &str, avail_chars: usize) -> String { + let wrapper = textwrap::Wrapper::new(avail_chars).break_words(false); + help.lines() + .map(|line| wrapper.fill(line)) + .collect::>() + .join("\n") +} + +#[cfg(test)] +mod test { + use super::wrap_help; + + #[test] + fn wrap_help_last_word() { + let help = String::from("foo bar baz"); + assert_eq!(wrap_help(&help, 5), "foo\nbar\nbaz"); + } +} diff --git a/clap/src/app/meta.rs b/clap/src/app/meta.rs new file mode 100644 index 0000000..c7f128f --- /dev/null +++ b/clap/src/app/meta.rs @@ -0,0 +1,33 @@ +#[doc(hidden)] +#[allow(missing_debug_implementations)] +#[derive(Default, Clone)] +pub struct AppMeta<'b> { + pub name: String, + pub bin_name: Option, + pub author: Option<&'b str>, + pub version: Option<&'b str>, + pub long_version: Option<&'b str>, + pub about: Option<&'b str>, + pub long_about: Option<&'b str>, + pub more_help: Option<&'b str>, + pub pre_help: Option<&'b str>, + pub aliases: Option>, // (name, visible) + pub usage_str: Option<&'b str>, + pub usage: Option, + pub help_str: Option<&'b str>, + pub disp_ord: usize, + pub term_w: Option, + pub max_w: Option, + pub template: Option<&'b str>, +} + +impl<'b> AppMeta<'b> { + pub fn new() -> Self { Default::default() } + pub fn with_name(s: String) -> Self { + AppMeta { + name: s, + disp_ord: 999, + ..Default::default() + } + } +} diff --git a/clap/src/app/mod.rs b/clap/src/app/mod.rs new file mode 100644 index 0000000..3a1a383 --- /dev/null +++ b/clap/src/app/mod.rs @@ -0,0 +1,1839 @@ +mod settings; +pub mod parser; +mod meta; +mod help; +mod validator; +mod usage; + +// Std +use std::env; +use std::ffi::{OsStr, OsString}; +use std::fmt; +use std::io::{self, BufRead, BufWriter, Write}; +use std::path::Path; +use std::process; +use std::rc::Rc; +use std::result::Result as StdResult; + +// Third Party +#[cfg(feature = "yaml")] +use yaml_rust::Yaml; + +// Internal +use app::help::Help; +use app::parser::Parser; +use args::{AnyArg, Arg, ArgGroup, ArgMatcher, ArgMatches, ArgSettings}; +use errors::Result as ClapResult; +pub use self::settings::AppSettings; +use completions::Shell; +use map::{self, VecMap}; + +/// Used to create a representation of a command line program and all possible command line +/// arguments. Application settings are set using the "builder pattern" with the +/// [`App::get_matches`] family of methods being the terminal methods that starts the +/// runtime-parsing process. These methods then return information about the user supplied +/// arguments (or lack there of). +/// +/// **NOTE:** There aren't any mandatory "options" that one must set. The "options" may +/// also appear in any order (so long as one of the [`App::get_matches`] methods is the last method +/// called). +/// +/// # Examples +/// +/// ```no_run +/// # use clap::{App, Arg}; +/// let m = App::new("My Program") +/// .author("Me, me@mail.com") +/// .version("1.0.2") +/// .about("Explains in brief what the program does") +/// .arg( +/// Arg::with_name("in_file").index(1) +/// ) +/// .after_help("Longer explanation to appear after the options when \ +/// displaying the help information from --help or -h") +/// .get_matches(); +/// +/// // Your program logic starts here... +/// ``` +/// [`App::get_matches`]: ./struct.App.html#method.get_matches +#[allow(missing_debug_implementations)] +pub struct App<'a, 'b> +where + 'a: 'b, +{ + #[doc(hidden)] pub p: Parser<'a, 'b>, +} + + +impl<'a, 'b> App<'a, 'b> { + /// Creates a new instance of an application requiring a name. The name may be, but doesn't + /// have to be same as the binary. The name will be displayed to the user when they request to + /// print version or help and usage information. + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, Arg}; + /// let prog = App::new("My Program") + /// # ; + /// ``` + pub fn new>(n: S) -> Self { + App { + p: Parser::with_name(n.into()), + } + } + + /// Get the name of the app + pub fn get_name(&self) -> &str { &self.p.meta.name } + + /// Get the name of the binary + pub fn get_bin_name(&self) -> Option<&str> { self.p.meta.bin_name.as_ref().map(|s| s.as_str()) } + + /// Creates a new instance of an application requiring a name, but uses the [`crate_authors!`] + /// and [`crate_version!`] macros to fill in the [`App::author`] and [`App::version`] fields. + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, Arg}; + /// let prog = App::with_defaults("My Program") + /// # ; + /// ``` + /// [`crate_authors!`]: ./macro.crate_authors!.html + /// [`crate_version!`]: ./macro.crate_version!.html + /// [`App::author`]: ./struct.App.html#method.author + /// [`App::version`]: ./struct.App.html#method.author + #[deprecated(since="2.14.1", note="Can never work; use explicit App::author() and App::version() calls instead")] + pub fn with_defaults>(n: S) -> Self { + let mut a = App { + p: Parser::with_name(n.into()), + }; + a.p.meta.author = Some("Kevin K. "); + a.p.meta.version = Some("2.19.2"); + a + } + + /// Creates a new instance of [`App`] from a .yml (YAML) file. A full example of supported YAML + /// objects can be found in [`examples/17_yaml.rs`] and [`examples/17_yaml.yml`]. One great use + /// for using YAML is when supporting multiple languages and dialects, as each language could + /// be a distinct YAML file and determined at compiletime via `cargo` "features" in your + /// `Cargo.toml` + /// + /// In order to use this function you must compile `clap` with the `features = ["yaml"]` in + /// your settings for the `[dependencies.clap]` table of your `Cargo.toml` + /// + /// **NOTE:** Due to how the YAML objects are built there is a convenience macro for loading + /// the YAML file at compile time (relative to the current file, like modules work). That YAML + /// object can then be passed to this function. + /// + /// # Panics + /// + /// The YAML file must be properly formatted or this function will [`panic!`]. A good way to + /// ensure this doesn't happen is to run your program with the `--help` switch. If this passes + /// without error, you needn't worry because the YAML is properly formatted. + /// + /// # Examples + /// + /// The following example shows how to load a properly formatted YAML file to build an instance + /// of an [`App`] struct. + /// + /// ```ignore + /// # #[macro_use] + /// # extern crate clap; + /// # use clap::App; + /// # fn main() { + /// let yml = load_yaml!("app.yml"); + /// let app = App::from_yaml(yml); + /// + /// // continued logic goes here, such as `app.get_matches()` etc. + /// # } + /// ``` + /// [`App`]: ./struct.App.html + /// [`examples/17_yaml.rs`]: https://github.com/clap-rs/clap/blob/master/examples/17_yaml.rs + /// [`examples/17_yaml.yml`]: https://github.com/clap-rs/clap/blob/master/examples/17_yaml.yml + /// [`panic!`]: https://doc.rust-lang.org/std/macro.panic!.html + #[cfg(feature = "yaml")] + pub fn from_yaml(yaml: &'a Yaml) -> App<'a, 'a> { App::from(yaml) } + + /// Sets a string of author(s) that will be displayed to the user when they + /// request the help information with `--help` or `-h`. + /// + /// **Pro-tip:** Use `clap`s convenience macro [`crate_authors!`] to automatically set your + /// application's author(s) to the same thing as your crate at compile time. See the [`examples/`] + /// directory for more information + /// + /// See the [`examples/`] + /// directory for more information + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, Arg}; + /// App::new("myprog") + /// .author("Me, me@mymain.com") + /// # ; + /// ``` + /// [`crate_authors!`]: ./macro.crate_authors!.html + /// [`examples/`]: https://github.com/clap-rs/clap/tree/master/examples + pub fn author>(mut self, author: S) -> Self { + self.p.meta.author = Some(author.into()); + self + } + + /// Overrides the system-determined binary name. This should only be used when absolutely + /// necessary, such as when the binary name for your application is misleading, or perhaps + /// *not* how the user should invoke your program. + /// + /// **Pro-tip:** When building things such as third party `cargo` subcommands, this setting + /// **should** be used! + /// + /// **NOTE:** This command **should not** be used for [`SubCommand`]s. + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, Arg}; + /// App::new("My Program") + /// .bin_name("my_binary") + /// # ; + /// ``` + /// [`SubCommand`]: ./struct.SubCommand.html + pub fn bin_name>(mut self, name: S) -> Self { + self.p.meta.bin_name = Some(name.into()); + self + } + + /// Sets a string describing what the program does. This will be displayed when displaying help + /// information with `-h`. + /// + /// **NOTE:** If only `about` is provided, and not [`App::long_about`] but the user requests + /// `--help` clap will still display the contents of `about` appropriately + /// + /// **NOTE:** Only [`App::about`] is used in completion script generation in order to be + /// concise + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, Arg}; + /// App::new("myprog") + /// .about("Does really amazing things to great people") + /// # ; + /// ``` + /// [`App::long_about`]: ./struct.App.html#method.long_about + pub fn about>(mut self, about: S) -> Self { + self.p.meta.about = Some(about.into()); + self + } + + /// Sets a string describing what the program does. This will be displayed when displaying help + /// information. + /// + /// **NOTE:** If only `long_about` is provided, and not [`App::about`] but the user requests + /// `-h` clap will still display the contents of `long_about` appropriately + /// + /// **NOTE:** Only [`App::about`] is used in completion script generation in order to be + /// concise + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, Arg}; + /// App::new("myprog") + /// .long_about( + /// "Does really amazing things to great people. Now let's talk a little + /// more in depth about how this subcommand really works. It may take about + /// a few lines of text, but that's ok!") + /// # ; + /// ``` + /// [`App::about`]: ./struct.App.html#method.about + pub fn long_about>(mut self, about: S) -> Self { + self.p.meta.long_about = Some(about.into()); + self + } + + /// Sets the program's name. This will be displayed when displaying help information. + /// + /// **Pro-top:** This function is particularly useful when configuring a program via + /// [`App::from_yaml`] in conjunction with the [`crate_name!`] macro to derive the program's + /// name from its `Cargo.toml`. + /// + /// # Examples + /// ```ignore + /// # #[macro_use] + /// # extern crate clap; + /// # use clap::App; + /// # fn main() { + /// let yml = load_yaml!("app.yml"); + /// let app = App::from_yaml(yml) + /// .name(crate_name!()); + /// + /// // continued logic goes here, such as `app.get_matches()` etc. + /// # } + /// ``` + /// + /// [`App::from_yaml`]: ./struct.App.html#method.from_yaml + /// [`crate_name!`]: ./macro.crate_name.html + pub fn name>(mut self, name: S) -> Self { + self.p.meta.name = name.into(); + self + } + + /// Adds additional help information to be displayed in addition to auto-generated help. This + /// information is displayed **after** the auto-generated help information. This is often used + /// to describe how to use the arguments, or caveats to be noted. + /// + /// # Examples + /// + /// ```no_run + /// # use clap::App; + /// App::new("myprog") + /// .after_help("Does really amazing things to great people...but be careful with -R") + /// # ; + /// ``` + pub fn after_help>(mut self, help: S) -> Self { + self.p.meta.more_help = Some(help.into()); + self + } + + /// Adds additional help information to be displayed in addition to auto-generated help. This + /// information is displayed **before** the auto-generated help information. This is often used + /// for header information. + /// + /// # Examples + /// + /// ```no_run + /// # use clap::App; + /// App::new("myprog") + /// .before_help("Some info I'd like to appear before the help info") + /// # ; + /// ``` + pub fn before_help>(mut self, help: S) -> Self { + self.p.meta.pre_help = Some(help.into()); + self + } + + /// Sets a string of the version number to be displayed when displaying version or help + /// information with `-V`. + /// + /// **NOTE:** If only `version` is provided, and not [`App::long_version`] but the user + /// requests `--version` clap will still display the contents of `version` appropriately + /// + /// **Pro-tip:** Use `clap`s convenience macro [`crate_version!`] to automatically set your + /// application's version to the same thing as your crate at compile time. See the [`examples/`] + /// directory for more information + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, Arg}; + /// App::new("myprog") + /// .version("v0.1.24") + /// # ; + /// ``` + /// [`crate_version!`]: ./macro.crate_version!.html + /// [`examples/`]: https://github.com/clap-rs/clap/tree/master/examples + /// [`App::long_version`]: ./struct.App.html#method.long_version + pub fn version>(mut self, ver: S) -> Self { + self.p.meta.version = Some(ver.into()); + self + } + + /// Sets a string of the version number to be displayed when displaying version or help + /// information with `--version`. + /// + /// **NOTE:** If only `long_version` is provided, and not [`App::version`] but the user + /// requests `-V` clap will still display the contents of `long_version` appropriately + /// + /// **Pro-tip:** Use `clap`s convenience macro [`crate_version!`] to automatically set your + /// application's version to the same thing as your crate at compile time. See the [`examples/`] + /// directory for more information + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, Arg}; + /// App::new("myprog") + /// .long_version( + /// "v0.1.24 + /// commit: abcdef89726d + /// revision: 123 + /// release: 2 + /// binary: myprog") + /// # ; + /// ``` + /// [`crate_version!`]: ./macro.crate_version!.html + /// [`examples/`]: https://github.com/clap-rs/clap/tree/master/examples + /// [`App::version`]: ./struct.App.html#method.version + pub fn long_version>(mut self, ver: S) -> Self { + self.p.meta.long_version = Some(ver.into()); + self + } + + /// Sets a custom usage string to override the auto-generated usage string. + /// + /// This will be displayed to the user when errors are found in argument parsing, or when you + /// call [`ArgMatches::usage`] + /// + /// **CAUTION:** Using this setting disables `clap`s "context-aware" usage strings. After this + /// setting is set, this will be the only usage string displayed to the user! + /// + /// **NOTE:** You do not need to specify the "USAGE: \n\t" portion, as that will + /// still be applied by `clap`, you only need to specify the portion starting + /// with the binary name. + /// + /// **NOTE:** This will not replace the entire help message, *only* the portion + /// showing the usage. + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, Arg}; + /// App::new("myprog") + /// .usage("myapp [-clDas] ") + /// # ; + /// ``` + /// [`ArgMatches::usage`]: ./struct.ArgMatches.html#method.usage + pub fn usage>(mut self, usage: S) -> Self { + self.p.meta.usage_str = Some(usage.into()); + self + } + + /// Sets a custom help message and overrides the auto-generated one. This should only be used + /// when the auto-generated message does not suffice. + /// + /// This will be displayed to the user when they use `--help` or `-h` + /// + /// **NOTE:** This replaces the **entire** help message, so nothing will be auto-generated. + /// + /// **NOTE:** This **only** replaces the help message for the current command, meaning if you + /// are using subcommands, those help messages will still be auto-generated unless you + /// specify a [`Arg::help`] for them as well. + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, Arg}; + /// App::new("myapp") + /// .help("myapp v1.0\n\ + /// Does awesome things\n\ + /// (C) me@mail.com\n\n\ + /// + /// USAGE: myapp \n\n\ + /// + /// Options:\n\ + /// -h, --help Display this message\n\ + /// -V, --version Display version info\n\ + /// -s Do something with stuff\n\ + /// -v Be verbose\n\n\ + /// + /// Commmands:\n\ + /// help Prints this message\n\ + /// work Do some work") + /// # ; + /// ``` + /// [`Arg::help`]: ./struct.Arg.html#method.help + pub fn help>(mut self, help: S) -> Self { + self.p.meta.help_str = Some(help.into()); + self + } + + /// Sets the [`short`] for the auto-generated `help` argument. + /// + /// By default `clap` automatically assigns `h`, but this can be overridden if you have a + /// different argument which you'd prefer to use the `-h` short with. This can be done by + /// defining your own argument with a lowercase `h` as the [`short`]. + /// + /// `clap` lazily generates these `help` arguments **after** you've defined any arguments of + /// your own. + /// + /// **NOTE:** Any leading `-` characters will be stripped, and only the first + /// non `-` character will be used as the [`short`] version + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, Arg}; + /// App::new("myprog") + /// .help_short("H") // Using an uppercase `H` instead of the default lowercase `h` + /// # ; + /// ``` + /// [`short`]: ./struct.Arg.html#method.short + pub fn help_short + 'b>(mut self, s: S) -> Self { + self.p.help_short(s.as_ref()); + self + } + + /// Sets the [`short`] for the auto-generated `version` argument. + /// + /// By default `clap` automatically assigns `V`, but this can be overridden if you have a + /// different argument which you'd prefer to use the `-V` short with. This can be done by + /// defining your own argument with an uppercase `V` as the [`short`]. + /// + /// `clap` lazily generates these `version` arguments **after** you've defined any arguments of + /// your own. + /// + /// **NOTE:** Any leading `-` characters will be stripped, and only the first + /// non `-` character will be used as the `short` version + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, Arg}; + /// App::new("myprog") + /// .version_short("v") // Using a lowercase `v` instead of the default capital `V` + /// # ; + /// ``` + /// [`short`]: ./struct.Arg.html#method.short + pub fn version_short>(mut self, s: S) -> Self { + self.p.version_short(s.as_ref()); + self + } + + /// Sets the help text for the auto-generated `help` argument. + /// + /// By default `clap` sets this to `"Prints help information"`, but if you're using a + /// different convention for your help messages and would prefer a different phrasing you can + /// override it. + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, Arg}; + /// App::new("myprog") + /// .help_message("Print help information") // Perhaps you want imperative help messages + /// + /// # ; + /// ``` + pub fn help_message>(mut self, s: S) -> Self { + self.p.help_message = Some(s.into()); + self + } + + /// Sets the help text for the auto-generated `version` argument. + /// + /// By default `clap` sets this to `"Prints version information"`, but if you're using a + /// different convention for your help messages and would prefer a different phrasing then you + /// can change it. + /// + /// # Examples + /// ```no_run + /// # use clap::{App, Arg}; + /// App::new("myprog") + /// .version_message("Print version information") // Perhaps you want imperative help messages + /// # ; + /// ``` + pub fn version_message>(mut self, s: S) -> Self { + self.p.version_message = Some(s.into()); + self + } + + /// Sets the help template to be used, overriding the default format. + /// + /// Tags arg given inside curly brackets. + /// + /// Valid tags are: + /// + /// * `{bin}` - Binary name. + /// * `{version}` - Version number. + /// * `{author}` - Author information. + /// * `{about}` - General description (from [`App::about`]) + /// * `{usage}` - Automatically generated or given usage string. + /// * `{all-args}` - Help for all arguments (options, flags, positionals arguments, + /// and subcommands) including titles. + /// * `{unified}` - Unified help for options and flags. Note, you must *also* set + /// [`AppSettings::UnifiedHelpMessage`] to fully merge both options and + /// flags, otherwise the ordering is "best effort" + /// * `{flags}` - Help for flags. + /// * `{options}` - Help for options. + /// * `{positionals}` - Help for positionals arguments. + /// * `{subcommands}` - Help for subcommands. + /// * `{after-help}` - Help from [`App::after_help`] + /// * `{before-help}` - Help from [`App::before_help`] + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, Arg}; + /// App::new("myprog") + /// .version("1.0") + /// .template("{bin} ({version}) - {usage}") + /// # ; + /// ``` + /// **NOTE:** The template system is, on purpose, very simple. Therefore the tags have to be + /// written in lowercase and without spacing. + /// + /// [`App::about`]: ./struct.App.html#method.about + /// [`App::after_help`]: ./struct.App.html#method.after_help + /// [`App::before_help`]: ./struct.App.html#method.before_help + /// [`AppSettings::UnifiedHelpMessage`]: ./enum.AppSettings.html#variant.UnifiedHelpMessage + pub fn template>(mut self, s: S) -> Self { + self.p.meta.template = Some(s.into()); + self + } + + /// Enables a single command, or [`SubCommand`], level settings. + /// + /// See [`AppSettings`] for a full list of possibilities and examples. + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, Arg, AppSettings}; + /// App::new("myprog") + /// .setting(AppSettings::SubcommandRequired) + /// .setting(AppSettings::WaitOnError) + /// # ; + /// ``` + /// [`SubCommand`]: ./struct.SubCommand.html + /// [`AppSettings`]: ./enum.AppSettings.html + pub fn setting(mut self, setting: AppSettings) -> Self { + self.p.set(setting); + self + } + + /// Enables multiple command, or [`SubCommand`], level settings + /// + /// See [`AppSettings`] for a full list of possibilities and examples. + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, Arg, AppSettings}; + /// App::new("myprog") + /// .settings(&[AppSettings::SubcommandRequired, + /// AppSettings::WaitOnError]) + /// # ; + /// ``` + /// [`SubCommand`]: ./struct.SubCommand.html + /// [`AppSettings`]: ./enum.AppSettings.html + pub fn settings(mut self, settings: &[AppSettings]) -> Self { + for s in settings { + self.p.set(*s); + } + self + } + + /// Enables a single setting that is propagated down through all child [`SubCommand`]s. + /// + /// See [`AppSettings`] for a full list of possibilities and examples. + /// + /// **NOTE**: The setting is *only* propagated *down* and not up through parent commands. + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, Arg, AppSettings}; + /// App::new("myprog") + /// .global_setting(AppSettings::SubcommandRequired) + /// # ; + /// ``` + /// [`SubCommand`]: ./struct.SubCommand.html + /// [`AppSettings`]: ./enum.AppSettings.html + pub fn global_setting(mut self, setting: AppSettings) -> Self { + self.p.set(setting); + self.p.g_settings.set(setting); + self + } + + /// Enables multiple settings which are propagated *down* through all child [`SubCommand`]s. + /// + /// See [`AppSettings`] for a full list of possibilities and examples. + /// + /// **NOTE**: The setting is *only* propagated *down* and not up through parent commands. + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, Arg, AppSettings}; + /// App::new("myprog") + /// .global_settings(&[AppSettings::SubcommandRequired, + /// AppSettings::ColoredHelp]) + /// # ; + /// ``` + /// [`SubCommand`]: ./struct.SubCommand.html + /// [`AppSettings`]: ./enum.AppSettings.html + pub fn global_settings(mut self, settings: &[AppSettings]) -> Self { + for s in settings { + self.p.set(*s); + self.p.g_settings.set(*s) + } + self + } + + /// Disables a single command, or [`SubCommand`], level setting. + /// + /// See [`AppSettings`] for a full list of possibilities and examples. + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, AppSettings}; + /// App::new("myprog") + /// .unset_setting(AppSettings::ColorAuto) + /// # ; + /// ``` + /// [`SubCommand`]: ./struct.SubCommand.html + /// [`AppSettings`]: ./enum.AppSettings.html + pub fn unset_setting(mut self, setting: AppSettings) -> Self { + self.p.unset(setting); + self + } + + /// Disables multiple command, or [`SubCommand`], level settings. + /// + /// See [`AppSettings`] for a full list of possibilities and examples. + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, AppSettings}; + /// App::new("myprog") + /// .unset_settings(&[AppSettings::ColorAuto, + /// AppSettings::AllowInvalidUtf8]) + /// # ; + /// ``` + /// [`SubCommand`]: ./struct.SubCommand.html + /// [`AppSettings`]: ./enum.AppSettings.html + pub fn unset_settings(mut self, settings: &[AppSettings]) -> Self { + for s in settings { + self.p.unset(*s); + } + self + } + + /// Sets the terminal width at which to wrap help messages. Defaults to `120`. Using `0` will + /// ignore terminal widths and use source formatting. + /// + /// `clap` automatically tries to determine the terminal width on Unix, Linux, macOS and Windows + /// if the `wrap_help` cargo "feature" has been used while compiling. If the terminal width + /// cannot be determined, `clap` defaults to `120`. + /// + /// **NOTE:** This setting applies globally and *not* on a per-command basis. + /// + /// **NOTE:** This setting must be set **before** any subcommands are added! + /// + /// # Platform Specific + /// + /// Only Unix, Linux, macOS and Windows support automatic determination of terminal width. + /// Even on those platforms, this setting is useful if for any reason the terminal width + /// cannot be determined. + /// + /// # Examples + /// + /// ```no_run + /// # use clap::App; + /// App::new("myprog") + /// .set_term_width(80) + /// # ; + /// ``` + pub fn set_term_width(mut self, width: usize) -> Self { + self.p.meta.term_w = Some(width); + self + } + + /// Sets the max terminal width at which to wrap help messages. Using `0` will ignore terminal + /// widths and use source formatting. + /// + /// `clap` automatically tries to determine the terminal width on Unix, Linux, macOS and Windows + /// if the `wrap_help` cargo "feature" has been used while compiling, but one might want to + /// limit the size (e.g. when the terminal is running fullscreen). + /// + /// **NOTE:** This setting applies globally and *not* on a per-command basis. + /// + /// **NOTE:** This setting must be set **before** any subcommands are added! + /// + /// # Platform Specific + /// + /// Only Unix, Linux, macOS and Windows support automatic determination of terminal width. + /// + /// # Examples + /// + /// ```no_run + /// # use clap::App; + /// App::new("myprog") + /// .max_term_width(100) + /// # ; + /// ``` + pub fn max_term_width(mut self, w: usize) -> Self { + self.p.meta.max_w = Some(w); + self + } + + /// Adds an [argument] to the list of valid possibilities. + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, Arg}; + /// App::new("myprog") + /// // Adding a single "flag" argument with a short and help text, using Arg::with_name() + /// .arg( + /// Arg::with_name("debug") + /// .short("d") + /// .help("turns on debugging mode") + /// ) + /// // Adding a single "option" argument with a short, a long, and help text using the less + /// // verbose Arg::from_usage() + /// .arg( + /// Arg::from_usage("-c --config=[CONFIG] 'Optionally sets a config file to use'") + /// ) + /// # ; + /// ``` + /// [argument]: ./struct.Arg.html + pub fn arg>>(mut self, a: A) -> Self { + self.p.add_arg(a.into()); + self + } + + /// Adds multiple [arguments] to the list of valid possibilities + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, Arg}; + /// App::new("myprog") + /// .args( + /// &[Arg::from_usage("[debug] -d 'turns on debugging info'"), + /// Arg::with_name("input").index(1).help("the input file to use")] + /// ) + /// # ; + /// ``` + /// [arguments]: ./struct.Arg.html + pub fn args(mut self, args: &[Arg<'a, 'b>]) -> Self { + for arg in args { + self.p.add_arg_ref(arg); + } + self + } + + /// A convenience method for adding a single [argument] from a usage type string. The string + /// used follows the same rules and syntax as [`Arg::from_usage`] + /// + /// **NOTE:** The downside to using this method is that you can not set any additional + /// properties of the [`Arg`] other than what [`Arg::from_usage`] supports. + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, Arg}; + /// App::new("myprog") + /// .arg_from_usage("-c --config= 'Sets a configuration file to use'") + /// # ; + /// ``` + /// [argument]: ./struct.Arg.html + /// [`Arg`]: ./struct.Arg.html + /// [`Arg::from_usage`]: ./struct.Arg.html#method.from_usage + pub fn arg_from_usage(mut self, usage: &'a str) -> Self { + self.p.add_arg(Arg::from_usage(usage)); + self + } + + /// Adds multiple [arguments] at once from a usage string, one per line. See + /// [`Arg::from_usage`] for details on the syntax and rules supported. + /// + /// **NOTE:** Like [`App::arg_from_usage`] the downside is you only set properties for the + /// [`Arg`]s which [`Arg::from_usage`] supports. + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, Arg}; + /// App::new("myprog") + /// .args_from_usage( + /// "-c --config=[FILE] 'Sets a configuration file to use' + /// [debug]... -d 'Sets the debugging level' + /// 'The input file to use'" + /// ) + /// # ; + /// ``` + /// [arguments]: ./struct.Arg.html + /// [`Arg::from_usage`]: ./struct.Arg.html#method.from_usage + /// [`App::arg_from_usage`]: ./struct.App.html#method.arg_from_usage + /// [`Arg`]: ./struct.Arg.html + pub fn args_from_usage(mut self, usage: &'a str) -> Self { + for line in usage.lines() { + let l = line.trim(); + if l.is_empty() { + continue; + } + self.p.add_arg(Arg::from_usage(l)); + } + self + } + + /// Allows adding a [`SubCommand`] alias, which function as "hidden" subcommands that + /// automatically dispatch as if this subcommand was used. This is more efficient, and easier + /// than creating multiple hidden subcommands as one only needs to check for the existence of + /// this command, and not all variants. + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, Arg, SubCommand}; + /// let m = App::new("myprog") + /// .subcommand(SubCommand::with_name("test") + /// .alias("do-stuff")) + /// .get_matches_from(vec!["myprog", "do-stuff"]); + /// assert_eq!(m.subcommand_name(), Some("test")); + /// ``` + /// [`SubCommand`]: ./struct.SubCommand.html + pub fn alias>(mut self, name: S) -> Self { + if let Some(ref mut als) = self.p.meta.aliases { + als.push((name.into(), false)); + } else { + self.p.meta.aliases = Some(vec![(name.into(), false)]); + } + self + } + + /// Allows adding [`SubCommand`] aliases, which function as "hidden" subcommands that + /// automatically dispatch as if this subcommand was used. This is more efficient, and easier + /// than creating multiple hidden subcommands as one only needs to check for the existence of + /// this command, and not all variants. + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg, SubCommand}; + /// let m = App::new("myprog") + /// .subcommand(SubCommand::with_name("test") + /// .aliases(&["do-stuff", "do-tests", "tests"])) + /// .arg(Arg::with_name("input") + /// .help("the file to add") + /// .index(1) + /// .required(false)) + /// .get_matches_from(vec!["myprog", "do-tests"]); + /// assert_eq!(m.subcommand_name(), Some("test")); + /// ``` + /// [`SubCommand`]: ./struct.SubCommand.html + pub fn aliases(mut self, names: &[&'b str]) -> Self { + if let Some(ref mut als) = self.p.meta.aliases { + for n in names { + als.push((n, false)); + } + } else { + self.p.meta.aliases = Some(names.iter().map(|n| (*n, false)).collect::>()); + } + self + } + + /// Allows adding a [`SubCommand`] alias that functions exactly like those defined with + /// [`App::alias`], except that they are visible inside the help message. + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, Arg, SubCommand}; + /// let m = App::new("myprog") + /// .subcommand(SubCommand::with_name("test") + /// .visible_alias("do-stuff")) + /// .get_matches_from(vec!["myprog", "do-stuff"]); + /// assert_eq!(m.subcommand_name(), Some("test")); + /// ``` + /// [`SubCommand`]: ./struct.SubCommand.html + /// [`App::alias`]: ./struct.App.html#method.alias + pub fn visible_alias>(mut self, name: S) -> Self { + if let Some(ref mut als) = self.p.meta.aliases { + als.push((name.into(), true)); + } else { + self.p.meta.aliases = Some(vec![(name.into(), true)]); + } + self + } + + /// Allows adding multiple [`SubCommand`] aliases that functions exactly like those defined + /// with [`App::aliases`], except that they are visible inside the help message. + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, Arg, SubCommand}; + /// let m = App::new("myprog") + /// .subcommand(SubCommand::with_name("test") + /// .visible_aliases(&["do-stuff", "tests"])) + /// .get_matches_from(vec!["myprog", "do-stuff"]); + /// assert_eq!(m.subcommand_name(), Some("test")); + /// ``` + /// [`SubCommand`]: ./struct.SubCommand.html + /// [`App::aliases`]: ./struct.App.html#method.aliases + pub fn visible_aliases(mut self, names: &[&'b str]) -> Self { + if let Some(ref mut als) = self.p.meta.aliases { + for n in names { + als.push((n, true)); + } + } else { + self.p.meta.aliases = Some(names.iter().map(|n| (*n, true)).collect::>()); + } + self + } + + /// Adds an [`ArgGroup`] to the application. [`ArgGroup`]s are a family of related arguments. + /// By placing them in a logical group, you can build easier requirement and exclusion rules. + /// For instance, you can make an entire [`ArgGroup`] required, meaning that one (and *only* + /// one) argument from that group must be present at runtime. + /// + /// You can also do things such as name an [`ArgGroup`] as a conflict to another argument. + /// Meaning any of the arguments that belong to that group will cause a failure if present with + /// the conflicting argument. + /// + /// Another added benefit of [`ArgGroup`]s is that you can extract a value from a group instead + /// of determining exactly which argument was used. + /// + /// Finally, using [`ArgGroup`]s to ensure exclusion between arguments is another very common + /// use + /// + /// # Examples + /// + /// The following example demonstrates using an [`ArgGroup`] to ensure that one, and only one, + /// of the arguments from the specified group is present at runtime. + /// + /// ```no_run + /// # use clap::{App, ArgGroup}; + /// App::new("app") + /// .args_from_usage( + /// "--set-ver [ver] 'set the version manually' + /// --major 'auto increase major' + /// --minor 'auto increase minor' + /// --patch 'auto increase patch'") + /// .group(ArgGroup::with_name("vers") + /// .args(&["set-ver", "major", "minor","patch"]) + /// .required(true)) + /// # ; + /// ``` + /// [`ArgGroup`]: ./struct.ArgGroup.html + pub fn group(mut self, group: ArgGroup<'a>) -> Self { + self.p.add_group(group); + self + } + + /// Adds multiple [`ArgGroup`]s to the [`App`] at once. + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, ArgGroup}; + /// App::new("app") + /// .args_from_usage( + /// "--set-ver [ver] 'set the version manually' + /// --major 'auto increase major' + /// --minor 'auto increase minor' + /// --patch 'auto increase patch' + /// -c [FILE] 'a config file' + /// -i [IFACE] 'an interface'") + /// .groups(&[ + /// ArgGroup::with_name("vers") + /// .args(&["set-ver", "major", "minor","patch"]) + /// .required(true), + /// ArgGroup::with_name("input") + /// .args(&["c", "i"]) + /// ]) + /// # ; + /// ``` + /// [`ArgGroup`]: ./struct.ArgGroup.html + /// [`App`]: ./struct.App.html + pub fn groups(mut self, groups: &[ArgGroup<'a>]) -> Self { + for g in groups { + self = self.group(g.into()); + } + self + } + + /// Adds a [`SubCommand`] to the list of valid possibilities. Subcommands are effectively + /// sub-[`App`]s, because they can contain their own arguments, subcommands, version, usage, + /// etc. They also function just like [`App`]s, in that they get their own auto generated help, + /// version, and usage. + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, Arg, SubCommand}; + /// App::new("myprog") + /// .subcommand(SubCommand::with_name("config") + /// .about("Controls configuration features") + /// .arg_from_usage(" 'Required configuration file to use'")) + /// # ; + /// ``` + /// [`SubCommand`]: ./struct.SubCommand.html + /// [`App`]: ./struct.App.html + pub fn subcommand(mut self, subcmd: App<'a, 'b>) -> Self { + self.p.add_subcommand(subcmd); + self + } + + /// Adds multiple subcommands to the list of valid possibilities by iterating over an + /// [`IntoIterator`] of [`SubCommand`]s + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg, SubCommand}; + /// # App::new("myprog") + /// .subcommands( vec![ + /// SubCommand::with_name("config").about("Controls configuration functionality") + /// .arg(Arg::with_name("config_file").index(1)), + /// SubCommand::with_name("debug").about("Controls debug functionality")]) + /// # ; + /// ``` + /// [`SubCommand`]: ./struct.SubCommand.html + /// [`IntoIterator`]: https://doc.rust-lang.org/std/iter/trait.IntoIterator.html + pub fn subcommands(mut self, subcmds: I) -> Self + where + I: IntoIterator>, + { + for subcmd in subcmds { + self.p.add_subcommand(subcmd); + } + self + } + + /// Allows custom ordering of [`SubCommand`]s within the help message. Subcommands with a lower + /// value will be displayed first in the help message. This is helpful when one would like to + /// emphasise frequently used subcommands, or prioritize those towards the top of the list. + /// Duplicate values **are** allowed. Subcommands with duplicate display orders will be + /// displayed in alphabetical order. + /// + /// **NOTE:** The default is 999 for all subcommands. + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, SubCommand}; + /// let m = App::new("cust-ord") + /// .subcommand(SubCommand::with_name("alpha") // typically subcommands are grouped + /// // alphabetically by name. Subcommands + /// // without a display_order have a value of + /// // 999 and are displayed alphabetically with + /// // all other 999 subcommands + /// .about("Some help and text")) + /// .subcommand(SubCommand::with_name("beta") + /// .display_order(1) // In order to force this subcommand to appear *first* + /// // all we have to do is give it a value lower than 999. + /// // Any other subcommands with a value of 1 will be displayed + /// // alphabetically with this one...then 2 values, then 3, etc. + /// .about("I should be first!")) + /// .get_matches_from(vec![ + /// "cust-ord", "--help" + /// ]); + /// ``` + /// + /// The above example displays the following help message + /// + /// ```text + /// cust-ord + /// + /// USAGE: + /// cust-ord [FLAGS] [OPTIONS] + /// + /// FLAGS: + /// -h, --help Prints help information + /// -V, --version Prints version information + /// + /// SUBCOMMANDS: + /// beta I should be first! + /// alpha Some help and text + /// ``` + /// [`SubCommand`]: ./struct.SubCommand.html + pub fn display_order(mut self, ord: usize) -> Self { + self.p.meta.disp_ord = ord; + self + } + + /// Prints the full help message to [`io::stdout()`] using a [`BufWriter`] using the same + /// method as if someone ran `-h` to request the help message + /// + /// **NOTE:** clap has the ability to distinguish between "short" and "long" help messages + /// depending on if the user ran [`-h` (short)] or [`--help` (long)] + /// + /// # Examples + /// + /// ```rust + /// # use clap::App; + /// let mut app = App::new("myprog"); + /// app.print_help(); + /// ``` + /// [`io::stdout()`]: https://doc.rust-lang.org/std/io/fn.stdout.html + /// [`BufWriter`]: https://doc.rust-lang.org/std/io/struct.BufWriter.html + /// [`-h` (short)]: ./struct.Arg.html#method.help + /// [`--help` (long)]: ./struct.Arg.html#method.long_help + pub fn print_help(&mut self) -> ClapResult<()> { + // If there are global arguments, or settings we need to propagate them down to subcommands + // before parsing incase we run into a subcommand + self.p.propagate_globals(); + self.p.propagate_settings(); + self.p.derive_display_order(); + + self.p.create_help_and_version(); + let out = io::stdout(); + let mut buf_w = BufWriter::new(out.lock()); + self.write_help(&mut buf_w) + } + + /// Prints the full help message to [`io::stdout()`] using a [`BufWriter`] using the same + /// method as if someone ran `--help` to request the help message + /// + /// **NOTE:** clap has the ability to distinguish between "short" and "long" help messages + /// depending on if the user ran [`-h` (short)] or [`--help` (long)] + /// + /// # Examples + /// + /// ```rust + /// # use clap::App; + /// let mut app = App::new("myprog"); + /// app.print_long_help(); + /// ``` + /// [`io::stdout()`]: https://doc.rust-lang.org/std/io/fn.stdout.html + /// [`BufWriter`]: https://doc.rust-lang.org/std/io/struct.BufWriter.html + /// [`-h` (short)]: ./struct.Arg.html#method.help + /// [`--help` (long)]: ./struct.Arg.html#method.long_help + pub fn print_long_help(&mut self) -> ClapResult<()> { + let out = io::stdout(); + let mut buf_w = BufWriter::new(out.lock()); + self.write_long_help(&mut buf_w) + } + + /// Writes the full help message to the user to a [`io::Write`] object in the same method as if + /// the user ran `-h` + /// + /// **NOTE:** clap has the ability to distinguish between "short" and "long" help messages + /// depending on if the user ran [`-h` (short)] or [`--help` (long)] + /// + /// **NOTE:** There is a known bug where this method does not write propagated global arguments + /// or autogenerated arguments (i.e. the default help/version args). Prefer + /// [`App::write_long_help`] instead if possible! + /// + /// # Examples + /// + /// ```rust + /// # use clap::App; + /// use std::io; + /// let mut app = App::new("myprog"); + /// let mut out = io::stdout(); + /// app.write_help(&mut out).expect("failed to write to stdout"); + /// ``` + /// [`io::Write`]: https://doc.rust-lang.org/std/io/trait.Write.html + /// [`-h` (short)]: ./struct.Arg.html#method.help + /// [`--help` (long)]: ./struct.Arg.html#method.long_help + pub fn write_help(&self, w: &mut W) -> ClapResult<()> { + // PENDING ISSUE: 808 + // https://github.com/clap-rs/clap/issues/808 + // If there are global arguments, or settings we need to propagate them down to subcommands + // before parsing incase we run into a subcommand + // self.p.propagate_globals(); + // self.p.propagate_settings(); + // self.p.derive_display_order(); + // self.p.create_help_and_version(); + + Help::write_app_help(w, self, false) + } + + /// Writes the full help message to the user to a [`io::Write`] object in the same method as if + /// the user ran `--help` + /// + /// **NOTE:** clap has the ability to distinguish between "short" and "long" help messages + /// depending on if the user ran [`-h` (short)] or [`--help` (long)] + /// + /// # Examples + /// + /// ```rust + /// # use clap::App; + /// use std::io; + /// let mut app = App::new("myprog"); + /// let mut out = io::stdout(); + /// app.write_long_help(&mut out).expect("failed to write to stdout"); + /// ``` + /// [`io::Write`]: https://doc.rust-lang.org/std/io/trait.Write.html + /// [`-h` (short)]: ./struct.Arg.html#method.help + /// [`--help` (long)]: ./struct.Arg.html#method.long_help + pub fn write_long_help(&mut self, w: &mut W) -> ClapResult<()> { + // If there are global arguments, or settings we need to propagate them down to subcommands + // before parsing incase we run into a subcommand + self.p.propagate_globals(); + self.p.propagate_settings(); + self.p.derive_display_order(); + self.p.create_help_and_version(); + + Help::write_app_help(w, self, true) + } + + /// Writes the version message to the user to a [`io::Write`] object as if the user ran `-V`. + /// + /// **NOTE:** clap has the ability to distinguish between "short" and "long" version messages + /// depending on if the user ran [`-V` (short)] or [`--version` (long)] + /// + /// # Examples + /// + /// ```rust + /// # use clap::App; + /// use std::io; + /// let mut app = App::new("myprog"); + /// let mut out = io::stdout(); + /// app.write_version(&mut out).expect("failed to write to stdout"); + /// ``` + /// [`io::Write`]: https://doc.rust-lang.org/std/io/trait.Write.html + /// [`-V` (short)]: ./struct.App.html#method.version + /// [`--version` (long)]: ./struct.App.html#method.long_version + pub fn write_version(&self, w: &mut W) -> ClapResult<()> { + self.p.write_version(w, false).map_err(From::from) + } + + /// Writes the version message to the user to a [`io::Write`] object + /// + /// **NOTE:** clap has the ability to distinguish between "short" and "long" version messages + /// depending on if the user ran [`-V` (short)] or [`--version` (long)] + /// + /// # Examples + /// + /// ```rust + /// # use clap::App; + /// use std::io; + /// let mut app = App::new("myprog"); + /// let mut out = io::stdout(); + /// app.write_long_version(&mut out).expect("failed to write to stdout"); + /// ``` + /// [`io::Write`]: https://doc.rust-lang.org/std/io/trait.Write.html + /// [`-V` (short)]: ./struct.App.html#method.version + /// [`--version` (long)]: ./struct.App.html#method.long_version + pub fn write_long_version(&self, w: &mut W) -> ClapResult<()> { + self.p.write_version(w, true).map_err(From::from) + } + + /// Generate a completions file for a specified shell at compile time. + /// + /// **NOTE:** to generate the file at compile time you must use a `build.rs` "Build Script" + /// + /// # Examples + /// + /// The following example generates a bash completion script via a `build.rs` script. In this + /// simple example, we'll demo a very small application with only a single subcommand and two + /// args. Real applications could be many multiple levels deep in subcommands, and have tens or + /// potentially hundreds of arguments. + /// + /// First, it helps if we separate out our `App` definition into a separate file. Whether you + /// do this as a function, or bare App definition is a matter of personal preference. + /// + /// ``` + /// // src/cli.rs + /// + /// use clap::{App, Arg, SubCommand}; + /// + /// pub fn build_cli() -> App<'static, 'static> { + /// App::new("compl") + /// .about("Tests completions") + /// .arg(Arg::with_name("file") + /// .help("some input file")) + /// .subcommand(SubCommand::with_name("test") + /// .about("tests things") + /// .arg(Arg::with_name("case") + /// .long("case") + /// .takes_value(true) + /// .help("the case to test"))) + /// } + /// ``` + /// + /// In our regular code, we can simply call this `build_cli()` function, then call + /// `get_matches()`, or any of the other normal methods directly after. For example: + /// + /// ```ignore + /// // src/main.rs + /// + /// mod cli; + /// + /// fn main() { + /// let m = cli::build_cli().get_matches(); + /// + /// // normal logic continues... + /// } + /// ``` + /// + /// Next, we set up our `Cargo.toml` to use a `build.rs` build script. + /// + /// ```toml + /// # Cargo.toml + /// build = "build.rs" + /// + /// [build-dependencies] + /// clap = "2.23" + /// ``` + /// + /// Next, we place a `build.rs` in our project root. + /// + /// ```ignore + /// extern crate clap; + /// + /// use clap::Shell; + /// + /// include!("src/cli.rs"); + /// + /// fn main() { + /// let outdir = match env::var_os("OUT_DIR") { + /// None => return, + /// Some(outdir) => outdir, + /// }; + /// let mut app = build_cli(); + /// app.gen_completions("myapp", // We need to specify the bin name manually + /// Shell::Bash, // Then say which shell to build completions for + /// outdir); // Then say where write the completions to + /// } + /// ``` + /// Now, once we compile there will be a `{bin_name}.bash` file in the directory. + /// Assuming we compiled with debug mode, it would be somewhere similar to + /// `/target/debug/build/myapp-/out/myapp.bash`. + /// + /// Fish shell completions will use the file format `{bin_name}.fish` + pub fn gen_completions, S: Into>( + &mut self, + bin_name: S, + for_shell: Shell, + out_dir: T, + ) { + self.p.meta.bin_name = Some(bin_name.into()); + self.p.gen_completions(for_shell, out_dir.into()); + } + + + /// Generate a completions file for a specified shell at runtime. Until `cargo install` can + /// install extra files like a completion script, this may be used e.g. in a command that + /// outputs the contents of the completion script, to be redirected into a file by the user. + /// + /// # Examples + /// + /// Assuming a separate `cli.rs` like the [example above](./struct.App.html#method.gen_completions), + /// we can let users generate a completion script using a command: + /// + /// ```ignore + /// // src/main.rs + /// + /// mod cli; + /// use std::io; + /// + /// fn main() { + /// let matches = cli::build_cli().get_matches(); + /// + /// if matches.is_present("generate-bash-completions") { + /// cli::build_cli().gen_completions_to("myapp", Shell::Bash, &mut io::stdout()); + /// } + /// + /// // normal logic continues... + /// } + /// + /// ``` + /// + /// Usage: + /// + /// ```shell + /// $ myapp generate-bash-completions > /usr/share/bash-completion/completions/myapp.bash + /// ``` + pub fn gen_completions_to>( + &mut self, + bin_name: S, + for_shell: Shell, + buf: &mut W, + ) { + self.p.meta.bin_name = Some(bin_name.into()); + self.p.gen_completions_to(for_shell, buf); + } + + /// Starts the parsing process, upon a failed parse an error will be displayed to the user and + /// the process will exit with the appropriate error code. By default this method gets all user + /// provided arguments from [`env::args_os`] in order to allow for invalid UTF-8 code points, + /// which are legal on many platforms. + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, Arg}; + /// let matches = App::new("myprog") + /// // Args and options go here... + /// .get_matches(); + /// ``` + /// [`env::args_os`]: https://doc.rust-lang.org/std/env/fn.args_os.html + pub fn get_matches(self) -> ArgMatches<'a> { self.get_matches_from(&mut env::args_os()) } + + /// Starts the parsing process. This method will return a [`clap::Result`] type instead of exiting + /// the process on failed parse. By default this method gets matches from [`env::args_os`] + /// + /// **NOTE:** This method WILL NOT exit when `--help` or `--version` (or short versions) are + /// used. It will return a [`clap::Error`], where the [`kind`] is a + /// [`ErrorKind::HelpDisplayed`] or [`ErrorKind::VersionDisplayed`] respectively. You must call + /// [`Error::exit`] or perform a [`std::process::exit`]. + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, Arg}; + /// let matches = App::new("myprog") + /// // Args and options go here... + /// .get_matches_safe() + /// .unwrap_or_else( |e| e.exit() ); + /// ``` + /// [`env::args_os`]: https://doc.rust-lang.org/std/env/fn.args_os.html + /// [`ErrorKind::HelpDisplayed`]: ./enum.ErrorKind.html#variant.HelpDisplayed + /// [`ErrorKind::VersionDisplayed`]: ./enum.ErrorKind.html#variant.VersionDisplayed + /// [`Error::exit`]: ./struct.Error.html#method.exit + /// [`std::process::exit`]: https://doc.rust-lang.org/std/process/fn.exit.html + /// [`clap::Result`]: ./type.Result.html + /// [`clap::Error`]: ./struct.Error.html + /// [`kind`]: ./struct.Error.html + pub fn get_matches_safe(self) -> ClapResult> { + // Start the parsing + self.get_matches_from_safe(&mut env::args_os()) + } + + /// Starts the parsing process. Like [`App::get_matches`] this method does not return a [`clap::Result`] + /// and will automatically exit with an error message. This method, however, lets you specify + /// what iterator to use when performing matches, such as a [`Vec`] of your making. + /// + /// **NOTE:** The first argument will be parsed as the binary name unless + /// [`AppSettings::NoBinaryName`] is used + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, Arg}; + /// let arg_vec = vec!["my_prog", "some", "args", "to", "parse"]; + /// + /// let matches = App::new("myprog") + /// // Args and options go here... + /// .get_matches_from(arg_vec); + /// ``` + /// [`App::get_matches`]: ./struct.App.html#method.get_matches + /// [`clap::Result`]: ./type.Result.html + /// [`Vec`]: https://doc.rust-lang.org/std/vec/struct.Vec.html + /// [`AppSettings::NoBinaryName`]: ./enum.AppSettings.html#variant.NoBinaryName + pub fn get_matches_from(mut self, itr: I) -> ArgMatches<'a> + where + I: IntoIterator, + T: Into + Clone, + { + self.get_matches_from_safe_borrow(itr).unwrap_or_else(|e| { + // Otherwise, write to stderr and exit + if e.use_stderr() { + wlnerr!("{}", e.message); + if self.p.is_set(AppSettings::WaitOnError) { + wlnerr!("\nPress [ENTER] / [RETURN] to continue..."); + let mut s = String::new(); + let i = io::stdin(); + i.lock().read_line(&mut s).unwrap(); + } + drop(self); + drop(e); + process::exit(1); + } + + drop(self); + e.exit() + }) + } + + /// Starts the parsing process. A combination of [`App::get_matches_from`], and + /// [`App::get_matches_safe`] + /// + /// **NOTE:** This method WILL NOT exit when `--help` or `--version` (or short versions) are + /// used. It will return a [`clap::Error`], where the [`kind`] is a [`ErrorKind::HelpDisplayed`] + /// or [`ErrorKind::VersionDisplayed`] respectively. You must call [`Error::exit`] or + /// perform a [`std::process::exit`] yourself. + /// + /// **NOTE:** The first argument will be parsed as the binary name unless + /// [`AppSettings::NoBinaryName`] is used + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, Arg}; + /// let arg_vec = vec!["my_prog", "some", "args", "to", "parse"]; + /// + /// let matches = App::new("myprog") + /// // Args and options go here... + /// .get_matches_from_safe(arg_vec) + /// .unwrap_or_else( |e| { panic!("An error occurs: {}", e) }); + /// ``` + /// [`App::get_matches_from`]: ./struct.App.html#method.get_matches_from + /// [`App::get_matches_safe`]: ./struct.App.html#method.get_matches_safe + /// [`ErrorKind::HelpDisplayed`]: ./enum.ErrorKind.html#variant.HelpDisplayed + /// [`ErrorKind::VersionDisplayed`]: ./enum.ErrorKind.html#variant.VersionDisplayed + /// [`Error::exit`]: ./struct.Error.html#method.exit + /// [`std::process::exit`]: https://doc.rust-lang.org/std/process/fn.exit.html + /// [`clap::Error`]: ./struct.Error.html + /// [`Error::exit`]: ./struct.Error.html#method.exit + /// [`kind`]: ./struct.Error.html + /// [`AppSettings::NoBinaryName`]: ./enum.AppSettings.html#variant.NoBinaryName + pub fn get_matches_from_safe(mut self, itr: I) -> ClapResult> + where + I: IntoIterator, + T: Into + Clone, + { + self.get_matches_from_safe_borrow(itr) + } + + /// Starts the parsing process without consuming the [`App`] struct `self`. This is normally not + /// the desired functionality, instead prefer [`App::get_matches_from_safe`] which *does* + /// consume `self`. + /// + /// **NOTE:** The first argument will be parsed as the binary name unless + /// [`AppSettings::NoBinaryName`] is used + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, Arg}; + /// let arg_vec = vec!["my_prog", "some", "args", "to", "parse"]; + /// + /// let mut app = App::new("myprog"); + /// // Args and options go here... + /// let matches = app.get_matches_from_safe_borrow(arg_vec) + /// .unwrap_or_else( |e| { panic!("An error occurs: {}", e) }); + /// ``` + /// [`App`]: ./struct.App.html + /// [`App::get_matches_from_safe`]: ./struct.App.html#method.get_matches_from_safe + /// [`AppSettings::NoBinaryName`]: ./enum.AppSettings.html#variant.NoBinaryName + pub fn get_matches_from_safe_borrow(&mut self, itr: I) -> ClapResult> + where + I: IntoIterator, + T: Into + Clone, + { + // If there are global arguments, or settings we need to propagate them down to subcommands + // before parsing incase we run into a subcommand + if !self.p.is_set(AppSettings::Propagated) { + self.p.propagate_globals(); + self.p.propagate_settings(); + self.p.derive_display_order(); + self.p.set(AppSettings::Propagated); + } + + let mut matcher = ArgMatcher::new(); + + let mut it = itr.into_iter(); + // Get the name of the program (argument 1 of env::args()) and determine the + // actual file + // that was used to execute the program. This is because a program called + // ./target/release/my_prog -a + // will have two arguments, './target/release/my_prog', '-a' but we don't want + // to display + // the full path when displaying help messages and such + if !self.p.is_set(AppSettings::NoBinaryName) { + if let Some(name) = it.next() { + let bn_os = name.into(); + let p = Path::new(&*bn_os); + if let Some(f) = p.file_name() { + if let Some(s) = f.to_os_string().to_str() { + if self.p.meta.bin_name.is_none() { + self.p.meta.bin_name = Some(s.to_owned()); + } + } + } + } + } + + // do the real parsing + if let Err(e) = self.p.get_matches_with(&mut matcher, &mut it.peekable()) { + return Err(e); + } + + let global_arg_vec: Vec<&str> = (&self).p.global_args.iter().map(|ga| ga.b.name).collect(); + matcher.propagate_globals(&global_arg_vec); + + Ok(matcher.into()) + } +} + +#[cfg(feature = "yaml")] +impl<'a> From<&'a Yaml> for App<'a, 'a> { + fn from(mut yaml: &'a Yaml) -> Self { + use args::SubCommand; + // We WANT this to panic on error...so expect() is good. + let mut is_sc = None; + let mut a = if let Some(name) = yaml["name"].as_str() { + App::new(name) + } else { + let yaml_hash = yaml.as_hash().unwrap(); + let sc_key = yaml_hash.keys().nth(0).unwrap(); + is_sc = Some(yaml_hash.get(sc_key).unwrap()); + App::new(sc_key.as_str().unwrap()) + }; + yaml = if let Some(sc) = is_sc { sc } else { yaml }; + + macro_rules! yaml_str { + ($a:ident, $y:ident, $i:ident) => { + if let Some(v) = $y[stringify!($i)].as_str() { + $a = $a.$i(v); + } else if $y[stringify!($i)] != Yaml::BadValue { + panic!("Failed to convert YAML value {:?} to a string", $y[stringify!($i)]); + } + }; + } + + yaml_str!(a, yaml, version); + yaml_str!(a, yaml, long_version); + yaml_str!(a, yaml, author); + yaml_str!(a, yaml, bin_name); + yaml_str!(a, yaml, about); + yaml_str!(a, yaml, long_about); + yaml_str!(a, yaml, before_help); + yaml_str!(a, yaml, after_help); + yaml_str!(a, yaml, template); + yaml_str!(a, yaml, usage); + yaml_str!(a, yaml, help); + yaml_str!(a, yaml, help_short); + yaml_str!(a, yaml, version_short); + yaml_str!(a, yaml, help_message); + yaml_str!(a, yaml, version_message); + yaml_str!(a, yaml, alias); + yaml_str!(a, yaml, visible_alias); + + if let Some(v) = yaml["display_order"].as_i64() { + a = a.display_order(v as usize); + } else if yaml["display_order"] != Yaml::BadValue { + panic!( + "Failed to convert YAML value {:?} to a u64", + yaml["display_order"] + ); + } + if let Some(v) = yaml["setting"].as_str() { + a = a.setting(v.parse().expect("unknown AppSetting found in YAML file")); + } else if yaml["setting"] != Yaml::BadValue { + panic!( + "Failed to convert YAML value {:?} to an AppSetting", + yaml["setting"] + ); + } + if let Some(v) = yaml["settings"].as_vec() { + for ys in v { + if let Some(s) = ys.as_str() { + a = a.setting(s.parse().expect("unknown AppSetting found in YAML file")); + } + } + } else if let Some(v) = yaml["settings"].as_str() { + a = a.setting(v.parse().expect("unknown AppSetting found in YAML file")); + } else if yaml["settings"] != Yaml::BadValue { + panic!( + "Failed to convert YAML value {:?} to a string", + yaml["settings"] + ); + } + if let Some(v) = yaml["global_setting"].as_str() { + a = a.setting(v.parse().expect("unknown AppSetting found in YAML file")); + } else if yaml["global_setting"] != Yaml::BadValue { + panic!( + "Failed to convert YAML value {:?} to an AppSetting", + yaml["setting"] + ); + } + if let Some(v) = yaml["global_settings"].as_vec() { + for ys in v { + if let Some(s) = ys.as_str() { + a = a.global_setting(s.parse().expect("unknown AppSetting found in YAML file")); + } + } + } else if let Some(v) = yaml["global_settings"].as_str() { + a = a.global_setting(v.parse().expect("unknown AppSetting found in YAML file")); + } else if yaml["global_settings"] != Yaml::BadValue { + panic!( + "Failed to convert YAML value {:?} to a string", + yaml["global_settings"] + ); + } + + macro_rules! vec_or_str { + ($a:ident, $y:ident, $as_vec:ident, $as_single:ident) => {{ + let maybe_vec = $y[stringify!($as_vec)].as_vec(); + if let Some(vec) = maybe_vec { + for ys in vec { + if let Some(s) = ys.as_str() { + $a = $a.$as_single(s); + } else { + panic!("Failed to convert YAML value {:?} to a string", ys); + } + } + } else { + if let Some(s) = $y[stringify!($as_vec)].as_str() { + $a = $a.$as_single(s); + } else if $y[stringify!($as_vec)] != Yaml::BadValue { + panic!("Failed to convert YAML value {:?} to either a vec or string", $y[stringify!($as_vec)]); + } + } + $a + } + }; + } + + a = vec_or_str!(a, yaml, aliases, alias); + a = vec_or_str!(a, yaml, visible_aliases, visible_alias); + + if let Some(v) = yaml["args"].as_vec() { + for arg_yaml in v { + a = a.arg(Arg::from_yaml(arg_yaml.as_hash().unwrap())); + } + } + if let Some(v) = yaml["subcommands"].as_vec() { + for sc_yaml in v { + a = a.subcommand(SubCommand::from_yaml(sc_yaml)); + } + } + if let Some(v) = yaml["groups"].as_vec() { + for ag_yaml in v { + a = a.group(ArgGroup::from(ag_yaml.as_hash().unwrap())); + } + } + + a + } +} + +impl<'a, 'b> Clone for App<'a, 'b> { + fn clone(&self) -> Self { App { p: self.p.clone() } } +} + +impl<'n, 'e> AnyArg<'n, 'e> for App<'n, 'e> { + fn name(&self) -> &'n str { + "" + } + fn overrides(&self) -> Option<&[&'e str]> { None } + fn requires(&self) -> Option<&[(Option<&'e str>, &'n str)]> { None } + fn blacklist(&self) -> Option<&[&'e str]> { None } + fn required_unless(&self) -> Option<&[&'e str]> { None } + fn val_names(&self) -> Option<&VecMap<&'e str>> { None } + fn is_set(&self, _: ArgSettings) -> bool { false } + fn val_terminator(&self) -> Option<&'e str> { None } + fn set(&mut self, _: ArgSettings) { + unreachable!("App struct does not support AnyArg::set, this is a bug!") + } + fn has_switch(&self) -> bool { false } + fn max_vals(&self) -> Option { None } + fn num_vals(&self) -> Option { None } + fn possible_vals(&self) -> Option<&[&'e str]> { None } + fn validator(&self) -> Option<&Rc StdResult<(), String>>> { None } + fn validator_os(&self) -> Option<&Rc StdResult<(), OsString>>> { None } + fn min_vals(&self) -> Option { None } + fn short(&self) -> Option { None } + fn long(&self) -> Option<&'e str> { None } + fn val_delim(&self) -> Option { None } + fn takes_value(&self) -> bool { true } + fn help(&self) -> Option<&'e str> { self.p.meta.about } + fn long_help(&self) -> Option<&'e str> { self.p.meta.long_about } + fn default_val(&self) -> Option<&'e OsStr> { None } + fn default_vals_ifs(&self) -> Option, &'e OsStr)>> { + None + } + fn env<'s>(&'s self) -> Option<(&'n OsStr, Option<&'s OsString>)> { None } + fn longest_filter(&self) -> bool { true } + fn aliases(&self) -> Option> { + if let Some(ref aliases) = self.p.meta.aliases { + let vis_aliases: Vec<_> = aliases + .iter() + .filter_map(|&(n, v)| if v { Some(n) } else { None }) + .collect(); + if vis_aliases.is_empty() { + None + } else { + Some(vis_aliases) + } + } else { + None + } + } +} + +impl<'n, 'e> fmt::Display for App<'n, 'e> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.p.meta.name) } +} diff --git a/clap/src/app/parser.rs b/clap/src/app/parser.rs new file mode 100644 index 0000000..decfde4 --- /dev/null +++ b/clap/src/app/parser.rs @@ -0,0 +1,2167 @@ +// Std +use std::ffi::{OsStr, OsString}; +use std::fmt::Display; +use std::fs::File; +use std::io::{self, BufWriter, Write}; +#[cfg(all(feature = "debug", not(any(target_os = "windows", target_arch = "wasm32"))))] +use std::os::unix::ffi::OsStrExt; +#[cfg(all(feature = "debug", any(target_os = "windows", target_arch = "wasm32")))] +use osstringext::OsStrExt3; +use std::path::PathBuf; +use std::slice::Iter; +use std::iter::Peekable; +use std::cell::Cell; + +// Internal +use INTERNAL_ERROR_MSG; +use INVALID_UTF8; +use SubCommand; +use app::App; +use app::help::Help; +use app::meta::AppMeta; +use app::settings::AppFlags; +use args::{AnyArg, Arg, ArgGroup, ArgMatcher, Base, FlagBuilder, OptBuilder, PosBuilder, Switched}; +use args::settings::ArgSettings; +use completions::ComplGen; +use errors::{Error, ErrorKind}; +use errors::Result as ClapResult; +use fmt::ColorWhen; +use osstringext::OsStrExt2; +use completions::Shell; +use suggestions; +use app::settings::AppSettings as AS; +use app::validator::Validator; +use app::usage; +use map::{self, VecMap}; + +#[derive(Debug, PartialEq, Copy, Clone)] +#[doc(hidden)] +pub enum ParseResult<'a> { + Flag, + Opt(&'a str), + Pos(&'a str), + MaybeHyphenValue, + MaybeNegNum, + NotFound, + ValuesDone, +} + +#[allow(missing_debug_implementations)] +#[doc(hidden)] +#[derive(Clone, Default)] +pub struct Parser<'a, 'b> +where + 'a: 'b, +{ + pub meta: AppMeta<'b>, + settings: AppFlags, + pub g_settings: AppFlags, + pub flags: Vec>, + pub opts: Vec>, + pub positionals: VecMap>, + pub subcommands: Vec>, + pub groups: Vec>, + pub global_args: Vec>, + pub required: Vec<&'a str>, + pub r_ifs: Vec<(&'a str, &'b str, &'a str)>, + pub overrides: Vec<(&'b str, &'a str)>, + help_short: Option, + version_short: Option, + cache: Option<&'a str>, + pub help_message: Option<&'a str>, + pub version_message: Option<&'a str>, + cur_idx: Cell, +} + +impl<'a, 'b> Parser<'a, 'b> +where + 'a: 'b, +{ + pub fn with_name(n: String) -> Self { + Parser { + meta: AppMeta::with_name(n), + g_settings: AppFlags::zeroed(), + cur_idx: Cell::new(0), + ..Default::default() + } + } + + pub fn help_short(&mut self, s: &str) { + let c = s.trim_left_matches(|c| c == '-') + .chars() + .nth(0) + .unwrap_or('h'); + self.help_short = Some(c); + } + + pub fn version_short(&mut self, s: &str) { + let c = s.trim_left_matches(|c| c == '-') + .chars() + .nth(0) + .unwrap_or('V'); + self.version_short = Some(c); + } + + pub fn gen_completions_to(&mut self, for_shell: Shell, buf: &mut W) { + if !self.is_set(AS::Propagated) { + self.propagate_help_version(); + self.build_bin_names(); + self.propagate_globals(); + self.propagate_settings(); + self.set(AS::Propagated); + } + + ComplGen::new(self).generate(for_shell, buf) + } + + pub fn gen_completions(&mut self, for_shell: Shell, od: OsString) { + use std::error::Error; + + let out_dir = PathBuf::from(od); + let name = &*self.meta.bin_name.as_ref().unwrap().clone(); + let file_name = match for_shell { + Shell::Bash => format!("{}.bash", name), + Shell::Fish => format!("{}.fish", name), + Shell::Zsh => format!("_{}", name), + Shell::PowerShell => format!("_{}.ps1", name), + Shell::Elvish => format!("{}.elv", name), + }; + + let mut file = match File::create(out_dir.join(file_name)) { + Err(why) => panic!("couldn't create completion file: {}", why.description()), + Ok(file) => file, + }; + self.gen_completions_to(for_shell, &mut file) + } + + #[inline] + fn app_debug_asserts(&self) -> bool { + assert!(self.verify_positionals()); + let should_err = self.groups.iter().all(|g| { + g.args.iter().all(|arg| { + (self.flags.iter().any(|f| &f.b.name == arg) + || self.opts.iter().any(|o| &o.b.name == arg) + || self.positionals.values().any(|p| &p.b.name == arg) + || self.groups.iter().any(|g| &g.name == arg)) + }) + }); + let g = self.groups.iter().find(|g| { + g.args.iter().any(|arg| { + !(self.flags.iter().any(|f| &f.b.name == arg) + || self.opts.iter().any(|o| &o.b.name == arg) + || self.positionals.values().any(|p| &p.b.name == arg) + || self.groups.iter().any(|g| &g.name == arg)) + }) + }); + assert!( + should_err, + "The group '{}' contains the arg '{}' that doesn't actually exist.", + g.unwrap().name, + g.unwrap() + .args + .iter() + .find(|arg| !(self.flags.iter().any(|f| &&f.b.name == arg) + || self.opts.iter().any(|o| &&o.b.name == arg) + || self.positionals.values().any(|p| &&p.b.name == arg) + || self.groups.iter().any(|g| &&g.name == arg))) + .unwrap() + ); + true + } + + #[inline] + fn debug_asserts(&self, a: &Arg) -> bool { + assert!( + !arg_names!(self).any(|name| name == a.b.name), + format!("Non-unique argument name: {} is already in use", a.b.name) + ); + if let Some(l) = a.s.long { + assert!( + !self.contains_long(l), + "Argument long must be unique\n\n\t--{} is already in use", + l + ); + } + if let Some(s) = a.s.short { + assert!( + !self.contains_short(s), + "Argument short must be unique\n\n\t-{} is already in use", + s + ); + } + let i = if a.index.is_none() { + (self.positionals.len() + 1) + } else { + a.index.unwrap() as usize + }; + assert!( + !self.positionals.contains_key(i), + "Argument \"{}\" has the same index as another positional \ + argument\n\n\tPerhaps try .multiple(true) to allow one positional argument \ + to take multiple values", + a.b.name + ); + assert!( + !(a.is_set(ArgSettings::Required) && a.is_set(ArgSettings::Global)), + "Global arguments cannot be required.\n\n\t'{}' is marked as \ + global and required", + a.b.name + ); + if a.b.is_set(ArgSettings::Last) { + assert!( + !self.positionals + .values() + .any(|p| p.b.is_set(ArgSettings::Last)), + "Only one positional argument may have last(true) set. Found two." + ); + assert!(a.s.long.is_none(), + "Flags or Options may not have last(true) set. {} has both a long and last(true) set.", + a.b.name); + assert!(a.s.short.is_none(), + "Flags or Options may not have last(true) set. {} has both a short and last(true) set.", + a.b.name); + } + true + } + + #[inline] + fn add_conditional_reqs(&mut self, a: &Arg<'a, 'b>) { + if let Some(ref r_ifs) = a.r_ifs { + for &(arg, val) in r_ifs { + self.r_ifs.push((arg, val, a.b.name)); + } + } + } + + #[inline] + fn add_arg_groups(&mut self, a: &Arg<'a, 'b>) { + if let Some(ref grps) = a.b.groups { + for g in grps { + let mut found = false; + if let Some(ref mut ag) = self.groups.iter_mut().find(|grp| &grp.name == g) { + ag.args.push(a.b.name); + found = true; + } + if !found { + let mut ag = ArgGroup::with_name(g); + ag.args.push(a.b.name); + self.groups.push(ag); + } + } + } + } + + #[inline] + fn add_reqs(&mut self, a: &Arg<'a, 'b>) { + if a.is_set(ArgSettings::Required) { + // If the arg is required, add all it's requirements to master required list + self.required.push(a.b.name); + if let Some(ref areqs) = a.b.requires { + for name in areqs + .iter() + .filter(|&&(val, _)| val.is_none()) + .map(|&(_, name)| name) + { + self.required.push(name); + } + } + } + } + + #[inline] + fn implied_settings(&mut self, a: &Arg<'a, 'b>) { + if a.is_set(ArgSettings::Last) { + // if an arg has `Last` set, we need to imply DontCollapseArgsInUsage so that args + // in the usage string don't get confused or left out. + self.set(AS::DontCollapseArgsInUsage); + self.set(AS::ContainsLast); + } + if let Some(l) = a.s.long { + if l == "version" { + self.unset(AS::NeedsLongVersion); + } else if l == "help" { + self.unset(AS::NeedsLongHelp); + } + } + } + + // actually adds the arguments + pub fn add_arg(&mut self, a: Arg<'a, 'b>) { + // if it's global we have to clone anyways + if a.is_set(ArgSettings::Global) { + return self.add_arg_ref(&a); + } + debug_assert!(self.debug_asserts(&a)); + self.add_conditional_reqs(&a); + self.add_arg_groups(&a); + self.add_reqs(&a); + self.implied_settings(&a); + if a.index.is_some() || (a.s.short.is_none() && a.s.long.is_none()) { + let i = if a.index.is_none() { + (self.positionals.len() + 1) + } else { + a.index.unwrap() as usize + }; + self.positionals + .insert(i, PosBuilder::from_arg(a, i as u64)); + } else if a.is_set(ArgSettings::TakesValue) { + let mut ob = OptBuilder::from(a); + ob.s.unified_ord = self.flags.len() + self.opts.len(); + self.opts.push(ob); + } else { + let mut fb = FlagBuilder::from(a); + fb.s.unified_ord = self.flags.len() + self.opts.len(); + self.flags.push(fb); + } + } + // actually adds the arguments but from a borrow (which means we have to do some cloning) + pub fn add_arg_ref(&mut self, a: &Arg<'a, 'b>) { + debug_assert!(self.debug_asserts(a)); + self.add_conditional_reqs(a); + self.add_arg_groups(a); + self.add_reqs(a); + self.implied_settings(a); + if a.index.is_some() || (a.s.short.is_none() && a.s.long.is_none()) { + let i = if a.index.is_none() { + (self.positionals.len() + 1) + } else { + a.index.unwrap() as usize + }; + let pb = PosBuilder::from_arg_ref(a, i as u64); + self.positionals.insert(i, pb); + } else if a.is_set(ArgSettings::TakesValue) { + let mut ob = OptBuilder::from(a); + ob.s.unified_ord = self.flags.len() + self.opts.len(); + self.opts.push(ob); + } else { + let mut fb = FlagBuilder::from(a); + fb.s.unified_ord = self.flags.len() + self.opts.len(); + self.flags.push(fb); + } + if a.is_set(ArgSettings::Global) { + self.global_args.push(a.into()); + } + } + + pub fn add_group(&mut self, group: ArgGroup<'a>) { + if group.required { + self.required.push(group.name); + if let Some(ref reqs) = group.requires { + self.required.extend_from_slice(reqs); + } + // if let Some(ref bl) = group.conflicts { + // self.blacklist.extend_from_slice(bl); + // } + } + if self.groups.iter().any(|g| g.name == group.name) { + let grp = self.groups + .iter_mut() + .find(|g| g.name == group.name) + .expect(INTERNAL_ERROR_MSG); + grp.args.extend_from_slice(&group.args); + grp.requires = group.requires.clone(); + grp.conflicts = group.conflicts.clone(); + grp.required = group.required; + } else { + self.groups.push(group); + } + } + + pub fn add_subcommand(&mut self, mut subcmd: App<'a, 'b>) { + debugln!( + "Parser::add_subcommand: term_w={:?}, name={}", + self.meta.term_w, + subcmd.p.meta.name + ); + subcmd.p.meta.term_w = self.meta.term_w; + if subcmd.p.meta.name == "help" { + self.unset(AS::NeedsSubcommandHelp); + } + + self.subcommands.push(subcmd); + } + + pub fn propagate_settings(&mut self) { + debugln!( + "Parser::propagate_settings: self={}, g_settings={:#?}", + self.meta.name, + self.g_settings + ); + for sc in &mut self.subcommands { + debugln!( + "Parser::propagate_settings: sc={}, settings={:#?}, g_settings={:#?}", + sc.p.meta.name, + sc.p.settings, + sc.p.g_settings + ); + // We have to create a new scope in order to tell rustc the borrow of `sc` is + // done and to recursively call this method + { + let vsc = self.settings.is_set(AS::VersionlessSubcommands); + let gv = self.settings.is_set(AS::GlobalVersion); + + if vsc { + sc.p.set(AS::DisableVersion); + } + if gv && sc.p.meta.version.is_none() && self.meta.version.is_some() { + sc.p.set(AS::GlobalVersion); + sc.p.meta.version = Some(self.meta.version.unwrap()); + } + sc.p.settings = sc.p.settings | self.g_settings; + sc.p.g_settings = sc.p.g_settings | self.g_settings; + sc.p.meta.term_w = self.meta.term_w; + sc.p.meta.max_w = self.meta.max_w; + } + sc.p.propagate_settings(); + } + } + + #[cfg_attr(feature = "lints", allow(needless_borrow))] + pub fn derive_display_order(&mut self) { + if self.is_set(AS::DeriveDisplayOrder) { + let unified = self.is_set(AS::UnifiedHelpMessage); + for (i, o) in self.opts + .iter_mut() + .enumerate() + .filter(|&(_, ref o)| o.s.disp_ord == 999) + { + o.s.disp_ord = if unified { o.s.unified_ord } else { i }; + } + for (i, f) in self.flags + .iter_mut() + .enumerate() + .filter(|&(_, ref f)| f.s.disp_ord == 999) + { + f.s.disp_ord = if unified { f.s.unified_ord } else { i }; + } + for (i, sc) in &mut self.subcommands + .iter_mut() + .enumerate() + .filter(|&(_, ref sc)| sc.p.meta.disp_ord == 999) + { + sc.p.meta.disp_ord = i; + } + } + for sc in &mut self.subcommands { + sc.p.derive_display_order(); + } + } + + pub fn required(&self) -> Iter<&str> { self.required.iter() } + + #[cfg_attr(feature = "lints", allow(needless_borrow))] + #[inline] + pub fn has_args(&self) -> bool { + !(self.flags.is_empty() && self.opts.is_empty() && self.positionals.is_empty()) + } + + #[inline] + pub fn has_opts(&self) -> bool { !self.opts.is_empty() } + + #[inline] + pub fn has_flags(&self) -> bool { !self.flags.is_empty() } + + #[inline] + pub fn has_positionals(&self) -> bool { !self.positionals.is_empty() } + + #[inline] + pub fn has_subcommands(&self) -> bool { !self.subcommands.is_empty() } + + #[inline] + pub fn has_visible_opts(&self) -> bool { + if self.opts.is_empty() { + return false; + } + self.opts.iter().any(|o| !o.is_set(ArgSettings::Hidden)) + } + + #[inline] + pub fn has_visible_flags(&self) -> bool { + if self.flags.is_empty() { + return false; + } + self.flags.iter().any(|f| !f.is_set(ArgSettings::Hidden)) + } + + #[inline] + pub fn has_visible_positionals(&self) -> bool { + if self.positionals.is_empty() { + return false; + } + self.positionals + .values() + .any(|p| !p.is_set(ArgSettings::Hidden)) + } + + #[inline] + pub fn has_visible_subcommands(&self) -> bool { + self.has_subcommands() + && self.subcommands + .iter() + .filter(|sc| sc.p.meta.name != "help") + .any(|sc| !sc.p.is_set(AS::Hidden)) + } + + #[inline] + pub fn is_set(&self, s: AS) -> bool { self.settings.is_set(s) } + + #[inline] + pub fn set(&mut self, s: AS) { self.settings.set(s) } + + #[inline] + pub fn unset(&mut self, s: AS) { self.settings.unset(s) } + + #[cfg_attr(feature = "lints", allow(block_in_if_condition_stmt))] + pub fn verify_positionals(&self) -> bool { + // Because you must wait until all arguments have been supplied, this is the first chance + // to make assertions on positional argument indexes + // + // First we verify that the index highest supplied index, is equal to the number of + // positional arguments to verify there are no gaps (i.e. supplying an index of 1 and 3 + // but no 2) + if let Some((idx, p)) = self.positionals.iter().rev().next() { + assert!( + !(idx != self.positionals.len()), + "Found positional argument \"{}\" whose index is {} but there \ + are only {} positional arguments defined", + p.b.name, + idx, + self.positionals.len() + ); + } + + // Next we verify that only the highest index has a .multiple(true) (if any) + if self.positionals.values().any(|a| { + a.b.is_set(ArgSettings::Multiple) && (a.index as usize != self.positionals.len()) + }) { + let mut it = self.positionals.values().rev(); + let last = it.next().unwrap(); + let second_to_last = it.next().unwrap(); + // Either the final positional is required + // Or the second to last has a terminator or .last(true) set + let ok = last.is_set(ArgSettings::Required) + || (second_to_last.v.terminator.is_some() + || second_to_last.b.is_set(ArgSettings::Last)) + || last.is_set(ArgSettings::Last); + assert!( + ok, + "When using a positional argument with .multiple(true) that is *not the \ + last* positional argument, the last positional argument (i.e the one \ + with the highest index) *must* have .required(true) or .last(true) set." + ); + let ok = second_to_last.is_set(ArgSettings::Multiple) || last.is_set(ArgSettings::Last); + assert!( + ok, + "Only the last positional argument, or second to last positional \ + argument may be set to .multiple(true)" + ); + + let count = self.positionals + .values() + .filter(|p| p.b.settings.is_set(ArgSettings::Multiple) && p.v.num_vals.is_none()) + .count(); + let ok = count <= 1 + || (last.is_set(ArgSettings::Last) && last.is_set(ArgSettings::Multiple) + && second_to_last.is_set(ArgSettings::Multiple) + && count == 2); + assert!( + ok, + "Only one positional argument with .multiple(true) set is allowed per \ + command, unless the second one also has .last(true) set" + ); + } + + if self.is_set(AS::AllowMissingPositional) { + // Check that if a required positional argument is found, all positions with a lower + // index are also required. + let mut found = false; + let mut foundx2 = false; + for p in self.positionals.values().rev() { + if foundx2 && !p.b.settings.is_set(ArgSettings::Required) { + assert!( + p.b.is_set(ArgSettings::Required), + "Found positional argument which is not required with a lower \ + index than a required positional argument by two or more: {:?} \ + index {}", + p.b.name, + p.index + ); + } else if p.b.is_set(ArgSettings::Required) && !p.b.is_set(ArgSettings::Last) { + // Args that .last(true) don't count since they can be required and have + // positionals with a lower index that aren't required + // Imagine: prog [opt1] -- + // Both of these are valid invocations: + // $ prog r1 -- r2 + // $ prog r1 o1 -- r2 + if found { + foundx2 = true; + continue; + } + found = true; + continue; + } else { + found = false; + } + } + } else { + // Check that if a required positional argument is found, all positions with a lower + // index are also required + let mut found = false; + for p in self.positionals.values().rev() { + if found { + assert!( + p.b.is_set(ArgSettings::Required), + "Found positional argument which is not required with a lower \ + index than a required positional argument: {:?} index {}", + p.b.name, + p.index + ); + } else if p.b.is_set(ArgSettings::Required) && !p.b.is_set(ArgSettings::Last) { + // Args that .last(true) don't count since they can be required and have + // positionals with a lower index that aren't required + // Imagine: prog [opt1] -- + // Both of these are valid invocations: + // $ prog r1 -- r2 + // $ prog r1 o1 -- r2 + found = true; + continue; + } + } + } + if self.positionals + .values() + .any(|p| p.b.is_set(ArgSettings::Last) && p.b.is_set(ArgSettings::Required)) + && self.has_subcommands() && !self.is_set(AS::SubcommandsNegateReqs) + { + panic!( + "Having a required positional argument with .last(true) set *and* child \ + subcommands without setting SubcommandsNegateReqs isn't compatible." + ); + } + + true + } + + pub fn propagate_globals(&mut self) { + for sc in &mut self.subcommands { + // We have to create a new scope in order to tell rustc the borrow of `sc` is + // done and to recursively call this method + { + for a in &self.global_args { + sc.p.add_arg_ref(a); + } + } + sc.p.propagate_globals(); + } + } + + // Checks if the arg matches a subcommand name, or any of it's aliases (if defined) + fn possible_subcommand(&self, arg_os: &OsStr) -> (bool, Option<&str>) { + #[cfg(not(any(target_os = "windows", target_arch = "wasm32")))] + use std::os::unix::ffi::OsStrExt; + #[cfg(any(target_os = "windows", target_arch = "wasm32"))] + use osstringext::OsStrExt3; + debugln!("Parser::possible_subcommand: arg={:?}", arg_os); + fn starts(h: &str, n: &OsStr) -> bool { + let n_bytes = n.as_bytes(); + let h_bytes = OsStr::new(h).as_bytes(); + + h_bytes.starts_with(n_bytes) + } + + if self.is_set(AS::ArgsNegateSubcommands) && self.is_set(AS::ValidArgFound) { + return (false, None); + } + if !self.is_set(AS::InferSubcommands) { + if let Some(sc) = find_subcmd!(self, arg_os) { + return (true, Some(&sc.p.meta.name)); + } + } else { + let v = self.subcommands + .iter() + .filter(|s| { + starts(&s.p.meta.name[..], &*arg_os) + || (s.p.meta.aliases.is_some() + && s.p + .meta + .aliases + .as_ref() + .unwrap() + .iter() + .filter(|&&(a, _)| starts(a, &*arg_os)) + .count() == 1) + }) + .map(|sc| &sc.p.meta.name) + .collect::>(); + + for sc in &v { + if OsStr::new(sc) == arg_os { + return (true, Some(sc)); + } + } + + if v.len() == 1 { + return (true, Some(v[0])); + } + } + (false, None) + } + + fn parse_help_subcommand(&self, it: &mut I) -> ClapResult> + where + I: Iterator, + T: Into, + { + debugln!("Parser::parse_help_subcommand;"); + let cmds: Vec = it.map(|c| c.into()).collect(); + let mut help_help = false; + let mut bin_name = self.meta + .bin_name + .as_ref() + .unwrap_or(&self.meta.name) + .clone(); + let mut sc = { + let mut sc: &Parser = self; + for (i, cmd) in cmds.iter().enumerate() { + if &*cmd.to_string_lossy() == "help" { + // cmd help help + help_help = true; + } + if let Some(c) = sc.subcommands + .iter() + .find(|s| &*s.p.meta.name == cmd) + .map(|sc| &sc.p) + { + sc = c; + if i == cmds.len() - 1 { + break; + } + } else if let Some(c) = sc.subcommands + .iter() + .find(|s| { + if let Some(ref als) = s.p.meta.aliases { + als.iter().any(|&(a, _)| a == &*cmd.to_string_lossy()) + } else { + false + } + }) + .map(|sc| &sc.p) + { + sc = c; + if i == cmds.len() - 1 { + break; + } + } else { + return Err(Error::unrecognized_subcommand( + cmd.to_string_lossy().into_owned(), + self.meta.bin_name.as_ref().unwrap_or(&self.meta.name), + self.color(), + )); + } + bin_name = format!("{} {}", bin_name, &*sc.meta.name); + } + sc.clone() + }; + if help_help { + let mut pb = PosBuilder::new("subcommand", 1); + pb.b.help = Some("The subcommand whose help message to display"); + pb.set(ArgSettings::Multiple); + sc.positionals.insert(1, pb); + sc.settings = sc.settings | self.g_settings; + } else { + sc.create_help_and_version(); + } + if sc.meta.bin_name != self.meta.bin_name { + sc.meta.bin_name = Some(format!("{} {}", bin_name, sc.meta.name)); + } + Err(sc._help(false)) + } + + // allow wrong self convention due to self.valid_neg_num = true and it's a private method + #[cfg_attr(feature = "lints", allow(wrong_self_convention))] + fn is_new_arg(&mut self, arg_os: &OsStr, needs_val_of: ParseResult) -> bool { + debugln!("Parser::is_new_arg:{:?}:{:?}", arg_os, needs_val_of); + let app_wide_settings = if self.is_set(AS::AllowLeadingHyphen) { + true + } else if self.is_set(AS::AllowNegativeNumbers) { + let a = arg_os.to_string_lossy(); + if a.parse::().is_ok() || a.parse::().is_ok() { + self.set(AS::ValidNegNumFound); + true + } else { + false + } + } else { + false + }; + let arg_allows_tac = match needs_val_of { + ParseResult::Opt(name) => { + let o = self.opts + .iter() + .find(|o| o.b.name == name) + .expect(INTERNAL_ERROR_MSG); + (o.is_set(ArgSettings::AllowLeadingHyphen) || app_wide_settings) + } + ParseResult::Pos(name) => { + let p = self.positionals + .values() + .find(|p| p.b.name == name) + .expect(INTERNAL_ERROR_MSG); + (p.is_set(ArgSettings::AllowLeadingHyphen) || app_wide_settings) + } + ParseResult::ValuesDone => return true, + _ => false, + }; + debugln!("Parser::is_new_arg: arg_allows_tac={:?}", arg_allows_tac); + + // Is this a new argument, or values from a previous option? + let mut ret = if arg_os.starts_with(b"--") { + debugln!("Parser::is_new_arg: -- found"); + if arg_os.len() == 2 && !arg_allows_tac { + return true; // We have to return true so override everything else + } else if arg_allows_tac { + return false; + } + true + } else if arg_os.starts_with(b"-") { + debugln!("Parser::is_new_arg: - found"); + // a singe '-' by itself is a value and typically means "stdin" on unix systems + !(arg_os.len() == 1) + } else { + debugln!("Parser::is_new_arg: probably value"); + false + }; + + ret = ret && !arg_allows_tac; + + debugln!("Parser::is_new_arg: starts_new_arg={:?}", ret); + ret + } + + // The actual parsing function + #[cfg_attr(feature = "lints", allow(while_let_on_iterator, collapsible_if))] + pub fn get_matches_with( + &mut self, + matcher: &mut ArgMatcher<'a>, + it: &mut Peekable, + ) -> ClapResult<()> + where + I: Iterator, + T: Into + Clone, + { + debugln!("Parser::get_matches_with;"); + // Verify all positional assertions pass + debug_assert!(self.app_debug_asserts()); + if self.positionals.values().any(|a| { + a.b.is_set(ArgSettings::Multiple) && (a.index as usize != self.positionals.len()) + }) + && self.positionals + .values() + .last() + .map_or(false, |p| !p.is_set(ArgSettings::Last)) + { + self.settings.set(AS::LowIndexMultiplePositional); + } + let has_args = self.has_args(); + + // Next we create the `--help` and `--version` arguments and add them if + // necessary + self.create_help_and_version(); + + let mut subcmd_name: Option = None; + let mut needs_val_of: ParseResult<'a> = ParseResult::NotFound; + let mut pos_counter = 1; + let mut sc_is_external = false; + while let Some(arg) = it.next() { + let arg_os = arg.into(); + debugln!( + "Parser::get_matches_with: Begin parsing '{:?}' ({:?})", + arg_os, + &*arg_os.as_bytes() + ); + + self.unset(AS::ValidNegNumFound); + // Is this a new argument, or values from a previous option? + let starts_new_arg = self.is_new_arg(&arg_os, needs_val_of); + if !self.is_set(AS::TrailingValues) && arg_os.starts_with(b"--") && arg_os.len() == 2 + && starts_new_arg + { + debugln!("Parser::get_matches_with: setting TrailingVals=true"); + self.set(AS::TrailingValues); + continue; + } + + // Has the user already passed '--'? Meaning only positional args follow + if !self.is_set(AS::TrailingValues) { + // Does the arg match a subcommand name, or any of it's aliases (if defined) + { + match needs_val_of { + ParseResult::Opt(_) | ParseResult::Pos(_) => (), + _ => { + let (is_match, sc_name) = self.possible_subcommand(&arg_os); + debugln!( + "Parser::get_matches_with: possible_sc={:?}, sc={:?}", + is_match, + sc_name + ); + if is_match { + let sc_name = sc_name.expect(INTERNAL_ERROR_MSG); + if sc_name == "help" && self.is_set(AS::NeedsSubcommandHelp) { + self.parse_help_subcommand(it)?; + } + subcmd_name = Some(sc_name.to_owned()); + break; + } + } + } + } + + if starts_new_arg { + let check_all = self.is_set(AS::AllArgsOverrideSelf); + { + let any_arg = find_any_by_name!(self, self.cache.unwrap_or("")); + matcher.process_arg_overrides( + any_arg, + &mut self.overrides, + &mut self.required, + check_all, + ); + } + + if arg_os.starts_with(b"--") { + needs_val_of = self.parse_long_arg(matcher, &arg_os, it)?; + debugln!( + "Parser:get_matches_with: After parse_long_arg {:?}", + needs_val_of + ); + match needs_val_of { + ParseResult::Flag | ParseResult::Opt(..) | ParseResult::ValuesDone => { + continue + } + _ => (), + } + } else if arg_os.starts_with(b"-") && arg_os.len() != 1 { + // Try to parse short args like normal, if AllowLeadingHyphen or + // AllowNegativeNumbers is set, parse_short_arg will *not* throw + // an error, and instead return Ok(None) + needs_val_of = self.parse_short_arg(matcher, &arg_os)?; + // If it's None, we then check if one of those two AppSettings was set + debugln!( + "Parser:get_matches_with: After parse_short_arg {:?}", + needs_val_of + ); + match needs_val_of { + ParseResult::MaybeNegNum => { + if !(arg_os.to_string_lossy().parse::().is_ok() + || arg_os.to_string_lossy().parse::().is_ok()) + { + return Err(Error::unknown_argument( + &*arg_os.to_string_lossy(), + "", + &*usage::create_error_usage(self, matcher, None), + self.color(), + )); + } + } + ParseResult::Opt(..) | ParseResult::Flag | ParseResult::ValuesDone => { + continue + } + _ => (), + } + } + } else { + if let ParseResult::Opt(name) = needs_val_of { + // Check to see if parsing a value from a previous arg + let arg = self.opts + .iter() + .find(|o| o.b.name == name) + .expect(INTERNAL_ERROR_MSG); + // get the OptBuilder so we can check the settings + needs_val_of = self.add_val_to_arg(arg, &arg_os, matcher)?; + // get the next value from the iterator + continue; + } + } + } + + if !(self.is_set(AS::ArgsNegateSubcommands) && self.is_set(AS::ValidArgFound)) + && !self.is_set(AS::InferSubcommands) && !self.is_set(AS::AllowExternalSubcommands) + { + if let Some(cdate) = + suggestions::did_you_mean(&*arg_os.to_string_lossy(), sc_names!(self)) + { + return Err(Error::invalid_subcommand( + arg_os.to_string_lossy().into_owned(), + cdate, + self.meta.bin_name.as_ref().unwrap_or(&self.meta.name), + &*usage::create_error_usage(self, matcher, None), + self.color(), + )); + } + } + + let low_index_mults = self.is_set(AS::LowIndexMultiplePositional) + && pos_counter == (self.positionals.len() - 1); + let missing_pos = self.is_set(AS::AllowMissingPositional) + && (pos_counter == (self.positionals.len() - 1) + && !self.is_set(AS::TrailingValues)); + debugln!( + "Parser::get_matches_with: Positional counter...{}", + pos_counter + ); + debugln!( + "Parser::get_matches_with: Low index multiples...{:?}", + low_index_mults + ); + if low_index_mults || missing_pos { + if let Some(na) = it.peek() { + let n = (*na).clone().into(); + needs_val_of = if needs_val_of != ParseResult::ValuesDone { + if let Some(p) = self.positionals.get(pos_counter) { + ParseResult::Pos(p.b.name) + } else { + ParseResult::ValuesDone + } + } else { + ParseResult::ValuesDone + }; + let sc_match = { self.possible_subcommand(&n).0 }; + if self.is_new_arg(&n, needs_val_of) || sc_match + || suggestions::did_you_mean(&n.to_string_lossy(), sc_names!(self)) + .is_some() + { + debugln!("Parser::get_matches_with: Bumping the positional counter..."); + pos_counter += 1; + } + } else { + debugln!("Parser::get_matches_with: Bumping the positional counter..."); + pos_counter += 1; + } + } else if (self.is_set(AS::AllowMissingPositional) && self.is_set(AS::TrailingValues)) + || (self.is_set(AS::ContainsLast) && self.is_set(AS::TrailingValues)) + { + // Came to -- and one postional has .last(true) set, so we go immediately + // to the last (highest index) positional + debugln!("Parser::get_matches_with: .last(true) and --, setting last pos"); + pos_counter = self.positionals.len(); + } + if let Some(p) = self.positionals.get(pos_counter) { + if p.is_set(ArgSettings::Last) && !self.is_set(AS::TrailingValues) { + return Err(Error::unknown_argument( + &*arg_os.to_string_lossy(), + "", + &*usage::create_error_usage(self, matcher, None), + self.color(), + )); + } + if !self.is_set(AS::TrailingValues) + && (self.is_set(AS::TrailingVarArg) && pos_counter == self.positionals.len()) + { + self.settings.set(AS::TrailingValues); + } + if self.cache.map_or(true, |name| name != p.b.name) { + let check_all = self.is_set(AS::AllArgsOverrideSelf); + { + let any_arg = find_any_by_name!(self, self.cache.unwrap_or("")); + matcher.process_arg_overrides( + any_arg, + &mut self.overrides, + &mut self.required, + check_all, + ); + } + self.cache = Some(p.b.name); + } + let _ = self.add_val_to_arg(p, &arg_os, matcher)?; + + matcher.inc_occurrence_of(p.b.name); + let _ = self.groups_for_arg(p.b.name) + .and_then(|vec| Some(matcher.inc_occurrences_of(&*vec))); + + self.settings.set(AS::ValidArgFound); + // Only increment the positional counter if it doesn't allow multiples + if !p.b.settings.is_set(ArgSettings::Multiple) { + pos_counter += 1; + } + self.settings.set(AS::ValidArgFound); + } else if self.is_set(AS::AllowExternalSubcommands) { + // Get external subcommand name + let sc_name = match arg_os.to_str() { + Some(s) => s.to_string(), + None => { + if !self.is_set(AS::StrictUtf8) { + return Err(Error::invalid_utf8( + &*usage::create_error_usage(self, matcher, None), + self.color(), + )); + } + arg_os.to_string_lossy().into_owned() + } + }; + + // Collect the external subcommand args + let mut sc_m = ArgMatcher::new(); + while let Some(v) = it.next() { + let a = v.into(); + if a.to_str().is_none() && !self.is_set(AS::StrictUtf8) { + return Err(Error::invalid_utf8( + &*usage::create_error_usage(self, matcher, None), + self.color(), + )); + } + sc_m.add_val_to("", &a); + } + + matcher.subcommand(SubCommand { + name: sc_name, + matches: sc_m.into(), + }); + sc_is_external = true; + } else if !((self.is_set(AS::AllowLeadingHyphen) + || self.is_set(AS::AllowNegativeNumbers)) + && arg_os.starts_with(b"-")) + && !self.is_set(AS::InferSubcommands) + { + return Err(Error::unknown_argument( + &*arg_os.to_string_lossy(), + "", + &*usage::create_error_usage(self, matcher, None), + self.color(), + )); + } else if !has_args || self.is_set(AS::InferSubcommands) && self.has_subcommands() { + if let Some(cdate) = + suggestions::did_you_mean(&*arg_os.to_string_lossy(), sc_names!(self)) + { + return Err(Error::invalid_subcommand( + arg_os.to_string_lossy().into_owned(), + cdate, + self.meta.bin_name.as_ref().unwrap_or(&self.meta.name), + &*usage::create_error_usage(self, matcher, None), + self.color(), + )); + } else { + return Err(Error::unrecognized_subcommand( + arg_os.to_string_lossy().into_owned(), + self.meta.bin_name.as_ref().unwrap_or(&self.meta.name), + self.color(), + )); + } + } else { + return Err(Error::unknown_argument( + &*arg_os.to_string_lossy(), + "", + &*usage::create_error_usage(self, matcher, None), + self.color(), + )); + } + } + + if !sc_is_external { + if let Some(ref pos_sc_name) = subcmd_name { + let sc_name = { + find_subcmd!(self, pos_sc_name) + .expect(INTERNAL_ERROR_MSG) + .p + .meta + .name + .clone() + }; + self.parse_subcommand(&*sc_name, matcher, it)?; + } else if self.is_set(AS::SubcommandRequired) { + let bn = self.meta.bin_name.as_ref().unwrap_or(&self.meta.name); + return Err(Error::missing_subcommand( + bn, + &usage::create_error_usage(self, matcher, None), + self.color(), + )); + } else if self.is_set(AS::SubcommandRequiredElseHelp) { + debugln!("Parser::get_matches_with: SubcommandRequiredElseHelp=true"); + let mut out = vec![]; + self.write_help_err(&mut out)?; + return Err(Error { + message: String::from_utf8_lossy(&*out).into_owned(), + kind: ErrorKind::MissingArgumentOrSubcommand, + info: None, + }); + } + } + + // In case the last arg was new, we need to process it's overrides + let check_all = self.is_set(AS::AllArgsOverrideSelf); + { + let any_arg = find_any_by_name!(self, self.cache.unwrap_or("")); + matcher.process_arg_overrides( + any_arg, + &mut self.overrides, + &mut self.required, + check_all, + ); + } + + self.remove_overrides(matcher); + + Validator::new(self).validate(needs_val_of, subcmd_name, matcher) + } + + fn remove_overrides(&mut self, matcher: &mut ArgMatcher) { + debugln!("Parser::remove_overrides:{:?};", self.overrides); + for &(overr, name) in &self.overrides { + debugln!("Parser::remove_overrides:iter:({},{});", overr, name); + if matcher.is_present(overr) { + debugln!( + "Parser::remove_overrides:iter:({},{}): removing {};", + overr, + name, + name + ); + matcher.remove(name); + for i in (0..self.required.len()).rev() { + debugln!( + "Parser::remove_overrides:iter:({},{}): removing required {};", + overr, + name, + name + ); + if self.required[i] == name { + self.required.swap_remove(i); + break; + } + } + } + } + } + + fn propagate_help_version(&mut self) { + debugln!("Parser::propagate_help_version;"); + self.create_help_and_version(); + for sc in &mut self.subcommands { + sc.p.propagate_help_version(); + } + } + + fn build_bin_names(&mut self) { + debugln!("Parser::build_bin_names;"); + for sc in &mut self.subcommands { + debug!("Parser::build_bin_names:iter: bin_name set..."); + if sc.p.meta.bin_name.is_none() { + sdebugln!("No"); + let bin_name = format!( + "{}{}{}", + self.meta + .bin_name + .as_ref() + .unwrap_or(&self.meta.name.clone()), + if self.meta.bin_name.is_some() { + " " + } else { + "" + }, + &*sc.p.meta.name + ); + debugln!( + "Parser::build_bin_names:iter: Setting bin_name of {} to {}", + self.meta.name, + bin_name + ); + sc.p.meta.bin_name = Some(bin_name); + } else { + sdebugln!("yes ({:?})", sc.p.meta.bin_name); + } + debugln!( + "Parser::build_bin_names:iter: Calling build_bin_names from...{}", + sc.p.meta.name + ); + sc.p.build_bin_names(); + } + } + + fn parse_subcommand( + &mut self, + sc_name: &str, + matcher: &mut ArgMatcher<'a>, + it: &mut Peekable, + ) -> ClapResult<()> + where + I: Iterator, + T: Into + Clone, + { + use std::fmt::Write; + debugln!("Parser::parse_subcommand;"); + let mut mid_string = String::new(); + if !self.is_set(AS::SubcommandsNegateReqs) { + let mut hs: Vec<&str> = self.required.iter().map(|n| &**n).collect(); + for k in matcher.arg_names() { + hs.push(k); + } + let reqs = usage::get_required_usage_from(self, &hs, Some(matcher), None, false); + + for s in &reqs { + write!(&mut mid_string, " {}", s).expect(INTERNAL_ERROR_MSG); + } + } + mid_string.push_str(" "); + if let Some(ref mut sc) = self.subcommands + .iter_mut() + .find(|s| s.p.meta.name == sc_name) + { + let mut sc_matcher = ArgMatcher::new(); + // bin_name should be parent's bin_name + [] + the sc's name separated by + // a space + sc.p.meta.usage = Some(format!( + "{}{}{}", + self.meta.bin_name.as_ref().unwrap_or(&String::new()), + if self.meta.bin_name.is_some() { + &*mid_string + } else { + "" + }, + &*sc.p.meta.name + )); + sc.p.meta.bin_name = Some(format!( + "{}{}{}", + self.meta.bin_name.as_ref().unwrap_or(&String::new()), + if self.meta.bin_name.is_some() { + " " + } else { + "" + }, + &*sc.p.meta.name + )); + debugln!( + "Parser::parse_subcommand: About to parse sc={}", + sc.p.meta.name + ); + debugln!("Parser::parse_subcommand: sc settings={:#?}", sc.p.settings); + sc.p.get_matches_with(&mut sc_matcher, it)?; + matcher.subcommand(SubCommand { + name: sc.p.meta.name.clone(), + matches: sc_matcher.into(), + }); + } + Ok(()) + } + + pub fn groups_for_arg(&self, name: &str) -> Option> { + debugln!("Parser::groups_for_arg: name={}", name); + + if self.groups.is_empty() { + debugln!("Parser::groups_for_arg: No groups defined"); + return None; + } + let mut res = vec![]; + debugln!("Parser::groups_for_arg: Searching through groups..."); + for grp in &self.groups { + for a in &grp.args { + if a == &name { + sdebugln!("\tFound '{}'", grp.name); + res.push(&*grp.name); + } + } + } + if res.is_empty() { + return None; + } + + Some(res) + } + + pub fn args_in_group(&self, group: &str) -> Vec { + debug_assert!(self.app_debug_asserts()); + + let mut g_vec = vec![]; + let mut args = vec![]; + + for n in &self.groups + .iter() + .find(|g| g.name == group) + .expect(INTERNAL_ERROR_MSG) + .args + { + if let Some(f) = self.flags.iter().find(|f| &f.b.name == n) { + args.push(f.to_string()); + } else if let Some(f) = self.opts.iter().find(|o| &o.b.name == n) { + args.push(f.to_string()); + } else if let Some(p) = self.positionals.values().find(|p| &p.b.name == n) { + args.push(p.b.name.to_owned()); + } else { + g_vec.push(*n); + } + } + + for av in g_vec.iter().map(|g| self.args_in_group(g)) { + args.extend(av); + } + args.dedup(); + args.iter().map(ToOwned::to_owned).collect() + } + + pub fn arg_names_in_group(&self, group: &str) -> Vec<&'a str> { + let mut g_vec = vec![]; + let mut args = vec![]; + + for n in &self.groups + .iter() + .find(|g| g.name == group) + .expect(INTERNAL_ERROR_MSG) + .args + { + if self.groups.iter().any(|g| g.name == *n) { + args.extend(self.arg_names_in_group(n)); + g_vec.push(*n); + } else if !args.contains(n) { + args.push(*n); + } + } + + args.iter().map(|s| *s).collect() + } + + pub fn create_help_and_version(&mut self) { + debugln!("Parser::create_help_and_version;"); + // name is "hclap_help" because flags are sorted by name + if !self.is_set(AS::DisableHelpFlags) && !self.contains_long("help") { + debugln!("Parser::create_help_and_version: Building --help"); + if self.help_short.is_none() && !self.contains_short('h') { + self.help_short = Some('h'); + } + let arg = FlagBuilder { + b: Base { + name: "hclap_help", + help: self.help_message.or(Some("Prints help information")), + ..Default::default() + }, + s: Switched { + short: self.help_short, + long: Some("help"), + ..Default::default() + }, + }; + self.flags.push(arg); + } + if !self.is_set(AS::DisableVersion) && !self.contains_long("version") { + debugln!("Parser::create_help_and_version: Building --version"); + if self.version_short.is_none() && !self.contains_short('V') { + self.version_short = Some('V'); + } + // name is "vclap_version" because flags are sorted by name + let arg = FlagBuilder { + b: Base { + name: "vclap_version", + help: self.version_message.or(Some("Prints version information")), + ..Default::default() + }, + s: Switched { + short: self.version_short, + long: Some("version"), + ..Default::default() + }, + }; + self.flags.push(arg); + } + if !self.subcommands.is_empty() && !self.is_set(AS::DisableHelpSubcommand) + && self.is_set(AS::NeedsSubcommandHelp) + { + debugln!("Parser::create_help_and_version: Building help"); + self.subcommands.push( + App::new("help") + .about("Prints this message or the help of the given subcommand(s)"), + ); + } + } + + // Retrieves the names of all args the user has supplied thus far, except required ones + // because those will be listed in self.required + fn check_for_help_and_version_str(&self, arg: &OsStr) -> ClapResult<()> { + debugln!("Parser::check_for_help_and_version_str;"); + debug!( + "Parser::check_for_help_and_version_str: Checking if --{} is help or version...", + arg.to_str().unwrap() + ); + if arg == "help" && self.is_set(AS::NeedsLongHelp) { + sdebugln!("Help"); + return Err(self._help(true)); + } + if arg == "version" && self.is_set(AS::NeedsLongVersion) { + sdebugln!("Version"); + return Err(self._version(true)); + } + sdebugln!("Neither"); + + Ok(()) + } + + fn check_for_help_and_version_char(&self, arg: char) -> ClapResult<()> { + debugln!("Parser::check_for_help_and_version_char;"); + debug!( + "Parser::check_for_help_and_version_char: Checking if -{} is help or version...", + arg + ); + if let Some(h) = self.help_short { + if arg == h && self.is_set(AS::NeedsLongHelp) { + sdebugln!("Help"); + return Err(self._help(false)); + } + } + if let Some(v) = self.version_short { + if arg == v && self.is_set(AS::NeedsLongVersion) { + sdebugln!("Version"); + return Err(self._version(false)); + } + } + sdebugln!("Neither"); + Ok(()) + } + + fn use_long_help(&self) -> bool { + // In this case, both must be checked. This allows the retention of + // original formatting, but also ensures that the actual -h or --help + // specified by the user is sent through. If HiddenShortHelp is not included, + // then items specified with hidden_short_help will also be hidden. + let should_long = |v: &Base| { + v.long_help.is_some() || + v.is_set(ArgSettings::HiddenLongHelp) || + v.is_set(ArgSettings::HiddenShortHelp) + }; + + self.meta.long_about.is_some() + || self.flags.iter().any(|f| should_long(&f.b)) + || self.opts.iter().any(|o| should_long(&o.b)) + || self.positionals.values().any(|p| should_long(&p.b)) + || self.subcommands + .iter() + .any(|s| s.p.meta.long_about.is_some()) + } + + fn _help(&self, mut use_long: bool) -> Error { + debugln!("Parser::_help: use_long={:?}", use_long); + use_long = use_long && self.use_long_help(); + let mut buf = vec![]; + match Help::write_parser_help(&mut buf, self, use_long) { + Err(e) => e, + _ => Error { + message: String::from_utf8(buf).unwrap_or_default(), + kind: ErrorKind::HelpDisplayed, + info: None, + }, + } + } + + fn _version(&self, use_long: bool) -> Error { + debugln!("Parser::_version: "); + let out = io::stdout(); + let mut buf_w = BufWriter::new(out.lock()); + match self.print_version(&mut buf_w, use_long) { + Err(e) => e, + _ => Error { + message: String::new(), + kind: ErrorKind::VersionDisplayed, + info: None, + }, + } + } + + fn parse_long_arg( + &mut self, + matcher: &mut ArgMatcher<'a>, + full_arg: &OsStr, + it: &mut Peekable, + ) -> ClapResult> + where + I: Iterator, + T: Into + Clone, + { + // maybe here lifetime should be 'a + debugln!("Parser::parse_long_arg;"); + + // Update the current index + self.cur_idx.set(self.cur_idx.get() + 1); + + let mut val = None; + debug!("Parser::parse_long_arg: Does it contain '='..."); + let arg = if full_arg.contains_byte(b'=') { + let (p0, p1) = full_arg.trim_left_matches(b'-').split_at_byte(b'='); + sdebugln!("Yes '{:?}'", p1); + val = Some(p1); + p0 + } else { + sdebugln!("No"); + full_arg.trim_left_matches(b'-') + }; + + if let Some(opt) = find_opt_by_long!(@os self, arg) { + debugln!( + "Parser::parse_long_arg: Found valid opt '{}'", + opt.to_string() + ); + self.settings.set(AS::ValidArgFound); + let ret = self.parse_opt(val, opt, val.is_some(), matcher)?; + if self.cache.map_or(true, |name| name != opt.b.name) { + self.cache = Some(opt.b.name); + } + + return Ok(ret); + } else if let Some(flag) = find_flag_by_long!(@os self, arg) { + debugln!( + "Parser::parse_long_arg: Found valid flag '{}'", + flag.to_string() + ); + self.settings.set(AS::ValidArgFound); + // Only flags could be help or version, and we need to check the raw long + // so this is the first point to check + self.check_for_help_and_version_str(arg)?; + + self.parse_flag(flag, matcher)?; + + // Handle conflicts, requirements, etc. + if self.cache.map_or(true, |name| name != flag.b.name) { + self.cache = Some(flag.b.name); + } + + return Ok(ParseResult::Flag); + } else if self.is_set(AS::AllowLeadingHyphen) { + return Ok(ParseResult::MaybeHyphenValue); + } else if self.is_set(AS::ValidNegNumFound) { + return Ok(ParseResult::MaybeNegNum); + } + + debugln!("Parser::parse_long_arg: Didn't match anything"); + + let args_rest: Vec<_> = it.map(|x| x.clone().into()).collect(); + let args_rest2: Vec<_> = args_rest.iter().map(|x| x.to_str().expect(INVALID_UTF8)).collect(); + self.did_you_mean_error( + arg.to_str().expect(INVALID_UTF8), + matcher, + &args_rest2[..] + ).map(|_| ParseResult::NotFound) + } + + #[cfg_attr(feature = "lints", allow(len_zero))] + fn parse_short_arg( + &mut self, + matcher: &mut ArgMatcher<'a>, + full_arg: &OsStr, + ) -> ClapResult> { + debugln!("Parser::parse_short_arg: full_arg={:?}", full_arg); + let arg_os = full_arg.trim_left_matches(b'-'); + let arg = arg_os.to_string_lossy(); + + // If AllowLeadingHyphen is set, we want to ensure `-val` gets parsed as `-val` and not + // `-v` `-a` `-l` assuming `v` `a` and `l` are all, or mostly, valid shorts. + if self.is_set(AS::AllowLeadingHyphen) { + if arg.chars().any(|c| !self.contains_short(c)) { + debugln!( + "Parser::parse_short_arg: LeadingHyphenAllowed yet -{} isn't valid", + arg + ); + return Ok(ParseResult::MaybeHyphenValue); + } + } else if self.is_set(AS::ValidNegNumFound) { + // TODO: Add docs about having AllowNegativeNumbers and `-2` as a valid short + // May be better to move this to *after* not finding a valid flag/opt? + debugln!("Parser::parse_short_arg: Valid negative num..."); + return Ok(ParseResult::MaybeNegNum); + } + + let mut ret = ParseResult::NotFound; + for c in arg.chars() { + debugln!("Parser::parse_short_arg:iter:{}", c); + + // update each index because `-abcd` is four indices to clap + self.cur_idx.set(self.cur_idx.get() + 1); + + // Check for matching short options, and return the name if there is no trailing + // concatenated value: -oval + // Option: -o + // Value: val + if let Some(opt) = find_opt_by_short!(self, c) { + debugln!("Parser::parse_short_arg:iter:{}: Found valid opt", c); + self.settings.set(AS::ValidArgFound); + // Check for trailing concatenated value + let p: Vec<_> = arg.splitn(2, c).collect(); + debugln!( + "Parser::parse_short_arg:iter:{}: p[0]={:?}, p[1]={:?}", + c, + p[0].as_bytes(), + p[1].as_bytes() + ); + let i = p[0].as_bytes().len() + 1; + let val = if p[1].as_bytes().len() > 0 { + debugln!( + "Parser::parse_short_arg:iter:{}: val={:?} (bytes), val={:?} (ascii)", + c, + arg_os.split_at(i).1.as_bytes(), + arg_os.split_at(i).1 + ); + Some(arg_os.split_at(i).1) + } else { + None + }; + + // Default to "we're expecting a value later" + let ret = self.parse_opt(val, opt, false, matcher)?; + + if self.cache.map_or(true, |name| name != opt.b.name) { + self.cache = Some(opt.b.name); + } + + return Ok(ret); + } else if let Some(flag) = find_flag_by_short!(self, c) { + debugln!("Parser::parse_short_arg:iter:{}: Found valid flag", c); + self.settings.set(AS::ValidArgFound); + // Only flags can be help or version + self.check_for_help_and_version_char(c)?; + ret = self.parse_flag(flag, matcher)?; + + // Handle conflicts, requirements, overrides, etc. + // Must be called here due to mutabililty + if self.cache.map_or(true, |name| name != flag.b.name) { + self.cache = Some(flag.b.name); + } + } else { + let arg = format!("-{}", c); + return Err(Error::unknown_argument( + &*arg, + "", + &*usage::create_error_usage(self, matcher, None), + self.color(), + )); + } + } + Ok(ret) + } + + fn parse_opt( + &self, + val: Option<&OsStr>, + opt: &OptBuilder<'a, 'b>, + had_eq: bool, + matcher: &mut ArgMatcher<'a>, + ) -> ClapResult> { + debugln!("Parser::parse_opt; opt={}, val={:?}", opt.b.name, val); + debugln!("Parser::parse_opt; opt.settings={:?}", opt.b.settings); + let mut has_eq = false; + let no_val = val.is_none(); + let empty_vals = opt.is_set(ArgSettings::EmptyValues); + let min_vals_zero = opt.v.min_vals.unwrap_or(1) == 0; + let needs_eq = opt.is_set(ArgSettings::RequireEquals); + + debug!("Parser::parse_opt; Checking for val..."); + if let Some(fv) = val { + has_eq = fv.starts_with(&[b'=']) || had_eq; + let v = fv.trim_left_matches(b'='); + if !empty_vals && (v.len() == 0 || (needs_eq && !has_eq)) { + sdebugln!("Found Empty - Error"); + return Err(Error::empty_value( + opt, + &*usage::create_error_usage(self, matcher, None), + self.color(), + )); + } + sdebugln!("Found - {:?}, len: {}", v, v.len()); + debugln!( + "Parser::parse_opt: {:?} contains '='...{:?}", + fv, + fv.starts_with(&[b'=']) + ); + self.add_val_to_arg(opt, v, matcher)?; + } else if needs_eq && !(empty_vals || min_vals_zero) { + sdebugln!("None, but requires equals...Error"); + return Err(Error::empty_value( + opt, + &*usage::create_error_usage(self, matcher, None), + self.color(), + )); + } else { + sdebugln!("None"); + } + + matcher.inc_occurrence_of(opt.b.name); + // Increment or create the group "args" + self.groups_for_arg(opt.b.name) + .and_then(|vec| Some(matcher.inc_occurrences_of(&*vec))); + + let needs_delim = opt.is_set(ArgSettings::RequireDelimiter); + let mult = opt.is_set(ArgSettings::Multiple); + if no_val && min_vals_zero && !has_eq && needs_eq { + debugln!("Parser::parse_opt: More arg vals not required..."); + return Ok(ParseResult::ValuesDone); + } else if no_val || (mult && !needs_delim) && !has_eq && matcher.needs_more_vals(opt) { + debugln!("Parser::parse_opt: More arg vals required..."); + return Ok(ParseResult::Opt(opt.b.name)); + } + debugln!("Parser::parse_opt: More arg vals not required..."); + Ok(ParseResult::ValuesDone) + } + + fn add_val_to_arg( + &self, + arg: &A, + val: &OsStr, + matcher: &mut ArgMatcher<'a>, + ) -> ClapResult> + where + A: AnyArg<'a, 'b> + Display, + { + debugln!("Parser::add_val_to_arg; arg={}, val={:?}", arg.name(), val); + debugln!( + "Parser::add_val_to_arg; trailing_vals={:?}, DontDelimTrailingVals={:?}", + self.is_set(AS::TrailingValues), + self.is_set(AS::DontDelimitTrailingValues) + ); + if !(self.is_set(AS::TrailingValues) && self.is_set(AS::DontDelimitTrailingValues)) { + if let Some(delim) = arg.val_delim() { + if val.is_empty() { + Ok(self.add_single_val_to_arg(arg, val, matcher)?) + } else { + let mut iret = ParseResult::ValuesDone; + for v in val.split(delim as u32 as u8) { + iret = self.add_single_val_to_arg(arg, v, matcher)?; + } + // If there was a delimiter used, we're not looking for more values + if val.contains_byte(delim as u32 as u8) + || arg.is_set(ArgSettings::RequireDelimiter) + { + iret = ParseResult::ValuesDone; + } + Ok(iret) + } + } else { + self.add_single_val_to_arg(arg, val, matcher) + } + } else { + self.add_single_val_to_arg(arg, val, matcher) + } + } + + fn add_single_val_to_arg( + &self, + arg: &A, + v: &OsStr, + matcher: &mut ArgMatcher<'a>, + ) -> ClapResult> + where + A: AnyArg<'a, 'b> + Display, + { + debugln!("Parser::add_single_val_to_arg;"); + debugln!("Parser::add_single_val_to_arg: adding val...{:?}", v); + + // update the current index because each value is a distinct index to clap + self.cur_idx.set(self.cur_idx.get() + 1); + + // @TODO @docs @p4: docs for indices should probably note that a terminator isn't a value + // and therefore not reported in indices + if let Some(t) = arg.val_terminator() { + if t == v { + return Ok(ParseResult::ValuesDone); + } + } + + matcher.add_val_to(arg.name(), v); + matcher.add_index_to(arg.name(), self.cur_idx.get()); + + // Increment or create the group "args" + if let Some(grps) = self.groups_for_arg(arg.name()) { + for grp in grps { + matcher.add_val_to(&*grp, v); + } + } + + if matcher.needs_more_vals(arg) { + return Ok(ParseResult::Opt(arg.name())); + } + Ok(ParseResult::ValuesDone) + } + + fn parse_flag( + &self, + flag: &FlagBuilder<'a, 'b>, + matcher: &mut ArgMatcher<'a>, + ) -> ClapResult> { + debugln!("Parser::parse_flag;"); + + matcher.inc_occurrence_of(flag.b.name); + matcher.add_index_to(flag.b.name, self.cur_idx.get()); + + // Increment or create the group "args" + self.groups_for_arg(flag.b.name) + .and_then(|vec| Some(matcher.inc_occurrences_of(&*vec))); + + Ok(ParseResult::Flag) + } + + fn did_you_mean_error(&self, arg: &str, matcher: &mut ArgMatcher<'a>, args_rest: &[&str]) -> ClapResult<()> { + // Didn't match a flag or option + let suffix = suggestions::did_you_mean_flag_suffix(arg, &args_rest, longs!(self), &self.subcommands); + + // Add the arg to the matches to build a proper usage string + if let Some(name) = suffix.1 { + if let Some(opt) = find_opt_by_long!(self, name) { + self.groups_for_arg(&*opt.b.name) + .and_then(|grps| Some(matcher.inc_occurrences_of(&*grps))); + matcher.insert(&*opt.b.name); + } else if let Some(flg) = find_flag_by_long!(self, name) { + self.groups_for_arg(&*flg.b.name) + .and_then(|grps| Some(matcher.inc_occurrences_of(&*grps))); + matcher.insert(&*flg.b.name); + } + } + + let used_arg = format!("--{}", arg); + Err(Error::unknown_argument( + &*used_arg, + &*suffix.0, + &*usage::create_error_usage(self, matcher, None), + self.color(), + )) + } + + // Prints the version to the user and exits if quit=true + fn print_version(&self, w: &mut W, use_long: bool) -> ClapResult<()> { + self.write_version(w, use_long)?; + w.flush().map_err(Error::from) + } + + pub fn write_version(&self, w: &mut W, use_long: bool) -> io::Result<()> { + let ver = if use_long { + self.meta + .long_version + .unwrap_or_else(|| self.meta.version.unwrap_or("")) + } else { + self.meta + .version + .unwrap_or_else(|| self.meta.long_version.unwrap_or("")) + }; + if let Some(bn) = self.meta.bin_name.as_ref() { + if bn.contains(' ') { + // Incase we're dealing with subcommands i.e. git mv is translated to git-mv + write!(w, "{} {}", bn.replace(" ", "-"), ver) + } else { + write!(w, "{} {}", &self.meta.name[..], ver) + } + } else { + write!(w, "{} {}", &self.meta.name[..], ver) + } + } + + pub fn print_help(&self) -> ClapResult<()> { + let out = io::stdout(); + let mut buf_w = BufWriter::new(out.lock()); + self.write_help(&mut buf_w) + } + + pub fn write_help(&self, w: &mut W) -> ClapResult<()> { + Help::write_parser_help(w, self, false) + } + + pub fn write_long_help(&self, w: &mut W) -> ClapResult<()> { + Help::write_parser_help(w, self, true) + } + + pub fn write_help_err(&self, w: &mut W) -> ClapResult<()> { + Help::write_parser_help_to_stderr(w, self) + } + + pub fn add_defaults(&mut self, matcher: &mut ArgMatcher<'a>) -> ClapResult<()> { + debugln!("Parser::add_defaults;"); + macro_rules! add_val { + (@default $_self:ident, $a:ident, $m:ident) => { + if let Some(ref val) = $a.v.default_val { + debugln!("Parser::add_defaults:iter:{}: has default vals", $a.b.name); + if $m.get($a.b.name).map(|ma| ma.vals.len()).map(|len| len == 0).unwrap_or(false) { + debugln!("Parser::add_defaults:iter:{}: has no user defined vals", $a.b.name); + $_self.add_val_to_arg($a, OsStr::new(val), $m)?; + + if $_self.cache.map_or(true, |name| name != $a.name()) { + $_self.cache = Some($a.name()); + } + } else if $m.get($a.b.name).is_some() { + debugln!("Parser::add_defaults:iter:{}: has user defined vals", $a.b.name); + } else { + debugln!("Parser::add_defaults:iter:{}: wasn't used", $a.b.name); + + $_self.add_val_to_arg($a, OsStr::new(val), $m)?; + + if $_self.cache.map_or(true, |name| name != $a.name()) { + $_self.cache = Some($a.name()); + } + } + } else { + debugln!("Parser::add_defaults:iter:{}: doesn't have default vals", $a.b.name); + } + }; + ($_self:ident, $a:ident, $m:ident) => { + if let Some(ref vm) = $a.v.default_vals_ifs { + sdebugln!(" has conditional defaults"); + let mut done = false; + if $m.get($a.b.name).is_none() { + for &(arg, val, default) in vm.values() { + let add = if let Some(a) = $m.get(arg) { + if let Some(v) = val { + a.vals.iter().any(|value| v == value) + } else { + true + } + } else { + false + }; + if add { + $_self.add_val_to_arg($a, OsStr::new(default), $m)?; + if $_self.cache.map_or(true, |name| name != $a.name()) { + $_self.cache = Some($a.name()); + } + done = true; + break; + } + } + } + + if done { + continue; // outer loop (outside macro) + } + } else { + sdebugln!(" doesn't have conditional defaults"); + } + add_val!(@default $_self, $a, $m) + }; + } + + for o in &self.opts { + debug!("Parser::add_defaults:iter:{}:", o.b.name); + add_val!(self, o, matcher); + } + for p in self.positionals.values() { + debug!("Parser::add_defaults:iter:{}:", p.b.name); + add_val!(self, p, matcher); + } + Ok(()) + } + + pub fn add_env(&mut self, matcher: &mut ArgMatcher<'a>) -> ClapResult<()> { + macro_rules! add_val { + ($_self:ident, $a:ident, $m:ident) => { + if let Some(ref val) = $a.v.env { + if $m.get($a.b.name).map(|ma| ma.vals.len()).map(|len| len == 0).unwrap_or(false) { + if let Some(ref val) = val.1 { + $_self.add_val_to_arg($a, OsStr::new(val), $m)?; + + if $_self.cache.map_or(true, |name| name != $a.name()) { + $_self.cache = Some($a.name()); + } + } + } else { + if let Some(ref val) = val.1 { + $_self.add_val_to_arg($a, OsStr::new(val), $m)?; + + if $_self.cache.map_or(true, |name| name != $a.name()) { + $_self.cache = Some($a.name()); + } + } + } + } + }; + } + + for o in &self.opts { + add_val!(self, o, matcher); + } + for p in self.positionals.values() { + add_val!(self, p, matcher); + } + Ok(()) + } + + pub fn flags(&self) -> Iter> { self.flags.iter() } + + pub fn opts(&self) -> Iter> { self.opts.iter() } + + pub fn positionals(&self) -> map::Values> { self.positionals.values() } + + pub fn subcommands(&self) -> Iter { self.subcommands.iter() } + + // Should we color the output? None=determined by output location, true=yes, false=no + #[doc(hidden)] + pub fn color(&self) -> ColorWhen { + debugln!("Parser::color;"); + debug!("Parser::color: Color setting..."); + if self.is_set(AS::ColorNever) { + sdebugln!("Never"); + ColorWhen::Never + } else if self.is_set(AS::ColorAlways) { + sdebugln!("Always"); + ColorWhen::Always + } else { + sdebugln!("Auto"); + ColorWhen::Auto + } + } + + pub fn find_any_arg(&self, name: &str) -> Option<&AnyArg<'a, 'b>> { + if let Some(f) = find_by_name!(self, name, flags, iter) { + return Some(f); + } + if let Some(o) = find_by_name!(self, name, opts, iter) { + return Some(o); + } + if let Some(p) = find_by_name!(self, name, positionals, values) { + return Some(p); + } + None + } + + /// Check is a given string matches the binary name for this parser + fn is_bin_name(&self, value: &str) -> bool { + self.meta + .bin_name + .as_ref() + .and_then(|name| Some(value == name)) + .unwrap_or(false) + } + + /// Check is a given string is an alias for this parser + fn is_alias(&self, value: &str) -> bool { + self.meta + .aliases + .as_ref() + .and_then(|aliases| { + for alias in aliases { + if alias.0 == value { + return Some(true); + } + } + Some(false) + }) + .unwrap_or(false) + } + + // Only used for completion scripts due to bin_name messiness + #[cfg_attr(feature = "lints", allow(block_in_if_condition_stmt))] + pub fn find_subcommand(&'b self, sc: &str) -> Option<&'b App<'a, 'b>> { + debugln!("Parser::find_subcommand: sc={}", sc); + debugln!( + "Parser::find_subcommand: Currently in Parser...{}", + self.meta.bin_name.as_ref().unwrap() + ); + for s in &self.subcommands { + if s.p.is_bin_name(sc) { + return Some(s); + } + // XXX: why do we split here? + // isn't `sc` supposed to be single word already? + let last = sc.split(' ').rev().next().expect(INTERNAL_ERROR_MSG); + if s.p.is_alias(last) { + return Some(s); + } + + if let Some(app) = s.p.find_subcommand(sc) { + return Some(app); + } + } + None + } + + #[inline] + fn contains_long(&self, l: &str) -> bool { longs!(self).any(|al| al == &l) } + + #[inline] + fn contains_short(&self, s: char) -> bool { shorts!(self).any(|arg_s| arg_s == &s) } +} diff --git a/clap/src/app/settings.rs b/clap/src/app/settings.rs new file mode 100644 index 0000000..ec03997 --- /dev/null +++ b/clap/src/app/settings.rs @@ -0,0 +1,1174 @@ +// Std +#[allow(deprecated, unused_imports)] +use std::ascii::AsciiExt; +use std::str::FromStr; +use std::ops::BitOr; + +bitflags! { + struct Flags: u64 { + const SC_NEGATE_REQS = 1; + const SC_REQUIRED = 1 << 1; + const A_REQUIRED_ELSE_HELP = 1 << 2; + const GLOBAL_VERSION = 1 << 3; + const VERSIONLESS_SC = 1 << 4; + const UNIFIED_HELP = 1 << 5; + const WAIT_ON_ERROR = 1 << 6; + const SC_REQUIRED_ELSE_HELP= 1 << 7; + const NEEDS_LONG_HELP = 1 << 8; + const NEEDS_LONG_VERSION = 1 << 9; + const NEEDS_SC_HELP = 1 << 10; + const DISABLE_VERSION = 1 << 11; + const HIDDEN = 1 << 12; + const TRAILING_VARARG = 1 << 13; + const NO_BIN_NAME = 1 << 14; + const ALLOW_UNK_SC = 1 << 15; + const UTF8_STRICT = 1 << 16; + const UTF8_NONE = 1 << 17; + const LEADING_HYPHEN = 1 << 18; + const NO_POS_VALUES = 1 << 19; + const NEXT_LINE_HELP = 1 << 20; + const DERIVE_DISP_ORDER = 1 << 21; + const COLORED_HELP = 1 << 22; + const COLOR_ALWAYS = 1 << 23; + const COLOR_AUTO = 1 << 24; + const COLOR_NEVER = 1 << 25; + const DONT_DELIM_TRAIL = 1 << 26; + const ALLOW_NEG_NUMS = 1 << 27; + const LOW_INDEX_MUL_POS = 1 << 28; + const DISABLE_HELP_SC = 1 << 29; + const DONT_COLLAPSE_ARGS = 1 << 30; + const ARGS_NEGATE_SCS = 1 << 31; + const PROPAGATE_VALS_DOWN = 1 << 32; + const ALLOW_MISSING_POS = 1 << 33; + const TRAILING_VALUES = 1 << 34; + const VALID_NEG_NUM_FOUND = 1 << 35; + const PROPAGATED = 1 << 36; + const VALID_ARG_FOUND = 1 << 37; + const INFER_SUBCOMMANDS = 1 << 38; + const CONTAINS_LAST = 1 << 39; + const ARGS_OVERRIDE_SELF = 1 << 40; + const DISABLE_HELP_FLAGS = 1 << 41; + } +} + +#[doc(hidden)] +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct AppFlags(Flags); + +impl BitOr for AppFlags { + type Output = Self; + fn bitor(self, rhs: Self) -> Self { AppFlags(self.0 | rhs.0) } +} + +impl Default for AppFlags { + fn default() -> Self { + AppFlags( + Flags::NEEDS_LONG_VERSION | Flags::NEEDS_LONG_HELP | Flags::NEEDS_SC_HELP + | Flags::UTF8_NONE | Flags::COLOR_AUTO, + ) + } +} + +#[allow(deprecated)] +impl AppFlags { + pub fn new() -> Self { AppFlags::default() } + pub fn zeroed() -> Self { AppFlags(Flags::empty()) } + + impl_settings! { AppSettings, + ArgRequiredElseHelp => Flags::A_REQUIRED_ELSE_HELP, + ArgsNegateSubcommands => Flags::ARGS_NEGATE_SCS, + AllArgsOverrideSelf => Flags::ARGS_OVERRIDE_SELF, + AllowExternalSubcommands => Flags::ALLOW_UNK_SC, + AllowInvalidUtf8 => Flags::UTF8_NONE, + AllowLeadingHyphen => Flags::LEADING_HYPHEN, + AllowNegativeNumbers => Flags::ALLOW_NEG_NUMS, + AllowMissingPositional => Flags::ALLOW_MISSING_POS, + ColoredHelp => Flags::COLORED_HELP, + ColorAlways => Flags::COLOR_ALWAYS, + ColorAuto => Flags::COLOR_AUTO, + ColorNever => Flags::COLOR_NEVER, + DontDelimitTrailingValues => Flags::DONT_DELIM_TRAIL, + DontCollapseArgsInUsage => Flags::DONT_COLLAPSE_ARGS, + DeriveDisplayOrder => Flags::DERIVE_DISP_ORDER, + DisableHelpFlags => Flags::DISABLE_HELP_FLAGS, + DisableHelpSubcommand => Flags::DISABLE_HELP_SC, + DisableVersion => Flags::DISABLE_VERSION, + GlobalVersion => Flags::GLOBAL_VERSION, + HidePossibleValuesInHelp => Flags::NO_POS_VALUES, + Hidden => Flags::HIDDEN, + LowIndexMultiplePositional => Flags::LOW_INDEX_MUL_POS, + NeedsLongHelp => Flags::NEEDS_LONG_HELP, + NeedsLongVersion => Flags::NEEDS_LONG_VERSION, + NeedsSubcommandHelp => Flags::NEEDS_SC_HELP, + NoBinaryName => Flags::NO_BIN_NAME, + PropagateGlobalValuesDown=> Flags::PROPAGATE_VALS_DOWN, + StrictUtf8 => Flags::UTF8_STRICT, + SubcommandsNegateReqs => Flags::SC_NEGATE_REQS, + SubcommandRequired => Flags::SC_REQUIRED, + SubcommandRequiredElseHelp => Flags::SC_REQUIRED_ELSE_HELP, + TrailingVarArg => Flags::TRAILING_VARARG, + UnifiedHelpMessage => Flags::UNIFIED_HELP, + NextLineHelp => Flags::NEXT_LINE_HELP, + VersionlessSubcommands => Flags::VERSIONLESS_SC, + WaitOnError => Flags::WAIT_ON_ERROR, + TrailingValues => Flags::TRAILING_VALUES, + ValidNegNumFound => Flags::VALID_NEG_NUM_FOUND, + Propagated => Flags::PROPAGATED, + ValidArgFound => Flags::VALID_ARG_FOUND, + InferSubcommands => Flags::INFER_SUBCOMMANDS, + ContainsLast => Flags::CONTAINS_LAST + } +} + +/// Application level settings, which affect how [`App`] operates +/// +/// **NOTE:** When these settings are used, they apply only to current command, and are *not* +/// propagated down or up through child or parent subcommands +/// +/// [`App`]: ./struct.App.html +#[derive(Debug, PartialEq, Copy, Clone)] +pub enum AppSettings { + /// Specifies that any invalid UTF-8 code points should *not* be treated as an error. + /// This is the default behavior of `clap`. + /// + /// **NOTE:** Using argument values with invalid UTF-8 code points requires using + /// [`ArgMatches::os_value_of`], [`ArgMatches::os_values_of`], [`ArgMatches::lossy_value_of`], + /// or [`ArgMatches::lossy_values_of`] for those particular arguments which may contain invalid + /// UTF-8 values + /// + /// **NOTE:** This rule only applies to argument values, as flags, options, and + /// [`SubCommand`]s themselves only allow valid UTF-8 code points. + /// + /// # Platform Specific + /// + /// Non Windows systems only + /// + /// # Examples + /// + #[cfg_attr(not(unix), doc = " ```ignore")] + #[cfg_attr(unix, doc = " ```")] + /// # use clap::{App, AppSettings}; + /// use std::ffi::OsString; + /// use std::os::unix::ffi::{OsStrExt,OsStringExt}; + /// + /// let r = App::new("myprog") + /// //.setting(AppSettings::AllowInvalidUtf8) + /// .arg_from_usage(" 'some positional arg'") + /// .get_matches_from_safe( + /// vec![ + /// OsString::from("myprog"), + /// OsString::from_vec(vec![0xe9])]); + /// + /// assert!(r.is_ok()); + /// let m = r.unwrap(); + /// assert_eq!(m.value_of_os("arg").unwrap().as_bytes(), &[0xe9]); + /// ``` + /// [`ArgMatches::os_value_of`]: ./struct.ArgMatches.html#method.os_value_of + /// [`ArgMatches::os_values_of`]: ./struct.ArgMatches.html#method.os_values_of + /// [`ArgMatches::lossy_value_of`]: ./struct.ArgMatches.html#method.lossy_value_of + /// [`ArgMatches::lossy_values_of`]: ./struct.ArgMatches.html#method.lossy_values_of + /// [`SubCommand`]: ./struct.SubCommand.html + AllowInvalidUtf8, + + /// Essentially sets [`Arg::overrides_with("itself")`] for all arguments. + /// + /// **WARNING:** Positional arguments cannot override themselves (or we would never be able + /// to advance to the next positional). This setting ignores positional arguments. + /// [`Arg::overrides_with("itself")`]: ./struct.Arg.html#method.overrides_with + AllArgsOverrideSelf, + + /// Specifies that leading hyphens are allowed in argument *values*, such as negative numbers + /// like `-10`. (which would otherwise be parsed as another flag or option) + /// + /// **NOTE:** Use this setting with caution as it silences certain circumstances which would + /// otherwise be an error (such as accidentally forgetting to specify a value for leading + /// option). It is preferred to set this on a per argument basis, via [`Arg::allow_hyphen_values`] + /// + /// # Examples + /// + /// ```rust + /// # use clap::{Arg, App, AppSettings}; + /// // Imagine you needed to represent negative numbers as well, such as -10 + /// let m = App::new("nums") + /// .setting(AppSettings::AllowLeadingHyphen) + /// .arg(Arg::with_name("neg").index(1)) + /// .get_matches_from(vec![ + /// "nums", "-20" + /// ]); + /// + /// assert_eq!(m.value_of("neg"), Some("-20")); + /// # ; + /// ``` + /// [`Arg::allow_hyphen_values`]: ./struct.Arg.html#method.allow_hyphen_values + AllowLeadingHyphen, + + /// Allows negative numbers to pass as values. This is similar to + /// `AllowLeadingHyphen` except that it only allows numbers, all + /// other undefined leading hyphens will fail to parse. + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg, AppSettings}; + /// let res = App::new("myprog") + /// .version("v1.1") + /// .setting(AppSettings::AllowNegativeNumbers) + /// .arg(Arg::with_name("num")) + /// .get_matches_from_safe(vec![ + /// "myprog", "-20" + /// ]); + /// assert!(res.is_ok()); + /// let m = res.unwrap(); + /// assert_eq!(m.value_of("num").unwrap(), "-20"); + /// ``` + /// [`AllowLeadingHyphen`]: ./enum.AppSettings.html#variant.AllowLeadingHyphen + AllowNegativeNumbers, + + /// Allows one to implement two styles of CLIs where positionals can be used out of order. + /// + /// The first example is a CLI where the second to last positional argument is optional, but + /// the final positional argument is required. Such as `$ prog [optional] ` where one + /// of the two following usages is allowed: + /// + /// * `$ prog [optional] ` + /// * `$ prog ` + /// + /// This would otherwise not be allowed. This is useful when `[optional]` has a default value. + /// + /// **Note:** when using this style of "missing positionals" the final positional *must* be + /// [required] if `--` will not be used to skip to the final positional argument. + /// + /// **Note:** This style also only allows a single positional argument to be "skipped" without + /// the use of `--`. To skip more than one, see the second example. + /// + /// The second example is when one wants to skip multiple optional positional arguments, and use + /// of the `--` operator is OK (but not required if all arguments will be specified anyways). + /// + /// For example, imagine a CLI which has three positional arguments `[foo] [bar] [baz]...` where + /// `baz` accepts multiple values (similar to man `ARGS...` style training arguments). + /// + /// With this setting the following invocations are possible: + /// + /// * `$ prog foo bar baz1 baz2 baz3` + /// * `$ prog foo -- baz1 baz2 baz3` + /// * `$ prog -- baz1 baz2 baz3` + /// + /// # Examples + /// + /// Style number one from above: + /// + /// ```rust + /// # use clap::{App, Arg, AppSettings}; + /// // Assume there is an external subcommand named "subcmd" + /// let m = App::new("myprog") + /// .setting(AppSettings::AllowMissingPositional) + /// .arg(Arg::with_name("arg1")) + /// .arg(Arg::with_name("arg2") + /// .required(true)) + /// .get_matches_from(vec![ + /// "prog", "other" + /// ]); + /// + /// assert_eq!(m.value_of("arg1"), None); + /// assert_eq!(m.value_of("arg2"), Some("other")); + /// ``` + /// + /// Now the same example, but using a default value for the first optional positional argument + /// + /// ```rust + /// # use clap::{App, Arg, AppSettings}; + /// // Assume there is an external subcommand named "subcmd" + /// let m = App::new("myprog") + /// .setting(AppSettings::AllowMissingPositional) + /// .arg(Arg::with_name("arg1") + /// .default_value("something")) + /// .arg(Arg::with_name("arg2") + /// .required(true)) + /// .get_matches_from(vec![ + /// "prog", "other" + /// ]); + /// + /// assert_eq!(m.value_of("arg1"), Some("something")); + /// assert_eq!(m.value_of("arg2"), Some("other")); + /// ``` + /// Style number two from above: + /// + /// ```rust + /// # use clap::{App, Arg, AppSettings}; + /// // Assume there is an external subcommand named "subcmd" + /// let m = App::new("myprog") + /// .setting(AppSettings::AllowMissingPositional) + /// .arg(Arg::with_name("foo")) + /// .arg(Arg::with_name("bar")) + /// .arg(Arg::with_name("baz").multiple(true)) + /// .get_matches_from(vec![ + /// "prog", "foo", "bar", "baz1", "baz2", "baz3" + /// ]); + /// + /// assert_eq!(m.value_of("foo"), Some("foo")); + /// assert_eq!(m.value_of("bar"), Some("bar")); + /// assert_eq!(m.values_of("baz").unwrap().collect::>(), &["baz1", "baz2", "baz3"]); + /// ``` + /// + /// Now notice if we don't specify `foo` or `baz` but use the `--` operator. + /// + /// ```rust + /// # use clap::{App, Arg, AppSettings}; + /// // Assume there is an external subcommand named "subcmd" + /// let m = App::new("myprog") + /// .setting(AppSettings::AllowMissingPositional) + /// .arg(Arg::with_name("foo")) + /// .arg(Arg::with_name("bar")) + /// .arg(Arg::with_name("baz").multiple(true)) + /// .get_matches_from(vec![ + /// "prog", "--", "baz1", "baz2", "baz3" + /// ]); + /// + /// assert_eq!(m.value_of("foo"), None); + /// assert_eq!(m.value_of("bar"), None); + /// assert_eq!(m.values_of("baz").unwrap().collect::>(), &["baz1", "baz2", "baz3"]); + /// ``` + /// [required]: ./struct.Arg.html#method.required + AllowMissingPositional, + + /// Specifies that an unexpected positional argument, + /// which would otherwise cause a [`ErrorKind::UnknownArgument`] error, + /// should instead be treated as a [`SubCommand`] within the [`ArgMatches`] struct. + /// + /// **NOTE:** Use this setting with caution, + /// as a truly unexpected argument (i.e. one that is *NOT* an external subcommand) + /// will **not** cause an error and instead be treated as a potential subcommand. + /// One should check for such cases manually and inform the user appropriately. + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, AppSettings}; + /// // Assume there is an external subcommand named "subcmd" + /// let m = App::new("myprog") + /// .setting(AppSettings::AllowExternalSubcommands) + /// .get_matches_from(vec![ + /// "myprog", "subcmd", "--option", "value", "-fff", "--flag" + /// ]); + /// + /// // All trailing arguments will be stored under the subcommand's sub-matches using an empty + /// // string argument name + /// match m.subcommand() { + /// (external, Some(ext_m)) => { + /// let ext_args: Vec<&str> = ext_m.values_of("").unwrap().collect(); + /// assert_eq!(external, "subcmd"); + /// assert_eq!(ext_args, ["--option", "value", "-fff", "--flag"]); + /// }, + /// _ => {}, + /// } + /// ``` + /// [`ErrorKind::UnknownArgument`]: ./enum.ErrorKind.html#variant.UnknownArgument + /// [`SubCommand`]: ./struct.SubCommand.html + /// [`ArgMatches`]: ./struct.ArgMatches.html + AllowExternalSubcommands, + + /// Specifies that use of a valid [argument] negates [subcommands] being used after. By default + /// `clap` allows arguments between subcommands such as + /// ` [cmd_args] [cmd2_args] [cmd3_args]`. This setting disables that + /// functionality and says that arguments can only follow the *final* subcommand. For instance + /// using this setting makes only the following invocations possible: + /// + /// * ` [cmd3_args]` + /// * ` [cmd2_args]` + /// * ` [cmd_args]` + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, AppSettings}; + /// App::new("myprog") + /// .setting(AppSettings::ArgsNegateSubcommands) + /// # ; + /// ``` + /// [subcommands]: ./struct.SubCommand.html + /// [argument]: ./struct.Arg.html + ArgsNegateSubcommands, + + /// Specifies that the help text should be displayed (and then exit gracefully), + /// if no arguments are present at runtime (i.e. an empty run such as, `$ myprog`. + /// + /// **NOTE:** [`SubCommand`]s count as arguments + /// + /// **NOTE:** Setting [`Arg::default_value`] effectively disables this option as it will + /// ensure that some argument is always present. + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, AppSettings}; + /// App::new("myprog") + /// .setting(AppSettings::ArgRequiredElseHelp) + /// # ; + /// ``` + /// [`SubCommand`]: ./struct.SubCommand.html + /// [`Arg::default_value`]: ./struct.Arg.html#method.default_value + ArgRequiredElseHelp, + + /// Uses colorized help messages. + /// + /// **NOTE:** Must be compiled with the `color` cargo feature + /// + /// # Platform Specific + /// + /// This setting only applies to Unix, Linux, and macOS (i.e. non-Windows platforms) + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, Arg, SubCommand, AppSettings}; + /// App::new("myprog") + /// .setting(AppSettings::ColoredHelp) + /// .get_matches(); + /// ``` + ColoredHelp, + + /// Enables colored output only when the output is going to a terminal or TTY. + /// + /// **NOTE:** This is the default behavior of `clap`. + /// + /// **NOTE:** Must be compiled with the `color` cargo feature. + /// + /// # Platform Specific + /// + /// This setting only applies to Unix, Linux, and macOS (i.e. non-Windows platforms). + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, Arg, SubCommand, AppSettings}; + /// App::new("myprog") + /// .setting(AppSettings::ColorAuto) + /// .get_matches(); + /// ``` + ColorAuto, + + /// Enables colored output regardless of whether or not the output is going to a terminal/TTY. + /// + /// **NOTE:** Must be compiled with the `color` cargo feature. + /// + /// # Platform Specific + /// + /// This setting only applies to Unix, Linux, and macOS (i.e. non-Windows platforms). + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, Arg, SubCommand, AppSettings}; + /// App::new("myprog") + /// .setting(AppSettings::ColorAlways) + /// .get_matches(); + /// ``` + ColorAlways, + + /// Disables colored output no matter if the output is going to a terminal/TTY, or not. + /// + /// **NOTE:** Must be compiled with the `color` cargo feature + /// + /// # Platform Specific + /// + /// This setting only applies to Unix, Linux, and macOS (i.e. non-Windows platforms) + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, Arg, SubCommand, AppSettings}; + /// App::new("myprog") + /// .setting(AppSettings::ColorNever) + /// .get_matches(); + /// ``` + ColorNever, + + /// Disables the automatic collapsing of positional args into `[ARGS]` inside the usage string + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, Arg, SubCommand, AppSettings}; + /// App::new("myprog") + /// .setting(AppSettings::DontCollapseArgsInUsage) + /// .get_matches(); + /// ``` + DontCollapseArgsInUsage, + + /// Disables the automatic delimiting of values when `--` or [`AppSettings::TrailingVarArg`] + /// was used. + /// + /// **NOTE:** The same thing can be done manually by setting the final positional argument to + /// [`Arg::use_delimiter(false)`]. Using this setting is safer, because it's easier to locate + /// when making changes. + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, Arg, SubCommand, AppSettings}; + /// App::new("myprog") + /// .setting(AppSettings::DontDelimitTrailingValues) + /// .get_matches(); + /// ``` + /// [`AppSettings::TrailingVarArg`]: ./enum.AppSettings.html#variant.TrailingVarArg + /// [`Arg::use_delimiter(false)`]: ./struct.Arg.html#method.use_delimiter + DontDelimitTrailingValues, + + /// Disables `-h` and `--help` [`App`] without affecting any of the [`SubCommand`]s + /// (Defaults to `false`; application *does* have help flags) + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, AppSettings, ErrorKind}; + /// let res = App::new("myprog") + /// .setting(AppSettings::DisableHelpFlags) + /// .get_matches_from_safe(vec![ + /// "myprog", "-h" + /// ]); + /// assert!(res.is_err()); + /// assert_eq!(res.unwrap_err().kind, ErrorKind::UnknownArgument); + /// ``` + /// + /// ```rust + /// # use clap::{App, SubCommand, AppSettings, ErrorKind}; + /// let res = App::new("myprog") + /// .setting(AppSettings::DisableHelpFlags) + /// .subcommand(SubCommand::with_name("test")) + /// .get_matches_from_safe(vec![ + /// "myprog", "test", "-h" + /// ]); + /// assert!(res.is_err()); + /// assert_eq!(res.unwrap_err().kind, ErrorKind::HelpDisplayed); + /// ``` + /// [`SubCommand`]: ./struct.SubCommand.html + /// [`App`]: ./struct.App.html + DisableHelpFlags, + + /// Disables the `help` subcommand + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, AppSettings, ErrorKind, SubCommand}; + /// let res = App::new("myprog") + /// .version("v1.1") + /// .setting(AppSettings::DisableHelpSubcommand) + /// // Normally, creating a subcommand causes a `help` subcommand to automatically + /// // be generated as well + /// .subcommand(SubCommand::with_name("test")) + /// .get_matches_from_safe(vec![ + /// "myprog", "help" + /// ]); + /// assert!(res.is_err()); + /// assert_eq!(res.unwrap_err().kind, ErrorKind::UnknownArgument); + /// ``` + /// [`SubCommand`]: ./struct.SubCommand.html + DisableHelpSubcommand, + + /// Disables `-V` and `--version` [`App`] without affecting any of the [`SubCommand`]s + /// (Defaults to `false`; application *does* have a version flag) + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, AppSettings, ErrorKind}; + /// let res = App::new("myprog") + /// .version("v1.1") + /// .setting(AppSettings::DisableVersion) + /// .get_matches_from_safe(vec![ + /// "myprog", "-V" + /// ]); + /// assert!(res.is_err()); + /// assert_eq!(res.unwrap_err().kind, ErrorKind::UnknownArgument); + /// ``` + /// + /// ```rust + /// # use clap::{App, SubCommand, AppSettings, ErrorKind}; + /// let res = App::new("myprog") + /// .version("v1.1") + /// .setting(AppSettings::DisableVersion) + /// .subcommand(SubCommand::with_name("test")) + /// .get_matches_from_safe(vec![ + /// "myprog", "test", "-V" + /// ]); + /// assert!(res.is_err()); + /// assert_eq!(res.unwrap_err().kind, ErrorKind::VersionDisplayed); + /// ``` + /// [`SubCommand`]: ./struct.SubCommand.html + /// [`App`]: ./struct.App.html + DisableVersion, + + /// Displays the arguments and [`SubCommand`]s in the help message in the order that they were + /// declared in, and not alphabetically which is the default. + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, Arg, SubCommand, AppSettings}; + /// App::new("myprog") + /// .setting(AppSettings::DeriveDisplayOrder) + /// .get_matches(); + /// ``` + /// [`SubCommand`]: ./struct.SubCommand.html + DeriveDisplayOrder, + + /// Specifies to use the version of the current command for all child [`SubCommand`]s. + /// (Defaults to `false`; subcommands have independent version strings from their parents.) + /// + /// **NOTE:** The version for the current command **and** this setting must be set **prior** to + /// adding any child subcommands + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, Arg, SubCommand, AppSettings}; + /// App::new("myprog") + /// .version("v1.1") + /// .setting(AppSettings::GlobalVersion) + /// .subcommand(SubCommand::with_name("test")) + /// .get_matches(); + /// // running `$ myprog test --version` will display + /// // "myprog-test v1.1" + /// ``` + /// [`SubCommand`]: ./struct.SubCommand.html + GlobalVersion, + + /// Specifies that this [`SubCommand`] should be hidden from help messages + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg, AppSettings, SubCommand}; + /// App::new("myprog") + /// .subcommand(SubCommand::with_name("test") + /// .setting(AppSettings::Hidden)) + /// # ; + /// ``` + /// [`SubCommand`]: ./struct.SubCommand.html + Hidden, + + /// Tells `clap` *not* to print possible values when displaying help information. + /// This can be useful if there are many values, or they are explained elsewhere. + HidePossibleValuesInHelp, + + /// Tries to match unknown args to partial [`subcommands`] or their [aliases]. For example to + /// match a subcommand named `test`, one could use `t`, `te`, `tes`, and `test`. + /// + /// **NOTE:** The match *must not* be ambiguous at all in order to succeed. i.e. to match `te` + /// to `test` there could not also be a subcommand or alias `temp` because both start with `te` + /// + /// **CAUTION:** This setting can interfere with [positional/free arguments], take care when + /// designing CLIs which allow inferred subcommands and have potential positional/free + /// arguments whose values could start with the same characters as subcommands. If this is the + /// case, it's recommended to use settings such as [`AppSeettings::ArgsNegateSubcommands`] in + /// conjunction with this setting. + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, Arg, SubCommand, AppSettings}; + /// let m = App::new("prog") + /// .setting(AppSettings::InferSubcommands) + /// .subcommand(SubCommand::with_name("test")) + /// .get_matches_from(vec![ + /// "prog", "te" + /// ]); + /// assert_eq!(m.subcommand_name(), Some("test")); + /// ``` + /// [`subcommands`]: ./struct.SubCommand.html + /// [positional/free arguments]: ./struct.Arg.html#method.index + /// [aliases]: ./struct.App.html#method.alias + /// [`AppSeettings::ArgsNegateSubcommands`]: ./enum.AppSettings.html#variant.ArgsNegateSubcommands + InferSubcommands, + + /// Specifies that the parser should not assume the first argument passed is the binary name. + /// This is normally the case when using a "daemon" style mode, or an interactive CLI where one + /// one would not normally type the binary or program name for each command. + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg, AppSettings}; + /// let m = App::new("myprog") + /// .setting(AppSettings::NoBinaryName) + /// .arg(Arg::from_usage("... 'commands to run'")) + /// .get_matches_from(vec!["command", "set"]); + /// + /// let cmds: Vec<&str> = m.values_of("cmd").unwrap().collect(); + /// assert_eq!(cmds, ["command", "set"]); + /// ``` + NoBinaryName, + + /// Places the help string for all arguments on the line after the argument. + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, Arg, SubCommand, AppSettings}; + /// App::new("myprog") + /// .setting(AppSettings::NextLineHelp) + /// .get_matches(); + /// ``` + NextLineHelp, + + /// **DEPRECATED**: This setting is no longer required in order to propagate values up or down + /// + /// Specifies that the parser should propagate global arg's values down or up through any *used* + /// child subcommands. Meaning, if a subcommand wasn't used, the values won't be propagated to + /// said subcommand. + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg, AppSettings, SubCommand}; + /// let m = App::new("myprog") + /// .arg(Arg::from_usage("[cmd] 'command to run'") + /// .global(true)) + /// .subcommand(SubCommand::with_name("foo")) + /// .get_matches_from(vec!["myprog", "set", "foo"]); + /// + /// assert_eq!(m.value_of("cmd"), Some("set")); + /// + /// let sub_m = m.subcommand_matches("foo").unwrap(); + /// assert_eq!(sub_m.value_of("cmd"), Some("set")); + /// ``` + /// Now doing the same thing, but *not* using any subcommands will result in the value not being + /// propagated down. + /// + /// ```rust + /// # use clap::{App, Arg, AppSettings, SubCommand}; + /// let m = App::new("myprog") + /// .arg(Arg::from_usage("[cmd] 'command to run'") + /// .global(true)) + /// .subcommand(SubCommand::with_name("foo")) + /// .get_matches_from(vec!["myprog", "set"]); + /// + /// assert_eq!(m.value_of("cmd"), Some("set")); + /// + /// assert!(m.subcommand_matches("foo").is_none()); + /// ``` + #[deprecated(since = "2.27.0", note = "No longer required to propagate values")] + PropagateGlobalValuesDown, + + /// Allows [`SubCommand`]s to override all requirements of the parent command. + /// For example if you had a subcommand or top level application with a required argument + /// that is only required as long as there is no subcommand present, + /// using this setting would allow you to set those arguments to [`Arg::required(true)`] + /// and yet receive no error so long as the user uses a valid subcommand instead. + /// + /// **NOTE:** This defaults to false (using subcommand does *not* negate requirements) + /// + /// # Examples + /// + /// This first example shows that it is an error to not use a required argument + /// + /// ```rust + /// # use clap::{App, Arg, AppSettings, SubCommand, ErrorKind}; + /// let err = App::new("myprog") + /// .setting(AppSettings::SubcommandsNegateReqs) + /// .arg(Arg::with_name("opt").required(true)) + /// .subcommand(SubCommand::with_name("test")) + /// .get_matches_from_safe(vec![ + /// "myprog" + /// ]); + /// assert!(err.is_err()); + /// assert_eq!(err.unwrap_err().kind, ErrorKind::MissingRequiredArgument); + /// # ; + /// ``` + /// + /// This next example shows that it is no longer error to not use a required argument if a + /// valid subcommand is used. + /// + /// ```rust + /// # use clap::{App, Arg, AppSettings, SubCommand, ErrorKind}; + /// let noerr = App::new("myprog") + /// .setting(AppSettings::SubcommandsNegateReqs) + /// .arg(Arg::with_name("opt").required(true)) + /// .subcommand(SubCommand::with_name("test")) + /// .get_matches_from_safe(vec![ + /// "myprog", "test" + /// ]); + /// assert!(noerr.is_ok()); + /// # ; + /// ``` + /// [`Arg::required(true)`]: ./struct.Arg.html#method.required + /// [`SubCommand`]: ./struct.SubCommand.html + SubcommandsNegateReqs, + + /// Specifies that the help text should be displayed (before exiting gracefully) if no + /// [`SubCommand`]s are present at runtime (i.e. an empty run such as `$ myprog`). + /// + /// **NOTE:** This should *not* be used with [`AppSettings::SubcommandRequired`] as they do + /// nearly same thing; this prints the help text, and the other prints an error. + /// + /// **NOTE:** If the user specifies arguments at runtime, but no subcommand the help text will + /// still be displayed and exit. If this is *not* the desired result, consider using + /// [`AppSettings::ArgRequiredElseHelp`] instead. + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg, AppSettings}; + /// App::new("myprog") + /// .setting(AppSettings::SubcommandRequiredElseHelp) + /// # ; + /// ``` + /// [`SubCommand`]: ./struct.SubCommand.html + /// [`AppSettings::SubcommandRequired`]: ./enum.AppSettings.html#variant.SubcommandRequired + /// [`AppSettings::ArgRequiredElseHelp`]: ./enum.AppSettings.html#variant.ArgRequiredElseHelp + SubcommandRequiredElseHelp, + + /// Specifies that any invalid UTF-8 code points should be treated as an error and fail + /// with a [`ErrorKind::InvalidUtf8`] error. + /// + /// **NOTE:** This rule only applies to argument values; Things such as flags, options, and + /// [`SubCommand`]s themselves only allow valid UTF-8 code points. + /// + /// # Platform Specific + /// + /// Non Windows systems only + /// + /// # Examples + /// + #[cfg_attr(not(unix), doc = " ```ignore")] + #[cfg_attr(unix, doc = " ```")] + /// # use clap::{App, AppSettings, ErrorKind}; + /// use std::ffi::OsString; + /// use std::os::unix::ffi::OsStringExt; + /// + /// let m = App::new("myprog") + /// .setting(AppSettings::StrictUtf8) + /// .arg_from_usage(" 'some positional arg'") + /// .get_matches_from_safe( + /// vec![ + /// OsString::from("myprog"), + /// OsString::from_vec(vec![0xe9])]); + /// + /// assert!(m.is_err()); + /// assert_eq!(m.unwrap_err().kind, ErrorKind::InvalidUtf8); + /// ``` + /// [`SubCommand`]: ./struct.SubCommand.html + /// [`ErrorKind::InvalidUtf8`]: ./enum.ErrorKind.html#variant.InvalidUtf8 + StrictUtf8, + + /// Allows specifying that if no [`SubCommand`] is present at runtime, + /// error and exit gracefully. + /// + /// **NOTE:** This defaults to `false` (subcommands do *not* need to be present) + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, AppSettings, SubCommand, ErrorKind}; + /// let err = App::new("myprog") + /// .setting(AppSettings::SubcommandRequired) + /// .subcommand(SubCommand::with_name("test")) + /// .get_matches_from_safe(vec![ + /// "myprog", + /// ]); + /// assert!(err.is_err()); + /// assert_eq!(err.unwrap_err().kind, ErrorKind::MissingSubcommand); + /// # ; + /// ``` + /// [`SubCommand`]: ./struct.SubCommand.html + SubcommandRequired, + + /// Specifies that the final positional argument is a "VarArg" and that `clap` should not + /// attempt to parse any further args. + /// + /// The values of the trailing positional argument will contain all args from itself on. + /// + /// **NOTE:** The final positional argument **must** have [`Arg::multiple(true)`] or the usage + /// string equivalent. + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg, AppSettings}; + /// let m = App::new("myprog") + /// .setting(AppSettings::TrailingVarArg) + /// .arg(Arg::from_usage("... 'commands to run'")) + /// .get_matches_from(vec!["myprog", "arg1", "-r", "val1"]); + /// + /// let trail: Vec<&str> = m.values_of("cmd").unwrap().collect(); + /// assert_eq!(trail, ["arg1", "-r", "val1"]); + /// ``` + /// [`Arg::multiple(true)`]: ./struct.Arg.html#method.multiple + TrailingVarArg, + + /// Groups flags and options together, presenting a more unified help message + /// (a la `getopts` or `docopt` style). + /// + /// The default is that the auto-generated help message will group flags, and options + /// separately. + /// + /// **NOTE:** This setting is cosmetic only and does not affect any functionality. + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, Arg, SubCommand, AppSettings}; + /// App::new("myprog") + /// .setting(AppSettings::UnifiedHelpMessage) + /// .get_matches(); + /// // running `myprog --help` will display a unified "docopt" or "getopts" style help message + /// ``` + UnifiedHelpMessage, + + /// Disables `-V` and `--version` for all [`SubCommand`]s + /// (Defaults to `false`; subcommands *do* have version flags.) + /// + /// **NOTE:** This setting must be set **prior** to adding any subcommands. + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, SubCommand, AppSettings, ErrorKind}; + /// let res = App::new("myprog") + /// .version("v1.1") + /// .setting(AppSettings::VersionlessSubcommands) + /// .subcommand(SubCommand::with_name("test")) + /// .get_matches_from_safe(vec![ + /// "myprog", "test", "-V" + /// ]); + /// assert!(res.is_err()); + /// assert_eq!(res.unwrap_err().kind, ErrorKind::UnknownArgument); + /// ``` + /// [`SubCommand`]: ./struct.SubCommand.html + VersionlessSubcommands, + + /// Will display a message "Press \[ENTER\]/\[RETURN\] to continue..." and wait for user before + /// exiting + /// + /// This is most useful when writing an application which is run from a GUI shortcut, or on + /// Windows where a user tries to open the binary by double-clicking instead of using the + /// command line. + /// + /// **NOTE:** This setting is **not** recursive with [`SubCommand`]s, meaning if you wish this + /// behavior for all subcommands, you must set this on each command (needing this is extremely + /// rare) + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg, AppSettings}; + /// App::new("myprog") + /// .setting(AppSettings::WaitOnError) + /// # ; + /// ``` + /// [`SubCommand`]: ./struct.SubCommand.html + WaitOnError, + + #[doc(hidden)] NeedsLongVersion, + + #[doc(hidden)] NeedsLongHelp, + + #[doc(hidden)] NeedsSubcommandHelp, + + #[doc(hidden)] LowIndexMultiplePositional, + + #[doc(hidden)] TrailingValues, + + #[doc(hidden)] ValidNegNumFound, + + #[doc(hidden)] Propagated, + + #[doc(hidden)] ValidArgFound, + + #[doc(hidden)] ContainsLast, +} + +impl FromStr for AppSettings { + type Err = String; + fn from_str(s: &str) -> Result::Err> { + match &*s.to_ascii_lowercase() { + "disablehelpflags" => Ok(AppSettings::DisableHelpFlags), + "argrequiredelsehelp" => Ok(AppSettings::ArgRequiredElseHelp), + "argsnegatesubcommands" => Ok(AppSettings::ArgsNegateSubcommands), + "allowinvalidutf8" => Ok(AppSettings::AllowInvalidUtf8), + "allowleadinghyphen" => Ok(AppSettings::AllowLeadingHyphen), + "allowexternalsubcommands" => Ok(AppSettings::AllowExternalSubcommands), + "allownegativenumbers" => Ok(AppSettings::AllowNegativeNumbers), + "colorauto" => Ok(AppSettings::ColorAuto), + "coloralways" => Ok(AppSettings::ColorAlways), + "colornever" => Ok(AppSettings::ColorNever), + "coloredhelp" => Ok(AppSettings::ColoredHelp), + "derivedisplayorder" => Ok(AppSettings::DeriveDisplayOrder), + "dontcollapseargsinusage" => Ok(AppSettings::DontCollapseArgsInUsage), + "dontdelimittrailingvalues" => Ok(AppSettings::DontDelimitTrailingValues), + "disablehelpsubcommand" => Ok(AppSettings::DisableHelpSubcommand), + "disableversion" => Ok(AppSettings::DisableVersion), + "globalversion" => Ok(AppSettings::GlobalVersion), + "hidden" => Ok(AppSettings::Hidden), + "hidepossiblevaluesinhelp" => Ok(AppSettings::HidePossibleValuesInHelp), + "infersubcommands" => Ok(AppSettings::InferSubcommands), + "lowindexmultiplepositional" => Ok(AppSettings::LowIndexMultiplePositional), + "nobinaryname" => Ok(AppSettings::NoBinaryName), + "nextlinehelp" => Ok(AppSettings::NextLineHelp), + "strictutf8" => Ok(AppSettings::StrictUtf8), + "subcommandsnegatereqs" => Ok(AppSettings::SubcommandsNegateReqs), + "subcommandrequired" => Ok(AppSettings::SubcommandRequired), + "subcommandrequiredelsehelp" => Ok(AppSettings::SubcommandRequiredElseHelp), + "trailingvararg" => Ok(AppSettings::TrailingVarArg), + "unifiedhelpmessage" => Ok(AppSettings::UnifiedHelpMessage), + "versionlesssubcommands" => Ok(AppSettings::VersionlessSubcommands), + "waitonerror" => Ok(AppSettings::WaitOnError), + "validnegnumfound" => Ok(AppSettings::ValidNegNumFound), + "validargfound" => Ok(AppSettings::ValidArgFound), + "propagated" => Ok(AppSettings::Propagated), + "trailingvalues" => Ok(AppSettings::TrailingValues), + _ => Err("unknown AppSetting, cannot convert from str".to_owned()), + } + } +} + +#[cfg(test)] +mod test { + use super::AppSettings; + + #[test] + fn app_settings_fromstr() { + assert_eq!( + "disablehelpflags".parse::().unwrap(), + AppSettings::DisableHelpFlags + ); + assert_eq!( + "argsnegatesubcommands".parse::().unwrap(), + AppSettings::ArgsNegateSubcommands + ); + assert_eq!( + "argrequiredelsehelp".parse::().unwrap(), + AppSettings::ArgRequiredElseHelp + ); + assert_eq!( + "allowexternalsubcommands".parse::().unwrap(), + AppSettings::AllowExternalSubcommands + ); + assert_eq!( + "allowinvalidutf8".parse::().unwrap(), + AppSettings::AllowInvalidUtf8 + ); + assert_eq!( + "allowleadinghyphen".parse::().unwrap(), + AppSettings::AllowLeadingHyphen + ); + assert_eq!( + "allownegativenumbers".parse::().unwrap(), + AppSettings::AllowNegativeNumbers + ); + assert_eq!( + "coloredhelp".parse::().unwrap(), + AppSettings::ColoredHelp + ); + assert_eq!( + "colorauto".parse::().unwrap(), + AppSettings::ColorAuto + ); + assert_eq!( + "coloralways".parse::().unwrap(), + AppSettings::ColorAlways + ); + assert_eq!( + "colornever".parse::().unwrap(), + AppSettings::ColorNever + ); + assert_eq!( + "disablehelpsubcommand".parse::().unwrap(), + AppSettings::DisableHelpSubcommand + ); + assert_eq!( + "disableversion".parse::().unwrap(), + AppSettings::DisableVersion + ); + assert_eq!( + "dontcollapseargsinusage".parse::().unwrap(), + AppSettings::DontCollapseArgsInUsage + ); + assert_eq!( + "dontdelimittrailingvalues".parse::().unwrap(), + AppSettings::DontDelimitTrailingValues + ); + assert_eq!( + "derivedisplayorder".parse::().unwrap(), + AppSettings::DeriveDisplayOrder + ); + assert_eq!( + "globalversion".parse::().unwrap(), + AppSettings::GlobalVersion + ); + assert_eq!( + "hidden".parse::().unwrap(), + AppSettings::Hidden + ); + assert_eq!( + "hidepossiblevaluesinhelp".parse::().unwrap(), + AppSettings::HidePossibleValuesInHelp + ); + assert_eq!( + "lowindexmultiplePositional".parse::().unwrap(), + AppSettings::LowIndexMultiplePositional + ); + assert_eq!( + "nobinaryname".parse::().unwrap(), + AppSettings::NoBinaryName + ); + assert_eq!( + "nextlinehelp".parse::().unwrap(), + AppSettings::NextLineHelp + ); + assert_eq!( + "subcommandsnegatereqs".parse::().unwrap(), + AppSettings::SubcommandsNegateReqs + ); + assert_eq!( + "subcommandrequired".parse::().unwrap(), + AppSettings::SubcommandRequired + ); + assert_eq!( + "subcommandrequiredelsehelp".parse::().unwrap(), + AppSettings::SubcommandRequiredElseHelp + ); + assert_eq!( + "strictutf8".parse::().unwrap(), + AppSettings::StrictUtf8 + ); + assert_eq!( + "trailingvararg".parse::().unwrap(), + AppSettings::TrailingVarArg + ); + assert_eq!( + "unifiedhelpmessage".parse::().unwrap(), + AppSettings::UnifiedHelpMessage + ); + assert_eq!( + "versionlesssubcommands".parse::().unwrap(), + AppSettings::VersionlessSubcommands + ); + assert_eq!( + "waitonerror".parse::().unwrap(), + AppSettings::WaitOnError + ); + assert_eq!( + "validnegnumfound".parse::().unwrap(), + AppSettings::ValidNegNumFound + ); + assert_eq!( + "validargfound".parse::().unwrap(), + AppSettings::ValidArgFound + ); + assert_eq!( + "propagated".parse::().unwrap(), + AppSettings::Propagated + ); + assert_eq!( + "trailingvalues".parse::().unwrap(), + AppSettings::TrailingValues + ); + assert_eq!( + "infersubcommands".parse::().unwrap(), + AppSettings::InferSubcommands + ); + assert!("hahahaha".parse::().is_err()); + } +} diff --git a/clap/src/app/usage.rs b/clap/src/app/usage.rs new file mode 100644 index 0000000..6090588 --- /dev/null +++ b/clap/src/app/usage.rs @@ -0,0 +1,479 @@ +// std +use std::collections::{BTreeMap, VecDeque}; + +// Internal +use INTERNAL_ERROR_MSG; +use args::{AnyArg, ArgMatcher, PosBuilder}; +use args::settings::ArgSettings; +use app::settings::AppSettings as AS; +use app::parser::Parser; + +// Creates a usage string for display. This happens just after all arguments were parsed, but before +// any subcommands have been parsed (so as to give subcommands their own usage recursively) +pub fn create_usage_with_title(p: &Parser, used: &[&str]) -> String { + debugln!("usage::create_usage_with_title;"); + let mut usage = String::with_capacity(75); + usage.push_str("USAGE:\n "); + usage.push_str(&*create_usage_no_title(p, used)); + usage +} + +// Creates a usage string to be used in error message (i.e. one with currently used args) +pub fn create_error_usage<'a, 'b>( + p: &Parser<'a, 'b>, + matcher: &'b ArgMatcher<'a>, + extra: Option<&str>, +) -> String { + let mut args: Vec<_> = matcher + .arg_names() + .iter() + .filter(|n| { + if let Some(o) = find_by_name!(p, **n, opts, iter) { + !o.b.is_set(ArgSettings::Required) && !o.b.is_set(ArgSettings::Hidden) + } else if let Some(p) = find_by_name!(p, **n, positionals, values) { + !p.b.is_set(ArgSettings::Required) && p.b.is_set(ArgSettings::Hidden) + } else { + true // flags can't be required, so they're always true + } + }) + .map(|&n| n) + .collect(); + if let Some(r) = extra { + args.push(r); + } + create_usage_with_title(p, &*args) +} + +// Creates a usage string (*without title*) if one was not provided by the user manually. +pub fn create_usage_no_title(p: &Parser, used: &[&str]) -> String { + debugln!("usage::create_usage_no_title;"); + if let Some(u) = p.meta.usage_str { + String::from(&*u) + } else if used.is_empty() { + create_help_usage(p, true) + } else { + create_smart_usage(p, used) + } +} + +// Creates a usage string for display in help messages (i.e. not for errors) +pub fn create_help_usage(p: &Parser, incl_reqs: bool) -> String { + let mut usage = String::with_capacity(75); + let name = p.meta + .usage + .as_ref() + .unwrap_or_else(|| p.meta.bin_name.as_ref().unwrap_or(&p.meta.name)); + usage.push_str(&*name); + let req_string = if incl_reqs { + let mut reqs: Vec<&str> = p.required().map(|r| &**r).collect(); + reqs.sort(); + reqs.dedup(); + get_required_usage_from(p, &reqs, None, None, false) + .iter() + .fold(String::new(), |a, s| a + &format!(" {}", s)[..]) + } else { + String::new() + }; + + let flags = needs_flags_tag(p); + if flags && !p.is_set(AS::UnifiedHelpMessage) { + usage.push_str(" [FLAGS]"); + } else if flags { + usage.push_str(" [OPTIONS]"); + } + if !p.is_set(AS::UnifiedHelpMessage) && p.opts.iter().any(|o| { + !o.is_set(ArgSettings::Required) && !o.is_set(ArgSettings::Hidden) + }) { + usage.push_str(" [OPTIONS]"); + } + + usage.push_str(&req_string[..]); + + let has_last = p.positionals.values().any(|p| p.is_set(ArgSettings::Last)); + // places a '--' in the usage string if there are args and options + // supporting multiple values + if p.opts.iter().any(|o| o.is_set(ArgSettings::Multiple)) + && p.positionals + .values() + .any(|p| !p.is_set(ArgSettings::Required)) + && !(p.has_visible_subcommands() || p.is_set(AS::AllowExternalSubcommands)) + && !has_last + { + usage.push_str(" [--]"); + } + let not_req_or_hidden = |p: &PosBuilder| { + (!p.is_set(ArgSettings::Required) || p.is_set(ArgSettings::Last)) + && !p.is_set(ArgSettings::Hidden) + }; + if p.has_positionals() && p.positionals.values().any(not_req_or_hidden) { + if let Some(args_tag) = get_args_tag(p, incl_reqs) { + usage.push_str(&*args_tag); + } else { + usage.push_str(" [ARGS]"); + } + if has_last && incl_reqs { + let pos = p.positionals + .values() + .find(|p| p.b.is_set(ArgSettings::Last)) + .expect(INTERNAL_ERROR_MSG); + debugln!("usage::create_help_usage: '{}' has .last(true)", pos.name()); + let req = pos.is_set(ArgSettings::Required); + if req + && p.positionals + .values() + .any(|p| !p.is_set(ArgSettings::Required)) + { + usage.push_str(" -- <"); + } else if req { + usage.push_str(" [--] <"); + } else { + usage.push_str(" [-- <"); + } + usage.push_str(&*pos.name_no_brackets()); + usage.push_str(">"); + usage.push_str(pos.multiple_str()); + if !req { + usage.push_str("]"); + } + } + } + + // incl_reqs is only false when this function is called recursively + if p.has_visible_subcommands() && incl_reqs || p.is_set(AS::AllowExternalSubcommands) { + if p.is_set(AS::SubcommandsNegateReqs) || p.is_set(AS::ArgsNegateSubcommands) { + if !p.is_set(AS::ArgsNegateSubcommands) { + usage.push_str("\n "); + usage.push_str(&*create_help_usage(p, false)); + usage.push_str(" "); + } else { + usage.push_str("\n "); + usage.push_str(&*name); + usage.push_str(" "); + } + } else if p.is_set(AS::SubcommandRequired) || p.is_set(AS::SubcommandRequiredElseHelp) { + usage.push_str(" "); + } else { + usage.push_str(" [SUBCOMMAND]"); + } + } + usage.shrink_to_fit(); + debugln!("usage::create_help_usage: usage={}", usage); + usage +} + +// Creates a context aware usage string, or "smart usage" from currently used +// args, and requirements +fn create_smart_usage(p: &Parser, used: &[&str]) -> String { + debugln!("usage::smart_usage;"); + let mut usage = String::with_capacity(75); + let mut hs: Vec<&str> = p.required().map(|s| &**s).collect(); + hs.extend_from_slice(used); + + let r_string = get_required_usage_from(p, &hs, None, None, false) + .iter() + .fold(String::new(), |acc, s| acc + &format!(" {}", s)[..]); + + usage.push_str( + &p.meta + .usage + .as_ref() + .unwrap_or_else(|| p.meta.bin_name.as_ref().unwrap_or(&p.meta.name))[..], + ); + usage.push_str(&*r_string); + if p.is_set(AS::SubcommandRequired) { + usage.push_str(" "); + } + usage.shrink_to_fit(); + usage +} + +// Gets the `[ARGS]` tag for the usage string +fn get_args_tag(p: &Parser, incl_reqs: bool) -> Option { + debugln!("usage::get_args_tag;"); + let mut count = 0; + 'outer: for pos in p.positionals + .values() + .filter(|pos| !pos.is_set(ArgSettings::Required)) + .filter(|pos| !pos.is_set(ArgSettings::Hidden)) + .filter(|pos| !pos.is_set(ArgSettings::Last)) + { + debugln!("usage::get_args_tag:iter:{}:", pos.b.name); + if let Some(g_vec) = p.groups_for_arg(pos.b.name) { + for grp_s in &g_vec { + debugln!("usage::get_args_tag:iter:{}:iter:{};", pos.b.name, grp_s); + // if it's part of a required group we don't want to count it + if p.groups.iter().any(|g| g.required && (&g.name == grp_s)) { + continue 'outer; + } + } + } + count += 1; + debugln!( + "usage::get_args_tag:iter: {} Args not required or hidden", + count + ); + } + if !p.is_set(AS::DontCollapseArgsInUsage) && count > 1 { + debugln!("usage::get_args_tag:iter: More than one, returning [ARGS]"); + return None; // [ARGS] + } else if count == 1 && incl_reqs { + let pos = p.positionals + .values() + .find(|pos| { + !pos.is_set(ArgSettings::Required) && !pos.is_set(ArgSettings::Hidden) + && !pos.is_set(ArgSettings::Last) + }) + .expect(INTERNAL_ERROR_MSG); + debugln!( + "usage::get_args_tag:iter: Exactly one, returning '{}'", + pos.name() + ); + return Some(format!( + " [{}]{}", + pos.name_no_brackets(), + pos.multiple_str() + )); + } else if p.is_set(AS::DontCollapseArgsInUsage) && !p.positionals.is_empty() && incl_reqs { + debugln!("usage::get_args_tag:iter: Don't collapse returning all"); + return Some( + p.positionals + .values() + .filter(|pos| !pos.is_set(ArgSettings::Required)) + .filter(|pos| !pos.is_set(ArgSettings::Hidden)) + .filter(|pos| !pos.is_set(ArgSettings::Last)) + .map(|pos| { + format!(" [{}]{}", pos.name_no_brackets(), pos.multiple_str()) + }) + .collect::>() + .join(""), + ); + } else if !incl_reqs { + debugln!("usage::get_args_tag:iter: incl_reqs=false, building secondary usage string"); + let highest_req_pos = p.positionals + .iter() + .filter_map(|(idx, pos)| { + if pos.b.is_set(ArgSettings::Required) && !pos.b.is_set(ArgSettings::Last) { + Some(idx) + } else { + None + } + }) + .max() + .unwrap_or_else(|| p.positionals.len()); + return Some( + p.positionals + .iter() + .filter_map(|(idx, pos)| { + if idx <= highest_req_pos { + Some(pos) + } else { + None + } + }) + .filter(|pos| !pos.is_set(ArgSettings::Required)) + .filter(|pos| !pos.is_set(ArgSettings::Hidden)) + .filter(|pos| !pos.is_set(ArgSettings::Last)) + .map(|pos| { + format!(" [{}]{}", pos.name_no_brackets(), pos.multiple_str()) + }) + .collect::>() + .join(""), + ); + } + Some("".into()) +} + +// Determines if we need the `[FLAGS]` tag in the usage string +fn needs_flags_tag(p: &Parser) -> bool { + debugln!("usage::needs_flags_tag;"); + 'outer: for f in &p.flags { + debugln!("usage::needs_flags_tag:iter: f={};", f.b.name); + if let Some(l) = f.s.long { + if l == "help" || l == "version" { + // Don't print `[FLAGS]` just for help or version + continue; + } + } + if let Some(g_vec) = p.groups_for_arg(f.b.name) { + for grp_s in &g_vec { + debugln!("usage::needs_flags_tag:iter:iter: grp_s={};", grp_s); + if p.groups.iter().any(|g| &g.name == grp_s && g.required) { + debugln!("usage::needs_flags_tag:iter:iter: Group is required"); + continue 'outer; + } + } + } + if f.is_set(ArgSettings::Hidden) { + continue; + } + debugln!("usage::needs_flags_tag:iter: [FLAGS] required"); + return true; + } + + debugln!("usage::needs_flags_tag: [FLAGS] not required"); + false +} + +// Returns the required args in usage string form by fully unrolling all groups +pub fn get_required_usage_from<'a, 'b>( + p: &Parser<'a, 'b>, + reqs: &[&'a str], + matcher: Option<&ArgMatcher<'a>>, + extra: Option<&str>, + incl_last: bool, +) -> VecDeque { + debugln!( + "usage::get_required_usage_from: reqs={:?}, extra={:?}", + reqs, + extra + ); + let mut desc_reqs: Vec<&str> = vec![]; + desc_reqs.extend(extra); + let mut new_reqs: Vec<&str> = vec![]; + macro_rules! get_requires { + (@group $a: ident, $v:ident, $p:ident) => {{ + if let Some(rl) = p.groups.iter() + .filter(|g| g.requires.is_some()) + .find(|g| &g.name == $a) + .map(|g| g.requires.as_ref().unwrap()) { + for r in rl { + if !$p.contains(&r) { + debugln!("usage::get_required_usage_from:iter:{}: adding group req={:?}", + $a, r); + $v.push(r); + } + } + } + }}; + ($a:ident, $what:ident, $how:ident, $v:ident, $p:ident) => {{ + if let Some(rl) = p.$what.$how() + .filter(|a| a.b.requires.is_some()) + .find(|arg| &arg.b.name == $a) + .map(|a| a.b.requires.as_ref().unwrap()) { + for &(_, r) in rl.iter() { + if !$p.contains(&r) { + debugln!("usage::get_required_usage_from:iter:{}: adding arg req={:?}", + $a, r); + $v.push(r); + } + } + } + }}; + } + // initialize new_reqs + for a in reqs { + get_requires!(a, flags, iter, new_reqs, reqs); + get_requires!(a, opts, iter, new_reqs, reqs); + get_requires!(a, positionals, values, new_reqs, reqs); + get_requires!(@group a, new_reqs, reqs); + } + desc_reqs.extend_from_slice(&*new_reqs); + debugln!( + "usage::get_required_usage_from: after init desc_reqs={:?}", + desc_reqs + ); + loop { + let mut tmp = vec![]; + for a in &new_reqs { + get_requires!(a, flags, iter, tmp, desc_reqs); + get_requires!(a, opts, iter, tmp, desc_reqs); + get_requires!(a, positionals, values, tmp, desc_reqs); + get_requires!(@group a, tmp, desc_reqs); + } + if tmp.is_empty() { + debugln!("usage::get_required_usage_from: no more children"); + break; + } else { + debugln!("usage::get_required_usage_from: after iter tmp={:?}", tmp); + debugln!( + "usage::get_required_usage_from: after iter new_reqs={:?}", + new_reqs + ); + desc_reqs.extend_from_slice(&*new_reqs); + new_reqs.clear(); + new_reqs.extend_from_slice(&*tmp); + debugln!( + "usage::get_required_usage_from: after iter desc_reqs={:?}", + desc_reqs + ); + } + } + desc_reqs.extend_from_slice(reqs); + desc_reqs.sort(); + desc_reqs.dedup(); + debugln!( + "usage::get_required_usage_from: final desc_reqs={:?}", + desc_reqs + ); + let mut ret_val = VecDeque::new(); + let args_in_groups = p.groups + .iter() + .filter(|gn| desc_reqs.contains(&gn.name)) + .flat_map(|g| p.arg_names_in_group(g.name)) + .collect::>(); + + let pmap = if let Some(m) = matcher { + desc_reqs + .iter() + .filter(|a| p.positionals.values().any(|p| &&p.b.name == a)) + .filter(|&pos| !m.contains(pos)) + .filter_map(|pos| p.positionals.values().find(|x| &x.b.name == pos)) + .filter(|&pos| incl_last || !pos.is_set(ArgSettings::Last)) + .filter(|pos| !args_in_groups.contains(&pos.b.name)) + .map(|pos| (pos.index, pos)) + .collect::>() // sort by index + } else { + desc_reqs + .iter() + .filter(|a| p.positionals.values().any(|pos| &&pos.b.name == a)) + .filter_map(|pos| p.positionals.values().find(|x| &x.b.name == pos)) + .filter(|&pos| incl_last || !pos.is_set(ArgSettings::Last)) + .filter(|pos| !args_in_groups.contains(&pos.b.name)) + .map(|pos| (pos.index, pos)) + .collect::>() // sort by index + }; + debugln!( + "usage::get_required_usage_from: args_in_groups={:?}", + args_in_groups + ); + for &p in pmap.values() { + let s = p.to_string(); + if args_in_groups.is_empty() || !args_in_groups.contains(&&*s) { + ret_val.push_back(s); + } + } + for a in desc_reqs + .iter() + .filter(|name| !p.positionals.values().any(|p| &&p.b.name == name)) + .filter(|name| !p.groups.iter().any(|g| &&g.name == name)) + .filter(|name| !args_in_groups.contains(name)) + .filter(|name| { + !(matcher.is_some() && matcher.as_ref().unwrap().contains(name)) + }) { + debugln!("usage::get_required_usage_from:iter:{}:", a); + let arg = find_by_name!(p, *a, flags, iter) + .map(|f| f.to_string()) + .unwrap_or_else(|| { + find_by_name!(p, *a, opts, iter) + .map(|o| o.to_string()) + .expect(INTERNAL_ERROR_MSG) + }); + ret_val.push_back(arg); + } + let mut g_vec: Vec = vec![]; + for g in desc_reqs + .iter() + .filter(|n| p.groups.iter().any(|g| &&g.name == n)) + { + let g_string = p.args_in_group(g).join("|"); + let elem = format!("<{}>", &g_string[..g_string.len()]); + if !g_vec.contains(&elem) { + g_vec.push(elem); + } + } + for g in g_vec { + ret_val.push_back(g); + } + + ret_val +} diff --git a/clap/src/app/validator.rs b/clap/src/app/validator.rs new file mode 100644 index 0000000..181b831 --- /dev/null +++ b/clap/src/app/validator.rs @@ -0,0 +1,573 @@ +// std +use std::fmt::Display; +#[allow(deprecated, unused_imports)] +use std::ascii::AsciiExt; + +// Internal +use INTERNAL_ERROR_MSG; +use INVALID_UTF8; +use args::{AnyArg, ArgMatcher, MatchedArg}; +use args::settings::ArgSettings; +use errors::{Error, ErrorKind}; +use errors::Result as ClapResult; +use app::settings::AppSettings as AS; +use app::parser::{ParseResult, Parser}; +use fmt::{Colorizer, ColorizerOption}; +use app::usage; + +pub struct Validator<'a, 'b, 'z>(&'z mut Parser<'a, 'b>) +where + 'a: 'b, + 'b: 'z; + +impl<'a, 'b, 'z> Validator<'a, 'b, 'z> { + pub fn new(p: &'z mut Parser<'a, 'b>) -> Self { Validator(p) } + + pub fn validate( + &mut self, + needs_val_of: ParseResult<'a>, + subcmd_name: Option, + matcher: &mut ArgMatcher<'a>, + ) -> ClapResult<()> { + debugln!("Validator::validate;"); + let mut reqs_validated = false; + self.0.add_env(matcher)?; + self.0.add_defaults(matcher)?; + if let ParseResult::Opt(a) = needs_val_of { + debugln!("Validator::validate: needs_val_of={:?}", a); + let o = { + self.0 + .opts + .iter() + .find(|o| o.b.name == a) + .expect(INTERNAL_ERROR_MSG) + .clone() + }; + self.validate_required(matcher)?; + reqs_validated = true; + let should_err = if let Some(v) = matcher.0.args.get(&*o.b.name) { + v.vals.is_empty() && !(o.v.min_vals.is_some() && o.v.min_vals.unwrap() == 0) + } else { + true + }; + if should_err { + return Err(Error::empty_value( + &o, + &*usage::create_error_usage(self.0, matcher, None), + self.0.color(), + )); + } + } + + if matcher.is_empty() && matcher.subcommand_name().is_none() + && self.0.is_set(AS::ArgRequiredElseHelp) + { + let mut out = vec![]; + self.0.write_help_err(&mut out)?; + return Err(Error { + message: String::from_utf8_lossy(&*out).into_owned(), + kind: ErrorKind::MissingArgumentOrSubcommand, + info: None, + }); + } + self.validate_blacklist(matcher)?; + if !(self.0.is_set(AS::SubcommandsNegateReqs) && subcmd_name.is_some()) && !reqs_validated { + self.validate_required(matcher)?; + } + self.validate_matched_args(matcher)?; + matcher.usage(usage::create_usage_with_title(self.0, &[])); + + Ok(()) + } + + fn validate_arg_values( + &self, + arg: &A, + ma: &MatchedArg, + matcher: &ArgMatcher<'a>, + ) -> ClapResult<()> + where + A: AnyArg<'a, 'b> + Display, + { + debugln!("Validator::validate_arg_values: arg={:?}", arg.name()); + for val in &ma.vals { + if self.0.is_set(AS::StrictUtf8) && val.to_str().is_none() { + debugln!( + "Validator::validate_arg_values: invalid UTF-8 found in val {:?}", + val + ); + return Err(Error::invalid_utf8( + &*usage::create_error_usage(self.0, matcher, None), + self.0.color(), + )); + } + if let Some(p_vals) = arg.possible_vals() { + debugln!("Validator::validate_arg_values: possible_vals={:?}", p_vals); + let val_str = val.to_string_lossy(); + let ok = if arg.is_set(ArgSettings::CaseInsensitive) { + p_vals.iter().any(|pv| pv.eq_ignore_ascii_case(&*val_str)) + } else { + p_vals.contains(&&*val_str) + }; + if !ok { + return Err(Error::invalid_value( + val_str, + p_vals, + arg, + &*usage::create_error_usage(self.0, matcher, None), + self.0.color(), + )); + } + } + if !arg.is_set(ArgSettings::EmptyValues) && val.is_empty() + && matcher.contains(&*arg.name()) + { + debugln!("Validator::validate_arg_values: illegal empty val found"); + return Err(Error::empty_value( + arg, + &*usage::create_error_usage(self.0, matcher, None), + self.0.color(), + )); + } + if let Some(vtor) = arg.validator() { + debug!("Validator::validate_arg_values: checking validator..."); + if let Err(e) = vtor(val.to_string_lossy().into_owned()) { + sdebugln!("error"); + return Err(Error::value_validation(Some(arg), e, self.0.color())); + } else { + sdebugln!("good"); + } + } + if let Some(vtor) = arg.validator_os() { + debug!("Validator::validate_arg_values: checking validator_os..."); + if let Err(e) = vtor(val) { + sdebugln!("error"); + return Err(Error::value_validation( + Some(arg), + (*e).to_string_lossy().to_string(), + self.0.color(), + )); + } else { + sdebugln!("good"); + } + } + } + Ok(()) + } + + fn build_err(&self, name: &str, matcher: &ArgMatcher) -> ClapResult<()> { + debugln!("build_err!: name={}", name); + let mut c_with = find_from!(self.0, &name, blacklist, matcher); + c_with = c_with.or( + self.0.find_any_arg(name).map_or(None, |aa| aa.blacklist()) + .map_or(None, + |bl| bl.iter().find(|arg| matcher.contains(arg))) + .map_or(None, |an| self.0.find_any_arg(an)) + .map_or(None, |aa| Some(format!("{}", aa))) + ); + debugln!("build_err!: '{:?}' conflicts with '{}'", c_with, &name); +// matcher.remove(&name); + let usg = usage::create_error_usage(self.0, matcher, None); + if let Some(f) = find_by_name!(self.0, name, flags, iter) { + debugln!("build_err!: It was a flag..."); + Err(Error::argument_conflict(f, c_with, &*usg, self.0.color())) + } else if let Some(o) = find_by_name!(self.0, name, opts, iter) { + debugln!("build_err!: It was an option..."); + Err(Error::argument_conflict(o, c_with, &*usg, self.0.color())) + } else { + match find_by_name!(self.0, name, positionals, values) { + Some(p) => { + debugln!("build_err!: It was a positional..."); + Err(Error::argument_conflict(p, c_with, &*usg, self.0.color())) + }, + None => panic!(INTERNAL_ERROR_MSG) + } + } + } + + fn validate_blacklist(&self, matcher: &mut ArgMatcher) -> ClapResult<()> { + debugln!("Validator::validate_blacklist;"); + let mut conflicts: Vec<&str> = vec![]; + for (&name, _) in matcher.iter() { + debugln!("Validator::validate_blacklist:iter:{};", name); + if let Some(grps) = self.0.groups_for_arg(name) { + for grp in &grps { + if let Some(g) = self.0.groups.iter().find(|g| &g.name == grp) { + if !g.multiple { + for arg in &g.args { + if arg == &name { + continue; + } + conflicts.push(arg); + } + } + if let Some(ref gc) = g.conflicts { + conflicts.extend(&*gc); + } + } + } + } + if let Some(arg) = find_any_by_name!(self.0, name) { + if let Some(bl) = arg.blacklist() { + for conf in bl { + if matcher.get(conf).is_some() { + conflicts.push(conf); + } + } + } + } else { + debugln!("Validator::validate_blacklist:iter:{}:group;", name); + let args = self.0.arg_names_in_group(name); + for arg in &args { + debugln!("Validator::validate_blacklist:iter:{}:group:iter:{};", name, arg); + if let Some(bl) = find_any_by_name!(self.0, *arg).unwrap().blacklist() { + for conf in bl { + if matcher.get(conf).is_some() { + conflicts.push(conf); + } + } + } + } + } + } + + for name in &conflicts { + debugln!( + "Validator::validate_blacklist:iter:{}: Checking blacklisted arg", + name + ); + let mut should_err = false; + if self.0.groups.iter().any(|g| &g.name == name) { + debugln!( + "Validator::validate_blacklist:iter:{}: groups contains it...", + name + ); + for n in self.0.arg_names_in_group(name) { + debugln!( + "Validator::validate_blacklist:iter:{}:iter:{}: looking in group...", + name, + n + ); + if matcher.contains(n) { + debugln!( + "Validator::validate_blacklist:iter:{}:iter:{}: matcher contains it...", + name, + n + ); + return self.build_err(n, matcher); + } + } + } else if let Some(ma) = matcher.get(name) { + debugln!( + "Validator::validate_blacklist:iter:{}: matcher contains it...", + name + ); + should_err = ma.occurs > 0; + } + if should_err { + return self.build_err(*name, matcher); + } + } + Ok(()) + } + + fn validate_matched_args(&self, matcher: &mut ArgMatcher<'a>) -> ClapResult<()> { + debugln!("Validator::validate_matched_args;"); + for (name, ma) in matcher.iter() { + debugln!( + "Validator::validate_matched_args:iter:{}: vals={:#?}", + name, + ma.vals + ); + if let Some(opt) = find_by_name!(self.0, *name, opts, iter) { + self.validate_arg_num_vals(opt, ma, matcher)?; + self.validate_arg_values(opt, ma, matcher)?; + self.validate_arg_requires(opt, ma, matcher)?; + self.validate_arg_num_occurs(opt, ma, matcher)?; + } else if let Some(flag) = find_by_name!(self.0, *name, flags, iter) { + self.validate_arg_requires(flag, ma, matcher)?; + self.validate_arg_num_occurs(flag, ma, matcher)?; + } else if let Some(pos) = find_by_name!(self.0, *name, positionals, values) { + self.validate_arg_num_vals(pos, ma, matcher)?; + self.validate_arg_num_occurs(pos, ma, matcher)?; + self.validate_arg_values(pos, ma, matcher)?; + self.validate_arg_requires(pos, ma, matcher)?; + } else { + let grp = self.0 + .groups + .iter() + .find(|g| &g.name == name) + .expect(INTERNAL_ERROR_MSG); + if let Some(ref g_reqs) = grp.requires { + if g_reqs.iter().any(|&n| !matcher.contains(n)) { + return self.missing_required_error(matcher, None); + } + } + } + } + Ok(()) + } + + fn validate_arg_num_occurs( + &self, + a: &A, + ma: &MatchedArg, + matcher: &ArgMatcher, + ) -> ClapResult<()> + where + A: AnyArg<'a, 'b> + Display, + { + debugln!("Validator::validate_arg_num_occurs: a={};", a.name()); + if ma.occurs > 1 && !a.is_set(ArgSettings::Multiple) { + // Not the first time, and we don't allow multiples + return Err(Error::unexpected_multiple_usage( + a, + &*usage::create_error_usage(self.0, matcher, None), + self.0.color(), + )); + } + Ok(()) + } + + fn validate_arg_num_vals( + &self, + a: &A, + ma: &MatchedArg, + matcher: &ArgMatcher, + ) -> ClapResult<()> + where + A: AnyArg<'a, 'b> + Display, + { + debugln!("Validator::validate_arg_num_vals:{}", a.name()); + if let Some(num) = a.num_vals() { + debugln!("Validator::validate_arg_num_vals: num_vals set...{}", num); + let should_err = if a.is_set(ArgSettings::Multiple) { + ((ma.vals.len() as u64) % num) != 0 + } else { + num != (ma.vals.len() as u64) + }; + if should_err { + debugln!("Validator::validate_arg_num_vals: Sending error WrongNumberOfValues"); + return Err(Error::wrong_number_of_values( + a, + num, + if a.is_set(ArgSettings::Multiple) { + (ma.vals.len() % num as usize) + } else { + ma.vals.len() + }, + if ma.vals.len() == 1 + || (a.is_set(ArgSettings::Multiple) && (ma.vals.len() % num as usize) == 1) + { + "as" + } else { + "ere" + }, + &*usage::create_error_usage(self.0, matcher, None), + self.0.color(), + )); + } + } + if let Some(num) = a.max_vals() { + debugln!("Validator::validate_arg_num_vals: max_vals set...{}", num); + if (ma.vals.len() as u64) > num { + debugln!("Validator::validate_arg_num_vals: Sending error TooManyValues"); + return Err(Error::too_many_values( + ma.vals + .iter() + .last() + .expect(INTERNAL_ERROR_MSG) + .to_str() + .expect(INVALID_UTF8), + a, + &*usage::create_error_usage(self.0, matcher, None), + self.0.color(), + )); + } + } + let min_vals_zero = if let Some(num) = a.min_vals() { + debugln!("Validator::validate_arg_num_vals: min_vals set: {}", num); + if (ma.vals.len() as u64) < num && num != 0 { + debugln!("Validator::validate_arg_num_vals: Sending error TooFewValues"); + return Err(Error::too_few_values( + a, + num, + ma.vals.len(), + &*usage::create_error_usage(self.0, matcher, None), + self.0.color(), + )); + } + num == 0 + } else { + false + }; + // Issue 665 (https://github.com/clap-rs/clap/issues/665) + // Issue 1105 (https://github.com/clap-rs/clap/issues/1105) + if a.takes_value() && !min_vals_zero && ma.vals.is_empty() { + return Err(Error::empty_value( + a, + &*usage::create_error_usage(self.0, matcher, None), + self.0.color(), + )); + } + Ok(()) + } + + fn validate_arg_requires( + &self, + a: &A, + ma: &MatchedArg, + matcher: &ArgMatcher, + ) -> ClapResult<()> + where + A: AnyArg<'a, 'b> + Display, + { + debugln!("Validator::validate_arg_requires:{};", a.name()); + if let Some(a_reqs) = a.requires() { + for &(val, name) in a_reqs.iter().filter(|&&(val, _)| val.is_some()) { + let missing_req = + |v| v == val.expect(INTERNAL_ERROR_MSG) && !matcher.contains(name); + if ma.vals.iter().any(missing_req) { + return self.missing_required_error(matcher, None); + } + } + for &(_, name) in a_reqs.iter().filter(|&&(val, _)| val.is_none()) { + if !matcher.contains(name) { + return self.missing_required_error(matcher, Some(name)); + } + } + } + Ok(()) + } + + fn validate_required(&mut self, matcher: &ArgMatcher) -> ClapResult<()> { + debugln!( + "Validator::validate_required: required={:?};", + self.0.required + ); + + let mut should_err = false; + let mut to_rem = Vec::new(); + for name in &self.0.required { + debugln!("Validator::validate_required:iter:{}:", name); + if matcher.contains(name) { + continue; + } + if to_rem.contains(name) { + continue; + } else if let Some(a) = find_any_by_name!(self.0, *name) { + if self.is_missing_required_ok(a, matcher) { + to_rem.push(a.name()); + if let Some(reqs) = a.requires() { + for r in reqs + .iter() + .filter(|&&(val, _)| val.is_none()) + .map(|&(_, name)| name) + { + to_rem.push(r); + } + } + continue; + } + } + should_err = true; + break; + } + if should_err { + for r in &to_rem { + 'inner: for i in (0 .. self.0.required.len()).rev() { + if &self.0.required[i] == r { + self.0.required.swap_remove(i); + break 'inner; + } + } + } + return self.missing_required_error(matcher, None); + } + + // Validate the conditionally required args + for &(a, v, r) in &self.0.r_ifs { + if let Some(ma) = matcher.get(a) { + if matcher.get(r).is_none() && ma.vals.iter().any(|val| val == v) { + return self.missing_required_error(matcher, Some(r)); + } + } + } + Ok(()) + } + + fn validate_arg_conflicts(&self, a: &AnyArg, matcher: &ArgMatcher) -> Option { + debugln!("Validator::validate_arg_conflicts: a={:?};", a.name()); + a.blacklist().map(|bl| { + bl.iter().any(|conf| { + matcher.contains(conf) + || self.0 + .groups + .iter() + .find(|g| &g.name == conf) + .map_or(false, |g| g.args.iter().any(|arg| matcher.contains(arg))) + }) + }) + } + + fn validate_required_unless(&self, a: &AnyArg, matcher: &ArgMatcher) -> Option { + debugln!("Validator::validate_required_unless: a={:?};", a.name()); + macro_rules! check { + ($how:ident, $_self:expr, $a:ident, $m:ident) => {{ + $a.required_unless().map(|ru| { + ru.iter().$how(|n| { + $m.contains(n) || { + if let Some(grp) = $_self.groups.iter().find(|g| &g.name == n) { + grp.args.iter().any(|arg| $m.contains(arg)) + } else { + false + } + } + }) + }) + }}; + } + if a.is_set(ArgSettings::RequiredUnlessAll) { + check!(all, self.0, a, matcher) + } else { + check!(any, self.0, a, matcher) + } + } + + fn missing_required_error(&self, matcher: &ArgMatcher, extra: Option<&str>) -> ClapResult<()> { + debugln!("Validator::missing_required_error: extra={:?}", extra); + let c = Colorizer::new(ColorizerOption { + use_stderr: true, + when: self.0.color(), + }); + let mut reqs = self.0.required.iter().map(|&r| &*r).collect::>(); + if let Some(r) = extra { + reqs.push(r); + } + reqs.retain(|n| !matcher.contains(n)); + reqs.dedup(); + debugln!("Validator::missing_required_error: reqs={:#?}", reqs); + let req_args = + usage::get_required_usage_from(self.0, &reqs[..], Some(matcher), extra, true) + .iter() + .fold(String::new(), |acc, s| { + acc + &format!("\n {}", c.error(s))[..] + }); + debugln!( + "Validator::missing_required_error: req_args={:#?}", + req_args + ); + Err(Error::missing_required_argument( + &*req_args, + &*usage::create_error_usage(self.0, matcher, extra), + self.0.color(), + )) + } + + #[inline] + fn is_missing_required_ok(&self, a: &AnyArg, matcher: &ArgMatcher) -> bool { + debugln!("Validator::is_missing_required_ok: a={}", a.name()); + self.validate_arg_conflicts(a, matcher).unwrap_or(false) + || self.validate_required_unless(a, matcher).unwrap_or(false) + } +} diff --git a/clap/src/args/any_arg.rs b/clap/src/args/any_arg.rs new file mode 100644 index 0000000..eee5228 --- /dev/null +++ b/clap/src/args/any_arg.rs @@ -0,0 +1,74 @@ +// Std +use std::rc::Rc; +use std::fmt as std_fmt; +use std::ffi::{OsStr, OsString}; + +// Internal +use args::settings::ArgSettings; +use map::{self, VecMap}; +use INTERNAL_ERROR_MSG; + +#[doc(hidden)] +pub trait AnyArg<'n, 'e>: std_fmt::Display { + fn name(&self) -> &'n str; + fn overrides(&self) -> Option<&[&'e str]>; + fn aliases(&self) -> Option>; + fn requires(&self) -> Option<&[(Option<&'e str>, &'n str)]>; + fn blacklist(&self) -> Option<&[&'e str]>; + fn required_unless(&self) -> Option<&[&'e str]>; + fn is_set(&self, ArgSettings) -> bool; + fn set(&mut self, ArgSettings); + fn has_switch(&self) -> bool; + fn max_vals(&self) -> Option; + fn min_vals(&self) -> Option; + fn num_vals(&self) -> Option; + fn possible_vals(&self) -> Option<&[&'e str]>; + fn validator(&self) -> Option<&Rc Result<(), String>>>; + fn validator_os(&self) -> Option<&Rc Result<(), OsString>>>; + fn short(&self) -> Option; + fn long(&self) -> Option<&'e str>; + fn val_delim(&self) -> Option; + fn takes_value(&self) -> bool; + fn val_names(&self) -> Option<&VecMap<&'e str>>; + fn help(&self) -> Option<&'e str>; + fn long_help(&self) -> Option<&'e str>; + fn default_val(&self) -> Option<&'e OsStr>; + fn default_vals_ifs(&self) -> Option, &'e OsStr)>>; + fn env<'s>(&'s self) -> Option<(&'n OsStr, Option<&'s OsString>)>; + fn longest_filter(&self) -> bool; + fn val_terminator(&self) -> Option<&'e str>; +} + +pub trait DispOrder { + fn disp_ord(&self) -> usize; +} + +impl<'n, 'e, 'z, T: ?Sized> AnyArg<'n, 'e> for &'z T where T: AnyArg<'n, 'e> + 'z { + fn name(&self) -> &'n str { (*self).name() } + fn overrides(&self) -> Option<&[&'e str]> { (*self).overrides() } + fn aliases(&self) -> Option> { (*self).aliases() } + fn requires(&self) -> Option<&[(Option<&'e str>, &'n str)]> { (*self).requires() } + fn blacklist(&self) -> Option<&[&'e str]> { (*self).blacklist() } + fn required_unless(&self) -> Option<&[&'e str]> { (*self).required_unless() } + fn is_set(&self, a: ArgSettings) -> bool { (*self).is_set(a) } + fn set(&mut self, _: ArgSettings) { panic!(INTERNAL_ERROR_MSG) } + fn has_switch(&self) -> bool { (*self).has_switch() } + fn max_vals(&self) -> Option { (*self).max_vals() } + fn min_vals(&self) -> Option { (*self).min_vals() } + fn num_vals(&self) -> Option { (*self).num_vals() } + fn possible_vals(&self) -> Option<&[&'e str]> { (*self).possible_vals() } + fn validator(&self) -> Option<&Rc Result<(), String>>> { (*self).validator() } + fn validator_os(&self) -> Option<&Rc Result<(), OsString>>> { (*self).validator_os() } + fn short(&self) -> Option { (*self).short() } + fn long(&self) -> Option<&'e str> { (*self).long() } + fn val_delim(&self) -> Option { (*self).val_delim() } + fn takes_value(&self) -> bool { (*self).takes_value() } + fn val_names(&self) -> Option<&VecMap<&'e str>> { (*self).val_names() } + fn help(&self) -> Option<&'e str> { (*self).help() } + fn long_help(&self) -> Option<&'e str> { (*self).long_help() } + fn default_val(&self) -> Option<&'e OsStr> { (*self).default_val() } + fn default_vals_ifs(&self) -> Option, &'e OsStr)>> { (*self).default_vals_ifs() } + fn env<'s>(&'s self) -> Option<(&'n OsStr, Option<&'s OsString>)> { (*self).env() } + fn longest_filter(&self) -> bool { (*self).longest_filter() } + fn val_terminator(&self) -> Option<&'e str> { (*self).val_terminator() } +} diff --git a/clap/src/args/arg.rs b/clap/src/args/arg.rs new file mode 100644 index 0000000..50a30ab --- /dev/null +++ b/clap/src/args/arg.rs @@ -0,0 +1,3954 @@ +#[cfg(feature = "yaml")] +use std::collections::BTreeMap; +use std::rc::Rc; +use std::ffi::{OsStr, OsString}; +#[cfg(any(target_os = "windows", target_arch = "wasm32"))] +use osstringext::OsStrExt3; +#[cfg(not(any(target_os = "windows", target_arch = "wasm32")))] +use std::os::unix::ffi::OsStrExt; +use std::env; + +#[cfg(feature = "yaml")] +use yaml_rust::Yaml; +use map::VecMap; + +use usage_parser::UsageParser; +use args::settings::ArgSettings; +use args::arg_builder::{Base, Switched, Valued}; + +/// The abstract representation of a command line argument. Used to set all the options and +/// relationships that define a valid argument for the program. +/// +/// There are two methods for constructing [`Arg`]s, using the builder pattern and setting options +/// manually, or using a usage string which is far less verbose but has fewer options. You can also +/// use a combination of the two methods to achieve the best of both worlds. +/// +/// # Examples +/// +/// ```rust +/// # use clap::Arg; +/// // Using the traditional builder pattern and setting each option manually +/// let cfg = Arg::with_name("config") +/// .short("c") +/// .long("config") +/// .takes_value(true) +/// .value_name("FILE") +/// .help("Provides a config file to myprog"); +/// // Using a usage string (setting a similar argument to the one above) +/// let input = Arg::from_usage("-i, --input=[FILE] 'Provides an input file to the program'"); +/// ``` +/// [`Arg`]: ./struct.Arg.html +#[allow(missing_debug_implementations)] +#[derive(Default, Clone)] +pub struct Arg<'a, 'b> +where + 'a: 'b, +{ + #[doc(hidden)] pub b: Base<'a, 'b>, + #[doc(hidden)] pub s: Switched<'b>, + #[doc(hidden)] pub v: Valued<'a, 'b>, + #[doc(hidden)] pub index: Option, + #[doc(hidden)] pub r_ifs: Option>, +} + +impl<'a, 'b> Arg<'a, 'b> { + /// Creates a new instance of [`Arg`] using a unique string name. The name will be used to get + /// information about whether or not the argument was used at runtime, get values, set + /// relationships with other args, etc.. + /// + /// **NOTE:** In the case of arguments that take values (i.e. [`Arg::takes_value(true)`]) + /// and positional arguments (i.e. those without a preceding `-` or `--`) the name will also + /// be displayed when the user prints the usage/help information of the program. + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg}; + /// Arg::with_name("config") + /// # ; + /// ``` + /// [`Arg::takes_value(true)`]: ./struct.Arg.html#method.takes_value + /// [`Arg`]: ./struct.Arg.html + pub fn with_name(n: &'a str) -> Self { + Arg { + b: Base::new(n), + ..Default::default() + } + } + + /// Creates a new instance of [`Arg`] from a .yml (YAML) file. + /// + /// # Examples + /// + /// ```ignore + /// # #[macro_use] + /// # extern crate clap; + /// # use clap::Arg; + /// # fn main() { + /// let yml = load_yaml!("arg.yml"); + /// let arg = Arg::from_yaml(yml); + /// # } + /// ``` + /// [`Arg`]: ./struct.Arg.html + #[cfg(feature = "yaml")] + pub fn from_yaml(y: &BTreeMap) -> Arg { + // We WANT this to panic on error...so expect() is good. + let name_yml = y.keys().nth(0).unwrap(); + let name_str = name_yml.as_str().unwrap(); + let mut a = Arg::with_name(name_str); + let arg_settings = y.get(name_yml).unwrap().as_hash().unwrap(); + + for (k, v) in arg_settings.iter() { + a = match k.as_str().unwrap() { + "short" => yaml_to_str!(a, v, short), + "long" => yaml_to_str!(a, v, long), + "aliases" => yaml_vec_or_str!(v, a, alias), + "help" => yaml_to_str!(a, v, help), + "long_help" => yaml_to_str!(a, v, long_help), + "required" => yaml_to_bool!(a, v, required), + "required_if" => yaml_tuple2!(a, v, required_if), + "required_ifs" => yaml_tuple2!(a, v, required_if), + "takes_value" => yaml_to_bool!(a, v, takes_value), + "index" => yaml_to_u64!(a, v, index), + "global" => yaml_to_bool!(a, v, global), + "multiple" => yaml_to_bool!(a, v, multiple), + "hidden" => yaml_to_bool!(a, v, hidden), + "next_line_help" => yaml_to_bool!(a, v, next_line_help), + "empty_values" => yaml_to_bool!(a, v, empty_values), + "group" => yaml_to_str!(a, v, group), + "number_of_values" => yaml_to_u64!(a, v, number_of_values), + "max_values" => yaml_to_u64!(a, v, max_values), + "min_values" => yaml_to_u64!(a, v, min_values), + "value_name" => yaml_to_str!(a, v, value_name), + "use_delimiter" => yaml_to_bool!(a, v, use_delimiter), + "allow_hyphen_values" => yaml_to_bool!(a, v, allow_hyphen_values), + "last" => yaml_to_bool!(a, v, last), + "require_delimiter" => yaml_to_bool!(a, v, require_delimiter), + "value_delimiter" => yaml_to_str!(a, v, value_delimiter), + "required_unless" => yaml_to_str!(a, v, required_unless), + "display_order" => yaml_to_usize!(a, v, display_order), + "default_value" => yaml_to_str!(a, v, default_value), + "default_value_if" => yaml_tuple3!(a, v, default_value_if), + "default_value_ifs" => yaml_tuple3!(a, v, default_value_if), + "env" => yaml_to_str!(a, v, env), + "value_names" => yaml_vec_or_str!(v, a, value_name), + "groups" => yaml_vec_or_str!(v, a, group), + "requires" => yaml_vec_or_str!(v, a, requires), + "requires_if" => yaml_tuple2!(a, v, requires_if), + "requires_ifs" => yaml_tuple2!(a, v, requires_if), + "conflicts_with" => yaml_vec_or_str!(v, a, conflicts_with), + "overrides_with" => yaml_vec_or_str!(v, a, overrides_with), + "possible_values" => yaml_vec_or_str!(v, a, possible_value), + "case_insensitive" => yaml_to_bool!(a, v, case_insensitive), + "required_unless_one" => yaml_vec_or_str!(v, a, required_unless), + "required_unless_all" => { + a = yaml_vec_or_str!(v, a, required_unless); + a.setb(ArgSettings::RequiredUnlessAll); + a + } + s => panic!( + "Unknown Arg setting '{}' in YAML file for arg '{}'", + s, name_str + ), + } + } + + a + } + + /// Creates a new instance of [`Arg`] from a usage string. Allows creation of basic settings + /// for the [`Arg`]. The syntax is flexible, but there are some rules to follow. + /// + /// **NOTE**: Not all settings may be set using the usage string method. Some properties are + /// only available via the builder pattern. + /// + /// **NOTE**: Only ASCII values are officially supported in [`Arg::from_usage`] strings. Some + /// UTF-8 codepoints may work just fine, but this is not guaranteed. + /// + /// # Syntax + /// + /// Usage strings typically following the form: + /// + /// ```notrust + /// [explicit name] [short] [long] [value names] [help string] + /// ``` + /// + /// This is not a hard rule as the attributes can appear in other orders. There are also + /// several additional sigils which denote additional settings. Below are the details of each + /// portion of the string. + /// + /// ### Explicit Name + /// + /// This is an optional field, if it's omitted the argument will use one of the additional + /// fields as the name using the following priority order: + /// + /// * Explicit Name (This always takes precedence when present) + /// * Long + /// * Short + /// * Value Name + /// + /// `clap` determines explicit names as the first string of characters between either `[]` or + /// `<>` where `[]` has the dual notation of meaning the argument is optional, and `<>` meaning + /// the argument is required. + /// + /// Explicit names may be followed by: + /// * The multiple denotation `...` + /// + /// Example explicit names as follows (`ename` for an optional argument, and `rname` for a + /// required argument): + /// + /// ```notrust + /// [ename] -s, --long 'some flag' + /// -r, --longer 'some other flag' + /// ``` + /// + /// ### Short + /// + /// This is set by placing a single character after a leading `-`. + /// + /// Shorts may be followed by + /// * The multiple denotation `...` + /// * An optional comma `,` which is cosmetic only + /// * Value notation + /// + /// Example shorts are as follows (`-s`, and `-r`): + /// + /// ```notrust + /// -s, --long 'some flag' + /// -r [val], --longer 'some option' + /// ``` + /// + /// ### Long + /// + /// This is set by placing a word (no spaces) after a leading `--`. + /// + /// Shorts may be followed by + /// * The multiple denotation `...` + /// * Value notation + /// + /// Example longs are as follows (`--some`, and `--rapid`): + /// + /// ```notrust + /// -s, --some 'some flag' + /// --rapid=[FILE] 'some option' + /// ``` + /// + /// ### Values (Value Notation) + /// + /// This is set by placing a word(s) between `[]` or `<>` optionally after `=` (although this + /// is cosmetic only and does not affect functionality). If an explicit name has **not** been + /// set, using `<>` will denote a required argument, and `[]` will denote an optional argument + /// + /// Values may be followed by + /// * The multiple denotation `...` + /// * More Value notation + /// + /// More than one value will also implicitly set the arguments number of values, i.e. having + /// two values, `--option [val1] [val2]` specifies that in order for option to be satisified it + /// must receive exactly two values + /// + /// Example values are as follows (`FILE`, and `SPEED`): + /// + /// ```notrust + /// -s, --some [FILE] 'some option' + /// --rapid=... 'some required multiple option' + /// ``` + /// + /// ### Help String + /// + /// The help string is denoted between a pair of single quotes `''` and may contain any + /// characters. + /// + /// Example help strings are as follows: + /// + /// ```notrust + /// -s, --some [FILE] 'some option' + /// --rapid=... 'some required multiple option' + /// ``` + /// + /// ### Additional Sigils + /// + /// Multiple notation `...` (three consecutive dots/periods) specifies that this argument may + /// be used multiple times. Do not confuse multiple occurrences (`...`) with multiple values. + /// `--option val1 val2` is a single occurrence with multiple values. `--flag --flag` is + /// multiple occurrences (and then you can obviously have instances of both as well) + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg}; + /// App::new("prog") + /// .args(&[ + /// Arg::from_usage("--config 'a required file for the configuration and no short'"), + /// Arg::from_usage("-d, --debug... 'turns on debugging information and allows multiples'"), + /// Arg::from_usage("[input] 'an optional input file to use'") + /// ]) + /// # ; + /// ``` + /// [`Arg`]: ./struct.Arg.html + /// [`Arg::from_usage`]: ./struct.Arg.html#method.from_usage + pub fn from_usage(u: &'a str) -> Self { + let parser = UsageParser::from_usage(u); + parser.parse() + } + + /// Sets the short version of the argument without the preceding `-`. + /// + /// By default `clap` automatically assigns `V` and `h` to the auto-generated `version` and + /// `help` arguments respectively. You may use the uppercase `V` or lowercase `h` for your own + /// arguments, in which case `clap` simply will not assign those to the auto-generated + /// `version` or `help` arguments. + /// + /// **NOTE:** Any leading `-` characters will be stripped, and only the first + /// non `-` character will be used as the [`short`] version + /// + /// # Examples + /// + /// To set [`short`] use a single valid UTF-8 code point. If you supply a leading `-` such as + /// `-c`, the `-` will be stripped. + /// + /// ```rust + /// # use clap::{App, Arg}; + /// Arg::with_name("config") + /// .short("c") + /// # ; + /// ``` + /// + /// Setting [`short`] allows using the argument via a single hyphen (`-`) such as `-c` + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("prog") + /// .arg(Arg::with_name("config") + /// .short("c")) + /// .get_matches_from(vec![ + /// "prog", "-c" + /// ]); + /// + /// assert!(m.is_present("config")); + /// ``` + /// [`short`]: ./struct.Arg.html#method.short + pub fn short>(mut self, s: S) -> Self { + self.s.short = s.as_ref().trim_left_matches(|c| c == '-').chars().nth(0); + self + } + + /// Sets the long version of the argument without the preceding `--`. + /// + /// By default `clap` automatically assigns `version` and `help` to the auto-generated + /// `version` and `help` arguments respectively. You may use the word `version` or `help` for + /// the long form of your own arguments, in which case `clap` simply will not assign those to + /// the auto-generated `version` or `help` arguments. + /// + /// **NOTE:** Any leading `-` characters will be stripped + /// + /// # Examples + /// + /// To set `long` use a word containing valid UTF-8 codepoints. If you supply a double leading + /// `--` such as `--config` they will be stripped. Hyphens in the middle of the word, however, + /// will *not* be stripped (i.e. `config-file` is allowed) + /// + /// ```rust + /// # use clap::{App, Arg}; + /// Arg::with_name("cfg") + /// .long("config") + /// # ; + /// ``` + /// + /// Setting `long` allows using the argument via a double hyphen (`--`) such as `--config` + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("prog") + /// .arg(Arg::with_name("cfg") + /// .long("config")) + /// .get_matches_from(vec![ + /// "prog", "--config" + /// ]); + /// + /// assert!(m.is_present("cfg")); + /// ``` + pub fn long(mut self, l: &'b str) -> Self { + self.s.long = Some(l.trim_left_matches(|c| c == '-')); + self + } + + /// Allows adding a [`Arg`] alias, which function as "hidden" arguments that + /// automatically dispatch as if this argument was used. This is more efficient, and easier + /// than creating multiple hidden arguments as one only needs to check for the existence of + /// this command, and not all variants. + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("prog") + /// .arg(Arg::with_name("test") + /// .long("test") + /// .alias("alias") + /// .takes_value(true)) + /// .get_matches_from(vec![ + /// "prog", "--alias", "cool" + /// ]); + /// assert!(m.is_present("test")); + /// assert_eq!(m.value_of("test"), Some("cool")); + /// ``` + /// [`Arg`]: ./struct.Arg.html + pub fn alias>(mut self, name: S) -> Self { + if let Some(ref mut als) = self.s.aliases { + als.push((name.into(), false)); + } else { + self.s.aliases = Some(vec![(name.into(), false)]); + } + self + } + + /// Allows adding [`Arg`] aliases, which function as "hidden" arguments that + /// automatically dispatch as if this argument was used. This is more efficient, and easier + /// than creating multiple hidden subcommands as one only needs to check for the existence of + /// this command, and not all variants. + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("prog") + /// .arg(Arg::with_name("test") + /// .long("test") + /// .aliases(&["do-stuff", "do-tests", "tests"]) + /// .help("the file to add") + /// .required(false)) + /// .get_matches_from(vec![ + /// "prog", "--do-tests" + /// ]); + /// assert!(m.is_present("test")); + /// ``` + /// [`Arg`]: ./struct.Arg.html + pub fn aliases(mut self, names: &[&'b str]) -> Self { + if let Some(ref mut als) = self.s.aliases { + for n in names { + als.push((n, false)); + } + } else { + self.s.aliases = Some(names.iter().map(|n| (*n, false)).collect::>()); + } + self + } + + /// Allows adding a [`Arg`] alias that functions exactly like those defined with + /// [`Arg::alias`], except that they are visible inside the help message. + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("prog") + /// .arg(Arg::with_name("test") + /// .visible_alias("something-awesome") + /// .long("test") + /// .takes_value(true)) + /// .get_matches_from(vec![ + /// "prog", "--something-awesome", "coffee" + /// ]); + /// assert!(m.is_present("test")); + /// assert_eq!(m.value_of("test"), Some("coffee")); + /// ``` + /// [`Arg`]: ./struct.Arg.html + /// [`App::alias`]: ./struct.Arg.html#method.alias + pub fn visible_alias>(mut self, name: S) -> Self { + if let Some(ref mut als) = self.s.aliases { + als.push((name.into(), true)); + } else { + self.s.aliases = Some(vec![(name.into(), true)]); + } + self + } + + /// Allows adding multiple [`Arg`] aliases that functions exactly like those defined + /// with [`Arg::aliases`], except that they are visible inside the help message. + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("prog") + /// .arg(Arg::with_name("test") + /// .long("test") + /// .visible_aliases(&["something", "awesome", "cool"])) + /// .get_matches_from(vec![ + /// "prog", "--awesome" + /// ]); + /// assert!(m.is_present("test")); + /// ``` + /// [`Arg`]: ./struct.Arg.html + /// [`App::aliases`]: ./struct.Arg.html#method.aliases + pub fn visible_aliases(mut self, names: &[&'b str]) -> Self { + if let Some(ref mut als) = self.s.aliases { + for n in names { + als.push((n, true)); + } + } else { + self.s.aliases = Some(names.iter().map(|n| (*n, true)).collect::>()); + } + self + } + + /// Sets the short help text of the argument that will be displayed to the user when they print + /// the help information with `-h`. Typically, this is a short (one line) description of the + /// arg. + /// + /// **NOTE:** If only `Arg::help` is provided, and not [`Arg::long_help`] but the user requests + /// `--help` clap will still display the contents of `help` appropriately + /// + /// **NOTE:** Only `Arg::help` is used in completion script generation in order to be concise + /// + /// # Examples + /// + /// Any valid UTF-8 is allowed in the help text. The one exception is when one wishes to + /// include a newline in the help text and have the following text be properly aligned with all + /// the other help text. + /// + /// ```rust + /// # use clap::{App, Arg}; + /// Arg::with_name("config") + /// .help("The config file used by the myprog") + /// # ; + /// ``` + /// + /// Setting `help` displays a short message to the side of the argument when the user passes + /// `-h` or `--help` (by default). + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("prog") + /// .arg(Arg::with_name("cfg") + /// .long("config") + /// .help("Some help text describing the --config arg")) + /// .get_matches_from(vec![ + /// "prog", "--help" + /// ]); + /// ``` + /// + /// The above example displays + /// + /// ```notrust + /// helptest + /// + /// USAGE: + /// helptest [FLAGS] + /// + /// FLAGS: + /// --config Some help text describing the --config arg + /// -h, --help Prints help information + /// -V, --version Prints version information + /// ``` + /// [`Arg::long_help`]: ./struct.Arg.html#method.long_help + pub fn help(mut self, h: &'b str) -> Self { + self.b.help = Some(h); + self + } + + /// Sets the long help text of the argument that will be displayed to the user when they print + /// the help information with `--help`. Typically this a more detailed (multi-line) message + /// that describes the arg. + /// + /// **NOTE:** If only `long_help` is provided, and not [`Arg::help`] but the user requests `-h` + /// clap will still display the contents of `long_help` appropriately + /// + /// **NOTE:** Only [`Arg::help`] is used in completion script generation in order to be concise + /// + /// # Examples + /// + /// Any valid UTF-8 is allowed in the help text. The one exception is when one wishes to + /// include a newline in the help text and have the following text be properly aligned with all + /// the other help text. + /// + /// ```rust + /// # use clap::{App, Arg}; + /// Arg::with_name("config") + /// .long_help( + /// "The config file used by the myprog must be in JSON format + /// with only valid keys and may not contain other nonsense + /// that cannot be read by this program. Obviously I'm going on + /// and on, so I'll stop now.") + /// # ; + /// ``` + /// + /// Setting `help` displays a short message to the side of the argument when the user passes + /// `-h` or `--help` (by default). + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("prog") + /// .arg(Arg::with_name("cfg") + /// .long("config") + /// .long_help( + /// "The config file used by the myprog must be in JSON format + /// with only valid keys and may not contain other nonsense + /// that cannot be read by this program. Obviously I'm going on + /// and on, so I'll stop now.")) + /// .get_matches_from(vec![ + /// "prog", "--help" + /// ]); + /// ``` + /// + /// The above example displays + /// + /// ```notrust + /// helptest + /// + /// USAGE: + /// helptest [FLAGS] + /// + /// FLAGS: + /// --config + /// The config file used by the myprog must be in JSON format + /// with only valid keys and may not contain other nonsense + /// that cannot be read by this program. Obviously I'm going on + /// and on, so I'll stop now. + /// + /// -h, --help + /// Prints help information + /// + /// -V, --version + /// Prints version information + /// ``` + /// [`Arg::help`]: ./struct.Arg.html#method.help + pub fn long_help(mut self, h: &'b str) -> Self { + self.b.long_help = Some(h); + self + } + + /// Specifies that this arg is the last, or final, positional argument (i.e. has the highest + /// index) and is *only* able to be accessed via the `--` syntax (i.e. `$ prog args -- + /// last_arg`). Even, if no other arguments are left to parse, if the user omits the `--` syntax + /// they will receive an [`UnknownArgument`] error. Setting an argument to `.last(true)` also + /// allows one to access this arg early using the `--` syntax. Accessing an arg early, even with + /// the `--` syntax is otherwise not possible. + /// + /// **NOTE:** This will change the usage string to look like `$ prog [FLAGS] [-- ]` if + /// `ARG` is marked as `.last(true)`. + /// + /// **NOTE:** This setting will imply [`AppSettings::DontCollapseArgsInUsage`] because failing + /// to set this can make the usage string very confusing. + /// + /// **NOTE**: This setting only applies to positional arguments, and has no affect on FLAGS / + /// OPTIONS + /// + /// **CAUTION:** Setting an argument to `.last(true)` *and* having child subcommands is not + /// recommended with the exception of *also* using [`AppSettings::ArgsNegateSubcommands`] + /// (or [`AppSettings::SubcommandsNegateReqs`] if the argument marked `.last(true)` is also + /// marked [`.required(true)`]) + /// + /// # Examples + /// + /// ```rust + /// # use clap::Arg; + /// Arg::with_name("args") + /// .last(true) + /// # ; + /// ``` + /// + /// Setting [`Arg::last(true)`] ensures the arg has the highest [index] of all positional args + /// and requires that the `--` syntax be used to access it early. + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let res = App::new("prog") + /// .arg(Arg::with_name("first")) + /// .arg(Arg::with_name("second")) + /// .arg(Arg::with_name("third").last(true)) + /// .get_matches_from_safe(vec![ + /// "prog", "one", "--", "three" + /// ]); + /// + /// assert!(res.is_ok()); + /// let m = res.unwrap(); + /// assert_eq!(m.value_of("third"), Some("three")); + /// assert!(m.value_of("second").is_none()); + /// ``` + /// + /// Even if the positional argument marked `.last(true)` is the only argument left to parse, + /// failing to use the `--` syntax results in an error. + /// + /// ```rust + /// # use clap::{App, Arg, ErrorKind}; + /// let res = App::new("prog") + /// .arg(Arg::with_name("first")) + /// .arg(Arg::with_name("second")) + /// .arg(Arg::with_name("third").last(true)) + /// .get_matches_from_safe(vec![ + /// "prog", "one", "two", "three" + /// ]); + /// + /// assert!(res.is_err()); + /// assert_eq!(res.unwrap_err().kind, ErrorKind::UnknownArgument); + /// ``` + /// [`Arg::last(true)`]: ./struct.Arg.html#method.last + /// [index]: ./struct.Arg.html#method.index + /// [`AppSettings::DontCollapseArgsInUsage`]: ./enum.AppSettings.html#variant.DontCollapseArgsInUsage + /// [`AppSettings::ArgsNegateSubcommands`]: ./enum.AppSettings.html#variant.ArgsNegateSubcommands + /// [`AppSettings::SubcommandsNegateReqs`]: ./enum.AppSettings.html#variant.SubcommandsNegateReqs + /// [`.required(true)`]: ./struct.Arg.html#method.required + /// [`UnknownArgument`]: ./enum.ErrorKind.html#variant.UnknownArgument + pub fn last(self, l: bool) -> Self { + if l { + self.set(ArgSettings::Last) + } else { + self.unset(ArgSettings::Last) + } + } + + /// Sets whether or not the argument is required by default. Required by default means it is + /// required, when no other conflicting rules have been evaluated. Conflicting rules take + /// precedence over being required. **Default:** `false` + /// + /// **NOTE:** Flags (i.e. not positional, or arguments that take values) cannot be required by + /// default. This is simply because if a flag should be required, it should simply be implied + /// as no additional information is required from user. Flags by their very nature are simply + /// yes/no, or true/false. + /// + /// # Examples + /// + /// ```rust + /// # use clap::Arg; + /// Arg::with_name("config") + /// .required(true) + /// # ; + /// ``` + /// + /// Setting [`Arg::required(true)`] requires that the argument be used at runtime. + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let res = App::new("prog") + /// .arg(Arg::with_name("cfg") + /// .required(true) + /// .takes_value(true) + /// .long("config")) + /// .get_matches_from_safe(vec![ + /// "prog", "--config", "file.conf" + /// ]); + /// + /// assert!(res.is_ok()); + /// ``` + /// + /// Setting [`Arg::required(true)`] and *not* supplying that argument is an error. + /// + /// ```rust + /// # use clap::{App, Arg, ErrorKind}; + /// let res = App::new("prog") + /// .arg(Arg::with_name("cfg") + /// .required(true) + /// .takes_value(true) + /// .long("config")) + /// .get_matches_from_safe(vec![ + /// "prog" + /// ]); + /// + /// assert!(res.is_err()); + /// assert_eq!(res.unwrap_err().kind, ErrorKind::MissingRequiredArgument); + /// ``` + /// [`Arg::required(true)`]: ./struct.Arg.html#method.required + pub fn required(self, r: bool) -> Self { + if r { + self.set(ArgSettings::Required) + } else { + self.unset(ArgSettings::Required) + } + } + + /// Requires that options use the `--option=val` syntax (i.e. an equals between the option and + /// associated value) **Default:** `false` + /// + /// **NOTE:** This setting also removes the default of allowing empty values and implies + /// [`Arg::empty_values(false)`]. + /// + /// # Examples + /// + /// ```rust + /// # use clap::Arg; + /// Arg::with_name("config") + /// .long("config") + /// .takes_value(true) + /// .require_equals(true) + /// # ; + /// ``` + /// + /// Setting [`Arg::require_equals(true)`] requires that the option have an equals sign between + /// it and the associated value. + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let res = App::new("prog") + /// .arg(Arg::with_name("cfg") + /// .require_equals(true) + /// .takes_value(true) + /// .long("config")) + /// .get_matches_from_safe(vec![ + /// "prog", "--config=file.conf" + /// ]); + /// + /// assert!(res.is_ok()); + /// ``` + /// + /// Setting [`Arg::require_equals(true)`] and *not* supplying the equals will cause an error + /// unless [`Arg::empty_values(true)`] is set. + /// + /// ```rust + /// # use clap::{App, Arg, ErrorKind}; + /// let res = App::new("prog") + /// .arg(Arg::with_name("cfg") + /// .require_equals(true) + /// .takes_value(true) + /// .long("config")) + /// .get_matches_from_safe(vec![ + /// "prog", "--config", "file.conf" + /// ]); + /// + /// assert!(res.is_err()); + /// assert_eq!(res.unwrap_err().kind, ErrorKind::EmptyValue); + /// ``` + /// [`Arg::require_equals(true)`]: ./struct.Arg.html#method.require_equals + /// [`Arg::empty_values(true)`]: ./struct.Arg.html#method.empty_values + /// [`Arg::empty_values(false)`]: ./struct.Arg.html#method.empty_values + pub fn require_equals(mut self, r: bool) -> Self { + if r { + self.unsetb(ArgSettings::EmptyValues); + self.set(ArgSettings::RequireEquals) + } else { + self.unset(ArgSettings::RequireEquals) + } + } + + /// Allows values which start with a leading hyphen (`-`) + /// + /// **WARNING**: Take caution when using this setting combined with [`Arg::multiple(true)`], as + /// this becomes ambiguous `$ prog --arg -- -- val`. All three `--, --, val` will be values + /// when the user may have thought the second `--` would constitute the normal, "Only + /// positional args follow" idiom. To fix this, consider using [`Arg::number_of_values(1)`] + /// + /// **WARNING**: When building your CLIs, consider the effects of allowing leading hyphens and + /// the user passing in a value that matches a valid short. For example `prog -opt -F` where + /// `-F` is supposed to be a value, yet `-F` is *also* a valid short for another arg. Care should + /// should be taken when designing these args. This is compounded by the ability to "stack" + /// short args. I.e. if `-val` is supposed to be a value, but `-v`, `-a`, and `-l` are all valid + /// shorts. + /// + /// # Examples + /// + /// ```rust + /// # use clap::Arg; + /// Arg::with_name("pattern") + /// .allow_hyphen_values(true) + /// # ; + /// ``` + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("prog") + /// .arg(Arg::with_name("pat") + /// .allow_hyphen_values(true) + /// .takes_value(true) + /// .long("pattern")) + /// .get_matches_from(vec![ + /// "prog", "--pattern", "-file" + /// ]); + /// + /// assert_eq!(m.value_of("pat"), Some("-file")); + /// ``` + /// + /// Not setting [`Arg::allow_hyphen_values(true)`] and supplying a value which starts with a + /// hyphen is an error. + /// + /// ```rust + /// # use clap::{App, Arg, ErrorKind}; + /// let res = App::new("prog") + /// .arg(Arg::with_name("pat") + /// .takes_value(true) + /// .long("pattern")) + /// .get_matches_from_safe(vec![ + /// "prog", "--pattern", "-file" + /// ]); + /// + /// assert!(res.is_err()); + /// assert_eq!(res.unwrap_err().kind, ErrorKind::UnknownArgument); + /// ``` + /// [`Arg::allow_hyphen_values(true)`]: ./struct.Arg.html#method.allow_hyphen_values + /// [`Arg::multiple(true)`]: ./struct.Arg.html#method.multiple + /// [`Arg::number_of_values(1)`]: ./struct.Arg.html#method.number_of_values + pub fn allow_hyphen_values(self, a: bool) -> Self { + if a { + self.set(ArgSettings::AllowLeadingHyphen) + } else { + self.unset(ArgSettings::AllowLeadingHyphen) + } + } + /// Sets an arg that override this arg's required setting. (i.e. this arg will be required + /// unless this other argument is present). + /// + /// **Pro Tip:** Using [`Arg::required_unless`] implies [`Arg::required`] and is therefore not + /// mandatory to also set. + /// + /// # Examples + /// + /// ```rust + /// # use clap::Arg; + /// Arg::with_name("config") + /// .required_unless("debug") + /// # ; + /// ``` + /// + /// Setting [`Arg::required_unless(name)`] requires that the argument be used at runtime + /// *unless* `name` is present. In the following example, the required argument is *not* + /// provided, but it's not an error because the `unless` arg has been supplied. + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let res = App::new("prog") + /// .arg(Arg::with_name("cfg") + /// .required_unless("dbg") + /// .takes_value(true) + /// .long("config")) + /// .arg(Arg::with_name("dbg") + /// .long("debug")) + /// .get_matches_from_safe(vec![ + /// "prog", "--debug" + /// ]); + /// + /// assert!(res.is_ok()); + /// ``` + /// + /// Setting [`Arg::required_unless(name)`] and *not* supplying `name` or this arg is an error. + /// + /// ```rust + /// # use clap::{App, Arg, ErrorKind}; + /// let res = App::new("prog") + /// .arg(Arg::with_name("cfg") + /// .required_unless("dbg") + /// .takes_value(true) + /// .long("config")) + /// .arg(Arg::with_name("dbg") + /// .long("debug")) + /// .get_matches_from_safe(vec![ + /// "prog" + /// ]); + /// + /// assert!(res.is_err()); + /// assert_eq!(res.unwrap_err().kind, ErrorKind::MissingRequiredArgument); + /// ``` + /// [`Arg::required_unless`]: ./struct.Arg.html#method.required_unless + /// [`Arg::required`]: ./struct.Arg.html#method.required + /// [`Arg::required_unless(name)`]: ./struct.Arg.html#method.required_unless + pub fn required_unless(mut self, name: &'a str) -> Self { + if let Some(ref mut vec) = self.b.r_unless { + vec.push(name); + } else { + self.b.r_unless = Some(vec![name]); + } + self.required(true) + } + + /// Sets args that override this arg's required setting. (i.e. this arg will be required unless + /// all these other arguments are present). + /// + /// **NOTE:** If you wish for this argument to only be required if *one of* these args are + /// present see [`Arg::required_unless_one`] + /// + /// # Examples + /// + /// ```rust + /// # use clap::Arg; + /// Arg::with_name("config") + /// .required_unless_all(&["cfg", "dbg"]) + /// # ; + /// ``` + /// + /// Setting [`Arg::required_unless_all(names)`] requires that the argument be used at runtime + /// *unless* *all* the args in `names` are present. In the following example, the required + /// argument is *not* provided, but it's not an error because all the `unless` args have been + /// supplied. + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let res = App::new("prog") + /// .arg(Arg::with_name("cfg") + /// .required_unless_all(&["dbg", "infile"]) + /// .takes_value(true) + /// .long("config")) + /// .arg(Arg::with_name("dbg") + /// .long("debug")) + /// .arg(Arg::with_name("infile") + /// .short("i") + /// .takes_value(true)) + /// .get_matches_from_safe(vec![ + /// "prog", "--debug", "-i", "file" + /// ]); + /// + /// assert!(res.is_ok()); + /// ``` + /// + /// Setting [`Arg::required_unless_all(names)`] and *not* supplying *all* of `names` or this + /// arg is an error. + /// + /// ```rust + /// # use clap::{App, Arg, ErrorKind}; + /// let res = App::new("prog") + /// .arg(Arg::with_name("cfg") + /// .required_unless_all(&["dbg", "infile"]) + /// .takes_value(true) + /// .long("config")) + /// .arg(Arg::with_name("dbg") + /// .long("debug")) + /// .arg(Arg::with_name("infile") + /// .short("i") + /// .takes_value(true)) + /// .get_matches_from_safe(vec![ + /// "prog" + /// ]); + /// + /// assert!(res.is_err()); + /// assert_eq!(res.unwrap_err().kind, ErrorKind::MissingRequiredArgument); + /// ``` + /// [`Arg::required_unless_one`]: ./struct.Arg.html#method.required_unless_one + /// [`Arg::required_unless_all(names)`]: ./struct.Arg.html#method.required_unless_all + pub fn required_unless_all(mut self, names: &[&'a str]) -> Self { + if let Some(ref mut vec) = self.b.r_unless { + for s in names { + vec.push(s); + } + } else { + self.b.r_unless = Some(names.iter().map(|s| *s).collect::>()); + } + self.setb(ArgSettings::RequiredUnlessAll); + self.required(true) + } + + /// Sets args that override this arg's [required] setting. (i.e. this arg will be required + /// unless *at least one of* these other arguments are present). + /// + /// **NOTE:** If you wish for this argument to only be required if *all of* these args are + /// present see [`Arg::required_unless_all`] + /// + /// # Examples + /// + /// ```rust + /// # use clap::Arg; + /// Arg::with_name("config") + /// .required_unless_all(&["cfg", "dbg"]) + /// # ; + /// ``` + /// + /// Setting [`Arg::required_unless_one(names)`] requires that the argument be used at runtime + /// *unless* *at least one of* the args in `names` are present. In the following example, the + /// required argument is *not* provided, but it's not an error because one the `unless` args + /// have been supplied. + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let res = App::new("prog") + /// .arg(Arg::with_name("cfg") + /// .required_unless_one(&["dbg", "infile"]) + /// .takes_value(true) + /// .long("config")) + /// .arg(Arg::with_name("dbg") + /// .long("debug")) + /// .arg(Arg::with_name("infile") + /// .short("i") + /// .takes_value(true)) + /// .get_matches_from_safe(vec![ + /// "prog", "--debug" + /// ]); + /// + /// assert!(res.is_ok()); + /// ``` + /// + /// Setting [`Arg::required_unless_one(names)`] and *not* supplying *at least one of* `names` + /// or this arg is an error. + /// + /// ```rust + /// # use clap::{App, Arg, ErrorKind}; + /// let res = App::new("prog") + /// .arg(Arg::with_name("cfg") + /// .required_unless_one(&["dbg", "infile"]) + /// .takes_value(true) + /// .long("config")) + /// .arg(Arg::with_name("dbg") + /// .long("debug")) + /// .arg(Arg::with_name("infile") + /// .short("i") + /// .takes_value(true)) + /// .get_matches_from_safe(vec![ + /// "prog" + /// ]); + /// + /// assert!(res.is_err()); + /// assert_eq!(res.unwrap_err().kind, ErrorKind::MissingRequiredArgument); + /// ``` + /// [required]: ./struct.Arg.html#method.required + /// [`Arg::required_unless_one(names)`]: ./struct.Arg.html#method.required_unless_one + /// [`Arg::required_unless_all`]: ./struct.Arg.html#method.required_unless_all + pub fn required_unless_one(mut self, names: &[&'a str]) -> Self { + if let Some(ref mut vec) = self.b.r_unless { + for s in names { + vec.push(s); + } + } else { + self.b.r_unless = Some(names.iter().map(|s| *s).collect::>()); + } + self.required(true) + } + + /// Sets a conflicting argument by name. I.e. when using this argument, + /// the following argument can't be present and vice versa. + /// + /// **NOTE:** Conflicting rules take precedence over being required by default. Conflict rules + /// only need to be set for one of the two arguments, they do not need to be set for each. + /// + /// **NOTE:** Defining a conflict is two-way, but does *not* need to defined for both arguments + /// (i.e. if A conflicts with B, defining A.conflicts_with(B) is sufficient. You do not need + /// need to also do B.conflicts_with(A)) + /// + /// # Examples + /// + /// ```rust + /// # use clap::Arg; + /// Arg::with_name("config") + /// .conflicts_with("debug") + /// # ; + /// ``` + /// + /// Setting conflicting argument, and having both arguments present at runtime is an error. + /// + /// ```rust + /// # use clap::{App, Arg, ErrorKind}; + /// let res = App::new("prog") + /// .arg(Arg::with_name("cfg") + /// .takes_value(true) + /// .conflicts_with("debug") + /// .long("config")) + /// .arg(Arg::with_name("debug") + /// .long("debug")) + /// .get_matches_from_safe(vec![ + /// "prog", "--debug", "--config", "file.conf" + /// ]); + /// + /// assert!(res.is_err()); + /// assert_eq!(res.unwrap_err().kind, ErrorKind::ArgumentConflict); + /// ``` + pub fn conflicts_with(mut self, name: &'a str) -> Self { + if let Some(ref mut vec) = self.b.blacklist { + vec.push(name); + } else { + self.b.blacklist = Some(vec![name]); + } + self + } + + /// The same as [`Arg::conflicts_with`] but allows specifying multiple two-way conlicts per + /// argument. + /// + /// **NOTE:** Conflicting rules take precedence over being required by default. Conflict rules + /// only need to be set for one of the two arguments, they do not need to be set for each. + /// + /// **NOTE:** Defining a conflict is two-way, but does *not* need to defined for both arguments + /// (i.e. if A conflicts with B, defining A.conflicts_with(B) is sufficient. You do not need + /// need to also do B.conflicts_with(A)) + /// + /// # Examples + /// + /// ```rust + /// # use clap::Arg; + /// Arg::with_name("config") + /// .conflicts_with_all(&["debug", "input"]) + /// # ; + /// ``` + /// + /// Setting conflicting argument, and having any of the arguments present at runtime with a + /// conflicting argument is an error. + /// + /// ```rust + /// # use clap::{App, Arg, ErrorKind}; + /// let res = App::new("prog") + /// .arg(Arg::with_name("cfg") + /// .takes_value(true) + /// .conflicts_with_all(&["debug", "input"]) + /// .long("config")) + /// .arg(Arg::with_name("debug") + /// .long("debug")) + /// .arg(Arg::with_name("input") + /// .index(1)) + /// .get_matches_from_safe(vec![ + /// "prog", "--config", "file.conf", "file.txt" + /// ]); + /// + /// assert!(res.is_err()); + /// assert_eq!(res.unwrap_err().kind, ErrorKind::ArgumentConflict); + /// ``` + /// [`Arg::conflicts_with`]: ./struct.Arg.html#method.conflicts_with + pub fn conflicts_with_all(mut self, names: &[&'a str]) -> Self { + if let Some(ref mut vec) = self.b.blacklist { + for s in names { + vec.push(s); + } + } else { + self.b.blacklist = Some(names.iter().map(|s| *s).collect::>()); + } + self + } + + /// Sets a overridable argument by name. I.e. this argument and the following argument + /// will override each other in POSIX style (whichever argument was specified at runtime + /// **last** "wins") + /// + /// **NOTE:** When an argument is overridden it is essentially as if it never was used, any + /// conflicts, requirements, etc. are evaluated **after** all "overrides" have been removed + /// + /// **WARNING:** Positional arguments cannot override themselves (or we would never be able + /// to advance to the next positional). If a positional agument lists itself as an override, + /// it is simply ignored. + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("prog") + /// .arg(Arg::from_usage("-f, --flag 'some flag'") + /// .conflicts_with("debug")) + /// .arg(Arg::from_usage("-d, --debug 'other flag'")) + /// .arg(Arg::from_usage("-c, --color 'third flag'") + /// .overrides_with("flag")) + /// .get_matches_from(vec![ + /// "prog", "-f", "-d", "-c"]); + /// // ^~~~~~~~~~~~^~~~~ flag is overridden by color + /// + /// assert!(m.is_present("color")); + /// assert!(m.is_present("debug")); // even though flag conflicts with debug, it's as if flag + /// // was never used because it was overridden with color + /// assert!(!m.is_present("flag")); + /// ``` + /// Care must be taken when using this setting, and having an arg override with itself. This + /// is common practice when supporting things like shell aliases, config files, etc. + /// However, when combined with multiple values, it can get dicy. + /// Here is how clap handles such situations: + /// + /// When a flag overrides itself, it's as if the flag was only ever used once (essentially + /// preventing a "Unexpected multiple usage" error): + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("posix") + /// .arg(Arg::from_usage("--flag 'some flag'").overrides_with("flag")) + /// .get_matches_from(vec!["posix", "--flag", "--flag"]); + /// assert!(m.is_present("flag")); + /// assert_eq!(m.occurrences_of("flag"), 1); + /// ``` + /// Making a arg `multiple(true)` and override itself is essentially meaningless. Therefore + /// clap ignores an override of self if it's a flag and it already accepts multiple occurrences. + /// + /// ``` + /// # use clap::{App, Arg}; + /// let m = App::new("posix") + /// .arg(Arg::from_usage("--flag... 'some flag'").overrides_with("flag")) + /// .get_matches_from(vec!["", "--flag", "--flag", "--flag", "--flag"]); + /// assert!(m.is_present("flag")); + /// assert_eq!(m.occurrences_of("flag"), 4); + /// ``` + /// Now notice with options (which *do not* set `multiple(true)`), it's as if only the last + /// occurrence happened. + /// + /// ``` + /// # use clap::{App, Arg}; + /// let m = App::new("posix") + /// .arg(Arg::from_usage("--opt [val] 'some option'").overrides_with("opt")) + /// .get_matches_from(vec!["", "--opt=some", "--opt=other"]); + /// assert!(m.is_present("opt")); + /// assert_eq!(m.occurrences_of("opt"), 1); + /// assert_eq!(m.value_of("opt"), Some("other")); + /// ``` + /// + /// Just like flags, options with `multiple(true)` set, will ignore the "override self" setting. + /// + /// ``` + /// # use clap::{App, Arg}; + /// let m = App::new("posix") + /// .arg(Arg::from_usage("--opt [val]... 'some option'") + /// .overrides_with("opt")) + /// .get_matches_from(vec!["", "--opt", "first", "over", "--opt", "other", "val"]); + /// assert!(m.is_present("opt")); + /// assert_eq!(m.occurrences_of("opt"), 2); + /// assert_eq!(m.values_of("opt").unwrap().collect::>(), &["first", "over", "other", "val"]); + /// ``` + /// + /// A safe thing to do if you'd like to support an option which supports multiple values, but + /// also is "overridable" by itself, is to use `use_delimiter(false)` and *not* use + /// `multiple(true)` while telling users to seperate values with a comma (i.e. `val1,val2`) + /// + /// ``` + /// # use clap::{App, Arg}; + /// let m = App::new("posix") + /// .arg(Arg::from_usage("--opt [val] 'some option'") + /// .overrides_with("opt") + /// .use_delimiter(false)) + /// .get_matches_from(vec!["", "--opt=some,other", "--opt=one,two"]); + /// assert!(m.is_present("opt")); + /// assert_eq!(m.occurrences_of("opt"), 1); + /// assert_eq!(m.values_of("opt").unwrap().collect::>(), &["one,two"]); + /// ``` + pub fn overrides_with(mut self, name: &'a str) -> Self { + if let Some(ref mut vec) = self.b.overrides { + vec.push(name); + } else { + self.b.overrides = Some(vec![name]); + } + self + } + + /// Sets multiple mutually overridable arguments by name. I.e. this argument and the following + /// argument will override each other in POSIX style (whichever argument was specified at + /// runtime **last** "wins") + /// + /// **NOTE:** When an argument is overridden it is essentially as if it never was used, any + /// conflicts, requirements, etc. are evaluated **after** all "overrides" have been removed + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("prog") + /// .arg(Arg::from_usage("-f, --flag 'some flag'") + /// .conflicts_with("color")) + /// .arg(Arg::from_usage("-d, --debug 'other flag'")) + /// .arg(Arg::from_usage("-c, --color 'third flag'") + /// .overrides_with_all(&["flag", "debug"])) + /// .get_matches_from(vec![ + /// "prog", "-f", "-d", "-c"]); + /// // ^~~~~~^~~~~~~~~ flag and debug are overridden by color + /// + /// assert!(m.is_present("color")); // even though flag conflicts with color, it's as if flag + /// // and debug were never used because they were overridden + /// // with color + /// assert!(!m.is_present("debug")); + /// assert!(!m.is_present("flag")); + /// ``` + pub fn overrides_with_all(mut self, names: &[&'a str]) -> Self { + if let Some(ref mut vec) = self.b.overrides { + for s in names { + vec.push(s); + } + } else { + self.b.overrides = Some(names.iter().map(|s| *s).collect::>()); + } + self + } + + /// Sets an argument by name that is required when this one is present I.e. when + /// using this argument, the following argument *must* be present. + /// + /// **NOTE:** [Conflicting] rules and [override] rules take precedence over being required + /// + /// # Examples + /// + /// ```rust + /// # use clap::Arg; + /// Arg::with_name("config") + /// .requires("input") + /// # ; + /// ``` + /// + /// Setting [`Arg::requires(name)`] requires that the argument be used at runtime if the + /// defining argument is used. If the defining argument isn't used, the other argument isn't + /// required + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let res = App::new("prog") + /// .arg(Arg::with_name("cfg") + /// .takes_value(true) + /// .requires("input") + /// .long("config")) + /// .arg(Arg::with_name("input") + /// .index(1)) + /// .get_matches_from_safe(vec![ + /// "prog" + /// ]); + /// + /// assert!(res.is_ok()); // We didn't use cfg, so input wasn't required + /// ``` + /// + /// Setting [`Arg::requires(name)`] and *not* supplying that argument is an error. + /// + /// ```rust + /// # use clap::{App, Arg, ErrorKind}; + /// let res = App::new("prog") + /// .arg(Arg::with_name("cfg") + /// .takes_value(true) + /// .requires("input") + /// .long("config")) + /// .arg(Arg::with_name("input") + /// .index(1)) + /// .get_matches_from_safe(vec![ + /// "prog", "--config", "file.conf" + /// ]); + /// + /// assert!(res.is_err()); + /// assert_eq!(res.unwrap_err().kind, ErrorKind::MissingRequiredArgument); + /// ``` + /// [`Arg::requires(name)`]: ./struct.Arg.html#method.requires + /// [Conflicting]: ./struct.Arg.html#method.conflicts_with + /// [override]: ./struct.Arg.html#method.overrides_with + pub fn requires(mut self, name: &'a str) -> Self { + if let Some(ref mut vec) = self.b.requires { + vec.push((None, name)); + } else { + let mut vec = vec![]; + vec.push((None, name)); + self.b.requires = Some(vec); + } + self + } + + /// Allows a conditional requirement. The requirement will only become valid if this arg's value + /// equals `val`. + /// + /// **NOTE:** If using YAML the values should be laid out as follows + /// + /// ```yaml + /// requires_if: + /// - [val, arg] + /// ``` + /// + /// # Examples + /// + /// ```rust + /// # use clap::Arg; + /// Arg::with_name("config") + /// .requires_if("val", "arg") + /// # ; + /// ``` + /// + /// Setting [`Arg::requires_if(val, arg)`] requires that the `arg` be used at runtime if the + /// defining argument's value is equal to `val`. If the defining argument is anything other than + /// `val`, the other argument isn't required. + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let res = App::new("prog") + /// .arg(Arg::with_name("cfg") + /// .takes_value(true) + /// .requires_if("my.cfg", "other") + /// .long("config")) + /// .arg(Arg::with_name("other")) + /// .get_matches_from_safe(vec![ + /// "prog", "--config", "some.cfg" + /// ]); + /// + /// assert!(res.is_ok()); // We didn't use --config=my.cfg, so other wasn't required + /// ``` + /// + /// Setting [`Arg::requires_if(val, arg)`] and setting the value to `val` but *not* supplying + /// `arg` is an error. + /// + /// ```rust + /// # use clap::{App, Arg, ErrorKind}; + /// let res = App::new("prog") + /// .arg(Arg::with_name("cfg") + /// .takes_value(true) + /// .requires_if("my.cfg", "input") + /// .long("config")) + /// .arg(Arg::with_name("input")) + /// .get_matches_from_safe(vec![ + /// "prog", "--config", "my.cfg" + /// ]); + /// + /// assert!(res.is_err()); + /// assert_eq!(res.unwrap_err().kind, ErrorKind::MissingRequiredArgument); + /// ``` + /// [`Arg::requires(name)`]: ./struct.Arg.html#method.requires + /// [Conflicting]: ./struct.Arg.html#method.conflicts_with + /// [override]: ./struct.Arg.html#method.overrides_with + pub fn requires_if(mut self, val: &'b str, arg: &'a str) -> Self { + if let Some(ref mut vec) = self.b.requires { + vec.push((Some(val), arg)); + } else { + self.b.requires = Some(vec![(Some(val), arg)]); + } + self + } + + /// Allows multiple conditional requirements. The requirement will only become valid if this arg's value + /// equals `val`. + /// + /// **NOTE:** If using YAML the values should be laid out as follows + /// + /// ```yaml + /// requires_if: + /// - [val, arg] + /// - [val2, arg2] + /// ``` + /// + /// # Examples + /// + /// ```rust + /// # use clap::Arg; + /// Arg::with_name("config") + /// .requires_ifs(&[ + /// ("val", "arg"), + /// ("other_val", "arg2"), + /// ]) + /// # ; + /// ``` + /// + /// Setting [`Arg::requires_ifs(&["val", "arg"])`] requires that the `arg` be used at runtime if the + /// defining argument's value is equal to `val`. If the defining argument's value is anything other + /// than `val`, `arg` isn't required. + /// + /// ```rust + /// # use clap::{App, Arg, ErrorKind}; + /// let res = App::new("prog") + /// .arg(Arg::with_name("cfg") + /// .takes_value(true) + /// .requires_ifs(&[ + /// ("special.conf", "opt"), + /// ("other.conf", "other"), + /// ]) + /// .long("config")) + /// .arg(Arg::with_name("opt") + /// .long("option") + /// .takes_value(true)) + /// .arg(Arg::with_name("other")) + /// .get_matches_from_safe(vec![ + /// "prog", "--config", "special.conf" + /// ]); + /// + /// assert!(res.is_err()); // We used --config=special.conf so --option is required + /// assert_eq!(res.unwrap_err().kind, ErrorKind::MissingRequiredArgument); + /// ``` + /// [`Arg::requires(name)`]: ./struct.Arg.html#method.requires + /// [Conflicting]: ./struct.Arg.html#method.conflicts_with + /// [override]: ./struct.Arg.html#method.overrides_with + pub fn requires_ifs(mut self, ifs: &[(&'b str, &'a str)]) -> Self { + if let Some(ref mut vec) = self.b.requires { + for &(val, arg) in ifs { + vec.push((Some(val), arg)); + } + } else { + let mut vec = vec![]; + for &(val, arg) in ifs { + vec.push((Some(val), arg)); + } + self.b.requires = Some(vec); + } + self + } + + /// Allows specifying that an argument is [required] conditionally. The requirement will only + /// become valid if the specified `arg`'s value equals `val`. + /// + /// **NOTE:** If using YAML the values should be laid out as follows + /// + /// ```yaml + /// required_if: + /// - [arg, val] + /// ``` + /// + /// # Examples + /// + /// ```rust + /// # use clap::Arg; + /// Arg::with_name("config") + /// .required_if("other_arg", "value") + /// # ; + /// ``` + /// + /// Setting [`Arg::required_if(arg, val)`] makes this arg required if the `arg` is used at + /// runtime and it's value is equal to `val`. If the `arg`'s value is anything other than `val`, + /// this argument isn't required. + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let res = App::new("prog") + /// .arg(Arg::with_name("cfg") + /// .takes_value(true) + /// .required_if("other", "special") + /// .long("config")) + /// .arg(Arg::with_name("other") + /// .long("other") + /// .takes_value(true)) + /// .get_matches_from_safe(vec![ + /// "prog", "--other", "not-special" + /// ]); + /// + /// assert!(res.is_ok()); // We didn't use --other=special, so "cfg" wasn't required + /// ``` + /// + /// Setting [`Arg::required_if(arg, val)`] and having `arg` used with a value of `val` but *not* + /// using this arg is an error. + /// + /// ```rust + /// # use clap::{App, Arg, ErrorKind}; + /// let res = App::new("prog") + /// .arg(Arg::with_name("cfg") + /// .takes_value(true) + /// .required_if("other", "special") + /// .long("config")) + /// .arg(Arg::with_name("other") + /// .long("other") + /// .takes_value(true)) + /// .get_matches_from_safe(vec![ + /// "prog", "--other", "special" + /// ]); + /// + /// assert!(res.is_err()); + /// assert_eq!(res.unwrap_err().kind, ErrorKind::MissingRequiredArgument); + /// ``` + /// [`Arg::requires(name)`]: ./struct.Arg.html#method.requires + /// [Conflicting]: ./struct.Arg.html#method.conflicts_with + /// [required]: ./struct.Arg.html#method.required + pub fn required_if(mut self, arg: &'a str, val: &'b str) -> Self { + if let Some(ref mut vec) = self.r_ifs { + vec.push((arg, val)); + } else { + self.r_ifs = Some(vec![(arg, val)]); + } + self + } + + /// Allows specifying that an argument is [required] based on multiple conditions. The + /// conditions are set up in a `(arg, val)` style tuple. The requirement will only become valid + /// if one of the specified `arg`'s value equals it's corresponding `val`. + /// + /// **NOTE:** If using YAML the values should be laid out as follows + /// + /// ```yaml + /// required_if: + /// - [arg, val] + /// - [arg2, val2] + /// ``` + /// + /// # Examples + /// + /// ```rust + /// # use clap::Arg; + /// Arg::with_name("config") + /// .required_ifs(&[ + /// ("extra", "val"), + /// ("option", "spec") + /// ]) + /// # ; + /// ``` + /// + /// Setting [`Arg::required_ifs(&[(arg, val)])`] makes this arg required if any of the `arg`s + /// are used at runtime and it's corresponding value is equal to `val`. If the `arg`'s value is + /// anything other than `val`, this argument isn't required. + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let res = App::new("prog") + /// .arg(Arg::with_name("cfg") + /// .required_ifs(&[ + /// ("extra", "val"), + /// ("option", "spec") + /// ]) + /// .takes_value(true) + /// .long("config")) + /// .arg(Arg::with_name("extra") + /// .takes_value(true) + /// .long("extra")) + /// .arg(Arg::with_name("option") + /// .takes_value(true) + /// .long("option")) + /// .get_matches_from_safe(vec![ + /// "prog", "--option", "other" + /// ]); + /// + /// assert!(res.is_ok()); // We didn't use --option=spec, or --extra=val so "cfg" isn't required + /// ``` + /// + /// Setting [`Arg::required_ifs(&[(arg, val)])`] and having any of the `arg`s used with it's + /// value of `val` but *not* using this arg is an error. + /// + /// ```rust + /// # use clap::{App, Arg, ErrorKind}; + /// let res = App::new("prog") + /// .arg(Arg::with_name("cfg") + /// .required_ifs(&[ + /// ("extra", "val"), + /// ("option", "spec") + /// ]) + /// .takes_value(true) + /// .long("config")) + /// .arg(Arg::with_name("extra") + /// .takes_value(true) + /// .long("extra")) + /// .arg(Arg::with_name("option") + /// .takes_value(true) + /// .long("option")) + /// .get_matches_from_safe(vec![ + /// "prog", "--option", "spec" + /// ]); + /// + /// assert!(res.is_err()); + /// assert_eq!(res.unwrap_err().kind, ErrorKind::MissingRequiredArgument); + /// ``` + /// [`Arg::requires(name)`]: ./struct.Arg.html#method.requires + /// [Conflicting]: ./struct.Arg.html#method.conflicts_with + /// [required]: ./struct.Arg.html#method.required + pub fn required_ifs(mut self, ifs: &[(&'a str, &'b str)]) -> Self { + if let Some(ref mut vec) = self.r_ifs { + for r_if in ifs { + vec.push((r_if.0, r_if.1)); + } + } else { + let mut vec = vec![]; + for r_if in ifs { + vec.push((r_if.0, r_if.1)); + } + self.r_ifs = Some(vec); + } + self + } + + /// Sets multiple arguments by names that are required when this one is present I.e. when + /// using this argument, the following arguments *must* be present. + /// + /// **NOTE:** [Conflicting] rules and [override] rules take precedence over being required + /// by default. + /// + /// # Examples + /// + /// ```rust + /// # use clap::Arg; + /// Arg::with_name("config") + /// .requires_all(&["input", "output"]) + /// # ; + /// ``` + /// + /// Setting [`Arg::requires_all(&[arg, arg2])`] requires that all the arguments be used at + /// runtime if the defining argument is used. If the defining argument isn't used, the other + /// argument isn't required + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let res = App::new("prog") + /// .arg(Arg::with_name("cfg") + /// .takes_value(true) + /// .requires("input") + /// .long("config")) + /// .arg(Arg::with_name("input") + /// .index(1)) + /// .arg(Arg::with_name("output") + /// .index(2)) + /// .get_matches_from_safe(vec![ + /// "prog" + /// ]); + /// + /// assert!(res.is_ok()); // We didn't use cfg, so input and output weren't required + /// ``` + /// + /// Setting [`Arg::requires_all(&[arg, arg2])`] and *not* supplying all the arguments is an + /// error. + /// + /// ```rust + /// # use clap::{App, Arg, ErrorKind}; + /// let res = App::new("prog") + /// .arg(Arg::with_name("cfg") + /// .takes_value(true) + /// .requires_all(&["input", "output"]) + /// .long("config")) + /// .arg(Arg::with_name("input") + /// .index(1)) + /// .arg(Arg::with_name("output") + /// .index(2)) + /// .get_matches_from_safe(vec![ + /// "prog", "--config", "file.conf", "in.txt" + /// ]); + /// + /// assert!(res.is_err()); + /// // We didn't use output + /// assert_eq!(res.unwrap_err().kind, ErrorKind::MissingRequiredArgument); + /// ``` + /// [Conflicting]: ./struct.Arg.html#method.conflicts_with + /// [override]: ./struct.Arg.html#method.overrides_with + /// [`Arg::requires_all(&[arg, arg2])`]: ./struct.Arg.html#method.requires_all + pub fn requires_all(mut self, names: &[&'a str]) -> Self { + if let Some(ref mut vec) = self.b.requires { + for s in names { + vec.push((None, s)); + } + } else { + let mut vec = vec![]; + for s in names { + vec.push((None, *s)); + } + self.b.requires = Some(vec); + } + self + } + + /// Specifies that the argument takes a value at run time. + /// + /// **NOTE:** values for arguments may be specified in any of the following methods + /// + /// * Using a space such as `-o value` or `--option value` + /// * Using an equals and no space such as `-o=value` or `--option=value` + /// * Use a short and no space such as `-ovalue` + /// + /// **NOTE:** By default, args which allow [multiple values] are delimited by commas, meaning + /// `--option=val1,val2,val3` is three values for the `--option` argument. If you wish to + /// change the delimiter to another character you can use [`Arg::value_delimiter(char)`], + /// alternatively you can turn delimiting values **OFF** by using [`Arg::use_delimiter(false)`] + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg}; + /// Arg::with_name("config") + /// .takes_value(true) + /// # ; + /// ``` + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("prog") + /// .arg(Arg::with_name("mode") + /// .long("mode") + /// .takes_value(true)) + /// .get_matches_from(vec![ + /// "prog", "--mode", "fast" + /// ]); + /// + /// assert!(m.is_present("mode")); + /// assert_eq!(m.value_of("mode"), Some("fast")); + /// ``` + /// [`Arg::value_delimiter(char)`]: ./struct.Arg.html#method.value_delimiter + /// [`Arg::use_delimiter(false)`]: ./struct.Arg.html#method.use_delimiter + /// [multiple values]: ./struct.Arg.html#method.multiple + pub fn takes_value(self, tv: bool) -> Self { + if tv { + self.set(ArgSettings::TakesValue) + } else { + self.unset(ArgSettings::TakesValue) + } + } + + /// Specifies if the possible values of an argument should be displayed in the help text or + /// not. Defaults to `false` (i.e. show possible values) + /// + /// This is useful for args with many values, or ones which are explained elsewhere in the + /// help text. + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg}; + /// Arg::with_name("config") + /// .hide_possible_values(true) + /// # ; + /// ``` + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("prog") + /// .arg(Arg::with_name("mode") + /// .long("mode") + /// .possible_values(&["fast", "slow"]) + /// .takes_value(true) + /// .hide_possible_values(true)); + /// + /// ``` + /// + /// If we were to run the above program with `--help` the `[values: fast, slow]` portion of + /// the help text would be omitted. + pub fn hide_possible_values(self, hide: bool) -> Self { + if hide { + self.set(ArgSettings::HidePossibleValues) + } else { + self.unset(ArgSettings::HidePossibleValues) + } + } + + /// Specifies if the default value of an argument should be displayed in the help text or + /// not. Defaults to `false` (i.e. show default value) + /// + /// This is useful when default behavior of an arg is explained elsewhere in the help text. + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg}; + /// Arg::with_name("config") + /// .hide_default_value(true) + /// # ; + /// ``` + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("connect") + /// .arg(Arg::with_name("host") + /// .long("host") + /// .default_value("localhost") + /// .hide_default_value(true)); + /// + /// ``` + /// + /// If we were to run the above program with `--help` the `[default: localhost]` portion of + /// the help text would be omitted. + pub fn hide_default_value(self, hide: bool) -> Self { + if hide { + self.set(ArgSettings::HideDefaultValue) + } else { + self.unset(ArgSettings::HideDefaultValue) + } + } + + /// Specifies the index of a positional argument **starting at** 1. + /// + /// **NOTE:** The index refers to position according to **other positional argument**. It does + /// not define position in the argument list as a whole. + /// + /// **NOTE:** If no [`Arg::short`], or [`Arg::long`] have been defined, you can optionally + /// leave off the `index` method, and the index will be assigned in order of evaluation. + /// Utilizing the `index` method allows for setting indexes out of order + /// + /// **NOTE:** When utilized with [`Arg::multiple(true)`], only the **last** positional argument + /// may be defined as multiple (i.e. with the highest index) + /// + /// # Panics + /// + /// Although not in this method directly, [`App`] will [`panic!`] if indexes are skipped (such + /// as defining `index(1)` and `index(3)` but not `index(2)`, or a positional argument is + /// defined as multiple and is not the highest index + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg}; + /// Arg::with_name("config") + /// .index(1) + /// # ; + /// ``` + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("prog") + /// .arg(Arg::with_name("mode") + /// .index(1)) + /// .arg(Arg::with_name("debug") + /// .long("debug")) + /// .get_matches_from(vec![ + /// "prog", "--debug", "fast" + /// ]); + /// + /// assert!(m.is_present("mode")); + /// assert_eq!(m.value_of("mode"), Some("fast")); // notice index(1) means "first positional" + /// // *not* first argument + /// ``` + /// [`Arg::short`]: ./struct.Arg.html#method.short + /// [`Arg::long`]: ./struct.Arg.html#method.long + /// [`Arg::multiple(true)`]: ./struct.Arg.html#method.multiple + /// [`App`]: ./struct.App.html + /// [`panic!`]: https://doc.rust-lang.org/std/macro.panic!.html + pub fn index(mut self, idx: u64) -> Self { + self.index = Some(idx); + self + } + + /// Specifies that the argument may appear more than once. For flags, this results + /// in the number of occurrences of the flag being recorded. For example `-ddd` or `-d -d -d` + /// would count as three occurrences. For options there is a distinct difference in multiple + /// occurrences vs multiple values. + /// + /// For example, `--opt val1 val2` is one occurrence, but two values. Whereas + /// `--opt val1 --opt val2` is two occurrences. + /// + /// **WARNING:** + /// + /// Setting `multiple(true)` for an [option] with no other details, allows multiple values + /// **and** multiple occurrences because it isn't possible to have more occurrences than values + /// for options. Because multiple values are allowed, `--option val1 val2 val3` is perfectly + /// valid, be careful when designing a CLI where positional arguments are expected after a + /// option which accepts multiple values, as `clap` will continue parsing *values* until it + /// reaches the max or specific number of values defined, or another flag or option. + /// + /// **Pro Tip**: + /// + /// It's possible to define an option which allows multiple occurrences, but only one value per + /// occurrence. To do this use [`Arg::number_of_values(1)`] in coordination with + /// [`Arg::multiple(true)`]. + /// + /// **WARNING:** + /// + /// When using args with `multiple(true)` on [options] or [positionals] (i.e. those args that + /// accept values) and [subcommands], one needs to consider the possibility of an argument value + /// being the same as a valid subcommand. By default `clap` will parse the argument in question + /// as a value *only if* a value is possible at that moment. Otherwise it will be parsed as a + /// subcommand. In effect, this means using `multiple(true)` with no additional parameters and + /// a possible value that coincides with a subcommand name, the subcommand cannot be called + /// unless another argument is passed first. + /// + /// As an example, consider a CLI with an option `--ui-paths=...` and subcommand `signer` + /// + /// The following would be parsed as values to `--ui-paths`. + /// + /// ```notrust + /// $ program --ui-paths path1 path2 signer + /// ``` + /// + /// This is because `--ui-paths` accepts multiple values. `clap` will continue parsing values + /// until another argument is reached and it knows `--ui-paths` is done. + /// + /// By adding additional parameters to `--ui-paths` we can solve this issue. Consider adding + /// [`Arg::number_of_values(1)`] as discussed above. The following are all valid, and `signer` + /// is parsed as both a subcommand and a value in the second case. + /// + /// ```notrust + /// $ program --ui-paths path1 signer + /// $ program --ui-paths path1 --ui-paths signer signer + /// ``` + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg}; + /// Arg::with_name("debug") + /// .short("d") + /// .multiple(true) + /// # ; + /// ``` + /// An example with flags + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("prog") + /// .arg(Arg::with_name("verbose") + /// .multiple(true) + /// .short("v")) + /// .get_matches_from(vec![ + /// "prog", "-v", "-v", "-v" // note, -vvv would have same result + /// ]); + /// + /// assert!(m.is_present("verbose")); + /// assert_eq!(m.occurrences_of("verbose"), 3); + /// ``` + /// + /// An example with options + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("prog") + /// .arg(Arg::with_name("file") + /// .multiple(true) + /// .takes_value(true) + /// .short("F")) + /// .get_matches_from(vec![ + /// "prog", "-F", "file1", "file2", "file3" + /// ]); + /// + /// assert!(m.is_present("file")); + /// assert_eq!(m.occurrences_of("file"), 1); // notice only one occurrence + /// let files: Vec<_> = m.values_of("file").unwrap().collect(); + /// assert_eq!(files, ["file1", "file2", "file3"]); + /// ``` + /// This is functionally equivalent to the example above + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("prog") + /// .arg(Arg::with_name("file") + /// .multiple(true) + /// .takes_value(true) + /// .short("F")) + /// .get_matches_from(vec![ + /// "prog", "-F", "file1", "-F", "file2", "-F", "file3" + /// ]); + /// let files: Vec<_> = m.values_of("file").unwrap().collect(); + /// assert_eq!(files, ["file1", "file2", "file3"]); + /// + /// assert!(m.is_present("file")); + /// assert_eq!(m.occurrences_of("file"), 3); // Notice 3 occurrences + /// let files: Vec<_> = m.values_of("file").unwrap().collect(); + /// assert_eq!(files, ["file1", "file2", "file3"]); + /// ``` + /// + /// A common mistake is to define an option which allows multiples, and a positional argument + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("prog") + /// .arg(Arg::with_name("file") + /// .multiple(true) + /// .takes_value(true) + /// .short("F")) + /// .arg(Arg::with_name("word") + /// .index(1)) + /// .get_matches_from(vec![ + /// "prog", "-F", "file1", "file2", "file3", "word" + /// ]); + /// + /// assert!(m.is_present("file")); + /// let files: Vec<_> = m.values_of("file").unwrap().collect(); + /// assert_eq!(files, ["file1", "file2", "file3", "word"]); // wait...what?! + /// assert!(!m.is_present("word")); // but we clearly used word! + /// ``` + /// The problem is clap doesn't know when to stop parsing values for "files". This is further + /// compounded by if we'd said `word -F file1 file2` it would have worked fine, so it would + /// appear to only fail sometimes...not good! + /// + /// A solution for the example above is to specify that `-F` only accepts one value, but is + /// allowed to appear multiple times + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("prog") + /// .arg(Arg::with_name("file") + /// .multiple(true) + /// .takes_value(true) + /// .number_of_values(1) + /// .short("F")) + /// .arg(Arg::with_name("word") + /// .index(1)) + /// .get_matches_from(vec![ + /// "prog", "-F", "file1", "-F", "file2", "-F", "file3", "word" + /// ]); + /// + /// assert!(m.is_present("file")); + /// let files: Vec<_> = m.values_of("file").unwrap().collect(); + /// assert_eq!(files, ["file1", "file2", "file3"]); + /// assert!(m.is_present("word")); + /// assert_eq!(m.value_of("word"), Some("word")); + /// ``` + /// As a final example, notice if we define [`Arg::number_of_values(1)`] and try to run the + /// problem example above, it would have been a runtime error with a pretty message to the + /// user :) + /// + /// ```rust + /// # use clap::{App, Arg, ErrorKind}; + /// let res = App::new("prog") + /// .arg(Arg::with_name("file") + /// .multiple(true) + /// .takes_value(true) + /// .number_of_values(1) + /// .short("F")) + /// .arg(Arg::with_name("word") + /// .index(1)) + /// .get_matches_from_safe(vec![ + /// "prog", "-F", "file1", "file2", "file3", "word" + /// ]); + /// + /// assert!(res.is_err()); + /// assert_eq!(res.unwrap_err().kind, ErrorKind::UnknownArgument); + /// ``` + /// [option]: ./struct.Arg.html#method.takes_value + /// [options]: ./struct.Arg.html#method.takes_value + /// [subcommands]: ./struct.SubCommand.html + /// [positionals]: ./struct.Arg.html#method.index + /// [`Arg::number_of_values(1)`]: ./struct.Arg.html#method.number_of_values + /// [`Arg::multiple(true)`]: ./struct.Arg.html#method.multiple + pub fn multiple(self, multi: bool) -> Self { + if multi { + self.set(ArgSettings::Multiple) + } else { + self.unset(ArgSettings::Multiple) + } + } + + /// Specifies a value that *stops* parsing multiple values of a give argument. By default when + /// one sets [`multiple(true)`] on an argument, clap will continue parsing values for that + /// argument until it reaches another valid argument, or one of the other more specific settings + /// for multiple values is used (such as [`min_values`], [`max_values`] or + /// [`number_of_values`]). + /// + /// **NOTE:** This setting only applies to [options] and [positional arguments] + /// + /// **NOTE:** When the terminator is passed in on the command line, it is **not** stored as one + /// of the values + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg}; + /// Arg::with_name("vals") + /// .takes_value(true) + /// .multiple(true) + /// .value_terminator(";") + /// # ; + /// ``` + /// The following example uses two arguments, a sequence of commands, and the location in which + /// to perform them + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("prog") + /// .arg(Arg::with_name("cmds") + /// .multiple(true) + /// .allow_hyphen_values(true) + /// .value_terminator(";")) + /// .arg(Arg::with_name("location")) + /// .get_matches_from(vec![ + /// "prog", "find", "-type", "f", "-name", "special", ";", "/home/clap" + /// ]); + /// let cmds: Vec<_> = m.values_of("cmds").unwrap().collect(); + /// assert_eq!(&cmds, &["find", "-type", "f", "-name", "special"]); + /// assert_eq!(m.value_of("location"), Some("/home/clap")); + /// ``` + /// [options]: ./struct.Arg.html#method.takes_value + /// [positional arguments]: ./struct.Arg.html#method.index + /// [`multiple(true)`]: ./struct.Arg.html#method.multiple + /// [`min_values`]: ./struct.Arg.html#method.min_values + /// [`number_of_values`]: ./struct.Arg.html#method.number_of_values + /// [`max_values`]: ./struct.Arg.html#method.max_values + pub fn value_terminator(mut self, term: &'b str) -> Self { + self.setb(ArgSettings::TakesValue); + self.v.terminator = Some(term); + self + } + + /// Specifies that an argument can be matched to all child [`SubCommand`]s. + /// + /// **NOTE:** Global arguments *only* propagate down, **not** up (to parent commands), however + /// their values once a user uses them will be propagated back up to parents. In effect, this + /// means one should *define* all global arguments at the top level, however it doesn't matter + /// where the user *uses* the global argument. + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg}; + /// Arg::with_name("debug") + /// .short("d") + /// .global(true) + /// # ; + /// ``` + /// + /// For example, assume an application with two subcommands, and you'd like to define a + /// `--verbose` flag that can be called on any of the subcommands and parent, but you don't + /// want to clutter the source with three duplicate [`Arg`] definitions. + /// + /// ```rust + /// # use clap::{App, Arg, SubCommand}; + /// let m = App::new("prog") + /// .arg(Arg::with_name("verb") + /// .long("verbose") + /// .short("v") + /// .global(true)) + /// .subcommand(SubCommand::with_name("test")) + /// .subcommand(SubCommand::with_name("do-stuff")) + /// .get_matches_from(vec![ + /// "prog", "do-stuff", "--verbose" + /// ]); + /// + /// assert_eq!(m.subcommand_name(), Some("do-stuff")); + /// let sub_m = m.subcommand_matches("do-stuff").unwrap(); + /// assert!(sub_m.is_present("verb")); + /// ``` + /// [`SubCommand`]: ./struct.SubCommand.html + /// [required]: ./struct.Arg.html#method.required + /// [`ArgMatches`]: ./struct.ArgMatches.html + /// [`ArgMatches::is_present("flag")`]: ./struct.ArgMatches.html#method.is_present + /// [`Arg`]: ./struct.Arg.html + pub fn global(self, g: bool) -> Self { + if g { + self.set(ArgSettings::Global) + } else { + self.unset(ArgSettings::Global) + } + } + + /// Allows an argument to accept explicitly empty values. An empty value must be specified at + /// the command line with an explicit `""`, or `''` + /// + /// **NOTE:** Defaults to `true` (Explicitly empty values are allowed) + /// + /// **NOTE:** Implicitly sets [`Arg::takes_value(true)`] when set to `false` + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg}; + /// Arg::with_name("file") + /// .long("file") + /// .empty_values(false) + /// # ; + /// ``` + /// The default is to allow empty values, such as `--option ""` would be an empty value. But + /// we can change to make empty values become an error. + /// + /// ```rust + /// # use clap::{App, Arg, ErrorKind}; + /// let res = App::new("prog") + /// .arg(Arg::with_name("cfg") + /// .long("config") + /// .short("v") + /// .empty_values(false)) + /// .get_matches_from_safe(vec![ + /// "prog", "--config=" + /// ]); + /// + /// assert!(res.is_err()); + /// assert_eq!(res.unwrap_err().kind, ErrorKind::EmptyValue); + /// ``` + /// [`Arg::takes_value(true)`]: ./struct.Arg.html#method.takes_value + pub fn empty_values(mut self, ev: bool) -> Self { + if ev { + self.set(ArgSettings::EmptyValues) + } else { + self = self.set(ArgSettings::TakesValue); + self.unset(ArgSettings::EmptyValues) + } + } + + /// Hides an argument from help message output. + /// + /// **NOTE:** Implicitly sets [`Arg::hidden_short_help(true)`] and [`Arg::hidden_long_help(true)`] + /// when set to true + /// + /// **NOTE:** This does **not** hide the argument from usage strings on error + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg}; + /// Arg::with_name("debug") + /// .hidden(true) + /// # ; + /// ``` + /// Setting `hidden(true)` will hide the argument when displaying help text + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("prog") + /// .arg(Arg::with_name("cfg") + /// .long("config") + /// .hidden(true) + /// .help("Some help text describing the --config arg")) + /// .get_matches_from(vec![ + /// "prog", "--help" + /// ]); + /// ``` + /// + /// The above example displays + /// + /// ```notrust + /// helptest + /// + /// USAGE: + /// helptest [FLAGS] + /// + /// FLAGS: + /// -h, --help Prints help information + /// -V, --version Prints version information + /// ``` + /// [`Arg::hidden_short_help(true)`]: ./struct.Arg.html#method.hidden_short_help + /// [`Arg::hidden_long_help(true)`]: ./struct.Arg.html#method.hidden_long_help + pub fn hidden(self, h: bool) -> Self { + if h { + self.set(ArgSettings::Hidden) + } else { + self.unset(ArgSettings::Hidden) + } + } + + /// Specifies a list of possible values for this argument. At runtime, `clap` verifies that + /// only one of the specified values was used, or fails with an error message. + /// + /// **NOTE:** This setting only applies to [options] and [positional arguments] + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg}; + /// Arg::with_name("mode") + /// .takes_value(true) + /// .possible_values(&["fast", "slow", "medium"]) + /// # ; + /// ``` + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("prog") + /// .arg(Arg::with_name("mode") + /// .long("mode") + /// .takes_value(true) + /// .possible_values(&["fast", "slow", "medium"])) + /// .get_matches_from(vec![ + /// "prog", "--mode", "fast" + /// ]); + /// assert!(m.is_present("mode")); + /// assert_eq!(m.value_of("mode"), Some("fast")); + /// ``` + /// + /// The next example shows a failed parse from using a value which wasn't defined as one of the + /// possible values. + /// + /// ```rust + /// # use clap::{App, Arg, ErrorKind}; + /// let res = App::new("prog") + /// .arg(Arg::with_name("mode") + /// .long("mode") + /// .takes_value(true) + /// .possible_values(&["fast", "slow", "medium"])) + /// .get_matches_from_safe(vec![ + /// "prog", "--mode", "wrong" + /// ]); + /// assert!(res.is_err()); + /// assert_eq!(res.unwrap_err().kind, ErrorKind::InvalidValue); + /// ``` + /// [options]: ./struct.Arg.html#method.takes_value + /// [positional arguments]: ./struct.Arg.html#method.index + pub fn possible_values(mut self, names: &[&'b str]) -> Self { + if let Some(ref mut vec) = self.v.possible_vals { + for s in names { + vec.push(s); + } + } else { + self.v.possible_vals = Some(names.iter().map(|s| *s).collect::>()); + } + self + } + + /// Specifies a possible value for this argument, one at a time. At runtime, `clap` verifies + /// that only one of the specified values was used, or fails with error message. + /// + /// **NOTE:** This setting only applies to [options] and [positional arguments] + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg}; + /// Arg::with_name("mode") + /// .takes_value(true) + /// .possible_value("fast") + /// .possible_value("slow") + /// .possible_value("medium") + /// # ; + /// ``` + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("prog") + /// .arg(Arg::with_name("mode") + /// .long("mode") + /// .takes_value(true) + /// .possible_value("fast") + /// .possible_value("slow") + /// .possible_value("medium")) + /// .get_matches_from(vec![ + /// "prog", "--mode", "fast" + /// ]); + /// assert!(m.is_present("mode")); + /// assert_eq!(m.value_of("mode"), Some("fast")); + /// ``` + /// + /// The next example shows a failed parse from using a value which wasn't defined as one of the + /// possible values. + /// + /// ```rust + /// # use clap::{App, Arg, ErrorKind}; + /// let res = App::new("prog") + /// .arg(Arg::with_name("mode") + /// .long("mode") + /// .takes_value(true) + /// .possible_value("fast") + /// .possible_value("slow") + /// .possible_value("medium")) + /// .get_matches_from_safe(vec![ + /// "prog", "--mode", "wrong" + /// ]); + /// assert!(res.is_err()); + /// assert_eq!(res.unwrap_err().kind, ErrorKind::InvalidValue); + /// ``` + /// [options]: ./struct.Arg.html#method.takes_value + /// [positional arguments]: ./struct.Arg.html#method.index + pub fn possible_value(mut self, name: &'b str) -> Self { + if let Some(ref mut vec) = self.v.possible_vals { + vec.push(name); + } else { + self.v.possible_vals = Some(vec![name]); + } + self + } + + /// When used with [`Arg::possible_values`] it allows the argument value to pass validation even if + /// the case differs from that of the specified `possible_value`. + /// + /// **Pro Tip:** Use this setting with [`arg_enum!`] + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg}; + /// # use std::ascii::AsciiExt; + /// let m = App::new("pv") + /// .arg(Arg::with_name("option") + /// .long("--option") + /// .takes_value(true) + /// .possible_value("test123") + /// .case_insensitive(true)) + /// .get_matches_from(vec![ + /// "pv", "--option", "TeSt123", + /// ]); + /// + /// assert!(m.value_of("option").unwrap().eq_ignore_ascii_case("test123")); + /// ``` + /// + /// This setting also works when multiple values can be defined: + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("pv") + /// .arg(Arg::with_name("option") + /// .short("-o") + /// .long("--option") + /// .takes_value(true) + /// .possible_value("test123") + /// .possible_value("test321") + /// .multiple(true) + /// .case_insensitive(true)) + /// .get_matches_from(vec![ + /// "pv", "--option", "TeSt123", "teST123", "tESt321" + /// ]); + /// + /// let matched_vals = m.values_of("option").unwrap().collect::>(); + /// assert_eq!(&*matched_vals, &["TeSt123", "teST123", "tESt321"]); + /// ``` + /// [`Arg::case_insensitive(true)`]: ./struct.Arg.html#method.possible_values + /// [`arg_enum!`]: ./macro.arg_enum.html + pub fn case_insensitive(self, ci: bool) -> Self { + if ci { + self.set(ArgSettings::CaseInsensitive) + } else { + self.unset(ArgSettings::CaseInsensitive) + } + } + + /// Specifies the name of the [`ArgGroup`] the argument belongs to. + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg}; + /// Arg::with_name("debug") + /// .long("debug") + /// .group("mode") + /// # ; + /// ``` + /// + /// Multiple arguments can be a member of a single group and then the group checked as if it + /// was one of said arguments. + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("prog") + /// .arg(Arg::with_name("debug") + /// .long("debug") + /// .group("mode")) + /// .arg(Arg::with_name("verbose") + /// .long("verbose") + /// .group("mode")) + /// .get_matches_from(vec![ + /// "prog", "--debug" + /// ]); + /// assert!(m.is_present("mode")); + /// ``` + /// [`ArgGroup`]: ./struct.ArgGroup.html + pub fn group(mut self, name: &'a str) -> Self { + if let Some(ref mut vec) = self.b.groups { + vec.push(name); + } else { + self.b.groups = Some(vec![name]); + } + self + } + + /// Specifies the names of multiple [`ArgGroup`]'s the argument belongs to. + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg}; + /// Arg::with_name("debug") + /// .long("debug") + /// .groups(&["mode", "verbosity"]) + /// # ; + /// ``` + /// + /// Arguments can be members of multiple groups and then the group checked as if it + /// was one of said arguments. + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("prog") + /// .arg(Arg::with_name("debug") + /// .long("debug") + /// .groups(&["mode", "verbosity"])) + /// .arg(Arg::with_name("verbose") + /// .long("verbose") + /// .groups(&["mode", "verbosity"])) + /// .get_matches_from(vec![ + /// "prog", "--debug" + /// ]); + /// assert!(m.is_present("mode")); + /// assert!(m.is_present("verbosity")); + /// ``` + /// [`ArgGroup`]: ./struct.ArgGroup.html + pub fn groups(mut self, names: &[&'a str]) -> Self { + if let Some(ref mut vec) = self.b.groups { + for s in names { + vec.push(s); + } + } else { + self.b.groups = Some(names.into_iter().map(|s| *s).collect::>()); + } + self + } + + /// Specifies how many values are required to satisfy this argument. For example, if you had a + /// `-f ` argument where you wanted exactly 3 'files' you would set + /// `.number_of_values(3)`, and this argument wouldn't be satisfied unless the user provided + /// 3 and only 3 values. + /// + /// **NOTE:** Does *not* require [`Arg::multiple(true)`] to be set. Setting + /// [`Arg::multiple(true)`] would allow `-f -f ` where + /// as *not* setting [`Arg::multiple(true)`] would only allow one occurrence of this argument. + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg}; + /// Arg::with_name("file") + /// .short("f") + /// .number_of_values(3) + /// # ; + /// ``` + /// + /// Not supplying the correct number of values is an error + /// + /// ```rust + /// # use clap::{App, Arg, ErrorKind}; + /// let res = App::new("prog") + /// .arg(Arg::with_name("file") + /// .takes_value(true) + /// .number_of_values(2) + /// .short("F")) + /// .get_matches_from_safe(vec![ + /// "prog", "-F", "file1" + /// ]); + /// + /// assert!(res.is_err()); + /// assert_eq!(res.unwrap_err().kind, ErrorKind::WrongNumberOfValues); + /// ``` + /// [`Arg::multiple(true)`]: ./struct.Arg.html#method.multiple + pub fn number_of_values(mut self, qty: u64) -> Self { + self.setb(ArgSettings::TakesValue); + self.v.num_vals = Some(qty); + self + } + + /// Allows one to perform a custom validation on the argument value. You provide a closure + /// which accepts a [`String`] value, and return a [`Result`] where the [`Err(String)`] is a + /// message displayed to the user. + /// + /// **NOTE:** The error message does *not* need to contain the `error:` portion, only the + /// message as all errors will appear as + /// `error: Invalid value for '': ` where `` is replaced by the actual + /// arg, and `` is the `String` you return as the error. + /// + /// **NOTE:** There is a small performance hit for using validators, as they are implemented + /// with [`Rc`] pointers. And the value to be checked will be allocated an extra time in order + /// to to be passed to the closure. This performance hit is extremely minimal in the grand + /// scheme of things. + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg}; + /// fn has_at(v: String) -> Result<(), String> { + /// if v.contains("@") { return Ok(()); } + /// Err(String::from("The value did not contain the required @ sigil")) + /// } + /// let res = App::new("prog") + /// .arg(Arg::with_name("file") + /// .index(1) + /// .validator(has_at)) + /// .get_matches_from_safe(vec![ + /// "prog", "some@file" + /// ]); + /// assert!(res.is_ok()); + /// assert_eq!(res.unwrap().value_of("file"), Some("some@file")); + /// ``` + /// [`String`]: https://doc.rust-lang.org/std/string/struct.String.html + /// [`Result`]: https://doc.rust-lang.org/std/result/enum.Result.html + /// [`Err(String)`]: https://doc.rust-lang.org/std/result/enum.Result.html#variant.Err + /// [`Rc`]: https://doc.rust-lang.org/std/rc/struct.Rc.html + pub fn validator(mut self, f: F) -> Self + where + F: Fn(String) -> Result<(), String> + 'static, + { + self.v.validator = Some(Rc::new(f)); + self + } + + /// Works identically to Validator but is intended to be used with values that could + /// contain non UTF-8 formatted strings. + /// + /// # Examples + /// + #[cfg_attr(not(unix), doc = " ```ignore")] + #[cfg_attr(unix, doc = " ```rust")] + /// # use clap::{App, Arg}; + /// # use std::ffi::{OsStr, OsString}; + /// # use std::os::unix::ffi::OsStrExt; + /// fn has_ampersand(v: &OsStr) -> Result<(), OsString> { + /// if v.as_bytes().iter().any(|b| *b == b'&') { return Ok(()); } + /// Err(OsString::from("The value did not contain the required & sigil")) + /// } + /// let res = App::new("prog") + /// .arg(Arg::with_name("file") + /// .index(1) + /// .validator_os(has_ampersand)) + /// .get_matches_from_safe(vec![ + /// "prog", "Fish & chips" + /// ]); + /// assert!(res.is_ok()); + /// assert_eq!(res.unwrap().value_of("file"), Some("Fish & chips")); + /// ``` + /// [`String`]: https://doc.rust-lang.org/std/string/struct.String.html + /// [`OsStr`]: https://doc.rust-lang.org/std/ffi/struct.OsStr.html + /// [`OsString`]: https://doc.rust-lang.org/std/ffi/struct.OsString.html + /// [`Result`]: https://doc.rust-lang.org/std/result/enum.Result.html + /// [`Err(String)`]: https://doc.rust-lang.org/std/result/enum.Result.html#variant.Err + /// [`Rc`]: https://doc.rust-lang.org/std/rc/struct.Rc.html + pub fn validator_os(mut self, f: F) -> Self + where + F: Fn(&OsStr) -> Result<(), OsString> + 'static, + { + self.v.validator_os = Some(Rc::new(f)); + self + } + + /// Specifies the *maximum* number of values are for this argument. For example, if you had a + /// `-f ` argument where you wanted up to 3 'files' you would set `.max_values(3)`, and + /// this argument would be satisfied if the user provided, 1, 2, or 3 values. + /// + /// **NOTE:** This does *not* implicitly set [`Arg::multiple(true)`]. This is because + /// `-o val -o val` is multiple occurrences but a single value and `-o val1 val2` is a single + /// occurrence with multiple values. For positional arguments this **does** set + /// [`Arg::multiple(true)`] because there is no way to determine the difference between multiple + /// occurrences and multiple values. + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg}; + /// Arg::with_name("file") + /// .short("f") + /// .max_values(3) + /// # ; + /// ``` + /// + /// Supplying less than the maximum number of values is allowed + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let res = App::new("prog") + /// .arg(Arg::with_name("file") + /// .takes_value(true) + /// .max_values(3) + /// .short("F")) + /// .get_matches_from_safe(vec![ + /// "prog", "-F", "file1", "file2" + /// ]); + /// + /// assert!(res.is_ok()); + /// let m = res.unwrap(); + /// let files: Vec<_> = m.values_of("file").unwrap().collect(); + /// assert_eq!(files, ["file1", "file2"]); + /// ``` + /// + /// Supplying more than the maximum number of values is an error + /// + /// ```rust + /// # use clap::{App, Arg, ErrorKind}; + /// let res = App::new("prog") + /// .arg(Arg::with_name("file") + /// .takes_value(true) + /// .max_values(2) + /// .short("F")) + /// .get_matches_from_safe(vec![ + /// "prog", "-F", "file1", "file2", "file3" + /// ]); + /// + /// assert!(res.is_err()); + /// assert_eq!(res.unwrap_err().kind, ErrorKind::TooManyValues); + /// ``` + /// [`Arg::multiple(true)`]: ./struct.Arg.html#method.multiple + pub fn max_values(mut self, qty: u64) -> Self { + self.setb(ArgSettings::TakesValue); + self.v.max_vals = Some(qty); + self + } + + /// Specifies the *minimum* number of values for this argument. For example, if you had a + /// `-f ` argument where you wanted at least 2 'files' you would set + /// `.min_values(2)`, and this argument would be satisfied if the user provided, 2 or more + /// values. + /// + /// **NOTE:** This does not implicitly set [`Arg::multiple(true)`]. This is because + /// `-o val -o val` is multiple occurrences but a single value and `-o val1 val2` is a single + /// occurrence with multiple values. For positional arguments this **does** set + /// [`Arg::multiple(true)`] because there is no way to determine the difference between multiple + /// occurrences and multiple values. + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg}; + /// Arg::with_name("file") + /// .short("f") + /// .min_values(3) + /// # ; + /// ``` + /// + /// Supplying more than the minimum number of values is allowed + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let res = App::new("prog") + /// .arg(Arg::with_name("file") + /// .takes_value(true) + /// .min_values(2) + /// .short("F")) + /// .get_matches_from_safe(vec![ + /// "prog", "-F", "file1", "file2", "file3" + /// ]); + /// + /// assert!(res.is_ok()); + /// let m = res.unwrap(); + /// let files: Vec<_> = m.values_of("file").unwrap().collect(); + /// assert_eq!(files, ["file1", "file2", "file3"]); + /// ``` + /// + /// Supplying less than the minimum number of values is an error + /// + /// ```rust + /// # use clap::{App, Arg, ErrorKind}; + /// let res = App::new("prog") + /// .arg(Arg::with_name("file") + /// .takes_value(true) + /// .min_values(2) + /// .short("F")) + /// .get_matches_from_safe(vec![ + /// "prog", "-F", "file1" + /// ]); + /// + /// assert!(res.is_err()); + /// assert_eq!(res.unwrap_err().kind, ErrorKind::TooFewValues); + /// ``` + /// [`Arg::multiple(true)`]: ./struct.Arg.html#method.multiple + pub fn min_values(mut self, qty: u64) -> Self { + self.v.min_vals = Some(qty); + self.set(ArgSettings::TakesValue) + } + + /// Specifies whether or not an argument should allow grouping of multiple values via a + /// delimiter. I.e. should `--option=val1,val2,val3` be parsed as three values (`val1`, `val2`, + /// and `val3`) or as a single value (`val1,val2,val3`). Defaults to using `,` (comma) as the + /// value delimiter for all arguments that accept values (options and positional arguments) + /// + /// **NOTE:** The default is `false`. When set to `true` the default [`Arg::value_delimiter`] + /// is the comma `,`. + /// + /// # Examples + /// + /// The following example shows the default behavior. + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let delims = App::new("prog") + /// .arg(Arg::with_name("option") + /// .long("option") + /// .use_delimiter(true) + /// .takes_value(true)) + /// .get_matches_from(vec![ + /// "prog", "--option=val1,val2,val3", + /// ]); + /// + /// assert!(delims.is_present("option")); + /// assert_eq!(delims.occurrences_of("option"), 1); + /// assert_eq!(delims.values_of("option").unwrap().collect::>(), ["val1", "val2", "val3"]); + /// ``` + /// The next example shows the difference when turning delimiters off. This is the default + /// behavior + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let nodelims = App::new("prog") + /// .arg(Arg::with_name("option") + /// .long("option") + /// .use_delimiter(false) + /// .takes_value(true)) + /// .get_matches_from(vec![ + /// "prog", "--option=val1,val2,val3", + /// ]); + /// + /// assert!(nodelims.is_present("option")); + /// assert_eq!(nodelims.occurrences_of("option"), 1); + /// assert_eq!(nodelims.value_of("option").unwrap(), "val1,val2,val3"); + /// ``` + /// [`Arg::value_delimiter`]: ./struct.Arg.html#method.value_delimiter + pub fn use_delimiter(mut self, d: bool) -> Self { + if d { + if self.v.val_delim.is_none() { + self.v.val_delim = Some(','); + } + self.setb(ArgSettings::TakesValue); + self.setb(ArgSettings::UseValueDelimiter); + self.unset(ArgSettings::ValueDelimiterNotSet) + } else { + self.v.val_delim = None; + self.unsetb(ArgSettings::UseValueDelimiter); + self.unset(ArgSettings::ValueDelimiterNotSet) + } + } + + /// Specifies that *multiple values* may only be set using the delimiter. This means if an + /// if an option is encountered, and no delimiter is found, it automatically assumed that no + /// additional values for that option follow. This is unlike the default, where it is generally + /// assumed that more values will follow regardless of whether or not a delimiter is used. + /// + /// **NOTE:** The default is `false`. + /// + /// **NOTE:** Setting this to true implies [`Arg::use_delimiter(true)`] + /// + /// **NOTE:** It's a good idea to inform the user that use of a delimiter is required, either + /// through help text or other means. + /// + /// # Examples + /// + /// These examples demonstrate what happens when `require_delimiter(true)` is used. Notice + /// everything works in this first example, as we use a delimiter, as expected. + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let delims = App::new("prog") + /// .arg(Arg::with_name("opt") + /// .short("o") + /// .takes_value(true) + /// .multiple(true) + /// .require_delimiter(true)) + /// .get_matches_from(vec![ + /// "prog", "-o", "val1,val2,val3", + /// ]); + /// + /// assert!(delims.is_present("opt")); + /// assert_eq!(delims.values_of("opt").unwrap().collect::>(), ["val1", "val2", "val3"]); + /// ``` + /// In this next example, we will *not* use a delimiter. Notice it's now an error. + /// + /// ```rust + /// # use clap::{App, Arg, ErrorKind}; + /// let res = App::new("prog") + /// .arg(Arg::with_name("opt") + /// .short("o") + /// .takes_value(true) + /// .multiple(true) + /// .require_delimiter(true)) + /// .get_matches_from_safe(vec![ + /// "prog", "-o", "val1", "val2", "val3", + /// ]); + /// + /// assert!(res.is_err()); + /// let err = res.unwrap_err(); + /// assert_eq!(err.kind, ErrorKind::UnknownArgument); + /// ``` + /// What's happening is `-o` is getting `val1`, and because delimiters are required yet none + /// were present, it stops parsing `-o`. At this point it reaches `val2` and because no + /// positional arguments have been defined, it's an error of an unexpected argument. + /// + /// In this final example, we contrast the above with `clap`'s default behavior where the above + /// is *not* an error. + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let delims = App::new("prog") + /// .arg(Arg::with_name("opt") + /// .short("o") + /// .takes_value(true) + /// .multiple(true)) + /// .get_matches_from(vec![ + /// "prog", "-o", "val1", "val2", "val3", + /// ]); + /// + /// assert!(delims.is_present("opt")); + /// assert_eq!(delims.values_of("opt").unwrap().collect::>(), ["val1", "val2", "val3"]); + /// ``` + /// [`Arg::use_delimiter(true)`]: ./struct.Arg.html#method.use_delimiter + pub fn require_delimiter(mut self, d: bool) -> Self { + if d { + self = self.use_delimiter(true); + self.unsetb(ArgSettings::ValueDelimiterNotSet); + self.setb(ArgSettings::UseValueDelimiter); + self.set(ArgSettings::RequireDelimiter) + } else { + self = self.use_delimiter(false); + self.unsetb(ArgSettings::UseValueDelimiter); + self.unset(ArgSettings::RequireDelimiter) + } + } + + /// Specifies the separator to use when values are clumped together, defaults to `,` (comma). + /// + /// **NOTE:** implicitly sets [`Arg::use_delimiter(true)`] + /// + /// **NOTE:** implicitly sets [`Arg::takes_value(true)`] + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("prog") + /// .arg(Arg::with_name("config") + /// .short("c") + /// .long("config") + /// .value_delimiter(";")) + /// .get_matches_from(vec![ + /// "prog", "--config=val1;val2;val3" + /// ]); + /// + /// assert_eq!(m.values_of("config").unwrap().collect::>(), ["val1", "val2", "val3"]) + /// ``` + /// [`Arg::use_delimiter(true)`]: ./struct.Arg.html#method.use_delimiter + /// [`Arg::takes_value(true)`]: ./struct.Arg.html#method.takes_value + pub fn value_delimiter(mut self, d: &str) -> Self { + self.unsetb(ArgSettings::ValueDelimiterNotSet); + self.setb(ArgSettings::TakesValue); + self.setb(ArgSettings::UseValueDelimiter); + self.v.val_delim = Some( + d.chars() + .nth(0) + .expect("Failed to get value_delimiter from arg"), + ); + self + } + + /// Specify multiple names for values of option arguments. These names are cosmetic only, used + /// for help and usage strings only. The names are **not** used to access arguments. The values + /// of the arguments are accessed in numeric order (i.e. if you specify two names `one` and + /// `two` `one` will be the first matched value, `two` will be the second). + /// + /// This setting can be very helpful when describing the type of input the user should be + /// using, such as `FILE`, `INTERFACE`, etc. Although not required, it's somewhat convention to + /// use all capital letters for the value name. + /// + /// **Pro Tip:** It may help to use [`Arg::next_line_help(true)`] if there are long, or + /// multiple value names in order to not throw off the help text alignment of all options. + /// + /// **NOTE:** This implicitly sets [`Arg::number_of_values`] if the number of value names is + /// greater than one. I.e. be aware that the number of "names" you set for the values, will be + /// the *exact* number of values required to satisfy this argument + /// + /// **NOTE:** implicitly sets [`Arg::takes_value(true)`] + /// + /// **NOTE:** Does *not* require or imply [`Arg::multiple(true)`]. + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg}; + /// Arg::with_name("speed") + /// .short("s") + /// .value_names(&["fast", "slow"]) + /// # ; + /// ``` + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("prog") + /// .arg(Arg::with_name("io") + /// .long("io-files") + /// .value_names(&["INFILE", "OUTFILE"])) + /// .get_matches_from(vec![ + /// "prog", "--help" + /// ]); + /// ``` + /// Running the above program produces the following output + /// + /// ```notrust + /// valnames + /// + /// USAGE: + /// valnames [FLAGS] [OPTIONS] + /// + /// FLAGS: + /// -h, --help Prints help information + /// -V, --version Prints version information + /// + /// OPTIONS: + /// --io-files Some help text + /// ``` + /// [`Arg::next_line_help(true)`]: ./struct.Arg.html#method.next_line_help + /// [`Arg::number_of_values`]: ./struct.Arg.html#method.number_of_values + /// [`Arg::takes_value(true)`]: ./struct.Arg.html#method.takes_value + /// [`Arg::multiple(true)`]: ./struct.Arg.html#method.multiple + pub fn value_names(mut self, names: &[&'b str]) -> Self { + self.setb(ArgSettings::TakesValue); + if self.is_set(ArgSettings::ValueDelimiterNotSet) { + self.unsetb(ArgSettings::ValueDelimiterNotSet); + self.setb(ArgSettings::UseValueDelimiter); + } + if let Some(ref mut vals) = self.v.val_names { + let mut l = vals.len(); + for s in names { + vals.insert(l, s); + l += 1; + } + } else { + let mut vm = VecMap::new(); + for (i, n) in names.iter().enumerate() { + vm.insert(i, *n); + } + self.v.val_names = Some(vm); + } + self + } + + /// Specifies the name for value of [option] or [positional] arguments inside of help + /// documentation. This name is cosmetic only, the name is **not** used to access arguments. + /// This setting can be very helpful when describing the type of input the user should be + /// using, such as `FILE`, `INTERFACE`, etc. Although not required, it's somewhat convention to + /// use all capital letters for the value name. + /// + /// **NOTE:** implicitly sets [`Arg::takes_value(true)`] + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg}; + /// Arg::with_name("cfg") + /// .long("config") + /// .value_name("FILE") + /// # ; + /// ``` + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("prog") + /// .arg(Arg::with_name("config") + /// .long("config") + /// .value_name("FILE")) + /// .get_matches_from(vec![ + /// "prog", "--help" + /// ]); + /// ``` + /// Running the above program produces the following output + /// + /// ```notrust + /// valnames + /// + /// USAGE: + /// valnames [FLAGS] [OPTIONS] + /// + /// FLAGS: + /// -h, --help Prints help information + /// -V, --version Prints version information + /// + /// OPTIONS: + /// --config Some help text + /// ``` + /// [option]: ./struct.Arg.html#method.takes_value + /// [positional]: ./struct.Arg.html#method.index + /// [`Arg::takes_value(true)`]: ./struct.Arg.html#method.takes_value + pub fn value_name(mut self, name: &'b str) -> Self { + self.setb(ArgSettings::TakesValue); + if let Some(ref mut vals) = self.v.val_names { + let l = vals.len(); + vals.insert(l, name); + } else { + let mut vm = VecMap::new(); + vm.insert(0, name); + self.v.val_names = Some(vm); + } + self + } + + /// Specifies the value of the argument when *not* specified at runtime. + /// + /// **NOTE:** If the user *does not* use this argument at runtime, [`ArgMatches::occurrences_of`] + /// will return `0` even though the [`ArgMatches::value_of`] will return the default specified. + /// + /// **NOTE:** If the user *does not* use this argument at runtime [`ArgMatches::is_present`] will + /// still return `true`. If you wish to determine whether the argument was used at runtime or + /// not, consider [`ArgMatches::occurrences_of`] which will return `0` if the argument was *not* + /// used at runtime. + /// + /// **NOTE:** This setting is perfectly compatible with [`Arg::default_value_if`] but slightly + /// different. `Arg::default_value` *only* takes affect when the user has not provided this arg + /// at runtime. `Arg::default_value_if` however only takes affect when the user has not provided + /// a value at runtime **and** these other conditions are met as well. If you have set + /// `Arg::default_value` and `Arg::default_value_if`, and the user **did not** provide a this + /// arg at runtime, nor did were the conditions met for `Arg::default_value_if`, the + /// `Arg::default_value` will be applied. + /// + /// **NOTE:** This implicitly sets [`Arg::takes_value(true)`]. + /// + /// **NOTE:** This setting effectively disables `AppSettings::ArgRequiredElseHelp` if used in + /// conjunction as it ensures that some argument will always be present. + /// + /// # Examples + /// + /// First we use the default value without providing any value at runtime. + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("prog") + /// .arg(Arg::with_name("opt") + /// .long("myopt") + /// .default_value("myval")) + /// .get_matches_from(vec![ + /// "prog" + /// ]); + /// + /// assert_eq!(m.value_of("opt"), Some("myval")); + /// assert!(m.is_present("opt")); + /// assert_eq!(m.occurrences_of("opt"), 0); + /// ``` + /// + /// Next we provide a value at runtime to override the default. + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("prog") + /// .arg(Arg::with_name("opt") + /// .long("myopt") + /// .default_value("myval")) + /// .get_matches_from(vec![ + /// "prog", "--myopt=non_default" + /// ]); + /// + /// assert_eq!(m.value_of("opt"), Some("non_default")); + /// assert!(m.is_present("opt")); + /// assert_eq!(m.occurrences_of("opt"), 1); + /// ``` + /// [`ArgMatches::occurrences_of`]: ./struct.ArgMatches.html#method.occurrences_of + /// [`ArgMatches::value_of`]: ./struct.ArgMatches.html#method.value_of + /// [`Arg::takes_value(true)`]: ./struct.Arg.html#method.takes_value + /// [`ArgMatches::is_present`]: ./struct.ArgMatches.html#method.is_present + /// [`Arg::default_value_if`]: ./struct.Arg.html#method.default_value_if + pub fn default_value(self, val: &'a str) -> Self { + self.default_value_os(OsStr::from_bytes(val.as_bytes())) + } + + /// Provides a default value in the exact same manner as [`Arg::default_value`] + /// only using [`OsStr`]s instead. + /// [`Arg::default_value`]: ./struct.Arg.html#method.default_value + /// [`OsStr`]: https://doc.rust-lang.org/std/ffi/struct.OsStr.html + pub fn default_value_os(mut self, val: &'a OsStr) -> Self { + self.setb(ArgSettings::TakesValue); + self.v.default_val = Some(val); + self + } + + /// Specifies the value of the argument if `arg` has been used at runtime. If `val` is set to + /// `None`, `arg` only needs to be present. If `val` is set to `"some-val"` then `arg` must be + /// present at runtime **and** have the value `val`. + /// + /// **NOTE:** This setting is perfectly compatible with [`Arg::default_value`] but slightly + /// different. `Arg::default_value` *only* takes affect when the user has not provided this arg + /// at runtime. This setting however only takes affect when the user has not provided a value at + /// runtime **and** these other conditions are met as well. If you have set `Arg::default_value` + /// and `Arg::default_value_if`, and the user **did not** provide a this arg at runtime, nor did + /// were the conditions met for `Arg::default_value_if`, the `Arg::default_value` will be + /// applied. + /// + /// **NOTE:** This implicitly sets [`Arg::takes_value(true)`]. + /// + /// **NOTE:** If using YAML the values should be laid out as follows (`None` can be represented + /// as `null` in YAML) + /// + /// ```yaml + /// default_value_if: + /// - [arg, val, default] + /// ``` + /// + /// # Examples + /// + /// First we use the default value only if another arg is present at runtime. + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("prog") + /// .arg(Arg::with_name("flag") + /// .long("flag")) + /// .arg(Arg::with_name("other") + /// .long("other") + /// .default_value_if("flag", None, "default")) + /// .get_matches_from(vec![ + /// "prog", "--flag" + /// ]); + /// + /// assert_eq!(m.value_of("other"), Some("default")); + /// ``` + /// + /// Next we run the same test, but without providing `--flag`. + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("prog") + /// .arg(Arg::with_name("flag") + /// .long("flag")) + /// .arg(Arg::with_name("other") + /// .long("other") + /// .default_value_if("flag", None, "default")) + /// .get_matches_from(vec![ + /// "prog" + /// ]); + /// + /// assert_eq!(m.value_of("other"), None); + /// ``` + /// + /// Now lets only use the default value if `--opt` contains the value `special`. + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("prog") + /// .arg(Arg::with_name("opt") + /// .takes_value(true) + /// .long("opt")) + /// .arg(Arg::with_name("other") + /// .long("other") + /// .default_value_if("opt", Some("special"), "default")) + /// .get_matches_from(vec![ + /// "prog", "--opt", "special" + /// ]); + /// + /// assert_eq!(m.value_of("other"), Some("default")); + /// ``` + /// + /// We can run the same test and provide any value *other than* `special` and we won't get a + /// default value. + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("prog") + /// .arg(Arg::with_name("opt") + /// .takes_value(true) + /// .long("opt")) + /// .arg(Arg::with_name("other") + /// .long("other") + /// .default_value_if("opt", Some("special"), "default")) + /// .get_matches_from(vec![ + /// "prog", "--opt", "hahaha" + /// ]); + /// + /// assert_eq!(m.value_of("other"), None); + /// ``` + /// [`Arg::takes_value(true)`]: ./struct.Arg.html#method.takes_value + /// [`Arg::default_value`]: ./struct.Arg.html#method.default_value + pub fn default_value_if(self, arg: &'a str, val: Option<&'b str>, default: &'b str) -> Self { + self.default_value_if_os( + arg, + val.map(str::as_bytes).map(OsStr::from_bytes), + OsStr::from_bytes(default.as_bytes()), + ) + } + + /// Provides a conditional default value in the exact same manner as [`Arg::default_value_if`] + /// only using [`OsStr`]s instead. + /// [`Arg::default_value_if`]: ./struct.Arg.html#method.default_value_if + /// [`OsStr`]: https://doc.rust-lang.org/std/ffi/struct.OsStr.html + pub fn default_value_if_os( + mut self, + arg: &'a str, + val: Option<&'b OsStr>, + default: &'b OsStr, + ) -> Self { + self.setb(ArgSettings::TakesValue); + if let Some(ref mut vm) = self.v.default_vals_ifs { + let l = vm.len(); + vm.insert(l, (arg, val, default)); + } else { + let mut vm = VecMap::new(); + vm.insert(0, (arg, val, default)); + self.v.default_vals_ifs = Some(vm); + } + self + } + + /// Specifies multiple values and conditions in the same manner as [`Arg::default_value_if`]. + /// The method takes a slice of tuples in the `(arg, Option, default)` format. + /// + /// **NOTE**: The conditions are stored in order and evaluated in the same order. I.e. the first + /// if multiple conditions are true, the first one found will be applied and the ultimate value. + /// + /// **NOTE:** If using YAML the values should be laid out as follows + /// + /// ```yaml + /// default_value_if: + /// - [arg, val, default] + /// - [arg2, null, default2] + /// ``` + /// + /// # Examples + /// + /// First we use the default value only if another arg is present at runtime. + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("prog") + /// .arg(Arg::with_name("flag") + /// .long("flag")) + /// .arg(Arg::with_name("opt") + /// .long("opt") + /// .takes_value(true)) + /// .arg(Arg::with_name("other") + /// .long("other") + /// .default_value_ifs(&[ + /// ("flag", None, "default"), + /// ("opt", Some("channal"), "chan"), + /// ])) + /// .get_matches_from(vec![ + /// "prog", "--opt", "channal" + /// ]); + /// + /// assert_eq!(m.value_of("other"), Some("chan")); + /// ``` + /// + /// Next we run the same test, but without providing `--flag`. + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("prog") + /// .arg(Arg::with_name("flag") + /// .long("flag")) + /// .arg(Arg::with_name("other") + /// .long("other") + /// .default_value_ifs(&[ + /// ("flag", None, "default"), + /// ("opt", Some("channal"), "chan"), + /// ])) + /// .get_matches_from(vec![ + /// "prog" + /// ]); + /// + /// assert_eq!(m.value_of("other"), None); + /// ``` + /// + /// We can also see that these values are applied in order, and if more than one condition is + /// true, only the first evaluated "wins" + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("prog") + /// .arg(Arg::with_name("flag") + /// .long("flag")) + /// .arg(Arg::with_name("opt") + /// .long("opt") + /// .takes_value(true)) + /// .arg(Arg::with_name("other") + /// .long("other") + /// .default_value_ifs(&[ + /// ("flag", None, "default"), + /// ("opt", Some("channal"), "chan"), + /// ])) + /// .get_matches_from(vec![ + /// "prog", "--opt", "channal", "--flag" + /// ]); + /// + /// assert_eq!(m.value_of("other"), Some("default")); + /// ``` + /// [`Arg::takes_value(true)`]: ./struct.Arg.html#method.takes_value + /// [`Arg::default_value`]: ./struct.Arg.html#method.default_value + pub fn default_value_ifs(mut self, ifs: &[(&'a str, Option<&'b str>, &'b str)]) -> Self { + for &(arg, val, default) in ifs { + self = self.default_value_if_os( + arg, + val.map(str::as_bytes).map(OsStr::from_bytes), + OsStr::from_bytes(default.as_bytes()), + ); + } + self + } + + /// Provides multiple conditional default values in the exact same manner as + /// [`Arg::default_value_ifs`] only using [`OsStr`]s instead. + /// [`Arg::default_value_ifs`]: ./struct.Arg.html#method.default_value_ifs + /// [`OsStr`]: https://doc.rust-lang.org/std/ffi/struct.OsStr.html + #[cfg_attr(feature = "lints", allow(explicit_counter_loop))] + pub fn default_value_ifs_os(mut self, ifs: &[(&'a str, Option<&'b OsStr>, &'b OsStr)]) -> Self { + for &(arg, val, default) in ifs { + self = self.default_value_if_os(arg, val, default); + } + self + } + + /// Specifies that if the value is not passed in as an argument, that it should be retrieved + /// from the environment, if available. If it is not present in the environment, then default + /// rules will apply. + /// + /// **NOTE:** If the user *does not* use this argument at runtime, [`ArgMatches::occurrences_of`] + /// will return `0` even though the [`ArgMatches::value_of`] will return the default specified. + /// + /// **NOTE:** If the user *does not* use this argument at runtime [`ArgMatches::is_present`] will + /// return `true` if the variable is present in the environment . If you wish to determine whether + /// the argument was used at runtime or not, consider [`ArgMatches::occurrences_of`] which will + /// return `0` if the argument was *not* used at runtime. + /// + /// **NOTE:** This implicitly sets [`Arg::takes_value(true)`]. + /// + /// **NOTE:** If [`Arg::multiple(true)`] is set then [`Arg::use_delimiter(true)`] should also be + /// set. Otherwise, only a single argument will be returned from the environment variable. The + /// default delimiter is `,` and follows all the other delimiter rules. + /// + /// # Examples + /// + /// In this example, we show the variable coming from the environment: + /// + /// ```rust + /// # use std::env; + /// # use clap::{App, Arg}; + /// + /// env::set_var("MY_FLAG", "env"); + /// + /// let m = App::new("prog") + /// .arg(Arg::with_name("flag") + /// .long("flag") + /// .env("MY_FLAG")) + /// .get_matches_from(vec![ + /// "prog" + /// ]); + /// + /// assert_eq!(m.value_of("flag"), Some("env")); + /// ``` + /// + /// In this example, we show the variable coming from an option on the CLI: + /// + /// ```rust + /// # use std::env; + /// # use clap::{App, Arg}; + /// + /// env::set_var("MY_FLAG", "env"); + /// + /// let m = App::new("prog") + /// .arg(Arg::with_name("flag") + /// .long("flag") + /// .env("MY_FLAG")) + /// .get_matches_from(vec![ + /// "prog", "--flag", "opt" + /// ]); + /// + /// assert_eq!(m.value_of("flag"), Some("opt")); + /// ``` + /// + /// In this example, we show the variable coming from the environment even with the + /// presence of a default: + /// + /// ```rust + /// # use std::env; + /// # use clap::{App, Arg}; + /// + /// env::set_var("MY_FLAG", "env"); + /// + /// let m = App::new("prog") + /// .arg(Arg::with_name("flag") + /// .long("flag") + /// .env("MY_FLAG") + /// .default_value("default")) + /// .get_matches_from(vec![ + /// "prog" + /// ]); + /// + /// assert_eq!(m.value_of("flag"), Some("env")); + /// ``` + /// + /// In this example, we show the use of multiple values in a single environment variable: + /// + /// ```rust + /// # use std::env; + /// # use clap::{App, Arg}; + /// + /// env::set_var("MY_FLAG_MULTI", "env1,env2"); + /// + /// let m = App::new("prog") + /// .arg(Arg::with_name("flag") + /// .long("flag") + /// .env("MY_FLAG_MULTI") + /// .multiple(true) + /// .use_delimiter(true)) + /// .get_matches_from(vec![ + /// "prog" + /// ]); + /// + /// assert_eq!(m.values_of("flag").unwrap().collect::>(), vec!["env1", "env2"]); + /// ``` + /// [`ArgMatches::occurrences_of`]: ./struct.ArgMatches.html#method.occurrences_of + /// [`ArgMatches::value_of`]: ./struct.ArgMatches.html#method.value_of + /// [`ArgMatches::is_present`]: ./struct.ArgMatches.html#method.is_present + /// [`Arg::takes_value(true)`]: ./struct.Arg.html#method.takes_value + /// [`Arg::multiple(true)`]: ./struct.Arg.html#method.multiple + /// [`Arg::use_delimiter(true)`]: ./struct.Arg.html#method.use_delimiter + pub fn env(self, name: &'a str) -> Self { + self.env_os(OsStr::new(name)) + } + + /// Specifies that if the value is not passed in as an argument, that it should be retrieved + /// from the environment if available in the exact same manner as [`Arg::env`] only using + /// [`OsStr`]s instead. + pub fn env_os(mut self, name: &'a OsStr) -> Self { + self.setb(ArgSettings::TakesValue); + + self.v.env = Some((name, env::var_os(name))); + self + } + + /// @TODO @p2 @docs @release: write docs + pub fn hide_env_values(self, hide: bool) -> Self { + if hide { + self.set(ArgSettings::HideEnvValues) + } else { + self.unset(ArgSettings::HideEnvValues) + } + } + + /// When set to `true` the help string will be displayed on the line after the argument and + /// indented once. This can be helpful for arguments with very long or complex help messages. + /// This can also be helpful for arguments with very long flag names, or many/long value names. + /// + /// **NOTE:** To apply this setting to all arguments consider using + /// [`AppSettings::NextLineHelp`] + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("prog") + /// .arg(Arg::with_name("opt") + /// .long("long-option-flag") + /// .short("o") + /// .takes_value(true) + /// .value_names(&["value1", "value2"]) + /// .help("Some really long help and complex\n\ + /// help that makes more sense to be\n\ + /// on a line after the option") + /// .next_line_help(true)) + /// .get_matches_from(vec![ + /// "prog", "--help" + /// ]); + /// ``` + /// + /// The above example displays the following help message + /// + /// ```notrust + /// nlh + /// + /// USAGE: + /// nlh [FLAGS] [OPTIONS] + /// + /// FLAGS: + /// -h, --help Prints help information + /// -V, --version Prints version information + /// + /// OPTIONS: + /// -o, --long-option-flag + /// Some really long help and complex + /// help that makes more sense to be + /// on a line after the option + /// ``` + /// [`AppSettings::NextLineHelp`]: ./enum.AppSettings.html#variant.NextLineHelp + pub fn next_line_help(mut self, nlh: bool) -> Self { + if nlh { + self.setb(ArgSettings::NextLineHelp); + } else { + self.unsetb(ArgSettings::NextLineHelp); + } + self + } + + /// Allows custom ordering of args within the help message. Args with a lower value will be + /// displayed first in the help message. This is helpful when one would like to emphasise + /// frequently used args, or prioritize those towards the top of the list. Duplicate values + /// **are** allowed. Args with duplicate display orders will be displayed in alphabetical + /// order. + /// + /// **NOTE:** The default is 999 for all arguments. + /// + /// **NOTE:** This setting is ignored for [positional arguments] which are always displayed in + /// [index] order. + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("prog") + /// .arg(Arg::with_name("a") // Typically args are grouped alphabetically by name. + /// // Args without a display_order have a value of 999 and are + /// // displayed alphabetically with all other 999 valued args. + /// .long("long-option") + /// .short("o") + /// .takes_value(true) + /// .help("Some help and text")) + /// .arg(Arg::with_name("b") + /// .long("other-option") + /// .short("O") + /// .takes_value(true) + /// .display_order(1) // In order to force this arg to appear *first* + /// // all we have to do is give it a value lower than 999. + /// // Any other args with a value of 1 will be displayed + /// // alphabetically with this one...then 2 values, then 3, etc. + /// .help("I should be first!")) + /// .get_matches_from(vec![ + /// "prog", "--help" + /// ]); + /// ``` + /// + /// The above example displays the following help message + /// + /// ```notrust + /// cust-ord + /// + /// USAGE: + /// cust-ord [FLAGS] [OPTIONS] + /// + /// FLAGS: + /// -h, --help Prints help information + /// -V, --version Prints version information + /// + /// OPTIONS: + /// -O, --other-option I should be first! + /// -o, --long-option Some help and text + /// ``` + /// [positional arguments]: ./struct.Arg.html#method.index + /// [index]: ./struct.Arg.html#method.index + pub fn display_order(mut self, ord: usize) -> Self { + self.s.disp_ord = ord; + self + } + + /// Indicates that all parameters passed after this should not be parsed + /// individually, but rather passed in their entirety. It is worth noting + /// that setting this requires all values to come after a `--` to indicate they + /// should all be captured. For example: + /// + /// ```notrust + /// --foo something -- -v -v -v -b -b -b --baz -q -u -x + /// ``` + /// Will result in everything after `--` to be considered one raw argument. This behavior + /// may not be exactly what you are expecting and using [`AppSettings::TrailingVarArg`] + /// may be more appropriate. + /// + /// **NOTE:** Implicitly sets [`Arg::multiple(true)`], [`Arg::allow_hyphen_values(true)`], and + /// [`Arg::last(true)`] when set to `true` + /// + /// [`Arg::multiple(true)`]: ./struct.Arg.html#method.multiple + /// [`Arg::allow_hyphen_values(true)`]: ./struct.Arg.html#method.allow_hyphen_values + /// [`Arg::last(true)`]: ./struct.Arg.html#method.last + /// [`AppSettings::TrailingVarArg`]: ./enum.AppSettings.html#variant.TrailingVarArg + pub fn raw(self, raw: bool) -> Self { + self.multiple(raw).allow_hyphen_values(raw).last(raw) + } + + /// Hides an argument from short help message output. + /// + /// **NOTE:** This does **not** hide the argument from usage strings on error + /// + /// **NOTE:** Setting this option will cause next-line-help output style to be used + /// when long help (`--help`) is called. + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg}; + /// Arg::with_name("debug") + /// .hidden_short_help(true) + /// # ; + /// ``` + /// Setting `hidden_short_help(true)` will hide the argument when displaying short help text + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("prog") + /// .arg(Arg::with_name("cfg") + /// .long("config") + /// .hidden_short_help(true) + /// .help("Some help text describing the --config arg")) + /// .get_matches_from(vec![ + /// "prog", "-h" + /// ]); + /// ``` + /// + /// The above example displays + /// + /// ```notrust + /// helptest + /// + /// USAGE: + /// helptest [FLAGS] + /// + /// FLAGS: + /// -h, --help Prints help information + /// -V, --version Prints version information + /// ``` + /// + /// However, when --help is called + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("prog") + /// .arg(Arg::with_name("cfg") + /// .long("config") + /// .hidden_short_help(true) + /// .help("Some help text describing the --config arg")) + /// .get_matches_from(vec![ + /// "prog", "--help" + /// ]); + /// ``` + /// + /// Then the following would be displayed + /// + /// ```notrust + /// helptest + /// + /// USAGE: + /// helptest [FLAGS] + /// + /// FLAGS: + /// --config Some help text describing the --config arg + /// -h, --help Prints help information + /// -V, --version Prints version information + /// ``` + pub fn hidden_short_help(self, hide: bool) -> Self { + if hide { + self.set(ArgSettings::HiddenShortHelp) + } else { + self.unset(ArgSettings::HiddenShortHelp) + } + } + + /// Hides an argument from long help message output. + /// + /// **NOTE:** This does **not** hide the argument from usage strings on error + /// + /// **NOTE:** Setting this option will cause next-line-help output style to be used + /// when long help (`--help`) is called. + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg}; + /// Arg::with_name("debug") + /// .hidden_long_help(true) + /// # ; + /// ``` + /// Setting `hidden_long_help(true)` will hide the argument when displaying long help text + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("prog") + /// .arg(Arg::with_name("cfg") + /// .long("config") + /// .hidden_long_help(true) + /// .help("Some help text describing the --config arg")) + /// .get_matches_from(vec![ + /// "prog", "--help" + /// ]); + /// ``` + /// + /// The above example displays + /// + /// ```notrust + /// helptest + /// + /// USAGE: + /// helptest [FLAGS] + /// + /// FLAGS: + /// -h, --help Prints help information + /// -V, --version Prints version information + /// ``` + /// + /// However, when -h is called + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("prog") + /// .arg(Arg::with_name("cfg") + /// .long("config") + /// .hidden_long_help(true) + /// .help("Some help text describing the --config arg")) + /// .get_matches_from(vec![ + /// "prog", "-h" + /// ]); + /// ``` + /// + /// Then the following would be displayed + /// + /// ```notrust + /// helptest + /// + /// USAGE: + /// helptest [FLAGS] + /// + /// FLAGS: + /// --config Some help text describing the --config arg + /// -h, --help Prints help information + /// -V, --version Prints version information + /// ``` + pub fn hidden_long_help(self, hide: bool) -> Self { + if hide { + self.set(ArgSettings::HiddenLongHelp) + } else { + self.unset(ArgSettings::HiddenLongHelp) + } + } + + /// Checks if one of the [`ArgSettings`] settings is set for the argument. + /// + /// [`ArgSettings`]: ./enum.ArgSettings.html + pub fn is_set(&self, s: ArgSettings) -> bool { + self.b.is_set(s) + } + + /// Sets one of the [`ArgSettings`] settings for the argument. + /// + /// [`ArgSettings`]: ./enum.ArgSettings.html + pub fn set(mut self, s: ArgSettings) -> Self { + self.setb(s); + self + } + + /// Unsets one of the [`ArgSettings`] settings for the argument. + /// + /// [`ArgSettings`]: ./enum.ArgSettings.html + pub fn unset(mut self, s: ArgSettings) -> Self { + self.unsetb(s); + self + } + + #[doc(hidden)] + pub fn setb(&mut self, s: ArgSettings) { + self.b.set(s); + } + + #[doc(hidden)] + pub fn unsetb(&mut self, s: ArgSettings) { + self.b.unset(s); + } +} + +impl<'a, 'b, 'z> From<&'z Arg<'a, 'b>> for Arg<'a, 'b> { + fn from(a: &'z Arg<'a, 'b>) -> Self { + Arg { + b: a.b.clone(), + v: a.v.clone(), + s: a.s.clone(), + index: a.index, + r_ifs: a.r_ifs.clone(), + } + } +} + +impl<'n, 'e> PartialEq for Arg<'n, 'e> { + fn eq(&self, other: &Arg<'n, 'e>) -> bool { + self.b == other.b + } +} diff --git a/clap/src/args/arg_builder/base.rs b/clap/src/args/arg_builder/base.rs new file mode 100644 index 0000000..fef9d8a --- /dev/null +++ b/clap/src/args/arg_builder/base.rs @@ -0,0 +1,38 @@ +use args::{Arg, ArgFlags, ArgSettings}; + +#[derive(Debug, Clone, Default)] +pub struct Base<'a, 'b> +where + 'a: 'b, +{ + pub name: &'a str, + pub help: Option<&'b str>, + pub long_help: Option<&'b str>, + pub blacklist: Option>, + pub settings: ArgFlags, + pub r_unless: Option>, + pub overrides: Option>, + pub groups: Option>, + pub requires: Option, &'a str)>>, +} + +impl<'n, 'e> Base<'n, 'e> { + pub fn new(name: &'n str) -> Self { + Base { + name: name, + ..Default::default() + } + } + + pub fn set(&mut self, s: ArgSettings) { self.settings.set(s); } + pub fn unset(&mut self, s: ArgSettings) { self.settings.unset(s); } + pub fn is_set(&self, s: ArgSettings) -> bool { self.settings.is_set(s) } +} + +impl<'n, 'e, 'z> From<&'z Arg<'n, 'e>> for Base<'n, 'e> { + fn from(a: &'z Arg<'n, 'e>) -> Self { a.b.clone() } +} + +impl<'n, 'e> PartialEq for Base<'n, 'e> { + fn eq(&self, other: &Base<'n, 'e>) -> bool { self.name == other.name } +} diff --git a/clap/src/args/arg_builder/flag.rs b/clap/src/args/arg_builder/flag.rs new file mode 100644 index 0000000..641e777 --- /dev/null +++ b/clap/src/args/arg_builder/flag.rs @@ -0,0 +1,159 @@ +// Std +use std::convert::From; +use std::fmt::{Display, Formatter, Result}; +use std::rc::Rc; +use std::result::Result as StdResult; +use std::ffi::{OsStr, OsString}; +use std::mem; + +// Internal +use Arg; +use args::{AnyArg, ArgSettings, Base, DispOrder, Switched}; +use map::{self, VecMap}; + +#[derive(Default, Clone, Debug)] +#[doc(hidden)] +pub struct FlagBuilder<'n, 'e> +where + 'n: 'e, +{ + pub b: Base<'n, 'e>, + pub s: Switched<'e>, +} + +impl<'n, 'e> FlagBuilder<'n, 'e> { + pub fn new(name: &'n str) -> Self { + FlagBuilder { + b: Base::new(name), + ..Default::default() + } + } +} + +impl<'a, 'b, 'z> From<&'z Arg<'a, 'b>> for FlagBuilder<'a, 'b> { + fn from(a: &'z Arg<'a, 'b>) -> Self { + FlagBuilder { + b: Base::from(a), + s: Switched::from(a), + } + } +} + +impl<'a, 'b> From> for FlagBuilder<'a, 'b> { + fn from(mut a: Arg<'a, 'b>) -> Self { + FlagBuilder { + b: mem::replace(&mut a.b, Base::default()), + s: mem::replace(&mut a.s, Switched::default()), + } + } +} + +impl<'n, 'e> Display for FlagBuilder<'n, 'e> { + fn fmt(&self, f: &mut Formatter) -> Result { + if let Some(l) = self.s.long { + write!(f, "--{}", l)?; + } else { + write!(f, "-{}", self.s.short.unwrap())?; + } + + Ok(()) + } +} + +impl<'n, 'e> AnyArg<'n, 'e> for FlagBuilder<'n, 'e> { + fn name(&self) -> &'n str { self.b.name } + fn overrides(&self) -> Option<&[&'e str]> { self.b.overrides.as_ref().map(|o| &o[..]) } + fn requires(&self) -> Option<&[(Option<&'e str>, &'n str)]> { + self.b.requires.as_ref().map(|o| &o[..]) + } + fn blacklist(&self) -> Option<&[&'e str]> { self.b.blacklist.as_ref().map(|o| &o[..]) } + fn required_unless(&self) -> Option<&[&'e str]> { self.b.r_unless.as_ref().map(|o| &o[..]) } + fn is_set(&self, s: ArgSettings) -> bool { self.b.settings.is_set(s) } + fn has_switch(&self) -> bool { true } + fn takes_value(&self) -> bool { false } + fn set(&mut self, s: ArgSettings) { self.b.settings.set(s) } + fn max_vals(&self) -> Option { None } + fn val_names(&self) -> Option<&VecMap<&'e str>> { None } + fn num_vals(&self) -> Option { None } + fn possible_vals(&self) -> Option<&[&'e str]> { None } + fn validator(&self) -> Option<&Rc StdResult<(), String>>> { None } + fn validator_os(&self) -> Option<&Rc StdResult<(), OsString>>> { None } + fn min_vals(&self) -> Option { None } + fn short(&self) -> Option { self.s.short } + fn long(&self) -> Option<&'e str> { self.s.long } + fn val_delim(&self) -> Option { None } + fn help(&self) -> Option<&'e str> { self.b.help } + fn long_help(&self) -> Option<&'e str> { self.b.long_help } + fn val_terminator(&self) -> Option<&'e str> { None } + fn default_val(&self) -> Option<&'e OsStr> { None } + fn default_vals_ifs(&self) -> Option, &'e OsStr)>> { + None + } + fn env<'s>(&'s self) -> Option<(&'n OsStr, Option<&'s OsString>)> { None } + fn longest_filter(&self) -> bool { self.s.long.is_some() } + fn aliases(&self) -> Option> { + if let Some(ref aliases) = self.s.aliases { + let vis_aliases: Vec<_> = aliases + .iter() + .filter_map(|&(n, v)| if v { Some(n) } else { None }) + .collect(); + if vis_aliases.is_empty() { + None + } else { + Some(vis_aliases) + } + } else { + None + } + } +} + +impl<'n, 'e> DispOrder for FlagBuilder<'n, 'e> { + fn disp_ord(&self) -> usize { self.s.disp_ord } +} + +impl<'n, 'e> PartialEq for FlagBuilder<'n, 'e> { + fn eq(&self, other: &FlagBuilder<'n, 'e>) -> bool { self.b == other.b } +} + +#[cfg(test)] +mod test { + use args::settings::ArgSettings; + use super::FlagBuilder; + + #[test] + fn flagbuilder_display() { + let mut f = FlagBuilder::new("flg"); + f.b.settings.set(ArgSettings::Multiple); + f.s.long = Some("flag"); + + assert_eq!(&*format!("{}", f), "--flag"); + + let mut f2 = FlagBuilder::new("flg"); + f2.s.short = Some('f'); + + assert_eq!(&*format!("{}", f2), "-f"); + } + + #[test] + fn flagbuilder_display_single_alias() { + let mut f = FlagBuilder::new("flg"); + f.s.long = Some("flag"); + f.s.aliases = Some(vec![("als", true)]); + + assert_eq!(&*format!("{}", f), "--flag"); + } + + #[test] + fn flagbuilder_display_multiple_aliases() { + let mut f = FlagBuilder::new("flg"); + f.s.short = Some('f'); + f.s.aliases = Some(vec![ + ("alias_not_visible", false), + ("f2", true), + ("f3", true), + ("f4", true), + ]); + assert_eq!(&*format!("{}", f), "-f"); + } +} diff --git a/clap/src/args/arg_builder/mod.rs b/clap/src/args/arg_builder/mod.rs new file mode 100644 index 0000000..d1a7a66 --- /dev/null +++ b/clap/src/args/arg_builder/mod.rs @@ -0,0 +1,13 @@ +pub use self::flag::FlagBuilder; +pub use self::option::OptBuilder; +pub use self::positional::PosBuilder; +pub use self::base::Base; +pub use self::switched::Switched; +pub use self::valued::Valued; + +mod flag; +mod positional; +mod option; +mod base; +mod valued; +mod switched; diff --git a/clap/src/args/arg_builder/option.rs b/clap/src/args/arg_builder/option.rs new file mode 100644 index 0000000..4bb147a --- /dev/null +++ b/clap/src/args/arg_builder/option.rs @@ -0,0 +1,244 @@ +// Std +use std::fmt::{Display, Formatter, Result}; +use std::rc::Rc; +use std::result::Result as StdResult; +use std::ffi::{OsStr, OsString}; +use std::mem; + +// Internal +use args::{AnyArg, Arg, ArgSettings, Base, DispOrder, Switched, Valued}; +use map::{self, VecMap}; +use INTERNAL_ERROR_MSG; + +#[allow(missing_debug_implementations)] +#[doc(hidden)] +#[derive(Default, Clone)] +pub struct OptBuilder<'n, 'e> +where + 'n: 'e, +{ + pub b: Base<'n, 'e>, + pub s: Switched<'e>, + pub v: Valued<'n, 'e>, +} + +impl<'n, 'e> OptBuilder<'n, 'e> { + pub fn new(name: &'n str) -> Self { + OptBuilder { + b: Base::new(name), + ..Default::default() + } + } +} + +impl<'n, 'e, 'z> From<&'z Arg<'n, 'e>> for OptBuilder<'n, 'e> { + fn from(a: &'z Arg<'n, 'e>) -> Self { + OptBuilder { + b: Base::from(a), + s: Switched::from(a), + v: Valued::from(a), + } + } +} + +impl<'n, 'e> From> for OptBuilder<'n, 'e> { + fn from(mut a: Arg<'n, 'e>) -> Self { + a.v.fill_in(); + OptBuilder { + b: mem::replace(&mut a.b, Base::default()), + s: mem::replace(&mut a.s, Switched::default()), + v: mem::replace(&mut a.v, Valued::default()), + } + } +} + +impl<'n, 'e> Display for OptBuilder<'n, 'e> { + fn fmt(&self, f: &mut Formatter) -> Result { + debugln!("OptBuilder::fmt:{}", self.b.name); + let sep = if self.b.is_set(ArgSettings::RequireEquals) { + "=" + } else { + " " + }; + // Write the name such --long or -l + if let Some(l) = self.s.long { + write!(f, "--{}{}", l, sep)?; + } else { + write!(f, "-{}{}", self.s.short.unwrap(), sep)?; + } + let delim = if self.is_set(ArgSettings::RequireDelimiter) { + self.v.val_delim.expect(INTERNAL_ERROR_MSG) + } else { + ' ' + }; + + // Write the values such as + if let Some(ref vec) = self.v.val_names { + let mut it = vec.iter().peekable(); + while let Some((_, val)) = it.next() { + write!(f, "<{}>", val)?; + if it.peek().is_some() { + write!(f, "{}", delim)?; + } + } + let num = vec.len(); + if self.is_set(ArgSettings::Multiple) && num == 1 { + write!(f, "...")?; + } + } else if let Some(num) = self.v.num_vals { + let mut it = (0..num).peekable(); + while let Some(_) = it.next() { + write!(f, "<{}>", self.b.name)?; + if it.peek().is_some() { + write!(f, "{}", delim)?; + } + } + if self.is_set(ArgSettings::Multiple) && num == 1 { + write!(f, "...")?; + } + } else { + write!( + f, + "<{}>{}", + self.b.name, + if self.is_set(ArgSettings::Multiple) { + "..." + } else { + "" + } + )?; + } + + Ok(()) + } +} + +impl<'n, 'e> AnyArg<'n, 'e> for OptBuilder<'n, 'e> { + fn name(&self) -> &'n str { self.b.name } + fn overrides(&self) -> Option<&[&'e str]> { self.b.overrides.as_ref().map(|o| &o[..]) } + fn requires(&self) -> Option<&[(Option<&'e str>, &'n str)]> { + self.b.requires.as_ref().map(|o| &o[..]) + } + fn blacklist(&self) -> Option<&[&'e str]> { self.b.blacklist.as_ref().map(|o| &o[..]) } + fn required_unless(&self) -> Option<&[&'e str]> { self.b.r_unless.as_ref().map(|o| &o[..]) } + fn val_names(&self) -> Option<&VecMap<&'e str>> { self.v.val_names.as_ref() } + fn is_set(&self, s: ArgSettings) -> bool { self.b.settings.is_set(s) } + fn has_switch(&self) -> bool { true } + fn set(&mut self, s: ArgSettings) { self.b.settings.set(s) } + fn max_vals(&self) -> Option { self.v.max_vals } + fn val_terminator(&self) -> Option<&'e str> { self.v.terminator } + fn num_vals(&self) -> Option { self.v.num_vals } + fn possible_vals(&self) -> Option<&[&'e str]> { self.v.possible_vals.as_ref().map(|o| &o[..]) } + fn validator(&self) -> Option<&Rc StdResult<(), String>>> { + self.v.validator.as_ref() + } + fn validator_os(&self) -> Option<&Rc StdResult<(), OsString>>> { + self.v.validator_os.as_ref() + } + fn min_vals(&self) -> Option { self.v.min_vals } + fn short(&self) -> Option { self.s.short } + fn long(&self) -> Option<&'e str> { self.s.long } + fn val_delim(&self) -> Option { self.v.val_delim } + fn takes_value(&self) -> bool { true } + fn help(&self) -> Option<&'e str> { self.b.help } + fn long_help(&self) -> Option<&'e str> { self.b.long_help } + fn default_val(&self) -> Option<&'e OsStr> { self.v.default_val } + fn default_vals_ifs(&self) -> Option, &'e OsStr)>> { + self.v.default_vals_ifs.as_ref().map(|vm| vm.values()) + } + fn env<'s>(&'s self) -> Option<(&'n OsStr, Option<&'s OsString>)> { + self.v + .env + .as_ref() + .map(|&(key, ref value)| (key, value.as_ref())) + } + fn longest_filter(&self) -> bool { true } + fn aliases(&self) -> Option> { + if let Some(ref aliases) = self.s.aliases { + let vis_aliases: Vec<_> = aliases + .iter() + .filter_map(|&(n, v)| if v { Some(n) } else { None }) + .collect(); + if vis_aliases.is_empty() { + None + } else { + Some(vis_aliases) + } + } else { + None + } + } +} + +impl<'n, 'e> DispOrder for OptBuilder<'n, 'e> { + fn disp_ord(&self) -> usize { self.s.disp_ord } +} + +impl<'n, 'e> PartialEq for OptBuilder<'n, 'e> { + fn eq(&self, other: &OptBuilder<'n, 'e>) -> bool { self.b == other.b } +} + +#[cfg(test)] +mod test { + use args::settings::ArgSettings; + use super::OptBuilder; + use map::VecMap; + + #[test] + fn optbuilder_display1() { + let mut o = OptBuilder::new("opt"); + o.s.long = Some("option"); + o.b.settings.set(ArgSettings::Multiple); + + assert_eq!(&*format!("{}", o), "--option ..."); + } + + #[test] + fn optbuilder_display2() { + let mut v_names = VecMap::new(); + v_names.insert(0, "file"); + v_names.insert(1, "name"); + + let mut o2 = OptBuilder::new("opt"); + o2.s.short = Some('o'); + o2.v.val_names = Some(v_names); + + assert_eq!(&*format!("{}", o2), "-o "); + } + + #[test] + fn optbuilder_display3() { + let mut v_names = VecMap::new(); + v_names.insert(0, "file"); + v_names.insert(1, "name"); + + let mut o2 = OptBuilder::new("opt"); + o2.s.short = Some('o'); + o2.v.val_names = Some(v_names); + o2.b.settings.set(ArgSettings::Multiple); + + assert_eq!(&*format!("{}", o2), "-o "); + } + + #[test] + fn optbuilder_display_single_alias() { + let mut o = OptBuilder::new("opt"); + o.s.long = Some("option"); + o.s.aliases = Some(vec![("als", true)]); + + assert_eq!(&*format!("{}", o), "--option "); + } + + #[test] + fn optbuilder_display_multiple_aliases() { + let mut o = OptBuilder::new("opt"); + o.s.long = Some("option"); + o.s.aliases = Some(vec![ + ("als_not_visible", false), + ("als2", true), + ("als3", true), + ("als4", true), + ]); + assert_eq!(&*format!("{}", o), "--option "); + } +} diff --git a/clap/src/args/arg_builder/positional.rs b/clap/src/args/arg_builder/positional.rs new file mode 100644 index 0000000..43fdca4 --- /dev/null +++ b/clap/src/args/arg_builder/positional.rs @@ -0,0 +1,229 @@ +// Std +use std::borrow::Cow; +use std::fmt::{Display, Formatter, Result}; +use std::rc::Rc; +use std::result::Result as StdResult; +use std::ffi::{OsStr, OsString}; +use std::mem; + +// Internal +use Arg; +use args::{AnyArg, ArgSettings, Base, DispOrder, Valued}; +use INTERNAL_ERROR_MSG; +use map::{self, VecMap}; + +#[allow(missing_debug_implementations)] +#[doc(hidden)] +#[derive(Clone, Default)] +pub struct PosBuilder<'n, 'e> +where + 'n: 'e, +{ + pub b: Base<'n, 'e>, + pub v: Valued<'n, 'e>, + pub index: u64, +} + +impl<'n, 'e> PosBuilder<'n, 'e> { + pub fn new(name: &'n str, idx: u64) -> Self { + PosBuilder { + b: Base::new(name), + index: idx, + ..Default::default() + } + } + + pub fn from_arg_ref(a: &Arg<'n, 'e>, idx: u64) -> Self { + let mut pb = PosBuilder { + b: Base::from(a), + v: Valued::from(a), + index: idx, + }; + if a.v.max_vals.is_some() || a.v.min_vals.is_some() + || (a.v.num_vals.is_some() && a.v.num_vals.unwrap() > 1) + { + pb.b.settings.set(ArgSettings::Multiple); + } + pb + } + + pub fn from_arg(mut a: Arg<'n, 'e>, idx: u64) -> Self { + if a.v.max_vals.is_some() || a.v.min_vals.is_some() + || (a.v.num_vals.is_some() && a.v.num_vals.unwrap() > 1) + { + a.b.settings.set(ArgSettings::Multiple); + } + PosBuilder { + b: mem::replace(&mut a.b, Base::default()), + v: mem::replace(&mut a.v, Valued::default()), + index: idx, + } + } + + pub fn multiple_str(&self) -> &str { + let mult_vals = self.v + .val_names + .as_ref() + .map_or(true, |names| names.len() < 2); + if self.is_set(ArgSettings::Multiple) && mult_vals { + "..." + } else { + "" + } + } + + pub fn name_no_brackets(&self) -> Cow { + debugln!("PosBuilder::name_no_brackets;"); + let mut delim = String::new(); + delim.push(if self.is_set(ArgSettings::RequireDelimiter) { + self.v.val_delim.expect(INTERNAL_ERROR_MSG) + } else { + ' ' + }); + if let Some(ref names) = self.v.val_names { + debugln!("PosBuilder:name_no_brackets: val_names={:#?}", names); + if names.len() > 1 { + Cow::Owned( + names + .values() + .map(|n| format!("<{}>", n)) + .collect::>() + .join(&*delim), + ) + } else { + Cow::Borrowed(names.values().next().expect(INTERNAL_ERROR_MSG)) + } + } else { + debugln!("PosBuilder:name_no_brackets: just name"); + Cow::Borrowed(self.b.name) + } + } +} + +impl<'n, 'e> Display for PosBuilder<'n, 'e> { + fn fmt(&self, f: &mut Formatter) -> Result { + let mut delim = String::new(); + delim.push(if self.is_set(ArgSettings::RequireDelimiter) { + self.v.val_delim.expect(INTERNAL_ERROR_MSG) + } else { + ' ' + }); + if let Some(ref names) = self.v.val_names { + write!( + f, + "{}", + names + .values() + .map(|n| format!("<{}>", n)) + .collect::>() + .join(&*delim) + )?; + } else { + write!(f, "<{}>", self.b.name)?; + } + if self.b.settings.is_set(ArgSettings::Multiple) + && (self.v.val_names.is_none() || self.v.val_names.as_ref().unwrap().len() == 1) + { + write!(f, "...")?; + } + + Ok(()) + } +} + +impl<'n, 'e> AnyArg<'n, 'e> for PosBuilder<'n, 'e> { + fn name(&self) -> &'n str { self.b.name } + fn overrides(&self) -> Option<&[&'e str]> { self.b.overrides.as_ref().map(|o| &o[..]) } + fn requires(&self) -> Option<&[(Option<&'e str>, &'n str)]> { + self.b.requires.as_ref().map(|o| &o[..]) + } + fn blacklist(&self) -> Option<&[&'e str]> { self.b.blacklist.as_ref().map(|o| &o[..]) } + fn required_unless(&self) -> Option<&[&'e str]> { self.b.r_unless.as_ref().map(|o| &o[..]) } + fn val_names(&self) -> Option<&VecMap<&'e str>> { self.v.val_names.as_ref() } + fn is_set(&self, s: ArgSettings) -> bool { self.b.settings.is_set(s) } + fn set(&mut self, s: ArgSettings) { self.b.settings.set(s) } + fn has_switch(&self) -> bool { false } + fn max_vals(&self) -> Option { self.v.max_vals } + fn val_terminator(&self) -> Option<&'e str> { self.v.terminator } + fn num_vals(&self) -> Option { self.v.num_vals } + fn possible_vals(&self) -> Option<&[&'e str]> { self.v.possible_vals.as_ref().map(|o| &o[..]) } + fn validator(&self) -> Option<&Rc StdResult<(), String>>> { + self.v.validator.as_ref() + } + fn validator_os(&self) -> Option<&Rc StdResult<(), OsString>>> { + self.v.validator_os.as_ref() + } + fn min_vals(&self) -> Option { self.v.min_vals } + fn short(&self) -> Option { None } + fn long(&self) -> Option<&'e str> { None } + fn val_delim(&self) -> Option { self.v.val_delim } + fn takes_value(&self) -> bool { true } + fn help(&self) -> Option<&'e str> { self.b.help } + fn long_help(&self) -> Option<&'e str> { self.b.long_help } + fn default_vals_ifs(&self) -> Option, &'e OsStr)>> { + self.v.default_vals_ifs.as_ref().map(|vm| vm.values()) + } + fn default_val(&self) -> Option<&'e OsStr> { self.v.default_val } + fn env<'s>(&'s self) -> Option<(&'n OsStr, Option<&'s OsString>)> { + self.v + .env + .as_ref() + .map(|&(key, ref value)| (key, value.as_ref())) + } + fn longest_filter(&self) -> bool { true } + fn aliases(&self) -> Option> { None } +} + +impl<'n, 'e> DispOrder for PosBuilder<'n, 'e> { + fn disp_ord(&self) -> usize { self.index as usize } +} + +impl<'n, 'e> PartialEq for PosBuilder<'n, 'e> { + fn eq(&self, other: &PosBuilder<'n, 'e>) -> bool { self.b == other.b } +} + +#[cfg(test)] +mod test { + use args::settings::ArgSettings; + use super::PosBuilder; + use map::VecMap; + + #[test] + fn display_mult() { + let mut p = PosBuilder::new("pos", 1); + p.b.settings.set(ArgSettings::Multiple); + + assert_eq!(&*format!("{}", p), "..."); + } + + #[test] + fn display_required() { + let mut p2 = PosBuilder::new("pos", 1); + p2.b.settings.set(ArgSettings::Required); + + assert_eq!(&*format!("{}", p2), ""); + } + + #[test] + fn display_val_names() { + let mut p2 = PosBuilder::new("pos", 1); + let mut vm = VecMap::new(); + vm.insert(0, "file1"); + vm.insert(1, "file2"); + p2.v.val_names = Some(vm); + + assert_eq!(&*format!("{}", p2), " "); + } + + #[test] + fn display_val_names_req() { + let mut p2 = PosBuilder::new("pos", 1); + p2.b.settings.set(ArgSettings::Required); + let mut vm = VecMap::new(); + vm.insert(0, "file1"); + vm.insert(1, "file2"); + p2.v.val_names = Some(vm); + + assert_eq!(&*format!("{}", p2), " "); + } +} diff --git a/clap/src/args/arg_builder/switched.rs b/clap/src/args/arg_builder/switched.rs new file mode 100644 index 0000000..224b2f2 --- /dev/null +++ b/clap/src/args/arg_builder/switched.rs @@ -0,0 +1,38 @@ +use Arg; + +#[derive(Debug)] +pub struct Switched<'b> { + pub short: Option, + pub long: Option<&'b str>, + pub aliases: Option>, // (name, visible) + pub disp_ord: usize, + pub unified_ord: usize, +} + +impl<'e> Default for Switched<'e> { + fn default() -> Self { + Switched { + short: None, + long: None, + aliases: None, + disp_ord: 999, + unified_ord: 999, + } + } +} + +impl<'n, 'e, 'z> From<&'z Arg<'n, 'e>> for Switched<'e> { + fn from(a: &'z Arg<'n, 'e>) -> Self { a.s.clone() } +} + +impl<'e> Clone for Switched<'e> { + fn clone(&self) -> Self { + Switched { + short: self.short, + long: self.long, + aliases: self.aliases.clone(), + disp_ord: self.disp_ord, + unified_ord: self.unified_ord, + } + } +} diff --git a/clap/src/args/arg_builder/valued.rs b/clap/src/args/arg_builder/valued.rs new file mode 100644 index 0000000..d70854d --- /dev/null +++ b/clap/src/args/arg_builder/valued.rs @@ -0,0 +1,67 @@ +use std::rc::Rc; +use std::ffi::{OsStr, OsString}; + +use map::VecMap; + +use Arg; + +#[allow(missing_debug_implementations)] +#[derive(Clone)] +pub struct Valued<'a, 'b> +where + 'a: 'b, +{ + pub possible_vals: Option>, + pub val_names: Option>, + pub num_vals: Option, + pub max_vals: Option, + pub min_vals: Option, + pub validator: Option Result<(), String>>>, + pub validator_os: Option Result<(), OsString>>>, + pub val_delim: Option, + pub default_val: Option<&'b OsStr>, + pub default_vals_ifs: Option, &'b OsStr)>>, + pub env: Option<(&'a OsStr, Option)>, + pub terminator: Option<&'b str>, +} + +impl<'n, 'e> Default for Valued<'n, 'e> { + fn default() -> Self { + Valued { + possible_vals: None, + num_vals: None, + min_vals: None, + max_vals: None, + val_names: None, + validator: None, + validator_os: None, + val_delim: None, + default_val: None, + default_vals_ifs: None, + env: None, + terminator: None, + } + } +} + +impl<'n, 'e> Valued<'n, 'e> { + pub fn fill_in(&mut self) { + if let Some(ref vec) = self.val_names { + if vec.len() > 1 { + self.num_vals = Some(vec.len() as u64); + } + } + } +} + +impl<'n, 'e, 'z> From<&'z Arg<'n, 'e>> for Valued<'n, 'e> { + fn from(a: &'z Arg<'n, 'e>) -> Self { + let mut v = a.v.clone(); + if let Some(ref vec) = a.v.val_names { + if vec.len() > 1 { + v.num_vals = Some(vec.len() as u64); + } + } + v + } +} diff --git a/clap/src/args/arg_matcher.rs b/clap/src/args/arg_matcher.rs new file mode 100644 index 0000000..e1d8067 --- /dev/null +++ b/clap/src/args/arg_matcher.rs @@ -0,0 +1,218 @@ +// Std +use std::collections::hash_map::{Entry, Iter}; +use std::collections::HashMap; +use std::ffi::OsStr; +use std::ops::Deref; +use std::mem; + +// Internal +use args::{ArgMatches, MatchedArg, SubCommand}; +use args::AnyArg; +use args::settings::ArgSettings; + +#[doc(hidden)] +#[allow(missing_debug_implementations)] +pub struct ArgMatcher<'a>(pub ArgMatches<'a>); + +impl<'a> Default for ArgMatcher<'a> { + fn default() -> Self { ArgMatcher(ArgMatches::default()) } +} + +impl<'a> ArgMatcher<'a> { + pub fn new() -> Self { ArgMatcher::default() } + + pub fn process_arg_overrides<'b>(&mut self, a: Option<&AnyArg<'a, 'b>>, overrides: &mut Vec<(&'b str, &'a str)>, required: &mut Vec<&'a str>, check_all: bool) { + debugln!("ArgMatcher::process_arg_overrides:{:?};", a.map_or(None, |a| Some(a.name()))); + if let Some(aa) = a { + let mut self_done = false; + if let Some(a_overrides) = aa.overrides() { + for overr in a_overrides { + debugln!("ArgMatcher::process_arg_overrides:iter:{};", overr); + if overr == &aa.name() { + self_done = true; + self.handle_self_overrides(a); + } else if self.is_present(overr) { + debugln!("ArgMatcher::process_arg_overrides:iter:{}: removing from matches;", overr); + self.remove(overr); + for i in (0 .. required.len()).rev() { + if &required[i] == overr { + debugln!("ArgMatcher::process_arg_overrides:iter:{}: removing required;", overr); + required.swap_remove(i); + break; + } + } + overrides.push((overr, aa.name())); + } else { + overrides.push((overr, aa.name())); + } + } + } + if check_all && !self_done { + self.handle_self_overrides(a); + } + } + } + + pub fn handle_self_overrides<'b>(&mut self, a: Option<&AnyArg<'a, 'b>>) { + debugln!("ArgMatcher::handle_self_overrides:{:?};", a.map_or(None, |a| Some(a.name()))); + if let Some(aa) = a { + if !aa.has_switch() || aa.is_set(ArgSettings::Multiple) { + // positional args can't override self or else we would never advance to the next + + // Also flags with --multiple set are ignored otherwise we could never have more + // than one + return; + } + if let Some(ma) = self.get_mut(aa.name()) { + if ma.vals.len() > 1 { + // swap_remove(0) would be O(1) but does not preserve order, which + // we need + ma.vals.remove(0); + ma.occurs = 1; + } else if !aa.takes_value() && ma.occurs > 1 { + ma.occurs = 1; + } + } + } + } + + pub fn is_present(&self, name: &str) -> bool { + self.0.is_present(name) + } + + pub fn propagate_globals(&mut self, global_arg_vec: &[&'a str]) { + debugln!( "ArgMatcher::get_global_values: global_arg_vec={:?}", global_arg_vec ); + let mut vals_map = HashMap::new(); + self.fill_in_global_values(global_arg_vec, &mut vals_map); + } + + fn fill_in_global_values( + &mut self, + global_arg_vec: &[&'a str], + vals_map: &mut HashMap<&'a str, MatchedArg>, + ) { + for global_arg in global_arg_vec { + if let Some(ma) = self.get(global_arg) { + // We have to check if the parent's global arg wasn't used but still exists + // such as from a default value. + // + // For example, `myprog subcommand --global-arg=value` where --global-arg defines + // a default value of `other` myprog would have an existing MatchedArg for + // --global-arg where the value is `other`, however the occurs will be 0. + let to_update = if let Some(parent_ma) = vals_map.get(global_arg) { + if parent_ma.occurs > 0 && ma.occurs == 0 { + parent_ma.clone() + } else { + ma.clone() + } + } else { + ma.clone() + }; + vals_map.insert(global_arg, to_update); + } + } + if let Some(ref mut sc) = self.0.subcommand { + let mut am = ArgMatcher(mem::replace(&mut sc.matches, ArgMatches::new())); + am.fill_in_global_values(global_arg_vec, vals_map); + mem::swap(&mut am.0, &mut sc.matches); + } + + for (name, matched_arg) in vals_map.into_iter() { + self.0.args.insert(name, matched_arg.clone()); + } + } + + pub fn get_mut(&mut self, arg: &str) -> Option<&mut MatchedArg> { self.0.args.get_mut(arg) } + + pub fn get(&self, arg: &str) -> Option<&MatchedArg> { self.0.args.get(arg) } + + pub fn remove(&mut self, arg: &str) { self.0.args.remove(arg); } + + pub fn remove_all(&mut self, args: &[&str]) { + for &arg in args { + self.0.args.remove(arg); + } + } + + pub fn insert(&mut self, name: &'a str) { self.0.args.insert(name, MatchedArg::new()); } + + pub fn contains(&self, arg: &str) -> bool { self.0.args.contains_key(arg) } + + pub fn is_empty(&self) -> bool { self.0.args.is_empty() } + + pub fn usage(&mut self, usage: String) { self.0.usage = Some(usage); } + + pub fn arg_names(&'a self) -> Vec<&'a str> { self.0.args.keys().map(Deref::deref).collect() } + + pub fn entry(&mut self, arg: &'a str) -> Entry<&'a str, MatchedArg> { self.0.args.entry(arg) } + + pub fn subcommand(&mut self, sc: SubCommand<'a>) { self.0.subcommand = Some(Box::new(sc)); } + + pub fn subcommand_name(&self) -> Option<&str> { self.0.subcommand_name() } + + pub fn iter(&self) -> Iter<&str, MatchedArg> { self.0.args.iter() } + + pub fn inc_occurrence_of(&mut self, arg: &'a str) { + debugln!("ArgMatcher::inc_occurrence_of: arg={}", arg); + if let Some(a) = self.get_mut(arg) { + a.occurs += 1; + return; + } + debugln!("ArgMatcher::inc_occurrence_of: first instance"); + self.insert(arg); + } + + pub fn inc_occurrences_of(&mut self, args: &[&'a str]) { + debugln!("ArgMatcher::inc_occurrences_of: args={:?}", args); + for arg in args { + self.inc_occurrence_of(arg); + } + } + + pub fn add_val_to(&mut self, arg: &'a str, val: &OsStr) { + let ma = self.entry(arg).or_insert(MatchedArg { + occurs: 0, + indices: Vec::with_capacity(1), + vals: Vec::with_capacity(1), + }); + ma.vals.push(val.to_owned()); + } + + pub fn add_index_to(&mut self, arg: &'a str, idx: usize) { + let ma = self.entry(arg).or_insert(MatchedArg { + occurs: 0, + indices: Vec::with_capacity(1), + vals: Vec::new(), + }); + ma.indices.push(idx); + } + + pub fn needs_more_vals<'b, A>(&self, o: &A) -> bool + where + A: AnyArg<'a, 'b>, + { + debugln!("ArgMatcher::needs_more_vals: o={}", o.name()); + if let Some(ma) = self.get(o.name()) { + if let Some(num) = o.num_vals() { + debugln!("ArgMatcher::needs_more_vals: num_vals...{}", num); + return if o.is_set(ArgSettings::Multiple) { + ((ma.vals.len() as u64) % num) != 0 + } else { + num != (ma.vals.len() as u64) + }; + } else if let Some(num) = o.max_vals() { + debugln!("ArgMatcher::needs_more_vals: max_vals...{}", num); + return !((ma.vals.len() as u64) > num); + } else if o.min_vals().is_some() { + debugln!("ArgMatcher::needs_more_vals: min_vals...true"); + return true; + } + return o.is_set(ArgSettings::Multiple); + } + true + } +} + +impl<'a> Into> for ArgMatcher<'a> { + fn into(self) -> ArgMatches<'a> { self.0 } +} diff --git a/clap/src/args/arg_matches.rs b/clap/src/args/arg_matches.rs new file mode 100644 index 0000000..6cf70a4 --- /dev/null +++ b/clap/src/args/arg_matches.rs @@ -0,0 +1,963 @@ +// Std +use std::borrow::Cow; +use std::collections::HashMap; +use std::ffi::{OsStr, OsString}; +use std::iter::Map; +use std::slice::Iter; + +// Internal +use INVALID_UTF8; +use args::MatchedArg; +use args::SubCommand; + +/// Used to get information about the arguments that where supplied to the program at runtime by +/// the user. New instances of this struct are obtained by using the [`App::get_matches`] family of +/// methods. +/// +/// # Examples +/// +/// ```no_run +/// # use clap::{App, Arg}; +/// let matches = App::new("MyApp") +/// .arg(Arg::with_name("out") +/// .long("output") +/// .required(true) +/// .takes_value(true)) +/// .arg(Arg::with_name("debug") +/// .short("d") +/// .multiple(true)) +/// .arg(Arg::with_name("cfg") +/// .short("c") +/// .takes_value(true)) +/// .get_matches(); // builds the instance of ArgMatches +/// +/// // to get information about the "cfg" argument we created, such as the value supplied we use +/// // various ArgMatches methods, such as ArgMatches::value_of +/// if let Some(c) = matches.value_of("cfg") { +/// println!("Value for -c: {}", c); +/// } +/// +/// // The ArgMatches::value_of method returns an Option because the user may not have supplied +/// // that argument at runtime. But if we specified that the argument was "required" as we did +/// // with the "out" argument, we can safely unwrap because `clap` verifies that was actually +/// // used at runtime. +/// println!("Value for --output: {}", matches.value_of("out").unwrap()); +/// +/// // You can check the presence of an argument +/// if matches.is_present("out") { +/// // Another way to check if an argument was present, or if it occurred multiple times is to +/// // use occurrences_of() which returns 0 if an argument isn't found at runtime, or the +/// // number of times that it occurred, if it was. To allow an argument to appear more than +/// // once, you must use the .multiple(true) method, otherwise it will only return 1 or 0. +/// if matches.occurrences_of("debug") > 2 { +/// println!("Debug mode is REALLY on, don't be crazy"); +/// } else { +/// println!("Debug mode kind of on"); +/// } +/// } +/// ``` +/// [`App::get_matches`]: ./struct.App.html#method.get_matches +#[derive(Debug, Clone)] +pub struct ArgMatches<'a> { + #[doc(hidden)] pub args: HashMap<&'a str, MatchedArg>, + #[doc(hidden)] pub subcommand: Option>>, + #[doc(hidden)] pub usage: Option, +} + +impl<'a> Default for ArgMatches<'a> { + fn default() -> Self { + ArgMatches { + args: HashMap::new(), + subcommand: None, + usage: None, + } + } +} + +impl<'a> ArgMatches<'a> { + #[doc(hidden)] + pub fn new() -> Self { + ArgMatches { + ..Default::default() + } + } + + /// Gets the value of a specific [option] or [positional] argument (i.e. an argument that takes + /// an additional value at runtime). If the option wasn't present at runtime + /// it returns `None`. + /// + /// *NOTE:* If getting a value for an option or positional argument that allows multiples, + /// prefer [`ArgMatches::values_of`] as `ArgMatches::value_of` will only return the *first* + /// value. + /// + /// # Panics + /// + /// This method will [`panic!`] if the value contains invalid UTF-8 code points. + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("myapp") + /// .arg(Arg::with_name("output") + /// .takes_value(true)) + /// .get_matches_from(vec!["myapp", "something"]); + /// + /// assert_eq!(m.value_of("output"), Some("something")); + /// ``` + /// [option]: ./struct.Arg.html#method.takes_value + /// [positional]: ./struct.Arg.html#method.index + /// [`ArgMatches::values_of`]: ./struct.ArgMatches.html#method.values_of + /// [`panic!`]: https://doc.rust-lang.org/std/macro.panic!.html + pub fn value_of>(&self, name: S) -> Option<&str> { + if let Some(arg) = self.args.get(name.as_ref()) { + if let Some(v) = arg.vals.get(0) { + return Some(v.to_str().expect(INVALID_UTF8)); + } + } + None + } + + /// Gets the lossy value of a specific argument. If the argument wasn't present at runtime + /// it returns `None`. A lossy value is one which contains invalid UTF-8 code points, those + /// invalid points will be replaced with `\u{FFFD}` + /// + /// *NOTE:* If getting a value for an option or positional argument that allows multiples, + /// prefer [`Arg::values_of_lossy`] as `value_of_lossy()` will only return the *first* value. + /// + /// # Examples + /// + #[cfg_attr(not(unix), doc = " ```ignore")] + #[cfg_attr(unix, doc = " ```")] + /// # use clap::{App, Arg}; + /// use std::ffi::OsString; + /// use std::os::unix::ffi::{OsStrExt,OsStringExt}; + /// + /// let m = App::new("utf8") + /// .arg(Arg::from_usage(" 'some arg'")) + /// .get_matches_from(vec![OsString::from("myprog"), + /// // "Hi {0xe9}!" + /// OsString::from_vec(vec![b'H', b'i', b' ', 0xe9, b'!'])]); + /// assert_eq!(&*m.value_of_lossy("arg").unwrap(), "Hi \u{FFFD}!"); + /// ``` + /// [`Arg::values_of_lossy`]: ./struct.ArgMatches.html#method.values_of_lossy + pub fn value_of_lossy>(&'a self, name: S) -> Option> { + if let Some(arg) = self.args.get(name.as_ref()) { + if let Some(v) = arg.vals.get(0) { + return Some(v.to_string_lossy()); + } + } + None + } + + /// Gets the OS version of a string value of a specific argument. If the option wasn't present + /// at runtime it returns `None`. An OS value on Unix-like systems is any series of bytes, + /// regardless of whether or not they contain valid UTF-8 code points. Since [`String`]s in + /// Rust are guaranteed to be valid UTF-8, a valid filename on a Unix system as an argument + /// value may contain invalid UTF-8 code points. + /// + /// *NOTE:* If getting a value for an option or positional argument that allows multiples, + /// prefer [`ArgMatches::values_of_os`] as `Arg::value_of_os` will only return the *first* + /// value. + /// + /// # Examples + /// + #[cfg_attr(not(unix), doc = " ```ignore")] + #[cfg_attr(unix, doc = " ```")] + /// # use clap::{App, Arg}; + /// use std::ffi::OsString; + /// use std::os::unix::ffi::{OsStrExt,OsStringExt}; + /// + /// let m = App::new("utf8") + /// .arg(Arg::from_usage(" 'some arg'")) + /// .get_matches_from(vec![OsString::from("myprog"), + /// // "Hi {0xe9}!" + /// OsString::from_vec(vec![b'H', b'i', b' ', 0xe9, b'!'])]); + /// assert_eq!(&*m.value_of_os("arg").unwrap().as_bytes(), [b'H', b'i', b' ', 0xe9, b'!']); + /// ``` + /// [`String`]: https://doc.rust-lang.org/std/string/struct.String.html + /// [`ArgMatches::values_of_os`]: ./struct.ArgMatches.html#method.values_of_os + pub fn value_of_os>(&self, name: S) -> Option<&OsStr> { + self.args + .get(name.as_ref()) + .and_then(|arg| arg.vals.get(0).map(|v| v.as_os_str())) + } + + /// Gets a [`Values`] struct which implements [`Iterator`] for values of a specific argument + /// (i.e. an argument that takes multiple values at runtime). If the option wasn't present at + /// runtime it returns `None` + /// + /// # Panics + /// + /// This method will panic if any of the values contain invalid UTF-8 code points. + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("myprog") + /// .arg(Arg::with_name("output") + /// .multiple(true) + /// .short("o") + /// .takes_value(true)) + /// .get_matches_from(vec![ + /// "myprog", "-o", "val1", "val2", "val3" + /// ]); + /// let vals: Vec<&str> = m.values_of("output").unwrap().collect(); + /// assert_eq!(vals, ["val1", "val2", "val3"]); + /// ``` + /// [`Values`]: ./struct.Values.html + /// [`Iterator`]: https://doc.rust-lang.org/std/iter/trait.Iterator.html + pub fn values_of>(&'a self, name: S) -> Option> { + if let Some(arg) = self.args.get(name.as_ref()) { + fn to_str_slice(o: &OsString) -> &str { o.to_str().expect(INVALID_UTF8) } + let to_str_slice: fn(&OsString) -> &str = to_str_slice; // coerce to fn pointer + return Some(Values { + iter: arg.vals.iter().map(to_str_slice), + }); + } + None + } + + /// Gets the lossy values of a specific argument. If the option wasn't present at runtime + /// it returns `None`. A lossy value is one where if it contains invalid UTF-8 code points, + /// those invalid points will be replaced with `\u{FFFD}` + /// + /// # Examples + /// + #[cfg_attr(not(unix), doc = " ```ignore")] + #[cfg_attr(unix, doc = " ```")] + /// # use clap::{App, Arg}; + /// use std::ffi::OsString; + /// use std::os::unix::ffi::OsStringExt; + /// + /// let m = App::new("utf8") + /// .arg(Arg::from_usage("... 'some arg'")) + /// .get_matches_from(vec![OsString::from("myprog"), + /// // "Hi" + /// OsString::from_vec(vec![b'H', b'i']), + /// // "{0xe9}!" + /// OsString::from_vec(vec![0xe9, b'!'])]); + /// let mut itr = m.values_of_lossy("arg").unwrap().into_iter(); + /// assert_eq!(&itr.next().unwrap()[..], "Hi"); + /// assert_eq!(&itr.next().unwrap()[..], "\u{FFFD}!"); + /// assert_eq!(itr.next(), None); + /// ``` + pub fn values_of_lossy>(&'a self, name: S) -> Option> { + if let Some(arg) = self.args.get(name.as_ref()) { + return Some( + arg.vals + .iter() + .map(|v| v.to_string_lossy().into_owned()) + .collect(), + ); + } + None + } + + /// Gets a [`OsValues`] struct which is implements [`Iterator`] for [`OsString`] values of a + /// specific argument. If the option wasn't present at runtime it returns `None`. An OS value + /// on Unix-like systems is any series of bytes, regardless of whether or not they contain + /// valid UTF-8 code points. Since [`String`]s in Rust are guaranteed to be valid UTF-8, a valid + /// filename as an argument value on Linux (for example) may contain invalid UTF-8 code points. + /// + /// # Examples + /// + #[cfg_attr(not(unix), doc = " ```ignore")] + #[cfg_attr(unix, doc = " ```")] + /// # use clap::{App, Arg}; + /// use std::ffi::{OsStr,OsString}; + /// use std::os::unix::ffi::{OsStrExt,OsStringExt}; + /// + /// let m = App::new("utf8") + /// .arg(Arg::from_usage("... 'some arg'")) + /// .get_matches_from(vec![OsString::from("myprog"), + /// // "Hi" + /// OsString::from_vec(vec![b'H', b'i']), + /// // "{0xe9}!" + /// OsString::from_vec(vec![0xe9, b'!'])]); + /// + /// let mut itr = m.values_of_os("arg").unwrap().into_iter(); + /// assert_eq!(itr.next(), Some(OsStr::new("Hi"))); + /// assert_eq!(itr.next(), Some(OsStr::from_bytes(&[0xe9, b'!']))); + /// assert_eq!(itr.next(), None); + /// ``` + /// [`OsValues`]: ./struct.OsValues.html + /// [`Iterator`]: https://doc.rust-lang.org/std/iter/trait.Iterator.html + /// [`OsString`]: https://doc.rust-lang.org/std/ffi/struct.OsString.html + /// [`String`]: https://doc.rust-lang.org/std/string/struct.String.html + pub fn values_of_os>(&'a self, name: S) -> Option> { + fn to_str_slice(o: &OsString) -> &OsStr { &*o } + let to_str_slice: fn(&'a OsString) -> &'a OsStr = to_str_slice; // coerce to fn pointer + if let Some(arg) = self.args.get(name.as_ref()) { + return Some(OsValues { + iter: arg.vals.iter().map(to_str_slice), + }); + } + None + } + + /// Returns `true` if an argument was present at runtime, otherwise `false`. + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("myprog") + /// .arg(Arg::with_name("debug") + /// .short("d")) + /// .get_matches_from(vec![ + /// "myprog", "-d" + /// ]); + /// + /// assert!(m.is_present("debug")); + /// ``` + pub fn is_present>(&self, name: S) -> bool { + if let Some(ref sc) = self.subcommand { + if sc.name == name.as_ref() { + return true; + } + } + self.args.contains_key(name.as_ref()) + } + + /// Returns the number of times an argument was used at runtime. If an argument isn't present + /// it will return `0`. + /// + /// **NOTE:** This returns the number of times the argument was used, *not* the number of + /// values. For example, `-o val1 val2 val3 -o val4` would return `2` (2 occurrences, but 4 + /// values). + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("myprog") + /// .arg(Arg::with_name("debug") + /// .short("d") + /// .multiple(true)) + /// .get_matches_from(vec![ + /// "myprog", "-d", "-d", "-d" + /// ]); + /// + /// assert_eq!(m.occurrences_of("debug"), 3); + /// ``` + /// + /// This next example shows that counts actual uses of the argument, not just `-`'s + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("myprog") + /// .arg(Arg::with_name("debug") + /// .short("d") + /// .multiple(true)) + /// .arg(Arg::with_name("flag") + /// .short("f")) + /// .get_matches_from(vec![ + /// "myprog", "-ddfd" + /// ]); + /// + /// assert_eq!(m.occurrences_of("debug"), 3); + /// assert_eq!(m.occurrences_of("flag"), 1); + /// ``` + pub fn occurrences_of>(&self, name: S) -> u64 { + self.args.get(name.as_ref()).map_or(0, |a| a.occurs) + } + + /// Gets the starting index of the argument in respect to all other arguments. Indices are + /// similar to argv indices, but are not exactly 1:1. + /// + /// For flags (i.e. those arguments which don't have an associated value), indices refer + /// to occurrence of the switch, such as `-f`, or `--flag`. However, for options the indices + /// refer to the *values* `-o val` would therefore not represent two distinct indices, only the + /// index for `val` would be recorded. This is by design. + /// + /// Besides the flag/option descrepancy, the primary difference between an argv index and clap + /// index, is that clap continues counting once all arguments have properly seperated, whereas + /// an argv index does not. + /// + /// The examples should clear this up. + /// + /// *NOTE:* If an argument is allowed multiple times, this method will only give the *first* + /// index. + /// + /// # Examples + /// + /// The argv indices are listed in the comments below. See how they correspond to the clap + /// indices. Note that if it's not listed in a clap index, this is becuase it's not saved in + /// in an `ArgMatches` struct for querying. + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("myapp") + /// .arg(Arg::with_name("flag") + /// .short("f")) + /// .arg(Arg::with_name("option") + /// .short("o") + /// .takes_value(true)) + /// .get_matches_from(vec!["myapp", "-f", "-o", "val"]); + /// // ARGV idices: ^0 ^1 ^2 ^3 + /// // clap idices: ^1 ^3 + /// + /// assert_eq!(m.index_of("flag"), Some(1)); + /// assert_eq!(m.index_of("option"), Some(3)); + /// ``` + /// + /// Now notice, if we use one of the other styles of options: + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("myapp") + /// .arg(Arg::with_name("flag") + /// .short("f")) + /// .arg(Arg::with_name("option") + /// .short("o") + /// .takes_value(true)) + /// .get_matches_from(vec!["myapp", "-f", "-o=val"]); + /// // ARGV idices: ^0 ^1 ^2 + /// // clap idices: ^1 ^3 + /// + /// assert_eq!(m.index_of("flag"), Some(1)); + /// assert_eq!(m.index_of("option"), Some(3)); + /// ``` + /// + /// Things become much more complicated, or clear if we look at a more complex combination of + /// flags. Let's also throw in the final option style for good measure. + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("myapp") + /// .arg(Arg::with_name("flag") + /// .short("f")) + /// .arg(Arg::with_name("flag2") + /// .short("F")) + /// .arg(Arg::with_name("flag3") + /// .short("z")) + /// .arg(Arg::with_name("option") + /// .short("o") + /// .takes_value(true)) + /// .get_matches_from(vec!["myapp", "-fzF", "-oval"]); + /// // ARGV idices: ^0 ^1 ^2 + /// // clap idices: ^1,2,3 ^5 + /// // + /// // clap sees the above as 'myapp -f -z -F -o val' + /// // ^0 ^1 ^2 ^3 ^4 ^5 + /// assert_eq!(m.index_of("flag"), Some(1)); + /// assert_eq!(m.index_of("flag2"), Some(3)); + /// assert_eq!(m.index_of("flag3"), Some(2)); + /// assert_eq!(m.index_of("option"), Some(5)); + /// ``` + /// + /// One final combination of flags/options to see how they combine: + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("myapp") + /// .arg(Arg::with_name("flag") + /// .short("f")) + /// .arg(Arg::with_name("flag2") + /// .short("F")) + /// .arg(Arg::with_name("flag3") + /// .short("z")) + /// .arg(Arg::with_name("option") + /// .short("o") + /// .takes_value(true) + /// .multiple(true)) + /// .get_matches_from(vec!["myapp", "-fzFoval"]); + /// // ARGV idices: ^0 ^1 + /// // clap idices: ^1,2,3^5 + /// // + /// // clap sees the above as 'myapp -f -z -F -o val' + /// // ^0 ^1 ^2 ^3 ^4 ^5 + /// assert_eq!(m.index_of("flag"), Some(1)); + /// assert_eq!(m.index_of("flag2"), Some(3)); + /// assert_eq!(m.index_of("flag3"), Some(2)); + /// assert_eq!(m.index_of("option"), Some(5)); + /// ``` + /// + /// The last part to mention is when values are sent in multiple groups with a [delimiter]. + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("myapp") + /// .arg(Arg::with_name("option") + /// .short("o") + /// .takes_value(true) + /// .multiple(true)) + /// .get_matches_from(vec!["myapp", "-o=val1,val2,val3"]); + /// // ARGV idices: ^0 ^1 + /// // clap idices: ^2 ^3 ^4 + /// // + /// // clap sees the above as 'myapp -o val1 val2 val3' + /// // ^0 ^1 ^2 ^3 ^4 + /// assert_eq!(m.index_of("option"), Some(2)); + /// ``` + /// [`ArgMatches`]: ./struct.ArgMatches.html + /// [delimiter]: ./struct.Arg.html#method.value_delimiter + pub fn index_of>(&self, name: S) -> Option { + if let Some(arg) = self.args.get(name.as_ref()) { + if let Some(i) = arg.indices.get(0) { + return Some(*i); + } + } + None + } + + /// Gets all indices of the argument in respect to all other arguments. Indices are + /// similar to argv indices, but are not exactly 1:1. + /// + /// For flags (i.e. those arguments which don't have an associated value), indices refer + /// to occurrence of the switch, such as `-f`, or `--flag`. However, for options the indices + /// refer to the *values* `-o val` would therefore not represent two distinct indices, only the + /// index for `val` would be recorded. This is by design. + /// + /// *NOTE:* For more information about how clap indices compare to argv indices, see + /// [`ArgMatches::index_of`] + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("myapp") + /// .arg(Arg::with_name("option") + /// .short("o") + /// .takes_value(true) + /// .use_delimiter(true) + /// .multiple(true)) + /// .get_matches_from(vec!["myapp", "-o=val1,val2,val3"]); + /// // ARGV idices: ^0 ^1 + /// // clap idices: ^2 ^3 ^4 + /// // + /// // clap sees the above as 'myapp -o val1 val2 val3' + /// // ^0 ^1 ^2 ^3 ^4 + /// assert_eq!(m.indices_of("option").unwrap().collect::>(), &[2, 3, 4]); + /// ``` + /// + /// Another quick example is when flags and options are used together + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("myapp") + /// .arg(Arg::with_name("option") + /// .short("o") + /// .takes_value(true) + /// .multiple(true)) + /// .arg(Arg::with_name("flag") + /// .short("f") + /// .multiple(true)) + /// .get_matches_from(vec!["myapp", "-o", "val1", "-f", "-o", "val2", "-f"]); + /// // ARGV idices: ^0 ^1 ^2 ^3 ^4 ^5 ^6 + /// // clap idices: ^2 ^3 ^5 ^6 + /// + /// assert_eq!(m.indices_of("option").unwrap().collect::>(), &[2, 5]); + /// assert_eq!(m.indices_of("flag").unwrap().collect::>(), &[3, 6]); + /// ``` + /// + /// One final example, which is an odd case; if we *don't* use value delimiter as we did with + /// the first example above instead of `val1`, `val2` and `val3` all being distinc values, they + /// would all be a single value of `val1,val2,val3`, in which case case they'd only receive a + /// single index. + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("myapp") + /// .arg(Arg::with_name("option") + /// .short("o") + /// .takes_value(true) + /// .multiple(true)) + /// .get_matches_from(vec!["myapp", "-o=val1,val2,val3"]); + /// // ARGV idices: ^0 ^1 + /// // clap idices: ^2 + /// // + /// // clap sees the above as 'myapp -o "val1,val2,val3"' + /// // ^0 ^1 ^2 + /// assert_eq!(m.indices_of("option").unwrap().collect::>(), &[2]); + /// ``` + /// [`ArgMatches`]: ./struct.ArgMatches.html + /// [`ArgMatches::index_of`]: ./struct.ArgMatches.html#method.index_of + /// [delimiter]: ./struct.Arg.html#method.value_delimiter + pub fn indices_of>(&'a self, name: S) -> Option> { + if let Some(arg) = self.args.get(name.as_ref()) { + fn to_usize(i: &usize) -> usize { *i } + let to_usize: fn(&usize) -> usize = to_usize; // coerce to fn pointer + return Some(Indices { + iter: arg.indices.iter().map(to_usize), + }); + } + None + } + + /// Because [`Subcommand`]s are essentially "sub-[`App`]s" they have their own [`ArgMatches`] + /// as well. This method returns the [`ArgMatches`] for a particular subcommand or `None` if + /// the subcommand wasn't present at runtime. + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg, SubCommand}; + /// let app_m = App::new("myprog") + /// .arg(Arg::with_name("debug") + /// .short("d")) + /// .subcommand(SubCommand::with_name("test") + /// .arg(Arg::with_name("opt") + /// .long("option") + /// .takes_value(true))) + /// .get_matches_from(vec![ + /// "myprog", "-d", "test", "--option", "val" + /// ]); + /// + /// // Both parent commands, and child subcommands can have arguments present at the same times + /// assert!(app_m.is_present("debug")); + /// + /// // Get the subcommand's ArgMatches instance + /// if let Some(sub_m) = app_m.subcommand_matches("test") { + /// // Use the struct like normal + /// assert_eq!(sub_m.value_of("opt"), Some("val")); + /// } + /// ``` + /// [`Subcommand`]: ./struct.SubCommand.html + /// [`App`]: ./struct.App.html + /// [`ArgMatches`]: ./struct.ArgMatches.html + pub fn subcommand_matches>(&self, name: S) -> Option<&ArgMatches<'a>> { + if let Some(ref s) = self.subcommand { + if s.name == name.as_ref() { + return Some(&s.matches); + } + } + None + } + + /// Because [`Subcommand`]s are essentially "sub-[`App`]s" they have their own [`ArgMatches`] + /// as well.But simply getting the sub-[`ArgMatches`] doesn't help much if we don't also know + /// which subcommand was actually used. This method returns the name of the subcommand that was + /// used at runtime, or `None` if one wasn't. + /// + /// *NOTE*: Subcommands form a hierarchy, where multiple subcommands can be used at runtime, + /// but only a single subcommand from any group of sibling commands may used at once. + /// + /// An ASCII art depiction may help explain this better...Using a fictional version of `git` as + /// the demo subject. Imagine the following are all subcommands of `git` (note, the author is + /// aware these aren't actually all subcommands in the real `git` interface, but it makes + /// explanation easier) + /// + /// ```notrust + /// Top Level App (git) TOP + /// | + /// ----------------------------------------- + /// / | \ \ + /// clone push add commit LEVEL 1 + /// | / \ / \ | + /// url origin remote ref name message LEVEL 2 + /// / /\ + /// path remote local LEVEL 3 + /// ``` + /// + /// Given the above fictional subcommand hierarchy, valid runtime uses would be (not an all + /// inclusive list, and not including argument options per command for brevity and clarity): + /// + /// ```sh + /// $ git clone url + /// $ git push origin path + /// $ git add ref local + /// $ git commit message + /// ``` + /// + /// Notice only one command per "level" may be used. You could not, for example, do `$ git + /// clone url push origin path` + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, Arg, SubCommand}; + /// let app_m = App::new("git") + /// .subcommand(SubCommand::with_name("clone")) + /// .subcommand(SubCommand::with_name("push")) + /// .subcommand(SubCommand::with_name("commit")) + /// .get_matches(); + /// + /// match app_m.subcommand_name() { + /// Some("clone") => {}, // clone was used + /// Some("push") => {}, // push was used + /// Some("commit") => {}, // commit was used + /// _ => {}, // Either no subcommand or one not tested for... + /// } + /// ``` + /// [`Subcommand`]: ./struct.SubCommand.html + /// [`App`]: ./struct.App.html + /// [`ArgMatches`]: ./struct.ArgMatches.html + pub fn subcommand_name(&self) -> Option<&str> { + self.subcommand.as_ref().map(|sc| &sc.name[..]) + } + + /// This brings together [`ArgMatches::subcommand_matches`] and [`ArgMatches::subcommand_name`] + /// by returning a tuple with both pieces of information. + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, Arg, SubCommand}; + /// let app_m = App::new("git") + /// .subcommand(SubCommand::with_name("clone")) + /// .subcommand(SubCommand::with_name("push")) + /// .subcommand(SubCommand::with_name("commit")) + /// .get_matches(); + /// + /// match app_m.subcommand() { + /// ("clone", Some(sub_m)) => {}, // clone was used + /// ("push", Some(sub_m)) => {}, // push was used + /// ("commit", Some(sub_m)) => {}, // commit was used + /// _ => {}, // Either no subcommand or one not tested for... + /// } + /// ``` + /// + /// Another useful scenario is when you want to support third party, or external, subcommands. + /// In these cases you can't know the subcommand name ahead of time, so use a variable instead + /// with pattern matching! + /// + /// ```rust + /// # use clap::{App, AppSettings}; + /// // Assume there is an external subcommand named "subcmd" + /// let app_m = App::new("myprog") + /// .setting(AppSettings::AllowExternalSubcommands) + /// .get_matches_from(vec![ + /// "myprog", "subcmd", "--option", "value", "-fff", "--flag" + /// ]); + /// + /// // All trailing arguments will be stored under the subcommand's sub-matches using an empty + /// // string argument name + /// match app_m.subcommand() { + /// (external, Some(sub_m)) => { + /// let ext_args: Vec<&str> = sub_m.values_of("").unwrap().collect(); + /// assert_eq!(external, "subcmd"); + /// assert_eq!(ext_args, ["--option", "value", "-fff", "--flag"]); + /// }, + /// _ => {}, + /// } + /// ``` + /// [`ArgMatches::subcommand_matches`]: ./struct.ArgMatches.html#method.subcommand_matches + /// [`ArgMatches::subcommand_name`]: ./struct.ArgMatches.html#method.subcommand_name + pub fn subcommand(&self) -> (&str, Option<&ArgMatches<'a>>) { + self.subcommand + .as_ref() + .map_or(("", None), |sc| (&sc.name[..], Some(&sc.matches))) + } + + /// Returns a string slice of the usage statement for the [`App`] or [`SubCommand`] + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, Arg, SubCommand}; + /// let app_m = App::new("myprog") + /// .subcommand(SubCommand::with_name("test")) + /// .get_matches(); + /// + /// println!("{}", app_m.usage()); + /// ``` + /// [`Subcommand`]: ./struct.SubCommand.html + /// [`App`]: ./struct.App.html + pub fn usage(&self) -> &str { self.usage.as_ref().map_or("", |u| &u[..]) } +} + + +// The following were taken and adapated from vec_map source +// repo: https://github.com/contain-rs/vec-map +// commit: be5e1fa3c26e351761b33010ddbdaf5f05dbcc33 +// license: MIT - Copyright (c) 2015 The Rust Project Developers + +/// An iterator for getting multiple values out of an argument via the [`ArgMatches::values_of`] +/// method. +/// +/// # Examples +/// +/// ```rust +/// # use clap::{App, Arg}; +/// let m = App::new("myapp") +/// .arg(Arg::with_name("output") +/// .short("o") +/// .multiple(true) +/// .takes_value(true)) +/// .get_matches_from(vec!["myapp", "-o", "val1", "val2"]); +/// +/// let mut values = m.values_of("output").unwrap(); +/// +/// assert_eq!(values.next(), Some("val1")); +/// assert_eq!(values.next(), Some("val2")); +/// assert_eq!(values.next(), None); +/// ``` +/// [`ArgMatches::values_of`]: ./struct.ArgMatches.html#method.values_of +#[derive(Debug, Clone)] +pub struct Values<'a> { + iter: Map, fn(&'a OsString) -> &'a str>, +} + +impl<'a> Iterator for Values<'a> { + type Item = &'a str; + + fn next(&mut self) -> Option<&'a str> { self.iter.next() } + fn size_hint(&self) -> (usize, Option) { self.iter.size_hint() } +} + +impl<'a> DoubleEndedIterator for Values<'a> { + fn next_back(&mut self) -> Option<&'a str> { self.iter.next_back() } +} + +impl<'a> ExactSizeIterator for Values<'a> {} + +/// Creates an empty iterator. +impl<'a> Default for Values<'a> { + fn default() -> Self { + static EMPTY: [OsString; 0] = []; + // This is never called because the iterator is empty: + fn to_str_slice(_: &OsString) -> &str { unreachable!() }; + Values { + iter: EMPTY[..].iter().map(to_str_slice), + } + } +} + +/// An iterator for getting multiple values out of an argument via the [`ArgMatches::values_of_os`] +/// method. Usage of this iterator allows values which contain invalid UTF-8 code points unlike +/// [`Values`]. +/// +/// # Examples +/// +#[cfg_attr(not(unix), doc = " ```ignore")] +#[cfg_attr(unix, doc = " ```")] +/// # use clap::{App, Arg}; +/// use std::ffi::OsString; +/// use std::os::unix::ffi::{OsStrExt,OsStringExt}; +/// +/// let m = App::new("utf8") +/// .arg(Arg::from_usage(" 'some arg'")) +/// .get_matches_from(vec![OsString::from("myprog"), +/// // "Hi {0xe9}!" +/// OsString::from_vec(vec![b'H', b'i', b' ', 0xe9, b'!'])]); +/// assert_eq!(&*m.value_of_os("arg").unwrap().as_bytes(), [b'H', b'i', b' ', 0xe9, b'!']); +/// ``` +/// [`ArgMatches::values_of_os`]: ./struct.ArgMatches.html#method.values_of_os +/// [`Values`]: ./struct.Values.html +#[derive(Debug, Clone)] +pub struct OsValues<'a> { + iter: Map, fn(&'a OsString) -> &'a OsStr>, +} + +impl<'a> Iterator for OsValues<'a> { + type Item = &'a OsStr; + + fn next(&mut self) -> Option<&'a OsStr> { self.iter.next() } + fn size_hint(&self) -> (usize, Option) { self.iter.size_hint() } +} + +impl<'a> DoubleEndedIterator for OsValues<'a> { + fn next_back(&mut self) -> Option<&'a OsStr> { self.iter.next_back() } +} + +impl<'a> ExactSizeIterator for OsValues<'a> {} + +/// Creates an empty iterator. +impl<'a> Default for OsValues<'a> { + fn default() -> Self { + static EMPTY: [OsString; 0] = []; + // This is never called because the iterator is empty: + fn to_str_slice(_: &OsString) -> &OsStr { unreachable!() }; + OsValues { + iter: EMPTY[..].iter().map(to_str_slice), + } + } +} + +/// An iterator for getting multiple indices out of an argument via the [`ArgMatches::indices_of`] +/// method. +/// +/// # Examples +/// +/// ```rust +/// # use clap::{App, Arg}; +/// let m = App::new("myapp") +/// .arg(Arg::with_name("output") +/// .short("o") +/// .multiple(true) +/// .takes_value(true)) +/// .get_matches_from(vec!["myapp", "-o", "val1", "val2"]); +/// +/// let mut indices = m.indices_of("output").unwrap(); +/// +/// assert_eq!(indices.next(), Some(2)); +/// assert_eq!(indices.next(), Some(3)); +/// assert_eq!(indices.next(), None); +/// ``` +/// [`ArgMatches::indices_of`]: ./struct.ArgMatches.html#method.indices_of +#[derive(Debug, Clone)] +pub struct Indices<'a> { // would rather use '_, but: https://github.com/rust-lang/rust/issues/48469 + iter: Map, fn(&'a usize) -> usize>, +} + +impl<'a> Iterator for Indices<'a> { + type Item = usize; + + fn next(&mut self) -> Option { self.iter.next() } + fn size_hint(&self) -> (usize, Option) { self.iter.size_hint() } +} + +impl<'a> DoubleEndedIterator for Indices<'a> { + fn next_back(&mut self) -> Option { self.iter.next_back() } +} + +impl<'a> ExactSizeIterator for Indices<'a> {} + +/// Creates an empty iterator. +impl<'a> Default for Indices<'a> { + fn default() -> Self { + static EMPTY: [usize; 0] = []; + // This is never called because the iterator is empty: + fn to_usize(_: &usize) -> usize { unreachable!() }; + Indices { + iter: EMPTY[..].iter().map(to_usize), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_default_values() { + let mut values: Values = Values::default(); + assert_eq!(values.next(), None); + } + + #[test] + fn test_default_values_with_shorter_lifetime() { + let matches = ArgMatches::new(); + let mut values = matches.values_of("").unwrap_or_default(); + assert_eq!(values.next(), None); + } + + #[test] + fn test_default_osvalues() { + let mut values: OsValues = OsValues::default(); + assert_eq!(values.next(), None); + } + + #[test] + fn test_default_osvalues_with_shorter_lifetime() { + let matches = ArgMatches::new(); + let mut values = matches.values_of_os("").unwrap_or_default(); + assert_eq!(values.next(), None); + } + + #[test] + fn test_default_indices() { + let mut indices: Indices = Indices::default(); + assert_eq!(indices.next(), None); + } + + #[test] + fn test_default_indices_with_shorter_lifetime() { + let matches = ArgMatches::new(); + let mut indices = matches.indices_of("").unwrap_or_default(); + assert_eq!(indices.next(), None); + } +} diff --git a/clap/src/args/group.rs b/clap/src/args/group.rs new file mode 100644 index 0000000..f8bfb7a --- /dev/null +++ b/clap/src/args/group.rs @@ -0,0 +1,635 @@ +#[cfg(feature = "yaml")] +use std::collections::BTreeMap; +use std::fmt::{Debug, Formatter, Result}; + +#[cfg(feature = "yaml")] +use yaml_rust::Yaml; + +/// `ArgGroup`s are a family of related [arguments] and way for you to express, "Any of these +/// arguments". By placing arguments in a logical group, you can create easier requirement and +/// exclusion rules instead of having to list each argument individually, or when you want a rule +/// to apply "any but not all" arguments. +/// +/// For instance, you can make an entire `ArgGroup` required. If [`ArgGroup::multiple(true)`] is +/// set, this means that at least one argument from that group must be present. If +/// [`ArgGroup::multiple(false)`] is set (the default), one and *only* one must be present. +/// +/// You can also do things such as name an entire `ArgGroup` as a [conflict] or [requirement] for +/// another argument, meaning any of the arguments that belong to that group will cause a failure +/// if present, or must present respectively. +/// +/// Perhaps the most common use of `ArgGroup`s is to require one and *only* one argument to be +/// present out of a given set. Imagine that you had multiple arguments, and you want one of them +/// to be required, but making all of them required isn't feasible because perhaps they conflict +/// with each other. For example, lets say that you were building an application where one could +/// set a given version number by supplying a string with an option argument, i.e. +/// `--set-ver v1.2.3`, you also wanted to support automatically using a previous version number +/// and simply incrementing one of the three numbers. So you create three flags `--major`, +/// `--minor`, and `--patch`. All of these arguments shouldn't be used at one time but you want to +/// specify that *at least one* of them is used. For this, you can create a group. +/// +/// Finally, you may use `ArgGroup`s to pull a value from a group of arguments when you don't care +/// exactly which argument was actually used at runtime. +/// +/// # Examples +/// +/// The following example demonstrates using an `ArgGroup` to ensure that one, and only one, of +/// the arguments from the specified group is present at runtime. +/// +/// ```rust +/// # use clap::{App, ArgGroup, ErrorKind}; +/// let result = App::new("app") +/// .args_from_usage( +/// "--set-ver [ver] 'set the version manually' +/// --major 'auto increase major' +/// --minor 'auto increase minor' +/// --patch 'auto increase patch'") +/// .group(ArgGroup::with_name("vers") +/// .args(&["set-ver", "major", "minor", "patch"]) +/// .required(true)) +/// .get_matches_from_safe(vec!["app", "--major", "--patch"]); +/// // Because we used two args in the group it's an error +/// assert!(result.is_err()); +/// let err = result.unwrap_err(); +/// assert_eq!(err.kind, ErrorKind::ArgumentConflict); +/// ``` +/// This next example shows a passing parse of the same scenario +/// +/// ```rust +/// # use clap::{App, ArgGroup}; +/// let result = App::new("app") +/// .args_from_usage( +/// "--set-ver [ver] 'set the version manually' +/// --major 'auto increase major' +/// --minor 'auto increase minor' +/// --patch 'auto increase patch'") +/// .group(ArgGroup::with_name("vers") +/// .args(&["set-ver", "major", "minor","patch"]) +/// .required(true)) +/// .get_matches_from_safe(vec!["app", "--major"]); +/// assert!(result.is_ok()); +/// let matches = result.unwrap(); +/// // We may not know which of the args was used, so we can test for the group... +/// assert!(matches.is_present("vers")); +/// // we could also alternatively check each arg individually (not shown here) +/// ``` +/// [`ArgGroup::multiple(true)`]: ./struct.ArgGroup.html#method.multiple +/// [arguments]: ./struct.Arg.html +/// [conflict]: ./struct.Arg.html#method.conflicts_with +/// [requirement]: ./struct.Arg.html#method.requires +#[derive(Default)] +pub struct ArgGroup<'a> { + #[doc(hidden)] pub name: &'a str, + #[doc(hidden)] pub args: Vec<&'a str>, + #[doc(hidden)] pub required: bool, + #[doc(hidden)] pub requires: Option>, + #[doc(hidden)] pub conflicts: Option>, + #[doc(hidden)] pub multiple: bool, +} + +impl<'a> ArgGroup<'a> { + /// Creates a new instance of `ArgGroup` using a unique string name. The name will be used to + /// get values from the group or refer to the group inside of conflict and requirement rules. + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, ArgGroup}; + /// ArgGroup::with_name("config") + /// # ; + /// ``` + pub fn with_name(n: &'a str) -> Self { + ArgGroup { + name: n, + required: false, + args: vec![], + requires: None, + conflicts: None, + multiple: false, + } + } + + /// Creates a new instance of `ArgGroup` from a .yml (YAML) file. + /// + /// # Examples + /// + /// ```ignore + /// # #[macro_use] + /// # extern crate clap; + /// # use clap::ArgGroup; + /// # fn main() { + /// let yml = load_yaml!("group.yml"); + /// let ag = ArgGroup::from_yaml(yml); + /// # } + /// ``` + #[cfg(feature = "yaml")] + pub fn from_yaml(y: &'a Yaml) -> ArgGroup<'a> { ArgGroup::from(y.as_hash().unwrap()) } + + /// Adds an [argument] to this group by name + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg, ArgGroup}; + /// let m = App::new("myprog") + /// .arg(Arg::with_name("flag") + /// .short("f")) + /// .arg(Arg::with_name("color") + /// .short("c")) + /// .group(ArgGroup::with_name("req_flags") + /// .arg("flag") + /// .arg("color")) + /// .get_matches_from(vec!["myprog", "-f"]); + /// // maybe we don't know which of the two flags was used... + /// assert!(m.is_present("req_flags")); + /// // but we can also check individually if needed + /// assert!(m.is_present("flag")); + /// ``` + /// [argument]: ./struct.Arg.html + #[cfg_attr(feature = "lints", allow(should_assert_eq))] + pub fn arg(mut self, n: &'a str) -> Self { + assert!( + self.name != n, + "ArgGroup '{}' can not have same name as arg inside it", + &*self.name + ); + self.args.push(n); + self + } + + /// Adds multiple [arguments] to this group by name + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg, ArgGroup}; + /// let m = App::new("myprog") + /// .arg(Arg::with_name("flag") + /// .short("f")) + /// .arg(Arg::with_name("color") + /// .short("c")) + /// .group(ArgGroup::with_name("req_flags") + /// .args(&["flag", "color"])) + /// .get_matches_from(vec!["myprog", "-f"]); + /// // maybe we don't know which of the two flags was used... + /// assert!(m.is_present("req_flags")); + /// // but we can also check individually if needed + /// assert!(m.is_present("flag")); + /// ``` + /// [arguments]: ./struct.Arg.html + pub fn args(mut self, ns: &[&'a str]) -> Self { + for n in ns { + self = self.arg(n); + } + self + } + + /// Allows more than one of the ['Arg']s in this group to be used. (Default: `false`) + /// + /// # Examples + /// + /// Notice in this example we use *both* the `-f` and `-c` flags which are both part of the + /// group + /// + /// ```rust + /// # use clap::{App, Arg, ArgGroup}; + /// let m = App::new("myprog") + /// .arg(Arg::with_name("flag") + /// .short("f")) + /// .arg(Arg::with_name("color") + /// .short("c")) + /// .group(ArgGroup::with_name("req_flags") + /// .args(&["flag", "color"]) + /// .multiple(true)) + /// .get_matches_from(vec!["myprog", "-f", "-c"]); + /// // maybe we don't know which of the two flags was used... + /// assert!(m.is_present("req_flags")); + /// ``` + /// In this next example, we show the default behavior (i.e. `multiple(false)) which will throw + /// an error if more than one of the args in the group was used. + /// + /// ```rust + /// # use clap::{App, Arg, ArgGroup, ErrorKind}; + /// let result = App::new("myprog") + /// .arg(Arg::with_name("flag") + /// .short("f")) + /// .arg(Arg::with_name("color") + /// .short("c")) + /// .group(ArgGroup::with_name("req_flags") + /// .args(&["flag", "color"])) + /// .get_matches_from_safe(vec!["myprog", "-f", "-c"]); + /// // Because we used both args in the group it's an error + /// assert!(result.is_err()); + /// let err = result.unwrap_err(); + /// assert_eq!(err.kind, ErrorKind::ArgumentConflict); + /// ``` + /// ['Arg']: ./struct.Arg.html + pub fn multiple(mut self, m: bool) -> Self { + self.multiple = m; + self + } + + /// Sets the group as required or not. A required group will be displayed in the usage string + /// of the application in the format ``. A required `ArgGroup` simply states + /// that one argument from this group *must* be present at runtime (unless + /// conflicting with another argument). + /// + /// **NOTE:** This setting only applies to the current [`App`] / [`SubCommand`], and not + /// globally. + /// + /// **NOTE:** By default, [`ArgGroup::multiple`] is set to `false` which when combined with + /// `ArgGroup::required(true)` states, "One and *only one* arg must be used from this group. + /// Use of more than one arg is an error." Vice setting `ArgGroup::multiple(true)` which + /// states, '*At least* one arg from this group must be used. Using multiple is OK." + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg, ArgGroup, ErrorKind}; + /// let result = App::new("myprog") + /// .arg(Arg::with_name("flag") + /// .short("f")) + /// .arg(Arg::with_name("color") + /// .short("c")) + /// .group(ArgGroup::with_name("req_flags") + /// .args(&["flag", "color"]) + /// .required(true)) + /// .get_matches_from_safe(vec!["myprog"]); + /// // Because we didn't use any of the args in the group, it's an error + /// assert!(result.is_err()); + /// let err = result.unwrap_err(); + /// assert_eq!(err.kind, ErrorKind::MissingRequiredArgument); + /// ``` + /// [`App`]: ./struct.App.html + /// [`SubCommand`]: ./struct.SubCommand.html + /// [`ArgGroup::multiple`]: ./struct.ArgGroup.html#method.multiple + pub fn required(mut self, r: bool) -> Self { + self.required = r; + self + } + + /// Sets the requirement rules of this group. This is not to be confused with a + /// [required group]. Requirement rules function just like [argument requirement rules], you + /// can name other arguments or groups that must be present when any one of the arguments from + /// this group is used. + /// + /// **NOTE:** The name provided may be an argument, or group name + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg, ArgGroup, ErrorKind}; + /// let result = App::new("myprog") + /// .arg(Arg::with_name("flag") + /// .short("f")) + /// .arg(Arg::with_name("color") + /// .short("c")) + /// .arg(Arg::with_name("debug") + /// .short("d")) + /// .group(ArgGroup::with_name("req_flags") + /// .args(&["flag", "color"]) + /// .requires("debug")) + /// .get_matches_from_safe(vec!["myprog", "-c"]); + /// // because we used an arg from the group, and the group requires "-d" to be used, it's an + /// // error + /// assert!(result.is_err()); + /// let err = result.unwrap_err(); + /// assert_eq!(err.kind, ErrorKind::MissingRequiredArgument); + /// ``` + /// [required group]: ./struct.ArgGroup.html#method.required + /// [argument requirement rules]: ./struct.Arg.html#method.requires + pub fn requires(mut self, n: &'a str) -> Self { + if let Some(ref mut reqs) = self.requires { + reqs.push(n); + } else { + self.requires = Some(vec![n]); + } + self + } + + /// Sets the requirement rules of this group. This is not to be confused with a + /// [required group]. Requirement rules function just like [argument requirement rules], you + /// can name other arguments or groups that must be present when one of the arguments from this + /// group is used. + /// + /// **NOTE:** The names provided may be an argument, or group name + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg, ArgGroup, ErrorKind}; + /// let result = App::new("myprog") + /// .arg(Arg::with_name("flag") + /// .short("f")) + /// .arg(Arg::with_name("color") + /// .short("c")) + /// .arg(Arg::with_name("debug") + /// .short("d")) + /// .arg(Arg::with_name("verb") + /// .short("v")) + /// .group(ArgGroup::with_name("req_flags") + /// .args(&["flag", "color"]) + /// .requires_all(&["debug", "verb"])) + /// .get_matches_from_safe(vec!["myprog", "-c", "-d"]); + /// // because we used an arg from the group, and the group requires "-d" and "-v" to be used, + /// // yet we only used "-d" it's an error + /// assert!(result.is_err()); + /// let err = result.unwrap_err(); + /// assert_eq!(err.kind, ErrorKind::MissingRequiredArgument); + /// ``` + /// [required group]: ./struct.ArgGroup.html#method.required + /// [argument requirement rules]: ./struct.Arg.html#method.requires_all + pub fn requires_all(mut self, ns: &[&'a str]) -> Self { + for n in ns { + self = self.requires(n); + } + self + } + + /// Sets the exclusion rules of this group. Exclusion (aka conflict) rules function just like + /// [argument exclusion rules], you can name other arguments or groups that must *not* be + /// present when one of the arguments from this group are used. + /// + /// **NOTE:** The name provided may be an argument, or group name + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg, ArgGroup, ErrorKind}; + /// let result = App::new("myprog") + /// .arg(Arg::with_name("flag") + /// .short("f")) + /// .arg(Arg::with_name("color") + /// .short("c")) + /// .arg(Arg::with_name("debug") + /// .short("d")) + /// .group(ArgGroup::with_name("req_flags") + /// .args(&["flag", "color"]) + /// .conflicts_with("debug")) + /// .get_matches_from_safe(vec!["myprog", "-c", "-d"]); + /// // because we used an arg from the group, and the group conflicts with "-d", it's an error + /// assert!(result.is_err()); + /// let err = result.unwrap_err(); + /// assert_eq!(err.kind, ErrorKind::ArgumentConflict); + /// ``` + /// [argument exclusion rules]: ./struct.Arg.html#method.conflicts_with + pub fn conflicts_with(mut self, n: &'a str) -> Self { + if let Some(ref mut confs) = self.conflicts { + confs.push(n); + } else { + self.conflicts = Some(vec![n]); + } + self + } + + /// Sets the exclusion rules of this group. Exclusion rules function just like + /// [argument exclusion rules], you can name other arguments or groups that must *not* be + /// present when one of the arguments from this group are used. + /// + /// **NOTE:** The names provided may be an argument, or group name + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg, ArgGroup, ErrorKind}; + /// let result = App::new("myprog") + /// .arg(Arg::with_name("flag") + /// .short("f")) + /// .arg(Arg::with_name("color") + /// .short("c")) + /// .arg(Arg::with_name("debug") + /// .short("d")) + /// .arg(Arg::with_name("verb") + /// .short("v")) + /// .group(ArgGroup::with_name("req_flags") + /// .args(&["flag", "color"]) + /// .conflicts_with_all(&["debug", "verb"])) + /// .get_matches_from_safe(vec!["myprog", "-c", "-v"]); + /// // because we used an arg from the group, and the group conflicts with either "-v" or "-d" + /// // it's an error + /// assert!(result.is_err()); + /// let err = result.unwrap_err(); + /// assert_eq!(err.kind, ErrorKind::ArgumentConflict); + /// ``` + /// [argument exclusion rules]: ./struct.Arg.html#method.conflicts_with_all + pub fn conflicts_with_all(mut self, ns: &[&'a str]) -> Self { + for n in ns { + self = self.conflicts_with(n); + } + self + } +} + +impl<'a> Debug for ArgGroup<'a> { + fn fmt(&self, f: &mut Formatter) -> Result { + write!( + f, + "{{\n\ + \tname: {:?},\n\ + \targs: {:?},\n\ + \trequired: {:?},\n\ + \trequires: {:?},\n\ + \tconflicts: {:?},\n\ + }}", + self.name, + self.args, + self.required, + self.requires, + self.conflicts + ) + } +} + +impl<'a, 'z> From<&'z ArgGroup<'a>> for ArgGroup<'a> { + fn from(g: &'z ArgGroup<'a>) -> Self { + ArgGroup { + name: g.name, + required: g.required, + args: g.args.clone(), + requires: g.requires.clone(), + conflicts: g.conflicts.clone(), + multiple: g.multiple, + } + } +} + +#[cfg(feature = "yaml")] +impl<'a> From<&'a BTreeMap> for ArgGroup<'a> { + fn from(b: &'a BTreeMap) -> Self { + // We WANT this to panic on error...so expect() is good. + let mut a = ArgGroup::default(); + let group_settings = if b.len() == 1 { + let name_yml = b.keys().nth(0).expect("failed to get name"); + let name_str = name_yml + .as_str() + .expect("failed to convert arg YAML name to str"); + a.name = name_str; + b.get(name_yml) + .expect("failed to get name_str") + .as_hash() + .expect("failed to convert to a hash") + } else { + b + }; + + for (k, v) in group_settings { + a = match k.as_str().unwrap() { + "required" => a.required(v.as_bool().unwrap()), + "multiple" => a.multiple(v.as_bool().unwrap()), + "args" => yaml_vec_or_str!(v, a, arg), + "arg" => { + if let Some(ys) = v.as_str() { + a = a.arg(ys); + } + a + } + "requires" => yaml_vec_or_str!(v, a, requires), + "conflicts_with" => yaml_vec_or_str!(v, a, conflicts_with), + "name" => { + if let Some(ys) = v.as_str() { + a.name = ys; + } + a + } + s => panic!( + "Unknown ArgGroup setting '{}' in YAML file for \ + ArgGroup '{}'", + s, + a.name + ), + } + } + + a + } +} + +#[cfg(test)] +mod test { + use super::ArgGroup; + #[cfg(feature = "yaml")] + use yaml_rust::YamlLoader; + + #[test] + fn groups() { + let g = ArgGroup::with_name("test") + .arg("a1") + .arg("a4") + .args(&["a2", "a3"]) + .required(true) + .conflicts_with("c1") + .conflicts_with_all(&["c2", "c3"]) + .conflicts_with("c4") + .requires("r1") + .requires_all(&["r2", "r3"]) + .requires("r4"); + + let args = vec!["a1", "a4", "a2", "a3"]; + let reqs = vec!["r1", "r2", "r3", "r4"]; + let confs = vec!["c1", "c2", "c3", "c4"]; + + assert_eq!(g.args, args); + assert_eq!(g.requires, Some(reqs)); + assert_eq!(g.conflicts, Some(confs)); + } + + #[test] + fn test_debug() { + let g = ArgGroup::with_name("test") + .arg("a1") + .arg("a4") + .args(&["a2", "a3"]) + .required(true) + .conflicts_with("c1") + .conflicts_with_all(&["c2", "c3"]) + .conflicts_with("c4") + .requires("r1") + .requires_all(&["r2", "r3"]) + .requires("r4"); + + let args = vec!["a1", "a4", "a2", "a3"]; + let reqs = vec!["r1", "r2", "r3", "r4"]; + let confs = vec!["c1", "c2", "c3", "c4"]; + + let debug_str = format!( + "{{\n\ + \tname: \"test\",\n\ + \targs: {:?},\n\ + \trequired: {:?},\n\ + \trequires: {:?},\n\ + \tconflicts: {:?},\n\ + }}", + args, + true, + Some(reqs), + Some(confs) + ); + assert_eq!(&*format!("{:?}", g), &*debug_str); + } + + #[test] + fn test_from() { + let g = ArgGroup::with_name("test") + .arg("a1") + .arg("a4") + .args(&["a2", "a3"]) + .required(true) + .conflicts_with("c1") + .conflicts_with_all(&["c2", "c3"]) + .conflicts_with("c4") + .requires("r1") + .requires_all(&["r2", "r3"]) + .requires("r4"); + + let args = vec!["a1", "a4", "a2", "a3"]; + let reqs = vec!["r1", "r2", "r3", "r4"]; + let confs = vec!["c1", "c2", "c3", "c4"]; + + let g2 = ArgGroup::from(&g); + assert_eq!(g2.args, args); + assert_eq!(g2.requires, Some(reqs)); + assert_eq!(g2.conflicts, Some(confs)); + } + + #[cfg(feature = "yaml")] + #[cfg_attr(feature = "yaml", test)] + fn test_yaml() { + let g_yaml = "name: test +args: +- a1 +- a4 +- a2 +- a3 +conflicts_with: +- c1 +- c2 +- c3 +- c4 +requires: +- r1 +- r2 +- r3 +- r4"; + let yml = &YamlLoader::load_from_str(g_yaml).expect("failed to load YAML file")[0]; + let g = ArgGroup::from_yaml(yml); + let args = vec!["a1", "a4", "a2", "a3"]; + let reqs = vec!["r1", "r2", "r3", "r4"]; + let confs = vec!["c1", "c2", "c3", "c4"]; + assert_eq!(g.args, args); + assert_eq!(g.requires, Some(reqs)); + assert_eq!(g.conflicts, Some(confs)); + } +} + +impl<'a> Clone for ArgGroup<'a> { + fn clone(&self) -> Self { + ArgGroup { + name: self.name, + required: self.required, + args: self.args.clone(), + requires: self.requires.clone(), + conflicts: self.conflicts.clone(), + multiple: self.multiple, + } + } +} diff --git a/clap/src/args/macros.rs b/clap/src/args/macros.rs new file mode 100644 index 0000000..1de12f4 --- /dev/null +++ b/clap/src/args/macros.rs @@ -0,0 +1,109 @@ +#[cfg(feature = "yaml")] +macro_rules! yaml_tuple2 { + ($a:ident, $v:ident, $c:ident) => {{ + if let Some(vec) = $v.as_vec() { + for ys in vec { + if let Some(tup) = ys.as_vec() { + debug_assert_eq!(2, tup.len()); + $a = $a.$c(yaml_str!(tup[0]), yaml_str!(tup[1])); + } else { + panic!("Failed to convert YAML value to vec"); + } + } + } else { + panic!("Failed to convert YAML value to vec"); + } + $a + } + }; +} + +#[cfg(feature = "yaml")] +macro_rules! yaml_tuple3 { + ($a:ident, $v:ident, $c:ident) => {{ + if let Some(vec) = $v.as_vec() { + for ys in vec { + if let Some(tup) = ys.as_vec() { + debug_assert_eq!(3, tup.len()); + $a = $a.$c(yaml_str!(tup[0]), yaml_opt_str!(tup[1]), yaml_str!(tup[2])); + } else { + panic!("Failed to convert YAML value to vec"); + } + } + } else { + panic!("Failed to convert YAML value to vec"); + } + $a + } + }; +} + +#[cfg(feature = "yaml")] +macro_rules! yaml_vec_or_str { + ($v:ident, $a:ident, $c:ident) => {{ + let maybe_vec = $v.as_vec(); + if let Some(vec) = maybe_vec { + for ys in vec { + if let Some(s) = ys.as_str() { + $a = $a.$c(s); + } else { + panic!("Failed to convert YAML value {:?} to a string", ys); + } + } + } else { + if let Some(s) = $v.as_str() { + $a = $a.$c(s); + } else { + panic!("Failed to convert YAML value {:?} to either a vec or string", $v); + } + } + $a + } + }; +} + +#[cfg(feature = "yaml")] +macro_rules! yaml_opt_str { + ($v:expr) => {{ + if $v.is_null() { + Some($v.as_str().unwrap_or_else(|| panic!("failed to convert YAML {:?} value to a string", $v))) + } else { + None + } + }}; +} + +#[cfg(feature = "yaml")] +macro_rules! yaml_str { + ($v:expr) => {{ + $v.as_str().unwrap_or_else(|| panic!("failed to convert YAML {:?} value to a string", $v)) + }}; +} + +#[cfg(feature = "yaml")] +macro_rules! yaml_to_str { + ($a:ident, $v:ident, $c:ident) => {{ + $a.$c(yaml_str!($v)) + }}; +} + +#[cfg(feature = "yaml")] +macro_rules! yaml_to_bool { + ($a:ident, $v:ident, $c:ident) => {{ + $a.$c($v.as_bool().unwrap_or_else(|| panic!("failed to convert YAML {:?} value to a string", $v))) + }}; +} + +#[cfg(feature = "yaml")] +macro_rules! yaml_to_u64 { + ($a:ident, $v:ident, $c:ident) => {{ + $a.$c($v.as_i64().unwrap_or_else(|| panic!("failed to convert YAML {:?} value to a string", $v)) as u64) + }}; +} + +#[cfg(feature = "yaml")] +macro_rules! yaml_to_usize { + ($a:ident, $v:ident, $c:ident) => {{ + $a.$c($v.as_i64().unwrap_or_else(|| panic!("failed to convert YAML {:?} value to a string", $v)) as usize) + }}; +} diff --git a/clap/src/args/matched_arg.rs b/clap/src/args/matched_arg.rs new file mode 100644 index 0000000..eeda261 --- /dev/null +++ b/clap/src/args/matched_arg.rs @@ -0,0 +1,24 @@ +// Std +use std::ffi::OsString; + +#[doc(hidden)] +#[derive(Debug, Clone)] +pub struct MatchedArg { + #[doc(hidden)] pub occurs: u64, + #[doc(hidden)] pub indices: Vec, + #[doc(hidden)] pub vals: Vec, +} + +impl Default for MatchedArg { + fn default() -> Self { + MatchedArg { + occurs: 1, + indices: Vec::new(), + vals: Vec::new(), + } + } +} + +impl MatchedArg { + pub fn new() -> Self { MatchedArg::default() } +} diff --git a/clap/src/args/mod.rs b/clap/src/args/mod.rs new file mode 100644 index 0000000..21f9b85 --- /dev/null +++ b/clap/src/args/mod.rs @@ -0,0 +1,21 @@ +pub use self::any_arg::{AnyArg, DispOrder}; +pub use self::arg::Arg; +pub use self::arg_builder::{Base, FlagBuilder, OptBuilder, PosBuilder, Switched, Valued}; +pub use self::arg_matcher::ArgMatcher; +pub use self::arg_matches::{ArgMatches, OsValues, Values}; +pub use self::group::ArgGroup; +pub use self::matched_arg::MatchedArg; +pub use self::settings::{ArgFlags, ArgSettings}; +pub use self::subcommand::SubCommand; + +#[macro_use] +mod macros; +mod arg; +pub mod any_arg; +mod arg_matches; +mod arg_matcher; +mod subcommand; +mod arg_builder; +mod matched_arg; +mod group; +pub mod settings; diff --git a/clap/src/args/settings.rs b/clap/src/args/settings.rs new file mode 100644 index 0000000..7b0e0a2 --- /dev/null +++ b/clap/src/args/settings.rs @@ -0,0 +1,231 @@ +// Std +#[allow(deprecated, unused_imports)] +use std::ascii::AsciiExt; +use std::str::FromStr; + +bitflags! { + struct Flags: u32 { + const REQUIRED = 1; + const MULTIPLE = 1 << 1; + const EMPTY_VALS = 1 << 2; + const GLOBAL = 1 << 3; + const HIDDEN = 1 << 4; + const TAKES_VAL = 1 << 5; + const USE_DELIM = 1 << 6; + const NEXT_LINE_HELP = 1 << 7; + const R_UNLESS_ALL = 1 << 8; + const REQ_DELIM = 1 << 9; + const DELIM_NOT_SET = 1 << 10; + const HIDE_POS_VALS = 1 << 11; + const ALLOW_TAC_VALS = 1 << 12; + const REQUIRE_EQUALS = 1 << 13; + const LAST = 1 << 14; + const HIDE_DEFAULT_VAL = 1 << 15; + const CASE_INSENSITIVE = 1 << 16; + const HIDE_ENV_VALS = 1 << 17; + const HIDDEN_SHORT_H = 1 << 18; + const HIDDEN_LONG_H = 1 << 19; + } +} + +#[doc(hidden)] +#[derive(Debug, Clone, Copy)] +pub struct ArgFlags(Flags); + +impl ArgFlags { + pub fn new() -> Self { ArgFlags::default() } + + impl_settings!{ArgSettings, + Required => Flags::REQUIRED, + Multiple => Flags::MULTIPLE, + EmptyValues => Flags::EMPTY_VALS, + Global => Flags::GLOBAL, + Hidden => Flags::HIDDEN, + TakesValue => Flags::TAKES_VAL, + UseValueDelimiter => Flags::USE_DELIM, + NextLineHelp => Flags::NEXT_LINE_HELP, + RequiredUnlessAll => Flags::R_UNLESS_ALL, + RequireDelimiter => Flags::REQ_DELIM, + ValueDelimiterNotSet => Flags::DELIM_NOT_SET, + HidePossibleValues => Flags::HIDE_POS_VALS, + AllowLeadingHyphen => Flags::ALLOW_TAC_VALS, + RequireEquals => Flags::REQUIRE_EQUALS, + Last => Flags::LAST, + CaseInsensitive => Flags::CASE_INSENSITIVE, + HideEnvValues => Flags::HIDE_ENV_VALS, + HideDefaultValue => Flags::HIDE_DEFAULT_VAL, + HiddenShortHelp => Flags::HIDDEN_SHORT_H, + HiddenLongHelp => Flags::HIDDEN_LONG_H + } +} + +impl Default for ArgFlags { + fn default() -> Self { ArgFlags(Flags::EMPTY_VALS | Flags::DELIM_NOT_SET) } +} + +/// Various settings that apply to arguments and may be set, unset, and checked via getter/setter +/// methods [`Arg::set`], [`Arg::unset`], and [`Arg::is_set`] +/// +/// [`Arg::set`]: ./struct.Arg.html#method.set +/// [`Arg::unset`]: ./struct.Arg.html#method.unset +/// [`Arg::is_set`]: ./struct.Arg.html#method.is_set +#[derive(Debug, PartialEq, Copy, Clone)] +pub enum ArgSettings { + /// The argument must be used + Required, + /// The argument may be used multiple times such as `--flag --flag` + Multiple, + /// The argument allows empty values such as `--option ""` + EmptyValues, + /// The argument should be propagated down through all child [`SubCommand`]s + /// + /// [`SubCommand`]: ./struct.SubCommand.html + Global, + /// The argument should **not** be shown in help text + Hidden, + /// The argument accepts a value, such as `--option ` + TakesValue, + /// Determines if the argument allows values to be grouped via a delimiter + UseValueDelimiter, + /// Prints the help text on the line after the argument + NextLineHelp, + /// Requires the use of a value delimiter for all multiple values + RequireDelimiter, + /// Hides the possible values from the help string + HidePossibleValues, + /// Allows vals that start with a '-' + AllowLeadingHyphen, + /// Require options use `--option=val` syntax + RequireEquals, + /// Specifies that the arg is the last positional argument and may be accessed early via `--` + /// syntax + Last, + /// Hides the default value from the help string + HideDefaultValue, + /// Makes `Arg::possible_values` case insensitive + CaseInsensitive, + /// Hides ENV values in the help message + HideEnvValues, + /// The argument should **not** be shown in short help text + HiddenShortHelp, + /// The argument should **not** be shown in long help text + HiddenLongHelp, + #[doc(hidden)] RequiredUnlessAll, + #[doc(hidden)] ValueDelimiterNotSet, +} + +impl FromStr for ArgSettings { + type Err = String; + fn from_str(s: &str) -> Result::Err> { + match &*s.to_ascii_lowercase() { + "required" => Ok(ArgSettings::Required), + "multiple" => Ok(ArgSettings::Multiple), + "global" => Ok(ArgSettings::Global), + "emptyvalues" => Ok(ArgSettings::EmptyValues), + "hidden" => Ok(ArgSettings::Hidden), + "takesvalue" => Ok(ArgSettings::TakesValue), + "usevaluedelimiter" => Ok(ArgSettings::UseValueDelimiter), + "nextlinehelp" => Ok(ArgSettings::NextLineHelp), + "requiredunlessall" => Ok(ArgSettings::RequiredUnlessAll), + "requiredelimiter" => Ok(ArgSettings::RequireDelimiter), + "valuedelimiternotset" => Ok(ArgSettings::ValueDelimiterNotSet), + "hidepossiblevalues" => Ok(ArgSettings::HidePossibleValues), + "allowleadinghyphen" => Ok(ArgSettings::AllowLeadingHyphen), + "requireequals" => Ok(ArgSettings::RequireEquals), + "last" => Ok(ArgSettings::Last), + "hidedefaultvalue" => Ok(ArgSettings::HideDefaultValue), + "caseinsensitive" => Ok(ArgSettings::CaseInsensitive), + "hideenvvalues" => Ok(ArgSettings::HideEnvValues), + "hiddenshorthelp" => Ok(ArgSettings::HiddenShortHelp), + "hiddenlonghelp" => Ok(ArgSettings::HiddenLongHelp), + _ => Err("unknown ArgSetting, cannot convert from str".to_owned()), + } + } +} + +#[cfg(test)] +mod test { + use super::ArgSettings; + + #[test] + fn arg_settings_fromstr() { + assert_eq!( + "allowleadinghyphen".parse::().unwrap(), + ArgSettings::AllowLeadingHyphen + ); + assert_eq!( + "emptyvalues".parse::().unwrap(), + ArgSettings::EmptyValues + ); + assert_eq!( + "global".parse::().unwrap(), + ArgSettings::Global + ); + assert_eq!( + "hidepossiblevalues".parse::().unwrap(), + ArgSettings::HidePossibleValues + ); + assert_eq!( + "hidden".parse::().unwrap(), + ArgSettings::Hidden + ); + assert_eq!( + "multiple".parse::().unwrap(), + ArgSettings::Multiple + ); + assert_eq!( + "nextlinehelp".parse::().unwrap(), + ArgSettings::NextLineHelp + ); + assert_eq!( + "requiredunlessall".parse::().unwrap(), + ArgSettings::RequiredUnlessAll + ); + assert_eq!( + "requiredelimiter".parse::().unwrap(), + ArgSettings::RequireDelimiter + ); + assert_eq!( + "required".parse::().unwrap(), + ArgSettings::Required + ); + assert_eq!( + "takesvalue".parse::().unwrap(), + ArgSettings::TakesValue + ); + assert_eq!( + "usevaluedelimiter".parse::().unwrap(), + ArgSettings::UseValueDelimiter + ); + assert_eq!( + "valuedelimiternotset".parse::().unwrap(), + ArgSettings::ValueDelimiterNotSet + ); + assert_eq!( + "requireequals".parse::().unwrap(), + ArgSettings::RequireEquals + ); + assert_eq!("last".parse::().unwrap(), ArgSettings::Last); + assert_eq!( + "hidedefaultvalue".parse::().unwrap(), + ArgSettings::HideDefaultValue + ); + assert_eq!( + "caseinsensitive".parse::().unwrap(), + ArgSettings::CaseInsensitive + ); + assert_eq!( + "hideenvvalues".parse::().unwrap(), + ArgSettings::HideEnvValues + ); + assert_eq!( + "hiddenshorthelp".parse::().unwrap(), + ArgSettings::HiddenShortHelp + ); + assert_eq!( + "hiddenlonghelp".parse::().unwrap(), + ArgSettings::HiddenLongHelp + ); + assert!("hahahaha".parse::().is_err()); + } +} diff --git a/clap/src/args/subcommand.rs b/clap/src/args/subcommand.rs new file mode 100644 index 0000000..eebbf82 --- /dev/null +++ b/clap/src/args/subcommand.rs @@ -0,0 +1,66 @@ +// Third Party +#[cfg(feature = "yaml")] +use yaml_rust::Yaml; + +// Internal +use App; +use ArgMatches; + +/// The abstract representation of a command line subcommand. +/// +/// This struct describes all the valid options of the subcommand for the program. Subcommands are +/// essentially "sub-[`App`]s" and contain all the same possibilities (such as their own +/// [arguments], subcommands, and settings). +/// +/// # Examples +/// +/// ```rust +/// # use clap::{App, Arg, SubCommand}; +/// App::new("myprog") +/// .subcommand( +/// SubCommand::with_name("config") +/// .about("Used for configuration") +/// .arg(Arg::with_name("config_file") +/// .help("The configuration file to use") +/// .index(1))) +/// # ; +/// ``` +/// [`App`]: ./struct.App.html +/// [arguments]: ./struct.Arg.html +#[derive(Debug, Clone)] +pub struct SubCommand<'a> { + #[doc(hidden)] pub name: String, + #[doc(hidden)] pub matches: ArgMatches<'a>, +} + +impl<'a> SubCommand<'a> { + /// Creates a new instance of a subcommand requiring a name. The name will be displayed + /// to the user when they print version or help and usage information. + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg, SubCommand}; + /// App::new("myprog") + /// .subcommand( + /// SubCommand::with_name("config")) + /// # ; + /// ``` + pub fn with_name<'b>(name: &str) -> App<'a, 'b> { App::new(name) } + + /// Creates a new instance of a subcommand from a YAML (.yml) document + /// + /// # Examples + /// + /// ```ignore + /// # #[macro_use] + /// # extern crate clap; + /// # use clap::Subcommand; + /// # fn main() { + /// let sc_yaml = load_yaml!("test_subcommand.yml"); + /// let sc = SubCommand::from_yaml(sc_yaml); + /// # } + /// ``` + #[cfg(feature = "yaml")] + pub fn from_yaml(yaml: &Yaml) -> App { App::from_yaml(yaml) } +} diff --git a/clap/src/completions/bash.rs b/clap/src/completions/bash.rs new file mode 100644 index 0000000..37dfa66 --- /dev/null +++ b/clap/src/completions/bash.rs @@ -0,0 +1,219 @@ +// Std +use std::io::Write; + +// Internal +use app::parser::Parser; +use args::OptBuilder; +use completions; + +pub struct BashGen<'a, 'b> +where + 'a: 'b, +{ + p: &'b Parser<'a, 'b>, +} + +impl<'a, 'b> BashGen<'a, 'b> { + pub fn new(p: &'b Parser<'a, 'b>) -> Self { BashGen { p: p } } + + pub fn generate_to(&self, buf: &mut W) { + w!( + buf, + format!( + r#"_{name}() {{ + local i cur prev opts cmds + COMPREPLY=() + cur="${{COMP_WORDS[COMP_CWORD]}}" + prev="${{COMP_WORDS[COMP_CWORD-1]}}" + cmd="" + opts="" + + for i in ${{COMP_WORDS[@]}} + do + case "${{i}}" in + {name}) + cmd="{name}" + ;; + {subcmds} + *) + ;; + esac + done + + case "${{cmd}}" in + {name}) + opts="{name_opts}" + if [[ ${{cur}} == -* || ${{COMP_CWORD}} -eq 1 ]] ; then + COMPREPLY=( $(compgen -W "${{opts}}" -- "${{cur}}") ) + return 0 + fi + case "${{prev}}" in + {name_opts_details} + *) + COMPREPLY=() + ;; + esac + COMPREPLY=( $(compgen -W "${{opts}}" -- "${{cur}}") ) + return 0 + ;; + {subcmd_details} + esac +}} + +complete -F _{name} -o bashdefault -o default {name} +"#, + name = self.p.meta.bin_name.as_ref().unwrap(), + name_opts = self.all_options_for_path(self.p.meta.bin_name.as_ref().unwrap()), + name_opts_details = + self.option_details_for_path(self.p.meta.bin_name.as_ref().unwrap()), + subcmds = self.all_subcommands(), + subcmd_details = self.subcommand_details() + ).as_bytes() + ); + } + + fn all_subcommands(&self) -> String { + debugln!("BashGen::all_subcommands;"); + let mut subcmds = String::new(); + let scs = completions::all_subcommand_names(self.p); + + for sc in &scs { + subcmds = format!( + r#"{} + {name}) + cmd+="__{fn_name}" + ;;"#, + subcmds, + name = sc, + fn_name = sc.replace("-", "__") + ); + } + + subcmds + } + + fn subcommand_details(&self) -> String { + debugln!("BashGen::subcommand_details;"); + let mut subcmd_dets = String::new(); + let mut scs = completions::get_all_subcommand_paths(self.p, true); + scs.sort(); + scs.dedup(); + + for sc in &scs { + subcmd_dets = format!( + r#"{} + {subcmd}) + opts="{sc_opts}" + if [[ ${{cur}} == -* || ${{COMP_CWORD}} -eq {level} ]] ; then + COMPREPLY=( $(compgen -W "${{opts}}" -- "${{cur}}") ) + return 0 + fi + case "${{prev}}" in + {opts_details} + *) + COMPREPLY=() + ;; + esac + COMPREPLY=( $(compgen -W "${{opts}}" -- "${{cur}}") ) + return 0 + ;;"#, + subcmd_dets, + subcmd = sc.replace("-", "__"), + sc_opts = self.all_options_for_path(&*sc), + level = sc.split("__").map(|_| 1).fold(0, |acc, n| acc + n), + opts_details = self.option_details_for_path(&*sc) + ); + } + + subcmd_dets + } + + fn option_details_for_path(&self, path: &str) -> String { + debugln!("BashGen::option_details_for_path: path={}", path); + let mut p = self.p; + for sc in path.split("__").skip(1) { + debugln!("BashGen::option_details_for_path:iter: sc={}", sc); + p = &find_subcmd!(p, sc).unwrap().p; + } + let mut opts = String::new(); + for o in p.opts() { + if let Some(l) = o.s.long { + opts = format!( + "{} + --{}) + COMPREPLY=({}) + return 0 + ;;", + opts, + l, + self.vals_for(o) + ); + } + if let Some(s) = o.s.short { + opts = format!( + "{} + -{}) + COMPREPLY=({}) + return 0 + ;;", + opts, + s, + self.vals_for(o) + ); + } + } + opts + } + + fn vals_for(&self, o: &OptBuilder) -> String { + debugln!("BashGen::vals_for: o={}", o.b.name); + use args::AnyArg; + if let Some(vals) = o.possible_vals() { + format!(r#"$(compgen -W "{}" -- "${{cur}}")"#, vals.join(" ")) + } else { + String::from(r#"$(compgen -f "${cur}")"#) + } + } + + fn all_options_for_path(&self, path: &str) -> String { + debugln!("BashGen::all_options_for_path: path={}", path); + let mut p = self.p; + for sc in path.split("__").skip(1) { + debugln!("BashGen::all_options_for_path:iter: sc={}", sc); + p = &find_subcmd!(p, sc).unwrap().p; + } + let mut opts = shorts!(p).fold(String::new(), |acc, s| format!("{} -{}", acc, s)); + opts = format!( + "{} {}", + opts, + longs!(p).fold(String::new(), |acc, l| format!("{} --{}", acc, l)) + ); + opts = format!( + "{} {}", + opts, + p.positionals + .values() + .fold(String::new(), |acc, p| format!("{} {}", acc, p)) + ); + opts = format!( + "{} {}", + opts, + p.subcommands + .iter() + .fold(String::new(), |acc, s| format!("{} {}", acc, s.p.meta.name)) + ); + for sc in &p.subcommands { + if let Some(ref aliases) = sc.p.meta.aliases { + opts = format!( + "{} {}", + opts, + aliases + .iter() + .map(|&(n, _)| n) + .fold(String::new(), |acc, a| format!("{} {}", acc, a)) + ); + } + } + opts + } +} diff --git a/clap/src/completions/elvish.rs b/clap/src/completions/elvish.rs new file mode 100644 index 0000000..9a5f21a --- /dev/null +++ b/clap/src/completions/elvish.rs @@ -0,0 +1,126 @@ +// Std +use std::io::Write; + +// Internal +use app::parser::Parser; +use INTERNAL_ERROR_MSG; + +pub struct ElvishGen<'a, 'b> +where + 'a: 'b, +{ + p: &'b Parser<'a, 'b>, +} + +impl<'a, 'b> ElvishGen<'a, 'b> { + pub fn new(p: &'b Parser<'a, 'b>) -> Self { ElvishGen { p: p } } + + pub fn generate_to(&self, buf: &mut W) { + let bin_name = self.p.meta.bin_name.as_ref().unwrap(); + + let mut names = vec![]; + let subcommands_cases = + generate_inner(self.p, "", &mut names); + + let result = format!(r#" +edit:completion:arg-completer[{bin_name}] = [@words]{{ + fn spaces [n]{{ + repeat $n ' ' | joins '' + }} + fn cand [text desc]{{ + edit:complex-candidate $text &display-suffix=' '(spaces (- 14 (wcswidth $text)))$desc + }} + command = '{bin_name}' + for word $words[1:-1] {{ + if (has-prefix $word '-') {{ + break + }} + command = $command';'$word + }} + completions = [{subcommands_cases} + ] + $completions[$command] +}} +"#, + bin_name = bin_name, + subcommands_cases = subcommands_cases + ); + + w!(buf, result.as_bytes()); + } +} + +// Escape string inside single quotes +fn escape_string(string: &str) -> String { string.replace("'", "''") } + +fn get_tooltip(help: Option<&str>, data: T) -> String { + match help { + Some(help) => escape_string(help), + _ => data.to_string() + } +} + +fn generate_inner<'a, 'b, 'p>( + p: &'p Parser<'a, 'b>, + previous_command_name: &str, + names: &mut Vec<&'p str>, +) -> String { + debugln!("ElvishGen::generate_inner;"); + let command_name = if previous_command_name.is_empty() { + p.meta.bin_name.as_ref().expect(INTERNAL_ERROR_MSG).clone() + } else { + format!("{};{}", previous_command_name, &p.meta.name) + }; + + let mut completions = String::new(); + let preamble = String::from("\n cand "); + + for option in p.opts() { + if let Some(data) = option.s.short { + let tooltip = get_tooltip(option.b.help, data); + completions.push_str(&preamble); + completions.push_str(format!("-{} '{}'", data, tooltip).as_str()); + } + if let Some(data) = option.s.long { + let tooltip = get_tooltip(option.b.help, data); + completions.push_str(&preamble); + completions.push_str(format!("--{} '{}'", data, tooltip).as_str()); + } + } + + for flag in p.flags() { + if let Some(data) = flag.s.short { + let tooltip = get_tooltip(flag.b.help, data); + completions.push_str(&preamble); + completions.push_str(format!("-{} '{}'", data, tooltip).as_str()); + } + if let Some(data) = flag.s.long { + let tooltip = get_tooltip(flag.b.help, data); + completions.push_str(&preamble); + completions.push_str(format!("--{} '{}'", data, tooltip).as_str()); + } + } + + for subcommand in &p.subcommands { + let data = &subcommand.p.meta.name; + let tooltip = get_tooltip(subcommand.p.meta.about, data); + completions.push_str(&preamble); + completions.push_str(format!("{} '{}'", data, tooltip).as_str()); + } + + let mut subcommands_cases = format!( + r" + &'{}'= {{{} + }}", + &command_name, + completions + ); + + for subcommand in &p.subcommands { + let subcommand_subcommands_cases = + generate_inner(&subcommand.p, &command_name, names); + subcommands_cases.push_str(&subcommand_subcommands_cases); + } + + subcommands_cases +} diff --git a/clap/src/completions/fish.rs b/clap/src/completions/fish.rs new file mode 100644 index 0000000..c2c5a5e --- /dev/null +++ b/clap/src/completions/fish.rs @@ -0,0 +1,99 @@ +// Std +use std::io::Write; + +// Internal +use app::parser::Parser; + +pub struct FishGen<'a, 'b> +where + 'a: 'b, +{ + p: &'b Parser<'a, 'b>, +} + +impl<'a, 'b> FishGen<'a, 'b> { + pub fn new(p: &'b Parser<'a, 'b>) -> Self { FishGen { p: p } } + + pub fn generate_to(&self, buf: &mut W) { + let command = self.p.meta.bin_name.as_ref().unwrap(); + let mut buffer = String::new(); + gen_fish_inner(command, self, command, &mut buffer); + w!(buf, buffer.as_bytes()); + } +} + +// Escape string inside single quotes +fn escape_string(string: &str) -> String { string.replace("\\", "\\\\").replace("'", "\\'") } + +fn gen_fish_inner(root_command: &str, comp_gen: &FishGen, subcommand: &str, buffer: &mut String) { + debugln!("FishGen::gen_fish_inner;"); + // example : + // + // complete + // -c {command} + // -d "{description}" + // -s {short} + // -l {long} + // -a "{possible_arguments}" + // -r # if require parameter + // -f # don't use file completion + // -n "__fish_use_subcommand" # complete for command "myprog" + // -n "__fish_seen_subcommand_from subcmd1" # complete for command "myprog subcmd1" + + let mut basic_template = format!("complete -c {} -n ", root_command); + if root_command == subcommand { + basic_template.push_str("\"__fish_use_subcommand\""); + } else { + basic_template.push_str(format!("\"__fish_seen_subcommand_from {}\"", subcommand).as_str()); + } + + for option in comp_gen.p.opts() { + let mut template = basic_template.clone(); + if let Some(data) = option.s.short { + template.push_str(format!(" -s {}", data).as_str()); + } + if let Some(data) = option.s.long { + template.push_str(format!(" -l {}", data).as_str()); + } + if let Some(data) = option.b.help { + template.push_str(format!(" -d '{}'", escape_string(data)).as_str()); + } + if let Some(ref data) = option.v.possible_vals { + template.push_str(format!(" -r -f -a \"{}\"", data.join(" ")).as_str()); + } + buffer.push_str(template.as_str()); + buffer.push_str("\n"); + } + + for flag in comp_gen.p.flags() { + let mut template = basic_template.clone(); + if let Some(data) = flag.s.short { + template.push_str(format!(" -s {}", data).as_str()); + } + if let Some(data) = flag.s.long { + template.push_str(format!(" -l {}", data).as_str()); + } + if let Some(data) = flag.b.help { + template.push_str(format!(" -d '{}'", escape_string(data)).as_str()); + } + buffer.push_str(template.as_str()); + buffer.push_str("\n"); + } + + for subcommand in &comp_gen.p.subcommands { + let mut template = basic_template.clone(); + template.push_str(" -f"); + template.push_str(format!(" -a \"{}\"", &subcommand.p.meta.name).as_str()); + if let Some(data) = subcommand.p.meta.about { + template.push_str(format!(" -d '{}'", escape_string(data)).as_str()) + } + buffer.push_str(template.as_str()); + buffer.push_str("\n"); + } + + // generate options of subcommands + for subcommand in &comp_gen.p.subcommands { + let sub_comp_gen = FishGen::new(&subcommand.p); + gen_fish_inner(root_command, &sub_comp_gen, &subcommand.to_string(), buffer); + } +} diff --git a/clap/src/completions/macros.rs b/clap/src/completions/macros.rs new file mode 100644 index 0000000..653c72c --- /dev/null +++ b/clap/src/completions/macros.rs @@ -0,0 +1,28 @@ +macro_rules! w { + ($buf:expr, $to_w:expr) => { + match $buf.write_all($to_w) { + Ok(..) => (), + Err(..) => panic!("Failed to write to completions file"), + } + }; +} + +macro_rules! get_zsh_arg_conflicts { + ($p:ident, $arg:ident, $msg:ident) => { + if let Some(conf_vec) = $arg.blacklist() { + let mut v = vec![]; + for arg_name in conf_vec { + let arg = $p.find_any_arg(arg_name).expect($msg); + if let Some(s) = arg.short() { + v.push(format!("-{}", s)); + } + if let Some(l) = arg.long() { + v.push(format!("--{}", l)); + } + } + v.join(" ") + } else { + String::new() + } + } +} diff --git a/clap/src/completions/mod.rs b/clap/src/completions/mod.rs new file mode 100644 index 0000000..a3306d7 --- /dev/null +++ b/clap/src/completions/mod.rs @@ -0,0 +1,179 @@ +#[macro_use] +mod macros; +mod bash; +mod fish; +mod zsh; +mod powershell; +mod elvish; +mod shell; + +// Std +use std::io::Write; + +// Internal +use app::parser::Parser; +use self::bash::BashGen; +use self::fish::FishGen; +use self::zsh::ZshGen; +use self::powershell::PowerShellGen; +use self::elvish::ElvishGen; +pub use self::shell::Shell; + +pub struct ComplGen<'a, 'b> +where + 'a: 'b, +{ + p: &'b Parser<'a, 'b>, +} + +impl<'a, 'b> ComplGen<'a, 'b> { + pub fn new(p: &'b Parser<'a, 'b>) -> Self { ComplGen { p: p } } + + pub fn generate(&self, for_shell: Shell, buf: &mut W) { + match for_shell { + Shell::Bash => BashGen::new(self.p).generate_to(buf), + Shell::Fish => FishGen::new(self.p).generate_to(buf), + Shell::Zsh => ZshGen::new(self.p).generate_to(buf), + Shell::PowerShell => PowerShellGen::new(self.p).generate_to(buf), + Shell::Elvish => ElvishGen::new(self.p).generate_to(buf), + } + } +} + +// Gets all subcommands including child subcommands in the form of 'name' where the name +// is a single word (i.e. "install") of the path to said subcommand (i.e. +// "rustup toolchain install") +// +// Also note, aliases are treated as their own subcommands but duplicates of whatever they're +// aliasing. +pub fn all_subcommand_names(p: &Parser) -> Vec { + debugln!("all_subcommand_names;"); + let mut subcmds: Vec<_> = subcommands_of(p) + .iter() + .map(|&(ref n, _)| n.clone()) + .collect(); + for sc_v in p.subcommands.iter().map(|s| all_subcommand_names(&s.p)) { + subcmds.extend(sc_v); + } + subcmds.sort(); + subcmds.dedup(); + subcmds +} + +// Gets all subcommands including child subcommands in the form of ('name', 'bin_name') where the name +// is a single word (i.e. "install") of the path and full bin_name of said subcommand (i.e. +// "rustup toolchain install") +// +// Also note, aliases are treated as their own subcommands but duplicates of whatever they're +// aliasing. +pub fn all_subcommands(p: &Parser) -> Vec<(String, String)> { + debugln!("all_subcommands;"); + let mut subcmds: Vec<_> = subcommands_of(p); + for sc_v in p.subcommands.iter().map(|s| all_subcommands(&s.p)) { + subcmds.extend(sc_v); + } + subcmds +} + +// Gets all subcommands excluding child subcommands in the form of (name, bin_name) where the name +// is a single word (i.e. "install") and the bin_name is a space delineated list of the path to said +// subcommand (i.e. "rustup toolchain install") +// +// Also note, aliases are treated as their own subcommands but duplicates of whatever they're +// aliasing. +pub fn subcommands_of(p: &Parser) -> Vec<(String, String)> { + debugln!( + "subcommands_of: name={}, bin_name={}", + p.meta.name, + p.meta.bin_name.as_ref().unwrap() + ); + let mut subcmds = vec![]; + + debugln!( + "subcommands_of: Has subcommands...{:?}", + p.has_subcommands() + ); + if !p.has_subcommands() { + let mut ret = vec![]; + debugln!("subcommands_of: Looking for aliases..."); + if let Some(ref aliases) = p.meta.aliases { + for &(n, _) in aliases { + debugln!("subcommands_of:iter:iter: Found alias...{}", n); + let mut als_bin_name: Vec<_> = + p.meta.bin_name.as_ref().unwrap().split(' ').collect(); + als_bin_name.push(n); + let old = als_bin_name.len() - 2; + als_bin_name.swap_remove(old); + ret.push((n.to_owned(), als_bin_name.join(" "))); + } + } + return ret; + } + for sc in &p.subcommands { + debugln!( + "subcommands_of:iter: name={}, bin_name={}", + sc.p.meta.name, + sc.p.meta.bin_name.as_ref().unwrap() + ); + + debugln!("subcommands_of:iter: Looking for aliases..."); + if let Some(ref aliases) = sc.p.meta.aliases { + for &(n, _) in aliases { + debugln!("subcommands_of:iter:iter: Found alias...{}", n); + let mut als_bin_name: Vec<_> = + p.meta.bin_name.as_ref().unwrap().split(' ').collect(); + als_bin_name.push(n); + let old = als_bin_name.len() - 2; + als_bin_name.swap_remove(old); + subcmds.push((n.to_owned(), als_bin_name.join(" "))); + } + } + subcmds.push(( + sc.p.meta.name.clone(), + sc.p.meta.bin_name.as_ref().unwrap().clone(), + )); + } + subcmds +} + +pub fn get_all_subcommand_paths(p: &Parser, first: bool) -> Vec { + debugln!("get_all_subcommand_paths;"); + let mut subcmds = vec![]; + if !p.has_subcommands() { + if !first { + let name = &*p.meta.name; + let path = p.meta.bin_name.as_ref().unwrap().clone().replace(" ", "__"); + let mut ret = vec![path.clone()]; + if let Some(ref aliases) = p.meta.aliases { + for &(n, _) in aliases { + ret.push(path.replace(name, n)); + } + } + return ret; + } + return vec![]; + } + for sc in &p.subcommands { + let name = &*sc.p.meta.name; + let path = sc.p + .meta + .bin_name + .as_ref() + .unwrap() + .clone() + .replace(" ", "__"); + subcmds.push(path.clone()); + if let Some(ref aliases) = sc.p.meta.aliases { + for &(n, _) in aliases { + subcmds.push(path.replace(name, n)); + } + } + } + for sc_v in p.subcommands + .iter() + .map(|s| get_all_subcommand_paths(&s.p, false)) + { + subcmds.extend(sc_v); + } + subcmds +} diff --git a/clap/src/completions/powershell.rs b/clap/src/completions/powershell.rs new file mode 100644 index 0000000..9fc77c7 --- /dev/null +++ b/clap/src/completions/powershell.rs @@ -0,0 +1,139 @@ +// Std +use std::io::Write; + +// Internal +use app::parser::Parser; +use INTERNAL_ERROR_MSG; + +pub struct PowerShellGen<'a, 'b> +where + 'a: 'b, +{ + p: &'b Parser<'a, 'b>, +} + +impl<'a, 'b> PowerShellGen<'a, 'b> { + pub fn new(p: &'b Parser<'a, 'b>) -> Self { PowerShellGen { p: p } } + + pub fn generate_to(&self, buf: &mut W) { + let bin_name = self.p.meta.bin_name.as_ref().unwrap(); + + let mut names = vec![]; + let subcommands_cases = + generate_inner(self.p, "", &mut names); + + let result = format!(r#" +using namespace System.Management.Automation +using namespace System.Management.Automation.Language + +Register-ArgumentCompleter -Native -CommandName '{bin_name}' -ScriptBlock {{ + param($wordToComplete, $commandAst, $cursorPosition) + + $commandElements = $commandAst.CommandElements + $command = @( + '{bin_name}' + for ($i = 1; $i -lt $commandElements.Count; $i++) {{ + $element = $commandElements[$i] + if ($element -isnot [StringConstantExpressionAst] -or + $element.StringConstantType -ne [StringConstantType]::BareWord -or + $element.Value.StartsWith('-')) {{ + break + }} + $element.Value + }}) -join ';' + + $completions = @(switch ($command) {{{subcommands_cases} + }}) + + $completions.Where{{ $_.CompletionText -like "$wordToComplete*" }} | + Sort-Object -Property ListItemText +}} +"#, + bin_name = bin_name, + subcommands_cases = subcommands_cases + ); + + w!(buf, result.as_bytes()); + } +} + +// Escape string inside single quotes +fn escape_string(string: &str) -> String { string.replace("'", "''") } + +fn get_tooltip(help: Option<&str>, data: T) -> String { + match help { + Some(help) => escape_string(help), + _ => data.to_string() + } +} + +fn generate_inner<'a, 'b, 'p>( + p: &'p Parser<'a, 'b>, + previous_command_name: &str, + names: &mut Vec<&'p str>, +) -> String { + debugln!("PowerShellGen::generate_inner;"); + let command_name = if previous_command_name.is_empty() { + p.meta.bin_name.as_ref().expect(INTERNAL_ERROR_MSG).clone() + } else { + format!("{};{}", previous_command_name, &p.meta.name) + }; + + let mut completions = String::new(); + let preamble = String::from("\n [CompletionResult]::new("); + + for option in p.opts() { + if let Some(data) = option.s.short { + let tooltip = get_tooltip(option.b.help, data); + completions.push_str(&preamble); + completions.push_str(format!("'-{}', '{}', {}, '{}')", + data, data, "[CompletionResultType]::ParameterName", tooltip).as_str()); + } + if let Some(data) = option.s.long { + let tooltip = get_tooltip(option.b.help, data); + completions.push_str(&preamble); + completions.push_str(format!("'--{}', '{}', {}, '{}')", + data, data, "[CompletionResultType]::ParameterName", tooltip).as_str()); + } + } + + for flag in p.flags() { + if let Some(data) = flag.s.short { + let tooltip = get_tooltip(flag.b.help, data); + completions.push_str(&preamble); + completions.push_str(format!("'-{}', '{}', {}, '{}')", + data, data, "[CompletionResultType]::ParameterName", tooltip).as_str()); + } + if let Some(data) = flag.s.long { + let tooltip = get_tooltip(flag.b.help, data); + completions.push_str(&preamble); + completions.push_str(format!("'--{}', '{}', {}, '{}')", + data, data, "[CompletionResultType]::ParameterName", tooltip).as_str()); + } + } + + for subcommand in &p.subcommands { + let data = &subcommand.p.meta.name; + let tooltip = get_tooltip(subcommand.p.meta.about, data); + completions.push_str(&preamble); + completions.push_str(format!("'{}', '{}', {}, '{}')", + data, data, "[CompletionResultType]::ParameterValue", tooltip).as_str()); + } + + let mut subcommands_cases = format!( + r" + '{}' {{{} + break + }}", + &command_name, + completions + ); + + for subcommand in &p.subcommands { + let subcommand_subcommands_cases = + generate_inner(&subcommand.p, &command_name, names); + subcommands_cases.push_str(&subcommand_subcommands_cases); + } + + subcommands_cases +} diff --git a/clap/src/completions/shell.rs b/clap/src/completions/shell.rs new file mode 100644 index 0000000..19aab86 --- /dev/null +++ b/clap/src/completions/shell.rs @@ -0,0 +1,52 @@ +#[allow(deprecated, unused_imports)] +use std::ascii::AsciiExt; +use std::str::FromStr; +use std::fmt; + +/// Describes which shell to produce a completions file for +#[cfg_attr(feature = "lints", allow(enum_variant_names))] +#[derive(Debug, Copy, Clone)] +pub enum Shell { + /// Generates a .bash completion file for the Bourne Again SHell (BASH) + Bash, + /// Generates a .fish completion file for the Friendly Interactive SHell (fish) + Fish, + /// Generates a completion file for the Z SHell (ZSH) + Zsh, + /// Generates a completion file for PowerShell + PowerShell, + /// Generates a completion file for Elvish + Elvish, +} + +impl Shell { + /// A list of possible variants in `&'static str` form + pub fn variants() -> [&'static str; 5] { ["zsh", "bash", "fish", "powershell", "elvish"] } +} + +impl FromStr for Shell { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "ZSH" | _ if s.eq_ignore_ascii_case("zsh") => Ok(Shell::Zsh), + "FISH" | _ if s.eq_ignore_ascii_case("fish") => Ok(Shell::Fish), + "BASH" | _ if s.eq_ignore_ascii_case("bash") => Ok(Shell::Bash), + "POWERSHELL" | _ if s.eq_ignore_ascii_case("powershell") => Ok(Shell::PowerShell), + "ELVISH" | _ if s.eq_ignore_ascii_case("elvish") => Ok(Shell::Elvish), + _ => Err(String::from("[valid values: bash, fish, zsh, powershell, elvish]")), + } + } +} + +impl fmt::Display for Shell { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Shell::Bash => write!(f, "BASH"), + Shell::Fish => write!(f, "FISH"), + Shell::Zsh => write!(f, "ZSH"), + Shell::PowerShell => write!(f, "POWERSHELL"), + Shell::Elvish => write!(f, "ELVISH"), + } + } +} diff --git a/clap/src/completions/zsh.rs b/clap/src/completions/zsh.rs new file mode 100644 index 0000000..5d23fd2 --- /dev/null +++ b/clap/src/completions/zsh.rs @@ -0,0 +1,472 @@ +// Std +use std::io::Write; +#[allow(deprecated, unused_imports)] +use std::ascii::AsciiExt; + +// Internal +use app::App; +use app::parser::Parser; +use args::{AnyArg, ArgSettings}; +use completions; +use INTERNAL_ERROR_MSG; + +pub struct ZshGen<'a, 'b> +where + 'a: 'b, +{ + p: &'b Parser<'a, 'b>, +} + +impl<'a, 'b> ZshGen<'a, 'b> { + pub fn new(p: &'b Parser<'a, 'b>) -> Self { + debugln!("ZshGen::new;"); + ZshGen { p: p } + } + + pub fn generate_to(&self, buf: &mut W) { + debugln!("ZshGen::generate_to;"); + w!( + buf, + format!( + "\ +#compdef {name} + +autoload -U is-at-least + +_{name}() {{ + typeset -A opt_args + typeset -a _arguments_options + local ret=1 + + if is-at-least 5.2; then + _arguments_options=(-s -S -C) + else + _arguments_options=(-s -C) + fi + + local context curcontext=\"$curcontext\" state line + {initial_args} + {subcommands} +}} + +{subcommand_details} + +_{name} \"$@\"", + name = self.p.meta.bin_name.as_ref().unwrap(), + initial_args = get_args_of(self.p), + subcommands = get_subcommands_of(self.p), + subcommand_details = subcommand_details(self.p) + ).as_bytes() + ); + } +} + +// Displays the commands of a subcommand +// (( $+functions[_[bin_name_underscore]_commands] )) || +// _[bin_name_underscore]_commands() { +// local commands; commands=( +// '[arg_name]:[arg_help]' +// ) +// _describe -t commands '[bin_name] commands' commands "$@" +// +// Where the following variables are present: +// [bin_name_underscore]: The full space delineated bin_name, where spaces have been replaced by +// underscore characters +// [arg_name]: The name of the subcommand +// [arg_help]: The help message of the subcommand +// [bin_name]: The full space delineated bin_name +// +// Here's a snippet from rustup: +// +// (( $+functions[_rustup_commands] )) || +// _rustup_commands() { +// local commands; commands=( +// 'show:Show the active and installed toolchains' +// 'update:Update Rust toolchains' +// # ... snip for brevity +// 'help:Prints this message or the help of the given subcommand(s)' +// ) +// _describe -t commands 'rustup commands' commands "$@" +// +fn subcommand_details(p: &Parser) -> String { + debugln!("ZshGen::subcommand_details;"); + // First we do ourself + let mut ret = vec![ + format!( + "\ +(( $+functions[_{bin_name_underscore}_commands] )) || +_{bin_name_underscore}_commands() {{ + local commands; commands=( + {subcommands_and_args} + ) + _describe -t commands '{bin_name} commands' commands \"$@\" +}}", + bin_name_underscore = p.meta.bin_name.as_ref().unwrap().replace(" ", "__"), + bin_name = p.meta.bin_name.as_ref().unwrap(), + subcommands_and_args = subcommands_of(p) + ), + ]; + + // Next we start looping through all the children, grandchildren, etc. + let mut all_subcommands = completions::all_subcommands(p); + all_subcommands.sort(); + all_subcommands.dedup(); + for &(_, ref bin_name) in &all_subcommands { + debugln!("ZshGen::subcommand_details:iter: bin_name={}", bin_name); + ret.push(format!( + "\ +(( $+functions[_{bin_name_underscore}_commands] )) || +_{bin_name_underscore}_commands() {{ + local commands; commands=( + {subcommands_and_args} + ) + _describe -t commands '{bin_name} commands' commands \"$@\" +}}", + bin_name_underscore = bin_name.replace(" ", "__"), + bin_name = bin_name, + subcommands_and_args = subcommands_of(parser_of(p, bin_name)) + )); + } + + ret.join("\n") +} + +// Generates subcommand completions in form of +// +// '[arg_name]:[arg_help]' +// +// Where: +// [arg_name]: the subcommand's name +// [arg_help]: the help message of the subcommand +// +// A snippet from rustup: +// 'show:Show the active and installed toolchains' +// 'update:Update Rust toolchains' +fn subcommands_of(p: &Parser) -> String { + debugln!("ZshGen::subcommands_of;"); + let mut ret = vec![]; + fn add_sc(sc: &App, n: &str, ret: &mut Vec) { + debugln!("ZshGen::add_sc;"); + let s = format!( + "\"{name}:{help}\" \\", + name = n, + help = sc.p + .meta + .about + .unwrap_or("") + .replace("[", "\\[") + .replace("]", "\\]") + ); + if !s.is_empty() { + ret.push(s); + } + } + + // The subcommands + for sc in p.subcommands() { + debugln!( + "ZshGen::subcommands_of:iter: subcommand={}", + sc.p.meta.name + ); + add_sc(sc, &sc.p.meta.name, &mut ret); + if let Some(ref v) = sc.p.meta.aliases { + for alias in v.iter().filter(|&&(_, vis)| vis).map(|&(n, _)| n) { + add_sc(sc, alias, &mut ret); + } + } + } + + ret.join("\n") +} + +// Get's the subcommand section of a completion file +// This looks roughly like: +// +// case $state in +// ([bin_name]_args) +// curcontext=\"${curcontext%:*:*}:[name_hyphen]-command-$words[1]:\" +// case $line[1] in +// +// ([name]) +// _arguments -C -s -S \ +// [subcommand_args] +// && ret=0 +// +// [RECURSIVE_CALLS] +// +// ;;", +// +// [repeat] +// +// esac +// ;; +// esac", +// +// Where the following variables are present: +// [name] = The subcommand name in the form of "install" for "rustup toolchain install" +// [bin_name] = The full space delineated bin_name such as "rustup toolchain install" +// [name_hyphen] = The full space delineated bin_name, but replace spaces with hyphens +// [repeat] = From the same recursive calls, but for all subcommands +// [subcommand_args] = The same as zsh::get_args_of +fn get_subcommands_of(p: &Parser) -> String { + debugln!("get_subcommands_of;"); + + debugln!( + "get_subcommands_of: Has subcommands...{:?}", + p.has_subcommands() + ); + if !p.has_subcommands() { + return String::new(); + } + + let sc_names = completions::subcommands_of(p); + + let mut subcmds = vec![]; + for &(ref name, ref bin_name) in &sc_names { + let mut v = vec![format!("({})", name)]; + let subcommand_args = get_args_of(parser_of(p, &*bin_name)); + if !subcommand_args.is_empty() { + v.push(subcommand_args); + } + let subcommands = get_subcommands_of(parser_of(p, &*bin_name)); + if !subcommands.is_empty() { + v.push(subcommands); + } + v.push(String::from(";;")); + subcmds.push(v.join("\n")); + } + + format!( + "case $state in + ({name}) + words=($line[{pos}] \"${{words[@]}}\") + (( CURRENT += 1 )) + curcontext=\"${{curcontext%:*:*}}:{name_hyphen}-command-$line[{pos}]:\" + case $line[{pos}] in + {subcommands} + esac + ;; +esac", + name = p.meta.name, + name_hyphen = p.meta.bin_name.as_ref().unwrap().replace(" ", "-"), + subcommands = subcmds.join("\n"), + pos = p.positionals().len() + 1 + ) +} + +fn parser_of<'a, 'b>(p: &'b Parser<'a, 'b>, sc: &str) -> &'b Parser<'a, 'b> { + debugln!("parser_of: sc={}", sc); + if sc == p.meta.bin_name.as_ref().unwrap_or(&String::new()) { + return p; + } + &p.find_subcommand(sc).expect(INTERNAL_ERROR_MSG).p +} + +// Writes out the args section, which ends up being the flags, opts and postionals, and a jump to +// another ZSH function if there are subcommands. +// The structer works like this: +// ([conflicting_args]) [multiple] arg [takes_value] [[help]] [: :(possible_values)] +// ^-- list '-v -h' ^--'*' ^--'+' ^-- list 'one two three' +// +// An example from the rustup command: +// +// _arguments -C -s -S \ +// '(-h --help --verbose)-v[Enable verbose output]' \ +// '(-V -v --version --verbose --help)-h[Prints help information]' \ +// # ... snip for brevity +// ':: :_rustup_commands' \ # <-- displays subcommands +// '*::: :->rustup' \ # <-- displays subcommand args and child subcommands +// && ret=0 +// +// The args used for _arguments are as follows: +// -C: modify the $context internal variable +// -s: Allow stacking of short args (i.e. -a -b -c => -abc) +// -S: Do not complete anything after '--' and treat those as argument values +fn get_args_of(p: &Parser) -> String { + debugln!("get_args_of;"); + let mut ret = vec![String::from("_arguments \"${_arguments_options[@]}\" \\")]; + let opts = write_opts_of(p); + let flags = write_flags_of(p); + let positionals = write_positionals_of(p); + let sc_or_a = if p.has_subcommands() { + format!( + "\":: :_{name}_commands\" \\", + name = p.meta.bin_name.as_ref().unwrap().replace(" ", "__") + ) + } else { + String::new() + }; + let sc = if p.has_subcommands() { + format!("\"*::: :->{name}\" \\", name = p.meta.name) + } else { + String::new() + }; + + if !opts.is_empty() { + ret.push(opts); + } + if !flags.is_empty() { + ret.push(flags); + } + if !positionals.is_empty() { + ret.push(positionals); + } + if !sc_or_a.is_empty() { + ret.push(sc_or_a); + } + if !sc.is_empty() { + ret.push(sc); + } + ret.push(String::from("&& ret=0")); + + ret.join("\n") +} + +// Escape help string inside single quotes and brackets +fn escape_help(string: &str) -> String { + string + .replace("\\", "\\\\") + .replace("'", "'\\''") + .replace("[", "\\[") + .replace("]", "\\]") +} + +// Escape value string inside single quotes and parentheses +fn escape_value(string: &str) -> String { + string + .replace("\\", "\\\\") + .replace("'", "'\\''") + .replace("(", "\\(") + .replace(")", "\\)") + .replace(" ", "\\ ") +} + +fn write_opts_of(p: &Parser) -> String { + debugln!("write_opts_of;"); + let mut ret = vec![]; + for o in p.opts() { + debugln!("write_opts_of:iter: o={}", o.name()); + let help = o.help().map_or(String::new(), escape_help); + let mut conflicts = get_zsh_arg_conflicts!(p, o, INTERNAL_ERROR_MSG); + conflicts = if conflicts.is_empty() { + String::new() + } else { + format!("({})", conflicts) + }; + + let multiple = if o.is_set(ArgSettings::Multiple) { + "*" + } else { + "" + }; + let pv = if let Some(pv_vec) = o.possible_vals() { + format!(": :({})", pv_vec.iter().map( + |v| escape_value(*v)).collect::>().join(" ")) + } else { + String::new() + }; + if let Some(short) = o.short() { + let s = format!( + "'{conflicts}{multiple}-{arg}+[{help}]{possible_values}' \\", + conflicts = conflicts, + multiple = multiple, + arg = short, + possible_values = pv, + help = help + ); + + debugln!("write_opts_of:iter: Wrote...{}", &*s); + ret.push(s); + } + if let Some(long) = o.long() { + let l = format!( + "'{conflicts}{multiple}--{arg}=[{help}]{possible_values}' \\", + conflicts = conflicts, + multiple = multiple, + arg = long, + possible_values = pv, + help = help + ); + + debugln!("write_opts_of:iter: Wrote...{}", &*l); + ret.push(l); + } + } + + ret.join("\n") +} + +fn write_flags_of(p: &Parser) -> String { + debugln!("write_flags_of;"); + let mut ret = vec![]; + for f in p.flags() { + debugln!("write_flags_of:iter: f={}", f.name()); + let help = f.help().map_or(String::new(), escape_help); + let mut conflicts = get_zsh_arg_conflicts!(p, f, INTERNAL_ERROR_MSG); + conflicts = if conflicts.is_empty() { + String::new() + } else { + format!("({})", conflicts) + }; + + let multiple = if f.is_set(ArgSettings::Multiple) { + "*" + } else { + "" + }; + if let Some(short) = f.short() { + let s = format!( + "'{conflicts}{multiple}-{arg}[{help}]' \\", + multiple = multiple, + conflicts = conflicts, + arg = short, + help = help + ); + + debugln!("write_flags_of:iter: Wrote...{}", &*s); + ret.push(s); + } + + if let Some(long) = f.long() { + let l = format!( + "'{conflicts}{multiple}--{arg}[{help}]' \\", + conflicts = conflicts, + multiple = multiple, + arg = long, + help = help + ); + + debugln!("write_flags_of:iter: Wrote...{}", &*l); + ret.push(l); + } + } + + ret.join("\n") +} + +fn write_positionals_of(p: &Parser) -> String { + debugln!("write_positionals_of;"); + let mut ret = vec![]; + for arg in p.positionals() { + debugln!("write_positionals_of:iter: arg={}", arg.b.name); + let a = format!( + "'{optional}:{name}{help}:{action}' \\", + optional = if !arg.b.is_set(ArgSettings::Required) { ":" } else { "" }, + name = arg.b.name, + help = arg.b + .help + .map_or("".to_owned(), |v| " -- ".to_owned() + v) + .replace("[", "\\[") + .replace("]", "\\]"), + action = arg.possible_vals().map_or("_files".to_owned(), |values| { + format!("({})", + values.iter().map(|v| escape_value(*v)).collect::>().join(" ")) + }) + ); + + debugln!("write_positionals_of:iter: Wrote...{}", a); + ret.push(a); + } + + ret.join("\n") +} diff --git a/clap/src/errors.rs b/clap/src/errors.rs new file mode 100644 index 0000000..c6087c0 --- /dev/null +++ b/clap/src/errors.rs @@ -0,0 +1,912 @@ +// Std +use std::convert::From; +use std::error::Error as StdError; +use std::fmt as std_fmt; +use std::fmt::Display; +use std::io::{self, Write}; +use std::process; +use std::result::Result as StdResult; + +// Internal +use args::AnyArg; +use fmt::{ColorWhen, Colorizer, ColorizerOption}; +use suggestions; + +/// Short hand for [`Result`] type +/// +/// [`Result`]: https://doc.rust-lang.org/std/result/enum.Result.html +pub type Result = StdResult; + +/// Command line argument parser kind of error +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum ErrorKind { + /// Occurs when an [`Arg`] has a set of possible values, + /// and the user provides a value which isn't in that set. + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg, ErrorKind}; + /// let result = App::new("prog") + /// .arg(Arg::with_name("speed") + /// .possible_value("fast") + /// .possible_value("slow")) + /// .get_matches_from_safe(vec!["prog", "other"]); + /// assert!(result.is_err()); + /// assert_eq!(result.unwrap_err().kind, ErrorKind::InvalidValue); + /// ``` + /// [`Arg`]: ./struct.Arg.html + InvalidValue, + + /// Occurs when a user provides a flag, option, argument or subcommand which isn't defined. + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg, ErrorKind}; + /// let result = App::new("prog") + /// .arg(Arg::from_usage("--flag 'some flag'")) + /// .get_matches_from_safe(vec!["prog", "--other"]); + /// assert!(result.is_err()); + /// assert_eq!(result.unwrap_err().kind, ErrorKind::UnknownArgument); + /// ``` + UnknownArgument, + + /// Occurs when the user provides an unrecognized [`SubCommand`] which meets the threshold for + /// being similar enough to an existing subcommand. + /// If it doesn't meet the threshold, or the 'suggestions' feature is disabled, + /// the more general [`UnknownArgument`] error is returned. + /// + /// # Examples + /// + #[cfg_attr(not(feature = "suggestions"), doc = " ```no_run")] + #[cfg_attr(feature = "suggestions", doc = " ```")] + /// # use clap::{App, Arg, ErrorKind, SubCommand}; + /// let result = App::new("prog") + /// .subcommand(SubCommand::with_name("config") + /// .about("Used for configuration") + /// .arg(Arg::with_name("config_file") + /// .help("The configuration file to use") + /// .index(1))) + /// .get_matches_from_safe(vec!["prog", "confi"]); + /// assert!(result.is_err()); + /// assert_eq!(result.unwrap_err().kind, ErrorKind::InvalidSubcommand); + /// ``` + /// [`SubCommand`]: ./struct.SubCommand.html + /// [`UnknownArgument`]: ./enum.ErrorKind.html#variant.UnknownArgument + InvalidSubcommand, + + /// Occurs when the user provides an unrecognized [`SubCommand`] which either + /// doesn't meet the threshold for being similar enough to an existing subcommand, + /// or the 'suggestions' feature is disabled. + /// Otherwise the more detailed [`InvalidSubcommand`] error is returned. + /// + /// This error typically happens when passing additional subcommand names to the `help` + /// subcommand. Otherwise, the more general [`UnknownArgument`] error is used. + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg, ErrorKind, SubCommand}; + /// let result = App::new("prog") + /// .subcommand(SubCommand::with_name("config") + /// .about("Used for configuration") + /// .arg(Arg::with_name("config_file") + /// .help("The configuration file to use") + /// .index(1))) + /// .get_matches_from_safe(vec!["prog", "help", "nothing"]); + /// assert!(result.is_err()); + /// assert_eq!(result.unwrap_err().kind, ErrorKind::UnrecognizedSubcommand); + /// ``` + /// [`SubCommand`]: ./struct.SubCommand.html + /// [`InvalidSubcommand`]: ./enum.ErrorKind.html#variant.InvalidSubcommand + /// [`UnknownArgument`]: ./enum.ErrorKind.html#variant.UnknownArgument + UnrecognizedSubcommand, + + /// Occurs when the user provides an empty value for an option that does not allow empty + /// values. + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg, ErrorKind}; + /// let res = App::new("prog") + /// .arg(Arg::with_name("color") + /// .long("color") + /// .empty_values(false)) + /// .get_matches_from_safe(vec!["prog", "--color="]); + /// assert!(res.is_err()); + /// assert_eq!(res.unwrap_err().kind, ErrorKind::EmptyValue); + /// ``` + EmptyValue, + + /// Occurs when the user provides a value for an argument with a custom validation and the + /// value fails that validation. + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg, ErrorKind}; + /// fn is_numeric(val: String) -> Result<(), String> { + /// match val.parse::() { + /// Ok(..) => Ok(()), + /// Err(..) => Err(String::from("Value wasn't a number!")), + /// } + /// } + /// + /// let result = App::new("prog") + /// .arg(Arg::with_name("num") + /// .validator(is_numeric)) + /// .get_matches_from_safe(vec!["prog", "NotANumber"]); + /// assert!(result.is_err()); + /// assert_eq!(result.unwrap_err().kind, ErrorKind::ValueValidation); + /// ``` + ValueValidation, + + /// Occurs when a user provides more values for an argument than were defined by setting + /// [`Arg::max_values`]. + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg, ErrorKind}; + /// let result = App::new("prog") + /// .arg(Arg::with_name("arg") + /// .multiple(true) + /// .max_values(2)) + /// .get_matches_from_safe(vec!["prog", "too", "many", "values"]); + /// assert!(result.is_err()); + /// assert_eq!(result.unwrap_err().kind, ErrorKind::TooManyValues); + /// ``` + /// [`Arg::max_values`]: ./struct.Arg.html#method.max_values + TooManyValues, + + /// Occurs when the user provides fewer values for an argument than were defined by setting + /// [`Arg::min_values`]. + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg, ErrorKind}; + /// let result = App::new("prog") + /// .arg(Arg::with_name("some_opt") + /// .long("opt") + /// .min_values(3)) + /// .get_matches_from_safe(vec!["prog", "--opt", "too", "few"]); + /// assert!(result.is_err()); + /// assert_eq!(result.unwrap_err().kind, ErrorKind::TooFewValues); + /// ``` + /// [`Arg::min_values`]: ./struct.Arg.html#method.min_values + TooFewValues, + + /// Occurs when the user provides a different number of values for an argument than what's + /// been defined by setting [`Arg::number_of_values`] or than was implicitly set by + /// [`Arg::value_names`]. + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg, ErrorKind}; + /// let result = App::new("prog") + /// .arg(Arg::with_name("some_opt") + /// .long("opt") + /// .takes_value(true) + /// .number_of_values(2)) + /// .get_matches_from_safe(vec!["prog", "--opt", "wrong"]); + /// assert!(result.is_err()); + /// assert_eq!(result.unwrap_err().kind, ErrorKind::WrongNumberOfValues); + /// ``` + /// + /// [`Arg::number_of_values`]: ./struct.Arg.html#method.number_of_values + /// [`Arg::value_names`]: ./struct.Arg.html#method.value_names + WrongNumberOfValues, + + /// Occurs when the user provides two values which conflict with each other and can't be used + /// together. + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg, ErrorKind}; + /// let result = App::new("prog") + /// .arg(Arg::with_name("debug") + /// .long("debug") + /// .conflicts_with("color")) + /// .arg(Arg::with_name("color") + /// .long("color")) + /// .get_matches_from_safe(vec!["prog", "--debug", "--color"]); + /// assert!(result.is_err()); + /// assert_eq!(result.unwrap_err().kind, ErrorKind::ArgumentConflict); + /// ``` + ArgumentConflict, + + /// Occurs when the user does not provide one or more required arguments. + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg, ErrorKind}; + /// let result = App::new("prog") + /// .arg(Arg::with_name("debug") + /// .required(true)) + /// .get_matches_from_safe(vec!["prog"]); + /// assert!(result.is_err()); + /// assert_eq!(result.unwrap_err().kind, ErrorKind::MissingRequiredArgument); + /// ``` + MissingRequiredArgument, + + /// Occurs when a subcommand is required (as defined by [`AppSettings::SubcommandRequired`]), + /// but the user does not provide one. + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, AppSettings, SubCommand, ErrorKind}; + /// let err = App::new("prog") + /// .setting(AppSettings::SubcommandRequired) + /// .subcommand(SubCommand::with_name("test")) + /// .get_matches_from_safe(vec![ + /// "myprog", + /// ]); + /// assert!(err.is_err()); + /// assert_eq!(err.unwrap_err().kind, ErrorKind::MissingSubcommand); + /// # ; + /// ``` + /// [`AppSettings::SubcommandRequired`]: ./enum.AppSettings.html#variant.SubcommandRequired + MissingSubcommand, + + /// Occurs when either an argument or [`SubCommand`] is required, as defined by + /// [`AppSettings::ArgRequiredElseHelp`], but the user did not provide one. + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg, AppSettings, ErrorKind, SubCommand}; + /// let result = App::new("prog") + /// .setting(AppSettings::ArgRequiredElseHelp) + /// .subcommand(SubCommand::with_name("config") + /// .about("Used for configuration") + /// .arg(Arg::with_name("config_file") + /// .help("The configuration file to use"))) + /// .get_matches_from_safe(vec!["prog"]); + /// assert!(result.is_err()); + /// assert_eq!(result.unwrap_err().kind, ErrorKind::MissingArgumentOrSubcommand); + /// ``` + /// [`SubCommand`]: ./struct.SubCommand.html + /// [`AppSettings::ArgRequiredElseHelp`]: ./enum.AppSettings.html#variant.ArgRequiredElseHelp + MissingArgumentOrSubcommand, + + /// Occurs when the user provides multiple values to an argument which doesn't allow that. + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg, ErrorKind}; + /// let result = App::new("prog") + /// .arg(Arg::with_name("debug") + /// .long("debug") + /// .multiple(false)) + /// .get_matches_from_safe(vec!["prog", "--debug", "--debug"]); + /// assert!(result.is_err()); + /// assert_eq!(result.unwrap_err().kind, ErrorKind::UnexpectedMultipleUsage); + /// ``` + UnexpectedMultipleUsage, + + /// Occurs when the user provides a value containing invalid UTF-8 for an argument and + /// [`AppSettings::StrictUtf8`] is set. + /// + /// # Platform Specific + /// + /// Non-Windows platforms only (such as Linux, Unix, macOS, etc.) + /// + /// # Examples + /// + #[cfg_attr(not(unix), doc = " ```ignore")] + #[cfg_attr(unix, doc = " ```")] + /// # use clap::{App, Arg, ErrorKind, AppSettings}; + /// # use std::os::unix::ffi::OsStringExt; + /// # use std::ffi::OsString; + /// let result = App::new("prog") + /// .setting(AppSettings::StrictUtf8) + /// .arg(Arg::with_name("utf8") + /// .short("u") + /// .takes_value(true)) + /// .get_matches_from_safe(vec![OsString::from("myprog"), + /// OsString::from("-u"), + /// OsString::from_vec(vec![0xE9])]); + /// assert!(result.is_err()); + /// assert_eq!(result.unwrap_err().kind, ErrorKind::InvalidUtf8); + /// ``` + /// [`AppSettings::StrictUtf8`]: ./enum.AppSettings.html#variant.StrictUtf8 + InvalidUtf8, + + /// Not a true "error" as it means `--help` or similar was used. + /// The help message will be sent to `stdout`. + /// + /// **Note**: If the help is displayed due to an error (such as missing subcommands) it will + /// be sent to `stderr` instead of `stdout`. + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg, ErrorKind}; + /// let result = App::new("prog") + /// .get_matches_from_safe(vec!["prog", "--help"]); + /// assert!(result.is_err()); + /// assert_eq!(result.unwrap_err().kind, ErrorKind::HelpDisplayed); + /// ``` + HelpDisplayed, + + /// Not a true "error" as it means `--version` or similar was used. + /// The message will be sent to `stdout`. + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg, ErrorKind}; + /// let result = App::new("prog") + /// .get_matches_from_safe(vec!["prog", "--version"]); + /// assert!(result.is_err()); + /// assert_eq!(result.unwrap_err().kind, ErrorKind::VersionDisplayed); + /// ``` + VersionDisplayed, + + /// Occurs when using the [`value_t!`] and [`values_t!`] macros to convert an argument value + /// into type `T`, but the argument you requested wasn't used. I.e. you asked for an argument + /// with name `config` to be converted, but `config` wasn't used by the user. + /// [`value_t!`]: ./macro.value_t!.html + /// [`values_t!`]: ./macro.values_t!.html + ArgumentNotFound, + + /// Represents an [I/O error]. + /// Can occur when writing to `stderr` or `stdout` or reading a configuration file. + /// [I/O error]: https://doc.rust-lang.org/std/io/struct.Error.html + Io, + + /// Represents a [Format error] (which is a part of [`Display`]). + /// Typically caused by writing to `stderr` or `stdout`. + /// + /// [`Display`]: https://doc.rust-lang.org/std/fmt/trait.Display.html + /// [Format error]: https://doc.rust-lang.org/std/fmt/struct.Error.html + Format, +} + +/// Command Line Argument Parser Error +#[derive(Debug)] +pub struct Error { + /// Formatted error message + pub message: String, + /// The type of error + pub kind: ErrorKind, + /// Any additional information passed along, such as the argument name that caused the error + pub info: Option>, +} + +impl Error { + /// Should the message be written to `stdout` or not + pub fn use_stderr(&self) -> bool { + match self.kind { + ErrorKind::HelpDisplayed | ErrorKind::VersionDisplayed => false, + _ => true, + } + } + + /// Prints the error to `stderr` and exits with a status of `1` + pub fn exit(&self) -> ! { + if self.use_stderr() { + wlnerr!("{}", self.message); + process::exit(1); + } + let out = io::stdout(); + writeln!(&mut out.lock(), "{}", self.message).expect("Error writing Error to stdout"); + process::exit(0); + } + + #[doc(hidden)] + pub fn write_to(&self, w: &mut W) -> io::Result<()> { write!(w, "{}", self.message) } + + #[doc(hidden)] + pub fn argument_conflict( + arg: &AnyArg, + other: Option, + usage: U, + color: ColorWhen, + ) -> Self + where + O: Into, + U: Display, + { + let mut v = vec![arg.name().to_owned()]; + let c = Colorizer::new(ColorizerOption { + use_stderr: true, + when: color, + }); + Error { + message: format!( + "{} The argument '{}' cannot be used with {}\n\n\ + {}\n\n\ + For more information try {}", + c.error("error:"), + c.warning(&*arg.to_string()), + match other { + Some(name) => { + let n = name.into(); + v.push(n.clone()); + c.warning(format!("'{}'", n)) + } + None => c.none("one or more of the other specified arguments".to_owned()), + }, + usage, + c.good("--help") + ), + kind: ErrorKind::ArgumentConflict, + info: Some(v), + } + } + + #[doc(hidden)] + pub fn empty_value(arg: &AnyArg, usage: U, color: ColorWhen) -> Self + where + U: Display, + { + let c = Colorizer::new(ColorizerOption { + use_stderr: true, + when: color, + }); + Error { + message: format!( + "{} The argument '{}' requires a value but none was supplied\ + \n\n\ + {}\n\n\ + For more information try {}", + c.error("error:"), + c.warning(arg.to_string()), + usage, + c.good("--help") + ), + kind: ErrorKind::EmptyValue, + info: Some(vec![arg.name().to_owned()]), + } + } + + #[doc(hidden)] + pub fn invalid_value( + bad_val: B, + good_vals: &[G], + arg: &AnyArg, + usage: U, + color: ColorWhen, + ) -> Self + where + B: AsRef, + G: AsRef + Display, + U: Display, + { + let c = Colorizer::new(ColorizerOption { + use_stderr: true, + when: color, + }); + let suffix = suggestions::did_you_mean_value_suffix(bad_val.as_ref(), good_vals.iter()); + + let mut sorted = vec![]; + for v in good_vals { + let val = format!("{}", c.good(v)); + sorted.push(val); + } + sorted.sort(); + let valid_values = sorted.join(", "); + Error { + message: format!( + "{} '{}' isn't a valid value for '{}'\n\t\ + [possible values: {}]\n\ + {}\n\n\ + {}\n\n\ + For more information try {}", + c.error("error:"), + c.warning(bad_val.as_ref()), + c.warning(arg.to_string()), + valid_values, + suffix.0, + usage, + c.good("--help") + ), + kind: ErrorKind::InvalidValue, + info: Some(vec![arg.name().to_owned(), bad_val.as_ref().to_owned()]), + } + } + + #[doc(hidden)] + pub fn invalid_subcommand( + subcmd: S, + did_you_mean: D, + name: N, + usage: U, + color: ColorWhen, + ) -> Self + where + S: Into, + D: AsRef + Display, + N: Display, + U: Display, + { + let s = subcmd.into(); + let c = Colorizer::new(ColorizerOption { + use_stderr: true, + when: color, + }); + Error { + message: format!( + "{} The subcommand '{}' wasn't recognized\n\t\ + Did you mean '{}'?\n\n\ + If you believe you received this message in error, try \ + re-running with '{} {} {}'\n\n\ + {}\n\n\ + For more information try {}", + c.error("error:"), + c.warning(&*s), + c.good(did_you_mean.as_ref()), + name, + c.good("--"), + &*s, + usage, + c.good("--help") + ), + kind: ErrorKind::InvalidSubcommand, + info: Some(vec![s]), + } + } + + #[doc(hidden)] + pub fn unrecognized_subcommand(subcmd: S, name: N, color: ColorWhen) -> Self + where + S: Into, + N: Display, + { + let s = subcmd.into(); + let c = Colorizer::new(ColorizerOption { + use_stderr: true, + when: color, + }); + Error { + message: format!( + "{} The subcommand '{}' wasn't recognized\n\n\ + {}\n\t\ + {} help ...\n\n\ + For more information try {}", + c.error("error:"), + c.warning(&*s), + c.warning("USAGE:"), + name, + c.good("--help") + ), + kind: ErrorKind::UnrecognizedSubcommand, + info: Some(vec![s]), + } + } + + #[doc(hidden)] + pub fn missing_required_argument(required: R, usage: U, color: ColorWhen) -> Self + where + R: Display, + U: Display, + { + let c = Colorizer::new(ColorizerOption { + use_stderr: true, + when: color, + }); + Error { + message: format!( + "{} The following required arguments were not provided:{}\n\n\ + {}\n\n\ + For more information try {}", + c.error("error:"), + required, + usage, + c.good("--help") + ), + kind: ErrorKind::MissingRequiredArgument, + info: None, + } + } + + #[doc(hidden)] + pub fn missing_subcommand(name: N, usage: U, color: ColorWhen) -> Self + where + N: AsRef + Display, + U: Display, + { + let c = Colorizer::new(ColorizerOption { + use_stderr: true, + when: color, + }); + Error { + message: format!( + "{} '{}' requires a subcommand, but one was not provided\n\n\ + {}\n\n\ + For more information try {}", + c.error("error:"), + c.warning(name), + usage, + c.good("--help") + ), + kind: ErrorKind::MissingSubcommand, + info: None, + } + } + + + #[doc(hidden)] + pub fn invalid_utf8(usage: U, color: ColorWhen) -> Self + where + U: Display, + { + let c = Colorizer::new(ColorizerOption { + use_stderr: true, + when: color, + }); + Error { + message: format!( + "{} Invalid UTF-8 was detected in one or more arguments\n\n\ + {}\n\n\ + For more information try {}", + c.error("error:"), + usage, + c.good("--help") + ), + kind: ErrorKind::InvalidUtf8, + info: None, + } + } + + #[doc(hidden)] + pub fn too_many_values(val: V, arg: &AnyArg, usage: U, color: ColorWhen) -> Self + where + V: AsRef + Display + ToOwned, + U: Display, + { + let v = val.as_ref(); + let c = Colorizer::new(ColorizerOption { + use_stderr: true, + when: color, + }); + Error { + message: format!( + "{} The value '{}' was provided to '{}', but it wasn't expecting \ + any more values\n\n\ + {}\n\n\ + For more information try {}", + c.error("error:"), + c.warning(v), + c.warning(arg.to_string()), + usage, + c.good("--help") + ), + kind: ErrorKind::TooManyValues, + info: Some(vec![arg.name().to_owned(), v.to_owned()]), + } + } + + #[doc(hidden)] + pub fn too_few_values( + arg: &AnyArg, + min_vals: u64, + curr_vals: usize, + usage: U, + color: ColorWhen, + ) -> Self + where + U: Display, + { + let c = Colorizer::new(ColorizerOption { + use_stderr: true, + when: color, + }); + Error { + message: format!( + "{} The argument '{}' requires at least {} values, but only {} w{} \ + provided\n\n\ + {}\n\n\ + For more information try {}", + c.error("error:"), + c.warning(arg.to_string()), + c.warning(min_vals.to_string()), + c.warning(curr_vals.to_string()), + if curr_vals > 1 { "ere" } else { "as" }, + usage, + c.good("--help") + ), + kind: ErrorKind::TooFewValues, + info: Some(vec![arg.name().to_owned()]), + } + } + + #[doc(hidden)] + pub fn value_validation(arg: Option<&AnyArg>, err: String, color: ColorWhen) -> Self + { + let c = Colorizer::new(ColorizerOption { + use_stderr: true, + when: color, + }); + Error { + message: format!( + "{} Invalid value{}: {}", + c.error("error:"), + if let Some(a) = arg { + format!(" for '{}'", c.warning(a.to_string())) + } else { + "".to_string() + }, + err + ), + kind: ErrorKind::ValueValidation, + info: None, + } + } + + #[doc(hidden)] + pub fn value_validation_auto(err: String) -> Self { + let n: Option<&AnyArg> = None; + Error::value_validation(n, err, ColorWhen::Auto) + } + + #[doc(hidden)] + pub fn wrong_number_of_values( + arg: &AnyArg, + num_vals: u64, + curr_vals: usize, + suffix: S, + usage: U, + color: ColorWhen, + ) -> Self + where + S: Display, + U: Display, + { + let c = Colorizer::new(ColorizerOption { + use_stderr: true, + when: color, + }); + Error { + message: format!( + "{} The argument '{}' requires {} values, but {} w{} \ + provided\n\n\ + {}\n\n\ + For more information try {}", + c.error("error:"), + c.warning(arg.to_string()), + c.warning(num_vals.to_string()), + c.warning(curr_vals.to_string()), + suffix, + usage, + c.good("--help") + ), + kind: ErrorKind::WrongNumberOfValues, + info: Some(vec![arg.name().to_owned()]), + } + } + + #[doc(hidden)] + pub fn unexpected_multiple_usage(arg: &AnyArg, usage: U, color: ColorWhen) -> Self + where + U: Display, + { + let c = Colorizer::new(ColorizerOption { + use_stderr: true, + when: color, + }); + Error { + message: format!( + "{} The argument '{}' was provided more than once, but cannot \ + be used multiple times\n\n\ + {}\n\n\ + For more information try {}", + c.error("error:"), + c.warning(arg.to_string()), + usage, + c.good("--help") + ), + kind: ErrorKind::UnexpectedMultipleUsage, + info: Some(vec![arg.name().to_owned()]), + } + } + + #[doc(hidden)] + pub fn unknown_argument(arg: A, did_you_mean: &str, usage: U, color: ColorWhen) -> Self + where + A: Into, + U: Display, + { + let a = arg.into(); + let c = Colorizer::new(ColorizerOption { + use_stderr: true, + when: color, + }); + Error { + message: format!( + "{} Found argument '{}' which wasn't expected, or isn't valid in \ + this context{}\n\ + {}\n\n\ + For more information try {}", + c.error("error:"), + c.warning(&*a), + if did_you_mean.is_empty() { + "\n".to_owned() + } else { + format!("{}\n", did_you_mean) + }, + usage, + c.good("--help") + ), + kind: ErrorKind::UnknownArgument, + info: Some(vec![a]), + } + } + + #[doc(hidden)] + pub fn io_error(e: &Error, color: ColorWhen) -> Self { + let c = Colorizer::new(ColorizerOption { + use_stderr: true, + when: color, + }); + Error { + message: format!("{} {}", c.error("error:"), e.description()), + kind: ErrorKind::Io, + info: None, + } + } + + #[doc(hidden)] + pub fn argument_not_found_auto(arg: A) -> Self + where + A: Into, + { + let a = arg.into(); + let c = Colorizer::new(ColorizerOption { + use_stderr: true, + when: ColorWhen::Auto, + }); + Error { + message: format!( + "{} The argument '{}' wasn't found", + c.error("error:"), + a.clone() + ), + kind: ErrorKind::ArgumentNotFound, + info: Some(vec![a]), + } + } + + /// Create an error with a custom description. + /// + /// This can be used in combination with `Error::exit` to exit your program + /// with a custom error message. + pub fn with_description(description: &str, kind: ErrorKind) -> Self { + let c = Colorizer::new(ColorizerOption { + use_stderr: true, + when: ColorWhen::Auto, + }); + Error { + message: format!("{} {}", c.error("error:"), description), + kind: kind, + info: None, + } + } +} + +impl StdError for Error { + fn description(&self) -> &str { &*self.message } +} + +impl Display for Error { + fn fmt(&self, f: &mut std_fmt::Formatter) -> std_fmt::Result { writeln!(f, "{}", self.message) } +} + +impl From for Error { + fn from(e: io::Error) -> Self { Error::with_description(e.description(), ErrorKind::Io) } +} + +impl From for Error { + fn from(e: std_fmt::Error) -> Self { + Error::with_description(e.description(), ErrorKind::Format) + } +} diff --git a/clap/src/fmt.rs b/clap/src/fmt.rs new file mode 100644 index 0000000..108a635 --- /dev/null +++ b/clap/src/fmt.rs @@ -0,0 +1,189 @@ +#[cfg(all(feature = "color", not(target_os = "windows")))] +use ansi_term::ANSIString; + +#[cfg(all(feature = "color", not(target_os = "windows")))] +use ansi_term::Colour::{Green, Red, Yellow}; + +#[cfg(feature = "color")] +use atty; +use std::fmt; +use std::env; + +#[doc(hidden)] +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum ColorWhen { + Auto, + Always, + Never, +} + +#[cfg(feature = "color")] +pub fn is_a_tty(stderr: bool) -> bool { + debugln!("is_a_tty: stderr={:?}", stderr); + let stream = if stderr { + atty::Stream::Stderr + } else { + atty::Stream::Stdout + }; + atty::is(stream) +} + +#[cfg(not(feature = "color"))] +pub fn is_a_tty(_: bool) -> bool { + debugln!("is_a_tty;"); + false +} + +pub fn is_term_dumb() -> bool { env::var("TERM").ok() == Some(String::from("dumb")) } + +#[doc(hidden)] +pub struct ColorizerOption { + pub use_stderr: bool, + pub when: ColorWhen, +} + +#[doc(hidden)] +pub struct Colorizer { + when: ColorWhen, +} + +macro_rules! color { + ($_self:ident, $c:ident, $m:expr) => { + match $_self.when { + ColorWhen::Auto => Format::$c($m), + ColorWhen::Always => Format::$c($m), + ColorWhen::Never => Format::None($m), + } + }; +} + +impl Colorizer { + pub fn new(option: ColorizerOption) -> Colorizer { + let is_a_tty = is_a_tty(option.use_stderr); + let is_term_dumb = is_term_dumb(); + Colorizer { + when: match option.when { + ColorWhen::Auto if is_a_tty && !is_term_dumb => ColorWhen::Auto, + ColorWhen::Auto => ColorWhen::Never, + when => when, + } + } + } + + pub fn good(&self, msg: T) -> Format + where + T: fmt::Display + AsRef, + { + debugln!("Colorizer::good;"); + color!(self, Good, msg) + } + + pub fn warning(&self, msg: T) -> Format + where + T: fmt::Display + AsRef, + { + debugln!("Colorizer::warning;"); + color!(self, Warning, msg) + } + + pub fn error(&self, msg: T) -> Format + where + T: fmt::Display + AsRef, + { + debugln!("Colorizer::error;"); + color!(self, Error, msg) + } + + pub fn none(&self, msg: T) -> Format + where + T: fmt::Display + AsRef, + { + debugln!("Colorizer::none;"); + Format::None(msg) + } +} + +impl Default for Colorizer { + fn default() -> Self { + Colorizer::new(ColorizerOption { + use_stderr: true, + when: ColorWhen::Auto, + }) + } +} + +/// Defines styles for different types of error messages. Defaults to Error=Red, Warning=Yellow, +/// and Good=Green +#[derive(Debug)] +#[doc(hidden)] +pub enum Format { + /// Defines the style used for errors, defaults to Red + Error(T), + /// Defines the style used for warnings, defaults to Yellow + Warning(T), + /// Defines the style used for good values, defaults to Green + Good(T), + /// Defines no formatting style + None(T), +} + +#[cfg(all(feature = "color", not(target_os = "windows")))] +impl> Format { + fn format(&self) -> ANSIString { + match *self { + Format::Error(ref e) => Red.bold().paint(e.as_ref()), + Format::Warning(ref e) => Yellow.paint(e.as_ref()), + Format::Good(ref e) => Green.paint(e.as_ref()), + Format::None(ref e) => ANSIString::from(e.as_ref()), + } + } +} + +#[cfg(any(not(feature = "color"), target_os = "windows"))] +#[cfg_attr(feature = "lints", allow(match_same_arms))] +impl Format { + fn format(&self) -> &T { + match *self { + Format::Error(ref e) => e, + Format::Warning(ref e) => e, + Format::Good(ref e) => e, + Format::None(ref e) => e, + } + } +} + + +#[cfg(all(feature = "color", not(target_os = "windows")))] +impl> fmt::Display for Format { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", &self.format()) } +} + +#[cfg(any(not(feature = "color"), target_os = "windows"))] +impl fmt::Display for Format { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", &self.format()) } +} + +#[cfg(all(test, feature = "color", not(target_os = "windows")))] +mod test { + use ansi_term::ANSIString; + use ansi_term::Colour::{Green, Red, Yellow}; + use super::Format; + + #[test] + fn colored_output() { + let err = Format::Error("error"); + assert_eq!( + &*format!("{}", err), + &*format!("{}", Red.bold().paint("error")) + ); + let good = Format::Good("good"); + assert_eq!(&*format!("{}", good), &*format!("{}", Green.paint("good"))); + let warn = Format::Warning("warn"); + assert_eq!(&*format!("{}", warn), &*format!("{}", Yellow.paint("warn"))); + let none = Format::None("none"); + assert_eq!( + &*format!("{}", none), + &*format!("{}", ANSIString::from("none")) + ); + } +} diff --git a/clap/src/lib.rs b/clap/src/lib.rs new file mode 100644 index 0000000..0a3e1bb --- /dev/null +++ b/clap/src/lib.rs @@ -0,0 +1,629 @@ +// Copyright ⓒ 2015-2016 Kevin B. Knapp and [`clap-rs` contributors](https://github.com/clap-rs/clap/blob/master/CONTRIBUTORS.md). +// Licensed under the MIT license +// (see LICENSE or ) All files in the project carrying such +// notice may not be copied, modified, or distributed except according to those terms. + +//! `clap` is a simple-to-use, efficient, and full-featured library for parsing command line +//! arguments and subcommands when writing console/terminal applications. +//! +//! ## About +//! +//! `clap` is used to parse *and validate* the string of command line arguments provided by the user +//! at runtime. You provide the list of valid possibilities, and `clap` handles the rest. This means +//! you focus on your *applications* functionality, and less on the parsing and validating of +//! arguments. +//! +//! `clap` also provides the traditional version and help switches (or flags) 'for free' meaning +//! automatically with no configuration. It does this by checking list of valid possibilities you +//! supplied and adding only the ones you haven't already defined. If you are using subcommands, +//! `clap` will also auto-generate a `help` subcommand for you in addition to the traditional flags. +//! +//! Once `clap` parses the user provided string of arguments, it returns the matches along with any +//! applicable values. If the user made an error or typo, `clap` informs them of the mistake and +//! exits gracefully (or returns a `Result` type and allows you to perform any clean up prior to +//! exit). Because of this, you can make reasonable assumptions in your code about the validity of +//! the arguments. +//! +//! +//! ## Quick Example +//! +//! The following examples show a quick example of some of the very basic functionality of `clap`. +//! For more advanced usage, such as requirements, conflicts, groups, multiple values and +//! occurrences see the [documentation](https://docs.rs/clap/), [examples/] directory of +//! this repository or the [video tutorials]. +//! +//! **NOTE:** All of these examples are functionally the same, but show different styles in which to +//! use `clap` +//! +//! The first example shows a method that allows more advanced configuration options (not shown in +//! this small example), or even dynamically generating arguments when desired. The downside is it's +//! more verbose. +//! +//! ```no_run +//! // (Full example with detailed comments in examples/01b_quick_example.rs) +//! // +//! // This example demonstrates clap's full 'builder pattern' style of creating arguments which is +//! // more verbose, but allows easier editing, and at times more advanced options, or the possibility +//! // to generate arguments dynamically. +//! extern crate clap; +//! use clap::{Arg, App, SubCommand}; +//! +//! fn main() { +//! let matches = App::new("My Super Program") +//! .version("1.0") +//! .author("Kevin K. ") +//! .about("Does awesome things") +//! .arg(Arg::with_name("config") +//! .short("c") +//! .long("config") +//! .value_name("FILE") +//! .help("Sets a custom config file") +//! .takes_value(true)) +//! .arg(Arg::with_name("INPUT") +//! .help("Sets the input file to use") +//! .required(true) +//! .index(1)) +//! .arg(Arg::with_name("v") +//! .short("v") +//! .multiple(true) +//! .help("Sets the level of verbosity")) +//! .subcommand(SubCommand::with_name("test") +//! .about("controls testing features") +//! .version("1.3") +//! .author("Someone E. ") +//! .arg(Arg::with_name("debug") +//! .short("d") +//! .help("print debug information verbosely"))) +//! .get_matches(); +//! +//! // Gets a value for config if supplied by user, or defaults to "default.conf" +//! let config = matches.value_of("config").unwrap_or("default.conf"); +//! println!("Value for config: {}", config); +//! +//! // Calling .unwrap() is safe here because "INPUT" is required (if "INPUT" wasn't +//! // required we could have used an 'if let' to conditionally get the value) +//! println!("Using input file: {}", matches.value_of("INPUT").unwrap()); +//! +//! // Vary the output based on how many times the user used the "verbose" flag +//! // (i.e. 'myprog -v -v -v' or 'myprog -vvv' vs 'myprog -v' +//! match matches.occurrences_of("v") { +//! 0 => println!("No verbose info"), +//! 1 => println!("Some verbose info"), +//! 2 => println!("Tons of verbose info"), +//! 3 | _ => println!("Don't be crazy"), +//! } +//! +//! // You can handle information about subcommands by requesting their matches by name +//! // (as below), requesting just the name used, or both at the same time +//! if let Some(matches) = matches.subcommand_matches("test") { +//! if matches.is_present("debug") { +//! println!("Printing debug info..."); +//! } else { +//! println!("Printing normally..."); +//! } +//! } +//! +//! // more program logic goes here... +//! } +//! ``` +//! +//! The next example shows a far less verbose method, but sacrifices some of the advanced +//! configuration options (not shown in this small example). This method also takes a *very* minor +//! runtime penalty. +//! +//! ```no_run +//! // (Full example with detailed comments in examples/01a_quick_example.rs) +//! // +//! // This example demonstrates clap's "usage strings" method of creating arguments +//! // which is less verbose +//! extern crate clap; +//! use clap::{Arg, App, SubCommand}; +//! +//! fn main() { +//! let matches = App::new("myapp") +//! .version("1.0") +//! .author("Kevin K. ") +//! .about("Does awesome things") +//! .args_from_usage( +//! "-c, --config=[FILE] 'Sets a custom config file' +//! 'Sets the input file to use' +//! -v... 'Sets the level of verbosity'") +//! .subcommand(SubCommand::with_name("test") +//! .about("controls testing features") +//! .version("1.3") +//! .author("Someone E. ") +//! .arg_from_usage("-d, --debug 'Print debug information'")) +//! .get_matches(); +//! +//! // Same as previous example... +//! } +//! ``` +//! +//! This third method shows how you can use a YAML file to build your CLI and keep your Rust source +//! tidy or support multiple localized translations by having different YAML files for each +//! localization. +//! +//! First, create the `cli.yml` file to hold your CLI options, but it could be called anything we +//! like: +//! +//! ```yaml +//! name: myapp +//! version: "1.0" +//! author: Kevin K. +//! about: Does awesome things +//! args: +//! - config: +//! short: c +//! long: config +//! value_name: FILE +//! help: Sets a custom config file +//! takes_value: true +//! - INPUT: +//! help: Sets the input file to use +//! required: true +//! index: 1 +//! - verbose: +//! short: v +//! multiple: true +//! help: Sets the level of verbosity +//! subcommands: +//! - test: +//! about: controls testing features +//! version: "1.3" +//! author: Someone E. +//! args: +//! - debug: +//! short: d +//! help: print debug information +//! ``` +//! +//! Since this feature requires additional dependencies that not everyone may want, it is *not* +//! compiled in by default and we need to enable a feature flag in Cargo.toml: +//! +//! Simply change your `clap = "~2.27.0"` to `clap = {version = "~2.27.0", features = ["yaml"]}`. +//! +//! At last we create our `main.rs` file just like we would have with the previous two examples: +//! +//! ```ignore +//! // (Full example with detailed comments in examples/17_yaml.rs) +//! // +//! // This example demonstrates clap's building from YAML style of creating arguments which is far +//! // more clean, but takes a very small performance hit compared to the other two methods. +//! #[macro_use] +//! extern crate clap; +//! use clap::App; +//! +//! fn main() { +//! // The YAML file is found relative to the current file, similar to how modules are found +//! let yaml = load_yaml!("cli.yml"); +//! let matches = App::from_yaml(yaml).get_matches(); +//! +//! // Same as previous examples... +//! } +//! ``` +//! +//! Finally there is a macro version, which is like a hybrid approach offering the speed of the +//! builder pattern (the first example), but without all the verbosity. +//! +//! ```no_run +//! #[macro_use] +//! extern crate clap; +//! +//! fn main() { +//! let matches = clap_app!(myapp => +//! (version: "1.0") +//! (author: "Kevin K. ") +//! (about: "Does awesome things") +//! (@arg CONFIG: -c --config +takes_value "Sets a custom config file") +//! (@arg INPUT: +required "Sets the input file to use") +//! (@arg debug: -d ... "Sets the level of debugging information") +//! (@subcommand test => +//! (about: "controls testing features") +//! (version: "1.3") +//! (author: "Someone E. ") +//! (@arg verbose: -v --verbose "Print test information verbosely") +//! ) +//! ).get_matches(); +//! +//! // Same as before... +//! } +//! ``` +//! +//! If you were to compile any of the above programs and run them with the flag `--help` or `-h` (or +//! `help` subcommand, since we defined `test` as a subcommand) the following would be output +//! +//! ```text +//! $ myprog --help +//! My Super Program 1.0 +//! Kevin K. +//! Does awesome things +//! +//! USAGE: +//! MyApp [FLAGS] [OPTIONS] [SUBCOMMAND] +//! +//! FLAGS: +//! -h, --help Prints this message +//! -v Sets the level of verbosity +//! -V, --version Prints version information +//! +//! OPTIONS: +//! -c, --config Sets a custom config file +//! +//! ARGS: +//! INPUT The input file to use +//! +//! SUBCOMMANDS: +//! help Prints this message +//! test Controls testing features +//! ``` +//! +//! **NOTE:** You could also run `myapp test --help` to see similar output and options for the +//! `test` subcommand. +//! +//! ## Try it! +//! +//! ### Pre-Built Test +//! +//! To try out the pre-built example, use the following steps: +//! +//! * Clone the repository `$ git clone https://github.com/clap-rs/clap && cd clap-rs/tests` +//! * Compile the example `$ cargo build --release` +//! * Run the help info `$ ./target/release/claptests --help` +//! * Play with the arguments! +//! +//! ### BYOB (Build Your Own Binary) +//! +//! To test out `clap`'s default auto-generated help/version follow these steps: +//! +//! * Create a new cargo project `$ cargo new fake --bin && cd fake` +//! * Add `clap` to your `Cargo.toml` +//! +//! ```toml +//! [dependencies] +//! clap = "2" +//! ``` +//! +//! * Add the following to your `src/main.rs` +//! +//! ```no_run +//! extern crate clap; +//! use clap::App; +//! +//! fn main() { +//! App::new("fake").version("v1.0-beta").get_matches(); +//! } +//! ``` +//! +//! * Build your program `$ cargo build --release` +//! * Run with help or version `$ ./target/release/fake --help` or `$ ./target/release/fake +//! --version` +//! +//! ## Usage +//! +//! For full usage, add `clap` as a dependency in your `Cargo.toml` (it is **highly** recommended to +//! use the `~major.minor.patch` style versions in your `Cargo.toml`, for more information see +//! [Compatibility Policy](#compatibility-policy)) to use from crates.io: +//! +//! ```toml +//! [dependencies] +//! clap = "~2.27.0" +//! ``` +//! +//! Or get the latest changes from the master branch at github: +//! +//! ```toml +//! [dependencies.clap] +//! git = "https://github.com/clap-rs/clap.git" +//! ``` +//! +//! Add `extern crate clap;` to your crate root. +//! +//! Define a list of valid arguments for your program (see the +//! [documentation](https://docs.rs/clap/) or [examples/] directory of this repo) +//! +//! Then run `cargo build` or `cargo update && cargo build` for your project. +//! +//! ### Optional Dependencies / Features +//! +//! #### Features enabled by default +//! +//! * `suggestions`: Turns on the `Did you mean '--myoption'?` feature for when users make typos. (builds dependency `strsim`) +//! * `color`: Turns on colored error messages. This feature only works on non-Windows OSs. (builds dependency `ansi-term` and `atty`) +//! * `wrap_help`: Wraps the help at the actual terminal width when +//! available, instead of 120 characters. (builds dependency `textwrap` +//! with feature `term_size`) +//! +//! To disable these, add this to your `Cargo.toml`: +//! +//! ```toml +//! [dependencies.clap] +//! version = "~2.27.0" +//! default-features = false +//! ``` +//! +//! You can also selectively enable only the features you'd like to include, by adding: +//! +//! ```toml +//! [dependencies.clap] +//! version = "~2.27.0" +//! default-features = false +//! +//! # Cherry-pick the features you'd like to use +//! features = [ "suggestions", "color" ] +//! ``` +//! +//! #### Opt-in features +//! +//! * **"yaml"**: Enables building CLIs from YAML documents. (builds dependency `yaml-rust`) +//! * **"unstable"**: Enables unstable `clap` features that may change from release to release +//! +//! ### Dependencies Tree +//! +//! The following graphic depicts `clap`s dependency graph (generated using +//! [cargo-graph](https://github.com/kbknapp/cargo-graph)). +//! +//! * **Dashed** Line: Optional dependency +//! * **Red** Color: **NOT** included by default (must use cargo `features` to enable) +//! * **Blue** Color: Dev dependency, only used while developing. +//! +//! ![clap dependencies](https://raw.githubusercontent.com/clap-rs/clap/master/clap_dep_graph.png) +//! +//! ### More Information +//! +//! You can find complete documentation on the [docs.rs](https://docs.rs/clap/) for this project. +//! +//! You can also find usage examples in the [examples/] directory of this repo. +//! +//! #### Video Tutorials +//! +//! There's also the video tutorial series [Argument Parsing with Rust v2][video tutorials]. +//! +//! These videos slowly trickle out as I finish them and currently a work in progress. +//! +//! ## How to Contribute +//! +//! Contributions are always welcome! And there is a multitude of ways in which you can help +//! depending on what you like to do, or are good at. Anything from documentation, code cleanup, +//! issue completion, new features, you name it, even filing issues is contributing and greatly +//! appreciated! +//! +//! Another really great way to help is if you find an interesting, or helpful way in which to use +//! `clap`. You can either add it to the [examples/] directory, or file an issue and tell +//! me. I'm all about giving credit where credit is due :) +//! +//! Please read [CONTRIBUTING.md](https://raw.githubusercontent.com/clap-rs/clap/master/.github/CONTRIBUTING.md) before you start contributing. +//! +//! +//! ### Testing Code +//! +//! To test with all features both enabled and disabled, you can run theese commands: +//! +//! ```text +//! $ cargo test --no-default-features +//! $ cargo test --features "yaml unstable" +//! ``` +//! +//! Alternatively, if you have [`just`](https://github.com/casey/just) installed you can run the +//! prebuilt recipes. *Not* using `just` is perfectly fine as well, it simply bundles commands +//! automatically. +//! +//! For example, to test the code, as above simply run: +//! +//! ```text +//! $ just run-tests +//! ``` +//! +//! From here on, I will list the appropriate `cargo` command as well as the `just` command. +//! +//! Sometimes it's helpful to only run a subset of the tests, which can be done via: +//! +//! ```text +//! $ cargo test --test +//! +//! # Or +//! +//! $ just run-test +//! ``` +//! +//! ### Linting Code +//! +//! During the CI process `clap` runs against many different lints using +//! [`clippy`](https://github.com/Manishearth/rust-clippy). In order to check if these lints pass on +//! your own computer prior to submitting a PR you'll need a nightly compiler. +//! +//! In order to check the code for lints run either: +//! +//! ```text +//! $ rustup override add nightly +//! $ cargo build --features lints +//! $ rustup override remove +//! +//! # Or +//! +//! $ just lint +//! ``` +//! +//! ### Debugging Code +//! +//! Another helpful technique is to see the `clap` debug output while developing features. In order +//! to see the debug output while running the full test suite or individual tests, run: +//! +//! ```text +//! $ cargo test --features debug +//! +//! # Or for individual tests +//! $ cargo test --test --features debug +//! +//! # The corresponding just command for individual debugging tests is: +//! $ just debug +//! ``` +//! +//! ### Goals +//! +//! There are a few goals of `clap` that I'd like to maintain throughout contributions. If your +//! proposed changes break, or go against any of these goals we'll discuss the changes further +//! before merging (but will *not* be ignored, all contributes are welcome!). These are by no means +//! hard-and-fast rules, as I'm no expert and break them myself from time to time (even if by +//! mistake or ignorance). +//! +//! * Remain backwards compatible when possible +//! - If backwards compatibility *must* be broken, use deprecation warnings if at all possible before +//! removing legacy code - This does not apply for security concerns +//! * Parse arguments quickly +//! - Parsing of arguments shouldn't slow down usage of the main program - This is also true of +//! generating help and usage information (although *slightly* less stringent, as the program is about +//! to exit) +//! * Try to be cognizant of memory usage +//! - Once parsing is complete, the memory footprint of `clap` should be low since the main program +//! is the star of the show +//! * `panic!` on *developer* error, exit gracefully on *end-user* error +//! +//! ### Compatibility Policy +//! +//! Because `clap` takes `SemVer` and compatibility seriously, this is the official policy regarding +//! breaking changes and previous versions of Rust. +//! +//! `clap` will pin the minimum required version of Rust to the CI builds. Bumping the minimum +//! version of Rust is considered a minor breaking change, meaning *at a minimum* the minor version +//! of `clap` will be bumped. +//! +//! In order to keep from being surprised by breaking changes, it is **highly** recommended to use +//! the `~major.minor.patch` style in your `Cargo.toml`: +//! +//! ```toml +//! [dependencies] clap = "~2.27.0" +//! ``` +//! +//! This will cause *only* the patch version to be updated upon a `cargo update` call, and therefore +//! cannot break due to new features, or bumped minimum versions of Rust. +//! +//! #### Minimum Version of Rust +//! +//! `clap` will officially support current stable Rust, minus two releases, but may work with prior +//! releases as well. For example, current stable Rust at the time of this writing is 1.21.0, +//! meaning `clap` is guaranteed to compile with 1.19.0 and beyond. At the 1.22.0 release, `clap` +//! will be guaranteed to compile with 1.20.0 and beyond, etc. +//! +//! Upon bumping the minimum version of Rust (assuming it's within the stable-2 range), it *must* be +//! clearly annotated in the `CHANGELOG.md` +//! +//! ## License +//! +//! `clap` is licensed under the MIT license. Please read the [LICENSE-MIT][license] file in +//! this repository for more information. +//! +//! [examples/]: https://github.com/clap-rs/clap/tree/master/examples +//! [video tutorials]: https://www.youtube.com/playlist?list=PLza5oFLQGTl2Z5T8g1pRkIynR3E0_pc7U +//! [license]: https://raw.githubusercontent.com/clap-rs/clap/master/LICENSE-MIT + +#![crate_type = "lib"] +#![doc(html_root_url = "https://docs.rs/clap/2.33.0")] +#![deny(missing_docs, missing_debug_implementations, missing_copy_implementations, trivial_casts, + unused_import_braces, unused_allocation)] +// Lints we'd like to deny but are currently failing for upstream crates +// unused_qualifications (bitflags, clippy) +// trivial_numeric_casts (bitflags) +#![cfg_attr(not(any(feature = "lints", feature = "nightly")), forbid(unstable_features))] +#![cfg_attr(feature = "lints", feature(plugin))] +#![cfg_attr(feature = "lints", plugin(clippy))] +// Need to disable deny(warnings) while deprecations are active +// #![cfg_attr(feature = "lints", deny(warnings))] +#![cfg_attr(feature = "lints", allow(cyclomatic_complexity))] +#![cfg_attr(feature = "lints", allow(doc_markdown))] +#![cfg_attr(feature = "lints", allow(explicit_iter_loop))] + +#[cfg(all(feature = "color", not(target_os = "windows")))] +extern crate ansi_term; +#[cfg(feature = "color")] +extern crate atty; +#[macro_use] +extern crate bitflags; +#[cfg(feature = "suggestions")] +extern crate strsim; +#[cfg(feature = "wrap_help")] +extern crate term_size; +extern crate textwrap; +extern crate unicode_width; +#[cfg(feature = "vec_map")] +extern crate vec_map; +#[cfg(feature = "yaml")] +extern crate yaml_rust; + +#[cfg(feature = "yaml")] +pub use yaml_rust::YamlLoader; +pub use args::{Arg, ArgGroup, ArgMatches, ArgSettings, OsValues, SubCommand, Values}; +pub use app::{App, AppSettings}; +pub use fmt::Format; +pub use errors::{Error, ErrorKind, Result}; +pub use completions::Shell; + +#[macro_use] +mod macros; +mod app; +mod args; +mod usage_parser; +mod fmt; +mod suggestions; +mod errors; +mod osstringext; +mod strext; +mod completions; +mod map; + +const INTERNAL_ERROR_MSG: &'static str = "Fatal internal error. Please consider filing a bug \ + report at https://github.com/clap-rs/clap/issues"; +const INVALID_UTF8: &'static str = "unexpected invalid UTF-8 code point"; + +#[cfg(unstable)] +pub use derive::{ArgEnum, ClapApp, FromArgMatches, IntoApp}; + +#[cfg(unstable)] +mod derive { + /// @TODO @release @docs + pub trait ClapApp: IntoApp + FromArgMatches + Sized { + /// @TODO @release @docs + fn parse() -> Self { Self::from_argmatches(Self::into_app().get_matches()) } + + /// @TODO @release @docs + fn parse_from(argv: I) -> Self + where + I: IntoIterator, + T: Into + Clone, + { + Self::from_argmatches(Self::into_app().get_matches_from(argv)) + } + + /// @TODO @release @docs + fn try_parse() -> Result { + Self::try_from_argmatches(Self::into_app().get_matches_safe()?) + } + + + /// @TODO @release @docs + fn try_parse_from(argv: I) -> Result + where + I: IntoIterator, + T: Into + Clone, + { + Self::try_from_argmatches(Self::into_app().get_matches_from_safe(argv)?) + } + } + + /// @TODO @release @docs + pub trait IntoApp { + /// @TODO @release @docs + fn into_app<'a, 'b>() -> clap::App<'a, 'b>; + } + + /// @TODO @release @docs + pub trait FromArgMatches: Sized { + /// @TODO @release @docs + fn from_argmatches<'a>(matches: clap::ArgMatches<'a>) -> Self; + + /// @TODO @release @docs + fn try_from_argmatches<'a>(matches: clap::ArgMatches<'a>) -> Result; + } + + /// @TODO @release @docs + pub trait ArgEnum {} +} diff --git a/clap/src/macros.rs b/clap/src/macros.rs new file mode 100644 index 0000000..8198e19 --- /dev/null +++ b/clap/src/macros.rs @@ -0,0 +1,1108 @@ +/// A convenience macro for loading the YAML file at compile time (relative to the current file, +/// like modules work). That YAML object can then be passed to this function. +/// +/// # Panics +/// +/// The YAML file must be properly formatted or this function will panic!(). A good way to +/// ensure this doesn't happen is to run your program with the `--help` switch. If this passes +/// without error, you needn't worry because the YAML is properly formatted. +/// +/// # Examples +/// +/// The following example shows how to load a properly formatted YAML file to build an instance +/// of an `App` struct. +/// +/// ```ignore +/// # #[macro_use] +/// # extern crate clap; +/// # use clap::App; +/// # fn main() { +/// let yml = load_yaml!("app.yml"); +/// let app = App::from_yaml(yml); +/// +/// // continued logic goes here, such as `app.get_matches()` etc. +/// # } +/// ``` +#[cfg(feature = "yaml")] +#[macro_export] +macro_rules! load_yaml { + ($yml:expr) => ( + &::clap::YamlLoader::load_from_str(include_str!($yml)).expect("failed to load YAML file")[0] + ); +} + +/// Convenience macro getting a typed value `T` where `T` implements [`std::str::FromStr`] from an +/// argument value. This macro returns a `Result` which allows you as the developer to +/// decide what you'd like to do on a failed parse. There are two types of errors, parse failures +/// and those where the argument wasn't present (such as a non-required argument). You can use +/// it to get a single value, or a iterator as with the [`ArgMatches::values_of`] +/// +/// # Examples +/// +/// ```no_run +/// # #[macro_use] +/// # extern crate clap; +/// # use clap::App; +/// # fn main() { +/// let matches = App::new("myapp") +/// .arg_from_usage("[length] 'Set the length to use as a pos whole num, i.e. 20'") +/// .get_matches(); +/// +/// let len = value_t!(matches.value_of("length"), u32).unwrap_or_else(|e| e.exit()); +/// let also_len = value_t!(matches, "length", u32).unwrap_or_else(|e| e.exit()); +/// +/// println!("{} + 2: {}", len, len + 2); +/// # } +/// ``` +/// [`std::str::FromStr`]: https://doc.rust-lang.org/std/str/trait.FromStr.html +/// [`ArgMatches::values_of`]: ./struct.ArgMatches.html#method.values_of +/// [`Result`]: https://doc.rust-lang.org/std/result/enum.Result.html +#[macro_export] +macro_rules! value_t { + ($m:ident, $v:expr, $t:ty) => { + value_t!($m.value_of($v), $t) + }; + ($m:ident.value_of($v:expr), $t:ty) => { + if let Some(v) = $m.value_of($v) { + match v.parse::<$t>() { + Ok(val) => Ok(val), + Err(_) => + Err(::clap::Error::value_validation_auto( + format!("The argument '{}' isn't a valid value", v))), + } + } else { + Err(::clap::Error::argument_not_found_auto($v)) + } + }; +} + +/// Convenience macro getting a typed value `T` where `T` implements [`std::str::FromStr`] or +/// exiting upon error, instead of returning a [`Result`] type. +/// +/// **NOTE:** This macro is for backwards compatibility sake. Prefer +/// [`value_t!(/* ... */).unwrap_or_else(|e| e.exit())`] +/// +/// # Examples +/// +/// ```no_run +/// # #[macro_use] +/// # extern crate clap; +/// # use clap::App; +/// # fn main() { +/// let matches = App::new("myapp") +/// .arg_from_usage("[length] 'Set the length to use as a pos whole num, i.e. 20'") +/// .get_matches(); +/// +/// let len = value_t_or_exit!(matches.value_of("length"), u32); +/// let also_len = value_t_or_exit!(matches, "length", u32); +/// +/// println!("{} + 2: {}", len, len + 2); +/// # } +/// ``` +/// [`std::str::FromStr`]: https://doc.rust-lang.org/std/str/trait.FromStr.html +/// [`Result`]: https://doc.rust-lang.org/std/result/enum.Result.html +/// [`value_t!(/* ... */).unwrap_or_else(|e| e.exit())`]: ./macro.value_t!.html +#[macro_export] +macro_rules! value_t_or_exit { + ($m:ident, $v:expr, $t:ty) => { + value_t_or_exit!($m.value_of($v), $t) + }; + ($m:ident.value_of($v:expr), $t:ty) => { + if let Some(v) = $m.value_of($v) { + match v.parse::<$t>() { + Ok(val) => val, + Err(_) => + ::clap::Error::value_validation_auto( + format!("The argument '{}' isn't a valid value", v)).exit(), + } + } else { + ::clap::Error::argument_not_found_auto($v).exit() + } + }; +} + +/// Convenience macro getting a typed value [`Vec`] where `T` implements [`std::str::FromStr`] +/// This macro returns a [`clap::Result>`] which allows you as the developer to decide +/// what you'd like to do on a failed parse. +/// +/// # Examples +/// +/// ```no_run +/// # #[macro_use] +/// # extern crate clap; +/// # use clap::App; +/// # fn main() { +/// let matches = App::new("myapp") +/// .arg_from_usage("[seq]... 'A sequence of pos whole nums, i.e. 20 45'") +/// .get_matches(); +/// +/// let vals = values_t!(matches.values_of("seq"), u32).unwrap_or_else(|e| e.exit()); +/// for v in &vals { +/// println!("{} + 2: {}", v, v + 2); +/// } +/// +/// let vals = values_t!(matches, "seq", u32).unwrap_or_else(|e| e.exit()); +/// for v in &vals { +/// println!("{} + 2: {}", v, v + 2); +/// } +/// # } +/// ``` +/// [`std::str::FromStr`]: https://doc.rust-lang.org/std/str/trait.FromStr.html +/// [`Vec`]: https://doc.rust-lang.org/std/vec/struct.Vec.html +/// [`clap::Result>`]: ./type.Result.html +#[macro_export] +macro_rules! values_t { + ($m:ident, $v:expr, $t:ty) => { + values_t!($m.values_of($v), $t) + }; + ($m:ident.values_of($v:expr), $t:ty) => { + if let Some(vals) = $m.values_of($v) { + let mut tmp = vec![]; + let mut err = None; + for pv in vals { + match pv.parse::<$t>() { + Ok(rv) => tmp.push(rv), + Err(..) => { + err = Some(::clap::Error::value_validation_auto( + format!("The argument '{}' isn't a valid value", pv))); + break + } + } + } + match err { + Some(e) => Err(e), + None => Ok(tmp), + } + } else { + Err(::clap::Error::argument_not_found_auto($v)) + } + }; +} + +/// Convenience macro getting a typed value [`Vec`] where `T` implements [`std::str::FromStr`] +/// or exiting upon error. +/// +/// **NOTE:** This macro is for backwards compatibility sake. Prefer +/// [`values_t!(/* ... */).unwrap_or_else(|e| e.exit())`] +/// +/// # Examples +/// +/// ```no_run +/// # #[macro_use] +/// # extern crate clap; +/// # use clap::App; +/// # fn main() { +/// let matches = App::new("myapp") +/// .arg_from_usage("[seq]... 'A sequence of pos whole nums, i.e. 20 45'") +/// .get_matches(); +/// +/// let vals = values_t_or_exit!(matches.values_of("seq"), u32); +/// for v in &vals { +/// println!("{} + 2: {}", v, v + 2); +/// } +/// +/// // type for example only +/// let vals: Vec = values_t_or_exit!(matches, "seq", u32); +/// for v in &vals { +/// println!("{} + 2: {}", v, v + 2); +/// } +/// # } +/// ``` +/// [`values_t!(/* ... */).unwrap_or_else(|e| e.exit())`]: ./macro.values_t!.html +/// [`std::str::FromStr`]: https://doc.rust-lang.org/std/str/trait.FromStr.html +/// [`Vec`]: https://doc.rust-lang.org/std/vec/struct.Vec.html +#[macro_export] +macro_rules! values_t_or_exit { + ($m:ident, $v:expr, $t:ty) => { + values_t_or_exit!($m.values_of($v), $t) + }; + ($m:ident.values_of($v:expr), $t:ty) => { + if let Some(vals) = $m.values_of($v) { + vals.map(|v| v.parse::<$t>().unwrap_or_else(|_|{ + ::clap::Error::value_validation_auto( + format!("One or more arguments aren't valid values")).exit() + })).collect::>() + } else { + ::clap::Error::argument_not_found_auto($v).exit() + } + }; +} + +// _clap_count_exprs! is derived from https://github.com/DanielKeep/rust-grabbag +// commit: 82a35ca5d9a04c3b920622d542104e3310ee5b07 +// License: MIT +// Copyright ⓒ 2015 grabbag contributors. +// Licensed under the MIT license (see LICENSE or ) or the Apache License, Version 2.0 (see LICENSE of +// ), at your option. All +// files in the project carrying such notice may not be copied, modified, +// or distributed except according to those terms. +// +/// Counts the number of comma-delimited expressions passed to it. The result is a compile-time +/// evaluable expression, suitable for use as a static array size, or the value of a `const`. +/// +/// # Examples +/// +/// ``` +/// # #[macro_use] extern crate clap; +/// # fn main() { +/// const COUNT: usize = _clap_count_exprs!(a, 5+1, "hi there!".into_string()); +/// assert_eq!(COUNT, 3); +/// # } +/// ``` +#[macro_export] +macro_rules! _clap_count_exprs { + () => { 0 }; + ($e:expr) => { 1 }; + ($e:expr, $($es:expr),+) => { 1 + $crate::_clap_count_exprs!($($es),*) }; +} + +/// Convenience macro to generate more complete enums with variants to be used as a type when +/// parsing arguments. This enum also provides a `variants()` function which can be used to +/// retrieve a `Vec<&'static str>` of the variant names, as well as implementing [`FromStr`] and +/// [`Display`] automatically. +/// +/// **NOTE:** Case insensitivity is supported for ASCII characters only. It's highly recommended to +/// use [`Arg::case_insensitive(true)`] for args that will be used with these enums +/// +/// **NOTE:** This macro automatically implements [`std::str::FromStr`] and [`std::fmt::Display`] +/// +/// **NOTE:** These enums support pub (or not) and uses of the `#[derive()]` traits +/// +/// # Examples +/// +/// ```rust +/// # #[macro_use] +/// # extern crate clap; +/// # use clap::{App, Arg}; +/// arg_enum!{ +/// #[derive(PartialEq, Debug)] +/// pub enum Foo { +/// Bar, +/// Baz, +/// Qux +/// } +/// } +/// // Foo enum can now be used via Foo::Bar, or Foo::Baz, etc +/// // and implements std::str::FromStr to use with the value_t! macros +/// fn main() { +/// let m = App::new("app") +/// .arg(Arg::from_usage(" 'the foo'") +/// .possible_values(&Foo::variants()) +/// .case_insensitive(true)) +/// .get_matches_from(vec![ +/// "app", "baz" +/// ]); +/// let f = value_t!(m, "foo", Foo).unwrap_or_else(|e| e.exit()); +/// +/// assert_eq!(f, Foo::Baz); +/// } +/// ``` +/// [`FromStr`]: https://doc.rust-lang.org/std/str/trait.FromStr.html +/// [`std::str::FromStr`]: https://doc.rust-lang.org/std/str/trait.FromStr.html +/// [`Display`]: https://doc.rust-lang.org/std/fmt/trait.Display.html +/// [`std::fmt::Display`]: https://doc.rust-lang.org/std/fmt/trait.Display.html +/// [`Arg::case_insensitive(true)`]: ./struct.Arg.html#method.case_insensitive +#[macro_export] +macro_rules! arg_enum { + (@as_item $($i:item)*) => ($($i)*); + (@impls ( $($tts:tt)* ) -> ($e:ident, $($v:ident),+)) => { + arg_enum!(@as_item + $($tts)* + + impl ::std::str::FromStr for $e { + type Err = String; + + fn from_str(s: &str) -> ::std::result::Result { + #[allow(deprecated, unused_imports)] + use ::std::ascii::AsciiExt; + match s { + $(stringify!($v) | + _ if s.eq_ignore_ascii_case(stringify!($v)) => Ok($e::$v)),+, + _ => Err({ + let v = vec![ + $(stringify!($v),)+ + ]; + format!("valid values: {}", + v.join(", ")) + }), + } + } + } + impl ::std::fmt::Display for $e { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + match *self { + $($e::$v => write!(f, stringify!($v)),)+ + } + } + } + impl $e { + #[allow(dead_code)] + pub fn variants() -> [&'static str; $crate::_clap_count_exprs!($(stringify!($v)),+)] { + [ + $(stringify!($v),)+ + ] + } + }); + }; + ($(#[$($m:meta),+])+ pub enum $e:ident { $($v:ident $(=$val:expr)*,)+ } ) => { + arg_enum!(@impls + ($(#[$($m),+])+ + pub enum $e { + $($v$(=$val)*),+ + }) -> ($e, $($v),+) + ); + }; + ($(#[$($m:meta),+])+ pub enum $e:ident { $($v:ident $(=$val:expr)*),+ } ) => { + arg_enum!(@impls + ($(#[$($m),+])+ + pub enum $e { + $($v$(=$val)*),+ + }) -> ($e, $($v),+) + ); + }; + ($(#[$($m:meta),+])+ enum $e:ident { $($v:ident $(=$val:expr)*,)+ } ) => { + arg_enum!(@impls + ($(#[$($m),+])+ + enum $e { + $($v$(=$val)*),+ + }) -> ($e, $($v),+) + ); + }; + ($(#[$($m:meta),+])+ enum $e:ident { $($v:ident $(=$val:expr)*),+ } ) => { + arg_enum!(@impls + ($(#[$($m),+])+ + enum $e { + $($v$(=$val)*),+ + }) -> ($e, $($v),+) + ); + }; + (pub enum $e:ident { $($v:ident $(=$val:expr)*,)+ } ) => { + arg_enum!(@impls + (pub enum $e { + $($v$(=$val)*),+ + }) -> ($e, $($v),+) + ); + }; + (pub enum $e:ident { $($v:ident $(=$val:expr)*),+ } ) => { + arg_enum!(@impls + (pub enum $e { + $($v$(=$val)*),+ + }) -> ($e, $($v),+) + ); + }; + (enum $e:ident { $($v:ident $(=$val:expr)*,)+ } ) => { + arg_enum!(@impls + (enum $e { + $($v$(=$val)*),+ + }) -> ($e, $($v),+) + ); + }; + (enum $e:ident { $($v:ident $(=$val:expr)*),+ } ) => { + arg_enum!(@impls + (enum $e { + $($v$(=$val)*),+ + }) -> ($e, $($v),+) + ); + }; +} + +/// Allows you to pull the version from your Cargo.toml at compile time as +/// `MAJOR.MINOR.PATCH_PKGVERSION_PRE` +/// +/// # Examples +/// +/// ```no_run +/// # #[macro_use] +/// # extern crate clap; +/// # use clap::App; +/// # fn main() { +/// let m = App::new("app") +/// .version(crate_version!()) +/// .get_matches(); +/// # } +/// ``` +#[cfg(not(feature = "no_cargo"))] +#[macro_export] +macro_rules! crate_version { + () => { + env!("CARGO_PKG_VERSION") + }; +} + +/// Allows you to pull the authors for the app from your Cargo.toml at +/// compile time in the form: +/// `"author1 lastname :author2 lastname "` +/// +/// You can replace the colons with a custom separator by supplying a +/// replacement string, so, for example, +/// `crate_authors!(",\n")` would become +/// `"author1 lastname ,\nauthor2 lastname ,\nauthor3 lastname "` +/// +/// # Examples +/// +/// ```no_run +/// # #[macro_use] +/// # extern crate clap; +/// # use clap::App; +/// # fn main() { +/// let m = App::new("app") +/// .author(crate_authors!("\n")) +/// .get_matches(); +/// # } +/// ``` +#[cfg(not(feature = "no_cargo"))] +#[macro_export] +macro_rules! crate_authors { + ($sep:expr) => {{ + use std::ops::Deref; + use std::sync::{ONCE_INIT, Once}; + + #[allow(missing_copy_implementations)] + #[allow(dead_code)] + struct CargoAuthors { __private_field: () }; + + impl Deref for CargoAuthors { + type Target = str; + + #[allow(unsafe_code)] + fn deref(&self) -> &'static str { + static ONCE: Once = ONCE_INIT; + static mut VALUE: *const String = 0 as *const String; + + unsafe { + ONCE.call_once(|| { + let s = env!("CARGO_PKG_AUTHORS").replace(':', $sep); + VALUE = Box::into_raw(Box::new(s)); + }); + + &(*VALUE)[..] + } + } + } + + &*CargoAuthors { __private_field: () } + }}; + () => { + env!("CARGO_PKG_AUTHORS") + }; +} + +/// Allows you to pull the description from your Cargo.toml at compile time. +/// +/// # Examples +/// +/// ```no_run +/// # #[macro_use] +/// # extern crate clap; +/// # use clap::App; +/// # fn main() { +/// let m = App::new("app") +/// .about(crate_description!()) +/// .get_matches(); +/// # } +/// ``` +#[cfg(not(feature = "no_cargo"))] +#[macro_export] +macro_rules! crate_description { + () => { + env!("CARGO_PKG_DESCRIPTION") + }; +} + +/// Allows you to pull the name from your Cargo.toml at compile time. +/// +/// # Examples +/// +/// ```no_run +/// # #[macro_use] +/// # extern crate clap; +/// # use clap::App; +/// # fn main() { +/// let m = App::new(crate_name!()) +/// .get_matches(); +/// # } +/// ``` +#[cfg(not(feature = "no_cargo"))] +#[macro_export] +macro_rules! crate_name { + () => { + env!("CARGO_PKG_NAME") + }; +} + +/// Allows you to build the `App` instance from your Cargo.toml at compile time. +/// +/// Equivalent to using the `crate_*!` macros with their respective fields. +/// +/// Provided separator is for the [`crate_authors!`](macro.crate_authors.html) macro, +/// refer to the documentation therefor. +/// +/// **NOTE:** Changing the values in your `Cargo.toml` does not trigger a re-build automatically, +/// and therefore won't change the generated output until you recompile. +/// +/// **Pro Tip:** In some cases you can "trick" the compiler into triggering a rebuild when your +/// `Cargo.toml` is changed by including this in your `src/main.rs` file +/// `include_str!("../Cargo.toml");` +/// +/// # Examples +/// +/// ```no_run +/// # #[macro_use] +/// # extern crate clap; +/// # fn main() { +/// let m = app_from_crate!().get_matches(); +/// # } +/// ``` +#[cfg(not(feature = "no_cargo"))] +#[macro_export] +macro_rules! app_from_crate { + () => { + $crate::App::new(crate_name!()) + .version(crate_version!()) + .author(crate_authors!()) + .about(crate_description!()) + }; + ($sep:expr) => { + $crate::App::new(crate_name!()) + .version(crate_version!()) + .author(crate_authors!($sep)) + .about(crate_description!()) + }; +} + +/// Build `App`, `Arg`s, `SubCommand`s and `Group`s with Usage-string like input +/// but without the associated parsing runtime cost. +/// +/// `clap_app!` also supports several shorthand syntaxes. +/// +/// # Examples +/// +/// ```no_run +/// # #[macro_use] +/// # extern crate clap; +/// # fn main() { +/// let matches = clap_app!(myapp => +/// (version: "1.0") +/// (author: "Kevin K. ") +/// (about: "Does awesome things") +/// (@arg CONFIG: -c --config +takes_value "Sets a custom config file") +/// (@arg INPUT: +required "Sets the input file to use") +/// (@arg debug: -d ... "Sets the level of debugging information") +/// (@group difficulty => +/// (@arg hard: -h --hard "Sets hard mode") +/// (@arg normal: -n --normal "Sets normal mode") +/// (@arg easy: -e --easy "Sets easy mode") +/// ) +/// (@subcommand test => +/// (about: "controls testing features") +/// (version: "1.3") +/// (author: "Someone E. ") +/// (@arg verbose: -v --verbose "Print test information verbosely") +/// ) +/// ) +/// .get_matches(); +/// # } +/// ``` +/// # Shorthand Syntax for Args +/// +/// * A single hyphen followed by a character (such as `-c`) sets the [`Arg::short`] +/// * A double hyphen followed by a character or word (such as `--config`) sets [`Arg::long`] +/// * If one wishes to use a [`Arg::long`] with a hyphen inside (i.e. `--config-file`), you +/// must use `--("config-file")` due to limitations of the Rust macro system. +/// * Three dots (`...`) sets [`Arg::multiple(true)`] +/// * Angled brackets after either a short or long will set [`Arg::value_name`] and +/// `Arg::required(true)` such as `--config ` = `Arg::value_name("FILE")` and +/// `Arg::required(true)` +/// * Square brackets after either a short or long will set [`Arg::value_name`] and +/// `Arg::required(false)` such as `--config [FILE]` = `Arg::value_name("FILE")` and +/// `Arg::required(false)` +/// * There are short hand syntaxes for Arg methods that accept booleans +/// * A plus sign will set that method to `true` such as `+required` = `Arg::required(true)` +/// * An exclamation will set that method to `false` such as `!required` = `Arg::required(false)` +/// * A `#{min, max}` will set [`Arg::min_values(min)`] and [`Arg::max_values(max)`] +/// * An asterisk (`*`) will set `Arg::required(true)` +/// * Curly brackets around a `fn` will set [`Arg::validator`] as in `{fn}` = `Arg::validator(fn)` +/// * An Arg method that accepts a string followed by square brackets will set that method such as +/// `conflicts_with[FOO]` will set `Arg::conflicts_with("FOO")` (note the lack of quotes around +/// `FOO` in the macro) +/// * An Arg method that takes a string and can be set multiple times (such as +/// [`Arg::conflicts_with`]) followed by square brackets and a list of values separated by spaces +/// will set that method such as `conflicts_with[FOO BAR BAZ]` will set +/// `Arg::conflicts_with("FOO")`, `Arg::conflicts_with("BAR")`, and `Arg::conflicts_with("BAZ")` +/// (note the lack of quotes around the values in the macro) +/// +/// # Shorthand Syntax for Groups +/// +/// * There are short hand syntaxes for `ArgGroup` methods that accept booleans +/// * A plus sign will set that method to `true` such as `+required` = `ArgGroup::required(true)` +/// * An exclamation will set that method to `false` such as `!required` = `ArgGroup::required(false)` +/// +/// [`Arg::short`]: ./struct.Arg.html#method.short +/// [`Arg::long`]: ./struct.Arg.html#method.long +/// [`Arg::multiple(true)`]: ./struct.Arg.html#method.multiple +/// [`Arg::value_name`]: ./struct.Arg.html#method.value_name +/// [`Arg::min_values(min)`]: ./struct.Arg.html#method.min_values +/// [`Arg::max_values(max)`]: ./struct.Arg.html#method.max_values +/// [`Arg::validator`]: ./struct.Arg.html#method.validator +/// [`Arg::conflicts_with`]: ./struct.Arg.html#method.conflicts_with +#[macro_export] +macro_rules! clap_app { + (@app ($builder:expr)) => { $builder }; + (@app ($builder:expr) (@arg ($name:expr): $($tail:tt)*) $($tt:tt)*) => { + clap_app!{ @app + ($builder.arg( + clap_app!{ @arg ($crate::Arg::with_name($name)) (-) $($tail)* })) + $($tt)* + } + }; + (@app ($builder:expr) (@arg $name:ident: $($tail:tt)*) $($tt:tt)*) => { + clap_app!{ @app + ($builder.arg( + clap_app!{ @arg ($crate::Arg::with_name(stringify!($name))) (-) $($tail)* })) + $($tt)* + } + }; + (@app ($builder:expr) (@setting $setting:ident) $($tt:tt)*) => { + clap_app!{ @app + ($builder.setting($crate::AppSettings::$setting)) + $($tt)* + } + }; +// Treat the application builder as an argument to set its attributes + (@app ($builder:expr) (@attributes $($attr:tt)*) $($tt:tt)*) => { + clap_app!{ @app (clap_app!{ @arg ($builder) $($attr)* }) $($tt)* } + }; + (@app ($builder:expr) (@group $name:ident => $($tail:tt)*) $($tt:tt)*) => { + clap_app!{ @app + (clap_app!{ @group ($builder, $crate::ArgGroup::with_name(stringify!($name))) $($tail)* }) + $($tt)* + } + }; + (@app ($builder:expr) (@group $name:ident !$ident:ident => $($tail:tt)*) $($tt:tt)*) => { + clap_app!{ @app + (clap_app!{ @group ($builder, $crate::ArgGroup::with_name(stringify!($name)).$ident(false)) $($tail)* }) + $($tt)* + } + }; + (@app ($builder:expr) (@group $name:ident +$ident:ident => $($tail:tt)*) $($tt:tt)*) => { + clap_app!{ @app + (clap_app!{ @group ($builder, $crate::ArgGroup::with_name(stringify!($name)).$ident(true)) $($tail)* }) + $($tt)* + } + }; +// Handle subcommand creation + (@app ($builder:expr) (@subcommand $name:ident => $($tail:tt)*) $($tt:tt)*) => { + clap_app!{ @app + ($builder.subcommand( + clap_app!{ @app ($crate::SubCommand::with_name(stringify!($name))) $($tail)* } + )) + $($tt)* + } + }; +// Yaml like function calls - used for setting various meta directly against the app + (@app ($builder:expr) ($ident:ident: $($v:expr),*) $($tt:tt)*) => { +// clap_app!{ @app ($builder.$ident($($v),*)) $($tt)* } + clap_app!{ @app + ($builder.$ident($($v),*)) + $($tt)* + } + }; + +// Add members to group and continue argument handling with the parent builder + (@group ($builder:expr, $group:expr)) => { $builder.group($group) }; + // Treat the group builder as an argument to set its attributes + (@group ($builder:expr, $group:expr) (@attributes $($attr:tt)*) $($tt:tt)*) => { + clap_app!{ @group ($builder, clap_app!{ @arg ($group) (-) $($attr)* }) $($tt)* } + }; + (@group ($builder:expr, $group:expr) (@arg $name:ident: $($tail:tt)*) $($tt:tt)*) => { + clap_app!{ @group + (clap_app!{ @app ($builder) (@arg $name: $($tail)*) }, + $group.arg(stringify!($name))) + $($tt)* + } + }; + +// No more tokens to munch + (@arg ($arg:expr) $modes:tt) => { $arg }; +// Shorthand tokens influenced by the usage_string + (@arg ($arg:expr) $modes:tt --($long:expr) $($tail:tt)*) => { + clap_app!{ @arg ($arg.long($long)) $modes $($tail)* } + }; + (@arg ($arg:expr) $modes:tt --$long:ident $($tail:tt)*) => { + clap_app!{ @arg ($arg.long(stringify!($long))) $modes $($tail)* } + }; + (@arg ($arg:expr) $modes:tt -$short:ident $($tail:tt)*) => { + clap_app!{ @arg ($arg.short(stringify!($short))) $modes $($tail)* } + }; + (@arg ($arg:expr) (-) <$var:ident> $($tail:tt)*) => { + clap_app!{ @arg ($arg.value_name(stringify!($var))) (+) +takes_value +required $($tail)* } + }; + (@arg ($arg:expr) (+) <$var:ident> $($tail:tt)*) => { + clap_app!{ @arg ($arg.value_name(stringify!($var))) (+) $($tail)* } + }; + (@arg ($arg:expr) (-) [$var:ident] $($tail:tt)*) => { + clap_app!{ @arg ($arg.value_name(stringify!($var))) (+) +takes_value $($tail)* } + }; + (@arg ($arg:expr) (+) [$var:ident] $($tail:tt)*) => { + clap_app!{ @arg ($arg.value_name(stringify!($var))) (+) $($tail)* } + }; + (@arg ($arg:expr) $modes:tt ... $($tail:tt)*) => { + clap_app!{ @arg ($arg) $modes +multiple $($tail)* } + }; +// Shorthand magic + (@arg ($arg:expr) $modes:tt #{$n:expr, $m:expr} $($tail:tt)*) => { + clap_app!{ @arg ($arg) $modes min_values($n) max_values($m) $($tail)* } + }; + (@arg ($arg:expr) $modes:tt * $($tail:tt)*) => { + clap_app!{ @arg ($arg) $modes +required $($tail)* } + }; +// !foo -> .foo(false) + (@arg ($arg:expr) $modes:tt !$ident:ident $($tail:tt)*) => { + clap_app!{ @arg ($arg.$ident(false)) $modes $($tail)* } + }; +// +foo -> .foo(true) + (@arg ($arg:expr) $modes:tt +$ident:ident $($tail:tt)*) => { + clap_app!{ @arg ($arg.$ident(true)) $modes $($tail)* } + }; +// Validator + (@arg ($arg:expr) $modes:tt {$fn_:expr} $($tail:tt)*) => { + clap_app!{ @arg ($arg.validator($fn_)) $modes $($tail)* } + }; + (@as_expr $expr:expr) => { $expr }; +// Help + (@arg ($arg:expr) $modes:tt $desc:tt) => { $arg.help(clap_app!{ @as_expr $desc }) }; +// Handle functions that need to be called multiple times for each argument + (@arg ($arg:expr) $modes:tt $ident:ident[$($target:ident)*] $($tail:tt)*) => { + clap_app!{ @arg ($arg $( .$ident(stringify!($target)) )*) $modes $($tail)* } + }; +// Inherit builder's functions, e.g. `index(2)`, `requires_if("val", "arg")` + (@arg ($arg:expr) $modes:tt $ident:ident($($expr:expr),*) $($tail:tt)*) => { + clap_app!{ @arg ($arg.$ident($($expr),*)) $modes $($tail)* } + }; +// Inherit builder's functions with trailing comma, e.g. `index(2,)`, `requires_if("val", "arg",)` + (@arg ($arg:expr) $modes:tt $ident:ident($($expr:expr,)*) $($tail:tt)*) => { + clap_app!{ @arg ($arg.$ident($($expr),*)) $modes $($tail)* } + }; + +// Build a subcommand outside of an app. + (@subcommand $name:ident => $($tail:tt)*) => { + clap_app!{ @app ($crate::SubCommand::with_name(stringify!($name))) $($tail)* } + }; +// Start the magic + (($name:expr) => $($tail:tt)*) => {{ + clap_app!{ @app ($crate::App::new($name)) $($tail)*} + }}; + + ($name:ident => $($tail:tt)*) => {{ + clap_app!{ @app ($crate::App::new(stringify!($name))) $($tail)*} + }}; +} + +macro_rules! impl_settings { + ($n:ident, $($v:ident => $c:path),+) => { + pub fn set(&mut self, s: $n) { + match s { + $($n::$v => self.0.insert($c)),+ + } + } + + pub fn unset(&mut self, s: $n) { + match s { + $($n::$v => self.0.remove($c)),+ + } + } + + pub fn is_set(&self, s: $n) -> bool { + match s { + $($n::$v => self.0.contains($c)),+ + } + } + }; +} + +// Convenience for writing to stderr thanks to https://github.com/BurntSushi +macro_rules! wlnerr( + ($($arg:tt)*) => ({ + use std::io::{Write, stderr}; + writeln!(&mut stderr(), $($arg)*).ok(); + }) +); + +#[cfg(feature = "debug")] +#[cfg_attr(feature = "debug", macro_use)] +#[cfg_attr(feature = "debug", allow(unused_macros))] +mod debug_macros { + macro_rules! debugln { + ($fmt:expr) => (println!(concat!("DEBUG:clap:", $fmt))); + ($fmt:expr, $($arg:tt)*) => (println!(concat!("DEBUG:clap:",$fmt), $($arg)*)); + } + macro_rules! sdebugln { + ($fmt:expr) => (println!($fmt)); + ($fmt:expr, $($arg:tt)*) => (println!($fmt, $($arg)*)); + } + macro_rules! debug { + ($fmt:expr) => (print!(concat!("DEBUG:clap:", $fmt))); + ($fmt:expr, $($arg:tt)*) => (print!(concat!("DEBUG:clap:",$fmt), $($arg)*)); + } + macro_rules! sdebug { + ($fmt:expr) => (print!($fmt)); + ($fmt:expr, $($arg:tt)*) => (print!($fmt, $($arg)*)); + } +} + +#[cfg(not(feature = "debug"))] +#[cfg_attr(not(feature = "debug"), macro_use)] +mod debug_macros { + macro_rules! debugln { + ($fmt:expr) => (); + ($fmt:expr, $($arg:tt)*) => (); + } + macro_rules! sdebugln { + ($fmt:expr) => (); + ($fmt:expr, $($arg:tt)*) => (); + } + macro_rules! debug { + ($fmt:expr) => (); + ($fmt:expr, $($arg:tt)*) => (); + } +} + +// Helper/deduplication macro for printing the correct number of spaces in help messages +// used in: +// src/args/arg_builder/*.rs +// src/app/mod.rs +macro_rules! write_nspaces { + ($dst:expr, $num:expr) => ({ + debugln!("write_spaces!: num={}", $num); + for _ in 0..$num { + $dst.write_all(b" ")?; + } + }) +} + +// convenience macro for remove an item from a vec +//macro_rules! vec_remove_all { +// ($vec:expr, $to_rem:expr) => { +// debugln!("vec_remove_all! to_rem={:?}", $to_rem); +// for i in (0 .. $vec.len()).rev() { +// let should_remove = $to_rem.any(|name| name == &$vec[i]); +// if should_remove { $vec.swap_remove(i); } +// } +// }; +//} +macro_rules! find_from { + ($_self:expr, $arg_name:expr, $from:ident, $matcher:expr) => {{ + let mut ret = None; + for k in $matcher.arg_names() { + if let Some(f) = find_by_name!($_self, k, flags, iter) { + if let Some(ref v) = f.$from() { + if v.contains($arg_name) { + ret = Some(f.to_string()); + } + } + } + if let Some(o) = find_by_name!($_self, k, opts, iter) { + if let Some(ref v) = o.$from() { + if v.contains(&$arg_name) { + ret = Some(o.to_string()); + } + } + } + if let Some(pos) = find_by_name!($_self, k, positionals, values) { + if let Some(ref v) = pos.$from() { + if v.contains($arg_name) { + ret = Some(pos.b.name.to_owned()); + } + } + } + } + ret + }}; +} + +//macro_rules! find_name_from { +// ($_self:expr, $arg_name:expr, $from:ident, $matcher:expr) => {{ +// let mut ret = None; +// for k in $matcher.arg_names() { +// if let Some(f) = find_by_name!($_self, k, flags, iter) { +// if let Some(ref v) = f.$from() { +// if v.contains($arg_name) { +// ret = Some(f.b.name); +// } +// } +// } +// if let Some(o) = find_by_name!($_self, k, opts, iter) { +// if let Some(ref v) = o.$from() { +// if v.contains(&$arg_name) { +// ret = Some(o.b.name); +// } +// } +// } +// if let Some(pos) = find_by_name!($_self, k, positionals, values) { +// if let Some(ref v) = pos.$from() { +// if v.contains($arg_name) { +// ret = Some(pos.b.name); +// } +// } +// } +// } +// ret +// }}; +//} + + +macro_rules! find_any_by_name { + ($p:expr, $name:expr) => { + { + fn as_trait_obj<'a, 'b, T: AnyArg<'a, 'b>>(x: &T) -> &AnyArg<'a, 'b> { x } + find_by_name!($p, $name, flags, iter).map(as_trait_obj).or( + find_by_name!($p, $name, opts, iter).map(as_trait_obj).or( + find_by_name!($p, $name, positionals, values).map(as_trait_obj) + ) + ) + } + } +} +// Finds an arg by name +macro_rules! find_by_name { + ($p:expr, $name:expr, $what:ident, $how:ident) => { + $p.$what.$how().find(|o| o.b.name == $name) + } +} + +// Finds an option including if it's aliased +macro_rules! find_opt_by_long { + (@os $_self:ident, $long:expr) => {{ + _find_by_long!($_self, $long, opts) + }}; + ($_self:ident, $long:expr) => {{ + _find_by_long!($_self, $long, opts) + }}; +} + +macro_rules! find_flag_by_long { + (@os $_self:ident, $long:expr) => {{ + _find_by_long!($_self, $long, flags) + }}; + ($_self:ident, $long:expr) => {{ + _find_by_long!($_self, $long, flags) + }}; +} + +macro_rules! _find_by_long { + ($_self:ident, $long:expr, $what:ident) => {{ + $_self.$what + .iter() + .filter(|a| a.s.long.is_some()) + .find(|a| { + a.s.long.unwrap() == $long || + (a.s.aliases.is_some() && + a.s + .aliases + .as_ref() + .unwrap() + .iter() + .any(|&(alias, _)| alias == $long)) + }) + }} +} + +// Finds an option +macro_rules! find_opt_by_short { + ($_self:ident, $short:expr) => {{ + _find_by_short!($_self, $short, opts) + }} +} + +macro_rules! find_flag_by_short { + ($_self:ident, $short:expr) => {{ + _find_by_short!($_self, $short, flags) + }} +} + +macro_rules! _find_by_short { + ($_self:ident, $short:expr, $what:ident) => {{ + $_self.$what + .iter() + .filter(|a| a.s.short.is_some()) + .find(|a| a.s.short.unwrap() == $short) + }} +} + +macro_rules! find_subcmd { + ($_self:expr, $sc:expr) => {{ + $_self.subcommands + .iter() + .find(|s| { + &*s.p.meta.name == $sc || + (s.p.meta.aliases.is_some() && + s.p + .meta + .aliases + .as_ref() + .unwrap() + .iter() + .any(|&(n, _)| n == $sc)) + }) + }}; +} + +macro_rules! shorts { + ($_self:ident) => {{ + _shorts_longs!($_self, short) + }}; +} + + +macro_rules! longs { + ($_self:ident) => {{ + _shorts_longs!($_self, long) + }}; +} + +macro_rules! _shorts_longs { + ($_self:ident, $what:ident) => {{ + $_self.flags + .iter() + .filter(|f| f.s.$what.is_some()) + .map(|f| f.s.$what.as_ref().unwrap()) + .chain($_self.opts.iter() + .filter(|o| o.s.$what.is_some()) + .map(|o| o.s.$what.as_ref().unwrap())) + }}; +} + +macro_rules! arg_names { + ($_self:ident) => {{ + _names!(@args $_self) + }}; +} + +macro_rules! sc_names { + ($_self:ident) => {{ + _names!(@sc $_self) + }}; +} + +macro_rules! _names { + (@args $_self:ident) => {{ + $_self.flags + .iter() + .map(|f| &*f.b.name) + .chain($_self.opts.iter() + .map(|o| &*o.b.name) + .chain($_self.positionals.values() + .map(|p| &*p.b.name))) + }}; + (@sc $_self:ident) => {{ + $_self.subcommands + .iter() + .map(|s| &*s.p.meta.name) + .chain($_self.subcommands + .iter() + .filter(|s| s.p.meta.aliases.is_some()) + .flat_map(|s| s.p.meta.aliases.as_ref().unwrap().iter().map(|&(n, _)| n))) + + }} +} diff --git a/clap/src/map.rs b/clap/src/map.rs new file mode 100644 index 0000000..063a860 --- /dev/null +++ b/clap/src/map.rs @@ -0,0 +1,74 @@ +#[cfg(feature = "vec_map")] +pub use vec_map::{Values, VecMap}; + +#[cfg(not(feature = "vec_map"))] +pub use self::vec_map::{Values, VecMap}; + +#[cfg(not(feature = "vec_map"))] +mod vec_map { + use std::collections::BTreeMap; + use std::collections::btree_map; + use std::fmt::{self, Debug, Formatter}; + + #[derive(Clone, Default, Debug)] + pub struct VecMap { + inner: BTreeMap, + } + + impl VecMap { + pub fn new() -> Self { + VecMap { + inner: Default::default(), + } + } + + pub fn len(&self) -> usize { self.inner.len() } + + pub fn is_empty(&self) -> bool { self.inner.is_empty() } + + pub fn insert(&mut self, key: usize, value: V) -> Option { + self.inner.insert(key, value) + } + + pub fn values(&self) -> Values { self.inner.values() } + + pub fn iter(&self) -> Iter { + Iter { + inner: self.inner.iter(), + } + } + + pub fn contains_key(&self, key: usize) -> bool { self.inner.contains_key(&key) } + + pub fn entry(&mut self, key: usize) -> Entry { self.inner.entry(key) } + + pub fn get(&self, key: usize) -> Option<&V> { self.inner.get(&key) } + } + + pub type Values<'a, V> = btree_map::Values<'a, usize, V>; + + pub type Entry<'a, V> = btree_map::Entry<'a, usize, V>; + + #[derive(Clone)] + pub struct Iter<'a, V: 'a> { + inner: btree_map::Iter<'a, usize, V>, + } + + impl<'a, V: 'a + Debug> Debug for Iter<'a, V> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.debug_list().entries(self.inner.clone()).finish() + } + } + + impl<'a, V: 'a> Iterator for Iter<'a, V> { + type Item = (usize, &'a V); + + fn next(&mut self) -> Option { self.inner.next().map(|(k, v)| (*k, v)) } + } + + impl<'a, V: 'a> DoubleEndedIterator for Iter<'a, V> { + fn next_back(&mut self) -> Option { + self.inner.next_back().map(|(k, v)| (*k, v)) + } + } +} diff --git a/clap/src/osstringext.rs b/clap/src/osstringext.rs new file mode 100644 index 0000000..061c01d --- /dev/null +++ b/clap/src/osstringext.rs @@ -0,0 +1,119 @@ +#[cfg(any(target_os = "windows", target_arch = "wasm32"))] +use INVALID_UTF8; +use std::ffi::OsStr; +#[cfg(not(any(target_os = "windows", target_arch = "wasm32")))] +use std::os::unix::ffi::OsStrExt; + +#[cfg(any(target_os = "windows", target_arch = "wasm32"))] +pub trait OsStrExt3 { + fn from_bytes(b: &[u8]) -> &Self; + fn as_bytes(&self) -> &[u8]; +} + +#[doc(hidden)] +pub trait OsStrExt2 { + fn starts_with(&self, s: &[u8]) -> bool; + fn split_at_byte(&self, b: u8) -> (&OsStr, &OsStr); + fn split_at(&self, i: usize) -> (&OsStr, &OsStr); + fn trim_left_matches(&self, b: u8) -> &OsStr; + fn contains_byte(&self, b: u8) -> bool; + fn split(&self, b: u8) -> OsSplit; +} + +#[cfg(any(target_os = "windows", target_arch = "wasm32"))] +impl OsStrExt3 for OsStr { + fn from_bytes(b: &[u8]) -> &Self { + use std::mem; + unsafe { mem::transmute(b) } + } + fn as_bytes(&self) -> &[u8] { + self.to_str().map(|s| s.as_bytes()).expect(INVALID_UTF8) + } +} + +impl OsStrExt2 for OsStr { + fn starts_with(&self, s: &[u8]) -> bool { + self.as_bytes().starts_with(s) + } + + fn contains_byte(&self, byte: u8) -> bool { + for b in self.as_bytes() { + if b == &byte { + return true; + } + } + false + } + + fn split_at_byte(&self, byte: u8) -> (&OsStr, &OsStr) { + for (i, b) in self.as_bytes().iter().enumerate() { + if b == &byte { + return ( + OsStr::from_bytes(&self.as_bytes()[..i]), + OsStr::from_bytes(&self.as_bytes()[i + 1..]), + ); + } + } + ( + &*self, + OsStr::from_bytes(&self.as_bytes()[self.len()..self.len()]), + ) + } + + fn trim_left_matches(&self, byte: u8) -> &OsStr { + let mut found = false; + for (i, b) in self.as_bytes().iter().enumerate() { + if b != &byte { + return OsStr::from_bytes(&self.as_bytes()[i..]); + } else { + found = true; + } + } + if found { + return OsStr::from_bytes(&self.as_bytes()[self.len()..]); + } + &*self + } + + fn split_at(&self, i: usize) -> (&OsStr, &OsStr) { + ( + OsStr::from_bytes(&self.as_bytes()[..i]), + OsStr::from_bytes(&self.as_bytes()[i..]), + ) + } + + fn split(&self, b: u8) -> OsSplit { + OsSplit { + sep: b, + val: self.as_bytes(), + pos: 0, + } + } +} + +#[doc(hidden)] +#[derive(Clone, Debug)] +pub struct OsSplit<'a> { + sep: u8, + val: &'a [u8], + pos: usize, +} + +impl<'a> Iterator for OsSplit<'a> { + type Item = &'a OsStr; + + fn next(&mut self) -> Option<&'a OsStr> { + debugln!("OsSplit::next: self={:?}", self); + if self.pos == self.val.len() { + return None; + } + let start = self.pos; + for b in &self.val[start..] { + self.pos += 1; + if *b == self.sep { + return Some(OsStr::from_bytes(&self.val[start..self.pos - 1])); + } + } + Some(OsStr::from_bytes(&self.val[start..])) + } +} diff --git a/clap/src/strext.rs b/clap/src/strext.rs new file mode 100644 index 0000000..6f81367 --- /dev/null +++ b/clap/src/strext.rs @@ -0,0 +1,16 @@ +pub trait _StrExt { + fn _is_char_boundary(&self, index: usize) -> bool; +} + +impl _StrExt for str { + #[inline] + fn _is_char_boundary(&self, index: usize) -> bool { + if index == self.len() { + return true; + } + match self.as_bytes().get(index) { + None => false, + Some(&b) => b < 128 || b >= 192, + } + } +} diff --git a/clap/src/suggestions.rs b/clap/src/suggestions.rs new file mode 100644 index 0000000..06071d2 --- /dev/null +++ b/clap/src/suggestions.rs @@ -0,0 +1,147 @@ +use app::App; +// Third Party +#[cfg(feature = "suggestions")] +use strsim; + +// Internal +use fmt::Format; + +/// Produces a string from a given list of possible values which is similar to +/// the passed in value `v` with a certain confidence. +/// Thus in a list of possible values like ["foo", "bar"], the value "fop" will yield +/// `Some("foo")`, whereas "blark" would yield `None`. +#[cfg(feature = "suggestions")] +#[cfg_attr(feature = "lints", allow(needless_lifetimes))] +pub fn did_you_mean<'a, T: ?Sized, I>(v: &str, possible_values: I) -> Option<&'a str> +where + T: AsRef + 'a, + I: IntoIterator, +{ + let mut candidate: Option<(f64, &str)> = None; + for pv in possible_values { + let confidence = strsim::jaro_winkler(v, pv.as_ref()); + if confidence > 0.8 && (candidate.is_none() || (candidate.as_ref().unwrap().0 < confidence)) + { + candidate = Some((confidence, pv.as_ref())); + } + } + match candidate { + None => None, + Some((_, candidate)) => Some(candidate), + } +} + +#[cfg(not(feature = "suggestions"))] +pub fn did_you_mean<'a, T: ?Sized, I>(_: &str, _: I) -> Option<&'a str> +where + T: AsRef + 'a, + I: IntoIterator, +{ + None +} + +/// Returns a suffix that can be empty, or is the standard 'did you mean' phrase +#[cfg_attr(feature = "lints", allow(needless_lifetimes))] +pub fn did_you_mean_flag_suffix<'z, T, I>( + arg: &str, + args_rest: &'z [&str], + longs: I, + subcommands: &'z [App], +) -> (String, Option<&'z str>) +where + T: AsRef + 'z, + I: IntoIterator, +{ + if let Some(candidate) = did_you_mean(arg, longs) { + let suffix = format!( + "\n\tDid you mean {}{}?", + Format::Good("--"), + Format::Good(candidate) + ); + return (suffix, Some(candidate)); + } + + subcommands + .into_iter() + .filter_map(|subcommand| { + let opts = subcommand + .p + .flags + .iter() + .filter_map(|f| f.s.long) + .chain(subcommand.p.opts.iter().filter_map(|o| o.s.long)); + + let candidate = match did_you_mean(arg, opts) { + Some(candidate) => candidate, + None => return None + }; + let score = match args_rest.iter().position(|x| *x == subcommand.get_name()) { + Some(score) => score, + None => return None + }; + + let suffix = format!( + "\n\tDid you mean to put '{}{}' after the subcommand '{}'?", + Format::Good("--"), + Format::Good(candidate), + Format::Good(subcommand.get_name()) + ); + + Some((score, (suffix, Some(candidate)))) + }) + .min_by_key(|&(score, _)| score) + .map(|(_, suggestion)| suggestion) + .unwrap_or_else(|| (String::new(), None)) +} + +/// Returns a suffix that can be empty, or is the standard 'did you mean' phrase +pub fn did_you_mean_value_suffix<'z, T, I>(arg: &str, values: I) -> (String, Option<&'z str>) +where + T: AsRef + 'z, + I: IntoIterator, +{ + match did_you_mean(arg, values) { + Some(candidate) => { + let suffix = format!("\n\tDid you mean '{}'?", Format::Good(candidate)); + (suffix, Some(candidate)) + } + None => (String::new(), None), + } +} + +#[cfg(all(test, features = "suggestions"))] +mod test { + use super::*; + + #[test] + fn possible_values_match() { + let p_vals = ["test", "possible", "values"]; + assert_eq!(did_you_mean("tst", p_vals.iter()), Some("test")); + } + + #[test] + fn possible_values_nomatch() { + let p_vals = ["test", "possible", "values"]; + assert!(did_you_mean("hahaahahah", p_vals.iter()).is_none()); + } + + #[test] + fn suffix_long() { + let p_vals = ["test", "possible", "values"]; + let suffix = "\n\tDid you mean \'--test\'?"; + assert_eq!( + did_you_mean_flag_suffix("tst", p_vals.iter(), []), + (suffix, Some("test")) + ); + } + + #[test] + fn suffix_enum() { + let p_vals = ["test", "possible", "values"]; + let suffix = "\n\tDid you mean \'test\'?"; + assert_eq!( + did_you_mean_value_suffix("tst", p_vals.iter()), + (suffix, Some("test")) + ); + } +} diff --git a/clap/src/usage_parser.rs b/clap/src/usage_parser.rs new file mode 100644 index 0000000..f6d5ac6 --- /dev/null +++ b/clap/src/usage_parser.rs @@ -0,0 +1,1347 @@ +// Internal +use INTERNAL_ERROR_MSG; +use args::Arg; +use args::settings::ArgSettings; +use map::VecMap; + +#[derive(PartialEq, Debug)] +enum UsageToken { + Name, + ValName, + Short, + Long, + Help, + Multiple, + Unknown, +} + +#[doc(hidden)] +#[derive(Debug)] +pub struct UsageParser<'a> { + usage: &'a str, + pos: usize, + start: usize, + prev: UsageToken, + explicit_name_set: bool, +} + +impl<'a> UsageParser<'a> { + fn new(usage: &'a str) -> Self { + debugln!("UsageParser::new: usage={:?}", usage); + UsageParser { + usage: usage, + pos: 0, + start: 0, + prev: UsageToken::Unknown, + explicit_name_set: false, + } + } + + pub fn from_usage(usage: &'a str) -> Self { + debugln!("UsageParser::from_usage;"); + UsageParser::new(usage) + } + + pub fn parse(mut self) -> Arg<'a, 'a> { + debugln!("UsageParser::parse;"); + let mut arg = Arg::default(); + loop { + debugln!("UsageParser::parse:iter: pos={};", self.pos); + self.stop_at(token); + if let Some(&c) = self.usage.as_bytes().get(self.pos) { + match c { + b'-' => self.short_or_long(&mut arg), + b'.' => self.multiple(&mut arg), + b'\'' => self.help(&mut arg), + _ => self.name(&mut arg), + } + } else { + break; + } + } + debug_assert!( + !arg.b.name.is_empty(), + format!( + "No name found for Arg when parsing usage string: {}", + self.usage + ) + ); + arg.v.num_vals = match arg.v.val_names { + Some(ref v) if v.len() >= 2 => Some(v.len() as u64), + _ => None, + }; + debugln!("UsageParser::parse: vals...{:?}", arg.v.val_names); + arg + } + + fn name(&mut self, arg: &mut Arg<'a, 'a>) { + debugln!("UsageParser::name;"); + if *self.usage + .as_bytes() + .get(self.pos) + .expect(INTERNAL_ERROR_MSG) == b'<' && !self.explicit_name_set + { + arg.setb(ArgSettings::Required); + } + self.pos += 1; + self.stop_at(name_end); + let name = &self.usage[self.start..self.pos]; + if self.prev == UsageToken::Unknown { + debugln!("UsageParser::name: setting name...{}", name); + arg.b.name = name; + if arg.s.long.is_none() && arg.s.short.is_none() { + debugln!("UsageParser::name: explicit name set..."); + self.explicit_name_set = true; + self.prev = UsageToken::Name; + } + } else { + debugln!("UsageParser::name: setting val name...{}", name); + if let Some(ref mut v) = arg.v.val_names { + let len = v.len(); + v.insert(len, name); + } else { + let mut v = VecMap::new(); + v.insert(0, name); + arg.v.val_names = Some(v); + arg.setb(ArgSettings::TakesValue); + } + self.prev = UsageToken::ValName; + } + } + + fn stop_at(&mut self, f: F) + where + F: Fn(u8) -> bool, + { + debugln!("UsageParser::stop_at;"); + self.start = self.pos; + self.pos += self.usage[self.start..] + .bytes() + .take_while(|&b| f(b)) + .count(); + } + + fn short_or_long(&mut self, arg: &mut Arg<'a, 'a>) { + debugln!("UsageParser::short_or_long;"); + self.pos += 1; + if *self.usage + .as_bytes() + .get(self.pos) + .expect(INTERNAL_ERROR_MSG) == b'-' + { + self.pos += 1; + self.long(arg); + return; + } + self.short(arg) + } + + fn long(&mut self, arg: &mut Arg<'a, 'a>) { + debugln!("UsageParser::long;"); + self.stop_at(long_end); + let name = &self.usage[self.start..self.pos]; + if !self.explicit_name_set { + debugln!("UsageParser::long: setting name...{}", name); + arg.b.name = name; + } + debugln!("UsageParser::long: setting long...{}", name); + arg.s.long = Some(name); + self.prev = UsageToken::Long; + } + + fn short(&mut self, arg: &mut Arg<'a, 'a>) { + debugln!("UsageParser::short;"); + let start = &self.usage[self.pos..]; + let short = start.chars().nth(0).expect(INTERNAL_ERROR_MSG); + debugln!("UsageParser::short: setting short...{}", short); + arg.s.short = Some(short); + if arg.b.name.is_empty() { + // --long takes precedence but doesn't set self.explicit_name_set + let name = &start[..short.len_utf8()]; + debugln!("UsageParser::short: setting name...{}", name); + arg.b.name = name; + } + self.prev = UsageToken::Short; + } + + // "something..." + fn multiple(&mut self, arg: &mut Arg) { + debugln!("UsageParser::multiple;"); + let mut dot_counter = 1; + let start = self.pos; + let mut bytes = self.usage[start..].bytes(); + while bytes.next() == Some(b'.') { + dot_counter += 1; + self.pos += 1; + if dot_counter == 3 { + debugln!("UsageParser::multiple: setting multiple"); + arg.setb(ArgSettings::Multiple); + if arg.is_set(ArgSettings::TakesValue) { + arg.setb(ArgSettings::UseValueDelimiter); + arg.unsetb(ArgSettings::ValueDelimiterNotSet); + if arg.v.val_delim.is_none() { + arg.v.val_delim = Some(','); + } + } + self.prev = UsageToken::Multiple; + self.pos += 1; + break; + } + } + } + + fn help(&mut self, arg: &mut Arg<'a, 'a>) { + debugln!("UsageParser::help;"); + self.stop_at(help_start); + self.start = self.pos + 1; + self.pos = self.usage.len() - 1; + debugln!( + "UsageParser::help: setting help...{}", + &self.usage[self.start..self.pos] + ); + arg.b.help = Some(&self.usage[self.start..self.pos]); + self.pos += 1; // Move to next byte to keep from thinking ending ' is a start + self.prev = UsageToken::Help; + } +} + +#[inline] +fn name_end(b: u8) -> bool { b != b']' && b != b'>' } + +#[inline] +fn token(b: u8) -> bool { b != b'\'' && b != b'.' && b != b'<' && b != b'[' && b != b'-' } + +#[inline] +fn long_end(b: u8) -> bool { + b != b'\'' && b != b'.' && b != b'<' && b != b'[' && b != b'=' && b != b' ' +} + +#[inline] +fn help_start(b: u8) -> bool { b != b'\'' } + +#[cfg(test)] +mod test { + use args::Arg; + use args::ArgSettings; + + #[test] + fn create_flag_usage() { + let a = Arg::from_usage("[flag] -f 'some help info'"); + assert_eq!(a.b.name, "flag"); + assert_eq!(a.s.short.unwrap(), 'f'); + assert!(a.s.long.is_none()); + assert_eq!(a.b.help.unwrap(), "some help info"); + assert!(!a.is_set(ArgSettings::Multiple)); + assert!(a.v.val_names.is_none()); + assert!(a.v.num_vals.is_none()); + + let b = Arg::from_usage("[flag] --flag 'some help info'"); + assert_eq!(b.b.name, "flag"); + assert_eq!(b.s.long.unwrap(), "flag"); + assert!(b.s.short.is_none()); + assert_eq!(b.b.help.unwrap(), "some help info"); + assert!(!b.is_set(ArgSettings::Multiple)); + assert!(a.v.val_names.is_none()); + assert!(a.v.num_vals.is_none()); + + let b = Arg::from_usage("--flag 'some help info'"); + assert_eq!(b.b.name, "flag"); + assert_eq!(b.s.long.unwrap(), "flag"); + assert!(b.s.short.is_none()); + assert_eq!(b.b.help.unwrap(), "some help info"); + assert!(!b.is_set(ArgSettings::Multiple)); + assert!(b.v.val_names.is_none()); + assert!(b.v.num_vals.is_none()); + + let c = Arg::from_usage("[flag] -f --flag 'some help info'"); + assert_eq!(c.b.name, "flag"); + assert_eq!(c.s.short.unwrap(), 'f'); + assert_eq!(c.s.long.unwrap(), "flag"); + assert_eq!(c.b.help.unwrap(), "some help info"); + assert!(!c.is_set(ArgSettings::Multiple)); + assert!(c.v.val_names.is_none()); + assert!(c.v.num_vals.is_none()); + + let d = Arg::from_usage("[flag] -f... 'some help info'"); + assert_eq!(d.b.name, "flag"); + assert_eq!(d.s.short.unwrap(), 'f'); + assert!(d.s.long.is_none()); + assert_eq!(d.b.help.unwrap(), "some help info"); + assert!(d.is_set(ArgSettings::Multiple)); + assert!(d.v.val_names.is_none()); + assert!(d.v.num_vals.is_none()); + + let e = Arg::from_usage("[flag] -f --flag... 'some help info'"); + assert_eq!(e.b.name, "flag"); + assert_eq!(e.s.long.unwrap(), "flag"); + assert_eq!(e.s.short.unwrap(), 'f'); + assert_eq!(e.b.help.unwrap(), "some help info"); + assert!(e.is_set(ArgSettings::Multiple)); + assert!(e.v.val_names.is_none()); + assert!(e.v.num_vals.is_none()); + + let e = Arg::from_usage("-f --flag... 'some help info'"); + assert_eq!(e.b.name, "flag"); + assert_eq!(e.s.long.unwrap(), "flag"); + assert_eq!(e.s.short.unwrap(), 'f'); + assert_eq!(e.b.help.unwrap(), "some help info"); + assert!(e.is_set(ArgSettings::Multiple)); + assert!(e.v.val_names.is_none()); + assert!(e.v.num_vals.is_none()); + + let e = Arg::from_usage("--flags"); + assert_eq!(e.b.name, "flags"); + assert_eq!(e.s.long.unwrap(), "flags"); + assert!(e.v.val_names.is_none()); + assert!(e.v.num_vals.is_none()); + + let e = Arg::from_usage("--flags..."); + assert_eq!(e.b.name, "flags"); + assert_eq!(e.s.long.unwrap(), "flags"); + assert!(e.is_set(ArgSettings::Multiple)); + assert!(e.v.val_names.is_none()); + assert!(e.v.num_vals.is_none()); + + let e = Arg::from_usage("[flags] -f"); + assert_eq!(e.b.name, "flags"); + assert_eq!(e.s.short.unwrap(), 'f'); + assert!(e.v.val_names.is_none()); + assert!(e.v.num_vals.is_none()); + + let e = Arg::from_usage("[flags] -f..."); + assert_eq!(e.b.name, "flags"); + assert_eq!(e.s.short.unwrap(), 'f'); + assert!(e.is_set(ArgSettings::Multiple)); + assert!(e.v.val_names.is_none()); + assert!(e.v.num_vals.is_none()); + + let a = Arg::from_usage("-f 'some help info'"); + assert_eq!(a.b.name, "f"); + assert_eq!(a.s.short.unwrap(), 'f'); + assert!(a.s.long.is_none()); + assert_eq!(a.b.help.unwrap(), "some help info"); + assert!(!a.is_set(ArgSettings::Multiple)); + assert!(a.v.val_names.is_none()); + assert!(a.v.num_vals.is_none()); + + let e = Arg::from_usage("-f"); + assert_eq!(e.b.name, "f"); + assert_eq!(e.s.short.unwrap(), 'f'); + assert!(e.v.val_names.is_none()); + assert!(e.v.num_vals.is_none()); + + let e = Arg::from_usage("-f..."); + assert_eq!(e.b.name, "f"); + assert_eq!(e.s.short.unwrap(), 'f'); + assert!(e.is_set(ArgSettings::Multiple)); + assert!(e.v.val_names.is_none()); + assert!(e.v.num_vals.is_none()); + } + + #[test] + fn create_option_usage0() { + // Short only + let a = Arg::from_usage("[option] -o [opt] 'some help info'"); + assert_eq!(a.b.name, "option"); + assert_eq!(a.s.short.unwrap(), 'o'); + assert!(a.s.long.is_none()); + assert_eq!(a.b.help.unwrap(), "some help info"); + assert!(!a.is_set(ArgSettings::Multiple)); + assert!(a.is_set(ArgSettings::TakesValue)); + assert!(!a.is_set(ArgSettings::Required)); + assert_eq!( + a.v.val_names.unwrap().values().collect::>(), + [&"opt"] + ); + assert!(a.v.num_vals.is_none()); + } + + #[test] + fn create_option_usage1() { + let b = Arg::from_usage("-o [opt] 'some help info'"); + assert_eq!(b.b.name, "o"); + assert_eq!(b.s.short.unwrap(), 'o'); + assert!(b.s.long.is_none()); + assert_eq!(b.b.help.unwrap(), "some help info"); + assert!(!b.is_set(ArgSettings::Multiple)); + assert!(b.is_set(ArgSettings::TakesValue)); + assert!(!b.is_set(ArgSettings::Required)); + assert_eq!( + b.v.val_names.unwrap().values().collect::>(), + [&"opt"] + ); + assert!(b.v.num_vals.is_none()); + } + + #[test] + fn create_option_usage2() { + let c = Arg::from_usage("Apache License, Version +2.0 or MIT license at your option. + + +
+ + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in this crate by you, as defined in the Apache-2.0 license, shall +be dual licensed as above, without any additional terms or conditions. + + + +[compl_err]: https://doc.rust-lang.org/std/macro.compile_error.html +[`proc_macro::Diagnostic`]: https://doc.rust-lang.org/proc_macro/struct.Diagnostic.html + +[crate::dummy]: https://docs.rs/proc-macro-error/0.4/proc_macro_error/dummy/index.html +[crate::multi]: https://docs.rs/proc-macro-error/0.4/proc_macro_error/multi/index.html + +[`abort_call_site!`]: https://docs.rs/proc-macro-error/0.4/proc_macro_error/macro.abort_call_site.html +[`abort!`]: https://docs.rs/proc-macro-error/0.4/proc_macro_error/macro.abort.html +[guide]: https://docs.rs/proc-macro-error diff --git a/proc-macro-error/proc-macro-error-attr/Cargo.toml b/proc-macro-error/proc-macro-error-attr/Cargo.toml new file mode 100644 index 0000000..6dee68e --- /dev/null +++ b/proc-macro-error/proc-macro-error-attr/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "proc-macro-error-attr" +version = "0.4.3" +authors = ["CreepySkeleton "] +edition = "2018" +description = "attribute macro for proc-macro-error crate" +license = "MIT OR Apache-2.0" +repository = "https://gitlab.com/CreepySkeleton/proc-macro-error" + +[lib] +proc-macro = true + +[dependencies] +quote = "1" +proc-macro2 = "1" +syn = { version = "1", default-features = false, features = ["derive", "parsing", "proc-macro"] } +syn-mid = "0.4" +rustversion = "1.0" diff --git a/proc-macro-error/proc-macro-error-attr/src/lib.rs b/proc-macro-error/proc-macro-error-attr/src/lib.rs new file mode 100644 index 0000000..3c1013b --- /dev/null +++ b/proc-macro-error/proc-macro-error-attr/src/lib.rs @@ -0,0 +1,162 @@ +extern crate proc_macro; + +use proc_macro::TokenStream; +use proc_macro2::Ident; +use quote::quote; +use std::iter::FromIterator; +use syn::{ + parse::{Parse, ParseStream}, + parse_macro_input, + punctuated::Punctuated, + Attribute, Token, +}; +use syn_mid::{Block, ItemFn}; + +use self::Setting::*; + +#[proc_macro_attribute] +pub fn proc_macro_error(attr: TokenStream, input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as ItemFn); + let mut settings = match syn::parse::(attr) { + Ok(settings) => settings, + Err(err) => { + let err = err.to_compile_error(); + return quote!(#input #err).into(); + } + }; + + let is_proc_macro = is_proc_macro(&input.attrs); + if is_proc_macro { + settings.set(AssertUnwindSafe); + } + + if detect_proc_macro_hack(&input.attrs) { + settings.set(ProcMacroHack); + } + + if !(settings.is_set(AllowNotMacro) || is_proc_macro) { + return quote!( + #input + compile_error!("#[proc_macro_error] attribute can be used only with a proc-macro\n\n \ + hint: if you are really sure that #[proc_macro_error] should be applied \ + to this exact function use #[proc_macro_error(allow_not_macro)]\n"); + ) + .into(); + } + + let ItemFn { + attrs, + vis, + constness, + asyncness, + unsafety, + abi, + fn_token, + ident, + generics, + inputs, + output, + block, + .. + } = input; + + let body = gen_body(block, settings); + + quote!( + #(#attrs)* + #vis + #constness + #asyncness + #unsafety + #abi + #fn_token + #ident + #generics + (#inputs) + #output + + { #body } + ) + .into() +} + +#[derive(PartialEq)] +enum Setting { + AssertUnwindSafe, + AllowNotMacro, + ProcMacroHack, +} + +impl Parse for Setting { + fn parse(input: ParseStream) -> syn::Result { + let ident: Ident = input.parse()?; + match &*ident.to_string() { + "assert_unwind_safe" => Ok(AssertUnwindSafe), + "allow_not_macro" => Ok(AllowNotMacro), + "proc_macro_hack" => Ok(ProcMacroHack), + _ => Err(syn::Error::new( + ident.span(), + format!( + "unknown setting `{}`, expected one of \ + `assert_unwind_safe`, `allow_not_macro`, `proc_macro_hack`", + ident + ), + )), + } + } +} + +struct Settings(Vec); +impl Parse for Settings { + fn parse(input: ParseStream) -> syn::Result { + let punct = Punctuated::::parse_terminated(input)?; + Ok(Settings(Vec::from_iter(punct))) + } +} + +impl Settings { + fn is_set(&self, setting: Setting) -> bool { + self.0.iter().any(|s| *s == setting) + } + + fn set(&mut self, setting: Setting) { + self.0.push(setting) + } +} + +#[rustversion::since(1.37)] +fn gen_body(block: Block, settings: Settings) -> proc_macro2::TokenStream { + let is_proc_macro_hack = settings.is_set(ProcMacroHack); + let closure = if settings.is_set(AssertUnwindSafe) { + quote!(::std::panic::AssertUnwindSafe(|| #block )) + } else { + quote!(|| #block) + }; + + quote!( ::proc_macro_error::entry_point(#closure, #is_proc_macro_hack) ) +} + +// FIXME: +// proc_macro::TokenStream does not implement UnwindSafe until 1.37.0. +// Considering this is the closure's return type the unwind safety check would fail +// for virtually every closure possible, the check is meaningless. +#[rustversion::before(1.37)] +fn gen_body(block: Block, settings: Settings) -> proc_macro2::TokenStream { + let is_proc_macro_hack = settings.is_set(ProcMacroHack); + let closure = quote!(::std::panic::AssertUnwindSafe(|| #block )); + quote!( ::proc_macro_error::entry_point(#closure, #is_proc_macro_hack) ) +} + +fn detect_proc_macro_hack(attrs: &[Attribute]) -> bool { + attrs + .iter() + .any(|attr| attr.path.is_ident("proc_macro_hack")) +} + +fn is_proc_macro(attrs: &[Attribute]) -> bool { + attrs.iter().any(|attr| { + attr.path.is_ident("proc_macro") + || attr.path.is_ident("proc_macro_derive") + || attr.path.is_ident("proc_macro_attribute") + }) +} diff --git a/proc-macro-error/proc-macro-error/Cargo.toml b/proc-macro-error/proc-macro-error/Cargo.toml new file mode 100644 index 0000000..2da213a --- /dev/null +++ b/proc-macro-error/proc-macro-error/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "proc-macro-error" +version = "0.4.4" +authors = ["CreepySkeleton "] +description = "Almost drop-in replacement to panics in proc-macros" + +repository = "https://gitlab.com/CreepySkeleton/proc-macro-error" +readme = "../README.md" +keywords = ["proc-macro", "error", "errors"] +categories = ["development-tools::procedural-macro-helpers"] +license = "MIT OR Apache-2.0" + +edition = "2018" +build = "build.rs" + +[badges] +maintenance = { status = "actively-developed" } + +[dependencies] +quote = "1" +proc-macro2 = "1" +syn = { version = "1", default-features = false } +proc-macro-error-attr = { path = "../proc-macro-error-attr", version = "0.4.3"} + +[build-dependencies] +rustversion = "1.0" diff --git a/proc-macro-error/proc-macro-error/build.rs b/proc-macro-error/proc-macro-error/build.rs new file mode 100644 index 0000000..04ddc11 --- /dev/null +++ b/proc-macro-error/proc-macro-error/build.rs @@ -0,0 +1,11 @@ +#[rustversion::nightly] +fn nightly() { + println!("cargo:rustc-cfg=pme_nightly"); +} + +#[rustversion::not(nightly)] +fn nightly() {} + +fn main() { + nightly() +} diff --git a/proc-macro-error/proc-macro-error/src/dummy.rs b/proc-macro-error/proc-macro-error/src/dummy.rs new file mode 100644 index 0000000..f7443b3 --- /dev/null +++ b/proc-macro-error/proc-macro-error/src/dummy.rs @@ -0,0 +1,136 @@ +//! Facility to emit dummy implementations (or whatever) in case +//! an error happen. +//! +//! `compile_error!` does not abort a compilation right away. This means +//! `rustc` doesn't just show you the error and abort, it carries on the +//! compilation process looking for other errors to report. +//! +//! Let's consider an example: +//! +//! ```rust,ignore +//! use proc_macro::TokenStream; +//! use proc_macro_error::*; +//! +//! trait MyTrait { +//! fn do_thing(); +//! } +//! +//! // this proc macro is supposed to generate MyTrait impl +//! #[proc_macro_derive(MyTrait)] +//! #[proc_macro_error] +//! fn example(input: TokenStream) -> TokenStream { +//! // somewhere deep inside +//! abort!(span, "something's wrong"); +//! +//! // this implementation will be generated if no error happened +//! quote! { +//! impl MyTrait for #name { +//! fn do_thing() {/* whatever */} +//! } +//! } +//! } +//! +//! // ================ +//! // in main.rs +//! +//! // this derive triggers an error +//! #[derive(MyTrait)] // first BOOM! +//! struct Foo; +//! +//! fn main() { +//! Foo::do_thing(); // second BOOM! +//! } +//! ``` +//! +//! The problem is: the generated token stream contains only `compile_error!` +//! invocation, the impl was not generated. That means user will see two compilation +//! errors: +//! +//! ```text +//! error: something's wrong +//! --> $DIR/probe.rs:9:10 +//! | +//! 9 |#[proc_macro_derive(MyTrait)] +//! | ^^^^^^^ +//! +//! error[E0599]: no function or associated item named `do_thing` found for type `Foo` in the current scope +//! --> src\main.rs:3:10 +//! | +//! 1 | struct Foo; +//! | ----------- function or associated item `do_thing` not found for this +//! 2 | fn main() { +//! 3 | Foo::do_thing(); // second BOOM! +//! | ^^^^^^^^ function or associated item not found in `Foo` +//! ``` +//! +//! But the second error is meaningless! We definitely need to fix this. +//! +//! Most used approach in cases like this is "dummy implementation" - +//! omit `impl MyTrait for #name` and fill functions bodies with `unimplemented!()`. +//! +//! This is how you do it: +//! +//! ```rust,ignore +//! use proc_macro::TokenStream; +//! use proc_macro_error::*; +//! +//! trait MyTrait { +//! fn do_thing(); +//! } +//! +//! // this proc macro is supposed to generate MyTrait impl +//! #[proc_macro_derive(MyTrait)] +//! #[proc_macro_error] +//! fn example(input: TokenStream) -> TokenStream { +//! // first of all - we set a dummy impl which will be appended to +//! // `compile_error!` invocations in case a trigger does happen +//! set_dummy(quote! { +//! impl MyTrait for #name { +//! fn do_thing() { unimplemented!() } +//! } +//! }); +//! +//! // somewhere deep inside +//! abort!(span, "something's wrong"); +//! +//! // this implementation will be generated if no error happened +//! quote! { +//! impl MyTrait for #name { +//! fn do_thing() {/* whatever */} +//! } +//! } +//! } +//! +//! // ================ +//! // in main.rs +//! +//! // this derive triggers an error +//! #[derive(MyTrait)] // first BOOM! +//! struct Foo; +//! +//! fn main() { +//! Foo::do_thing(); // no more errors! +//! } +//! ``` + +use proc_macro2::TokenStream; +use std::cell::Cell; + +use crate::check_correctness; + +thread_local! { + static DUMMY_IMPL: Cell> = Cell::new(None); +} + +/// Sets dummy token stream which will be appended to `compile_error!(msg);...` +/// invocations in case you'll emit any errors. +/// +/// See [guide](../index.html#guide). +pub fn set_dummy(dummy: TokenStream) -> Option { + check_correctness(); + DUMMY_IMPL.with(|old_dummy| old_dummy.replace(Some(dummy))) +} + +pub(crate) fn cleanup() -> Option { + DUMMY_IMPL.with(|old_dummy| old_dummy.replace(None)) +} diff --git a/proc-macro-error/proc-macro-error/src/lib.rs b/proc-macro-error/proc-macro-error/src/lib.rs new file mode 100644 index 0000000..5fb0223 --- /dev/null +++ b/proc-macro-error/proc-macro-error/src/lib.rs @@ -0,0 +1,514 @@ +//! # proc-macro-error +//! +//! This crate aims to make error reporting in proc-macros simple and easy to use. +//! Migrate from `panic!`-based errors for as little effort as possible! +//! +//! Also, there's ability to [append a dummy token stream](dummy/index.html) to your errors. +//! +//! ## Limitations +//! +//! - Warnings are emitted only on nightly, they're ignored on stable. +//! - "help" suggestions cannot have their own span info on stable, (they inherit parent span). +//! - If a panic occurs somewhere in your macro no errors will be displayed. This is not a +//! technical limitation but intentional design, `panic` is not for error reporting. +//! +//! ## Guide +//! +//! ### Macros +//! +//! First of all - **all the emitting-related API must be used within a function +//! annotated with [`#[proc_macro_error]`](#proc_macro_error-attribute) attribute**. You'll just get a +//! panic otherwise, no errors will be shown. +//! +//! For most of the time you will be using macros. +//! +//! - [`abort!`]: +//! +//! Very much panic-like usage - abort execution and show the error. Expands to [`!`] (never type). +//! +//! - [`abort_call_site!`]: +//! +//! Shortcut for `abort!(Span::call_site(), ...)`. Expands to [`!`] (never type). +//! +//! - [`emit_error!`]: +//! +//! [`proc_macro::Diagnostic`]-like usage - emit the error but do not abort the macro. +//! The compilation will fail nonetheless. Expands to [`()`] (unit type). +//! +//! - [`emit_call_site_error!`]: +//! +//! Shortcut for `emit_error!(Span::call_site(), ...)`. Expands to [`()`] (unit type). +//! +//! - [`emit_warning!`]: +//! +//! Like `emit_error!` but emit a warning instead of error. The compilation won't fail +//! because of warnings. +//! Expands to [`()`] (unit type). +//! +//! **Beware**: warnings are nightly only, they are completely ignored on stable. +//! +//! - [`emit_call_site_warning!`]: +//! +//! Shortcut for `emit_warning!(Span::call_site(), ...)`. Expands to `()` (unit type). +//! +//! - [`diagnostic`]: +//! +//! Build instance of `Diagnostic` in format-like style. +//! +//! ### Syntax +//! +//! All the macros have pretty much the same syntax: +//! +//! 1. ```ignore +//! abort!(single_expr) +//! ``` +//! Shortcut for `Diagnostic::from().abort()` +//! +//! 2. ```ignore +//! abort!(span, message) +//! ``` +//! Shortcut for `Diagnostic::spanned(span, message.to_string()).abort()` +//! +//! 3. ```ignore +//! abort!(span, format_literal, format_args...) +//! ``` +//! Shortcut for `Diagnostic::spanned(span, format!(format_literal, format_args...)).abort()` +//! +//! That's it. `abort!`, `emit_warning`, `emit_error` share this exact syntax. +//! `abort_call_site!`, `emit_call_site_warning`, `emit_call_site_error` lack 1 form +//! and do not take span in 2 and 3 forms. +//! +//! `diagnostic!` require `Level` instance between `span` and second argument (1 form is the same). +//! +//! #### Note attachments +//! +//! 3. Every macro can have "note" attachments (only 2 and 3 form). +//! ```ignore +//! let opt_help = if have_some_info { Some("did you mean `this`?") } else { None }; +//! +//! abort!( +//! span, message; // <--- attachments start with `;` (semicolon) +//! +//! help = "format {} {}", "arg1", "arg2"; // <--- every attachment ends with `;`, +//! // maybe except the last one +//! +//! note = "to_string"; // <--- one arg uses `.to_string()` instead of `format!()` +//! +//! yay = "I see what {} did here", "you"; // <--- "help =" and "hint =" are mapped to Diagnostic::help +//! // anything else is Diagnostic::note +//! +//! wow = note_span => "custom span"; // <--- attachments can have their own span +//! // it takes effect only on nightly though +//! +//! hint =? opt_help; // <-- "optional" attachment, get displayed only if `Some` +//! // must be single `Option` expression +//! +//! note =? note_span => opt_help // <-- optional attachments can have custom spans too +//! ) +//! ``` +//! +//! ### `#[proc_macro_error]` attribute +//! +//! **This attribute MUST be present on the top level of your macro.** +//! +//! This attribute performs the setup and cleanup necessary to make things work. +//! +//! #### Syntax +//! +//! `#[proc_macro_error]` or `#[proc_macro_error(settings...)]`, where `settings...` +//! is a comma-separated list of: +//! +//! - `proc_macro_hack`: +//! +//! To correctly cooperate with `#[proc_macro_hack]` `#[proc_macro_error]` +//! attribute must be placed *before* (above) it, like this: +//! +//! ```ignore +//! #[proc_macro_error] +//! #[proc_macro_hack] +//! #[proc_macro] +//! fn my_macro(input: TokenStream) -> TokenStream { +//! unimplemented!() +//! } +//! ``` +//! +//! If, for some reason, you can't place it like that you can use +//! `#[proc_macro_error(proc_macro_hack)]` instead. +//! +//! - `allow_not_macro`: +//! +//! By default, the attribute checks that it's applied to a proc-macro. +//! If none of `#[proc_macro]`, `#[proc_macro_derive]` nor `#[proc_macro_attribute]` are +//! present it will panic. It's the intention - this crate is supposed to be used only with +//! proc-macros. This setting is made to bypass the check, useful in certain +//! circumstances. +//! +//! Please note: the function this attribute is applied to must return `proc_macro::TokenStream`. +//! +//! - `assert_unwind_safe`: +//! +//! By default, your code must be [unwind safe]. If your code is not unwind safe but you believe +//! it's correct you can use this setting to bypass the check. This is typically needed +//! for code that uses `lazy_static` or `thread_local` with `Cell/RefCell` inside. +//! +//! This setting is implied if `#[proc_macro_error]` is applied to a function +//! marked as `#[proc_macro]`, `#[proc_macro_derive]` or `#[proc_macro_attribute]`. +//! +//! ### Diagnostic type +//! +//! [`Diagnostic`] type is intentionally designed to be API compatible with [`proc_macro::Diagnostic`]. +//! Not all API is implemented, only the part that can be reasonably implemented on stable. +//! +//! +//! [`abort!`]: macro.abort.html +//! [`emit_warning!`]: macro.emit_warning.html +//! [`emit_error!`]: macro.emit_error.html +//! [`abort_call_site!`]: macro.abort_call_site.html +//! [`emit_call_site_warning!`]: macro.emit_call_site_error.html +//! [`emit_call_site_error!`]: macro.emit_call_site_warning.html +//! [`diagnostic!`]: macro.diagnostic.html +//! [proc_macro_error]: ./../proc_macro_error_attr/attr.proc_macro_error.html +//! [`Diagnostic`]: struct.Diagnostic.html +//! [`proc_macro::Diagnostic`]: https://doc.rust-lang.org/proc_macro/struct.Diagnostic.html +//! [unwind safe]: https://doc.rust-lang.org/std/panic/trait.UnwindSafe.html#what-is-unwind-safety +//! [`!`]: https://doc.rust-lang.org/std/primitive.never.html +//! [`()`]: https://doc.rust-lang.org/std/primitive.unit.html + +#![cfg_attr(pme_nightly, feature(proc_macro_diagnostic))] +#![forbid(unsafe_code)] + +// reexports for use in macros +#[doc(hidden)] +pub extern crate proc_macro; +#[doc(hidden)] +pub extern crate proc_macro2; + +pub use self::dummy::set_dummy; +pub use proc_macro_error_attr::proc_macro_error; + +use proc_macro2::{Span, TokenStream}; +use quote::quote; +use quote::{quote_spanned, ToTokens}; +use std::cell::Cell; +use std::panic::{catch_unwind, resume_unwind, UnwindSafe}; + +pub mod dummy; + +mod macros; + +#[cfg(not(any(pme_nightly, nightly_fmt)))] +#[path = "stable.rs"] +mod imp; + +#[cfg(any(pme_nightly, nightly_fmt))] +#[path = "nightly.rs"] +mod imp; + +/// Represents a diagnostic level +/// +/// # Warnings +/// +/// Warnings are ignored on stable/beta +#[derive(Debug, PartialEq)] +pub enum Level { + Error, + Warning, + #[doc(hidden)] + NonExhaustive, +} + +/// Represents a single diagnostic message +#[derive(Debug)] +pub struct Diagnostic { + level: Level, + span: Span, + msg: String, + suggestions: Vec<(SuggestionKind, String, Option)>, +} + +/// This traits expands `Result>` with some handy shortcuts. +pub trait ResultExt { + type Ok; + + /// Behaves like `Result::unwrap`: if self is `Ok` yield the contained value, + /// otherwise abort macro execution via `abort!`. + fn unwrap_or_abort(self) -> Self::Ok; + + /// Behaves like `Result::expect`: if self is `Ok` yield the contained value, + /// otherwise abort macro execution via `abort!`. + /// If it aborts then resulting error message will be preceded with `message`. + fn expect_or_abort(self, msg: &str) -> Self::Ok; +} + +/// This traits expands `Option` with some handy shortcuts. +pub trait OptionExt { + type Some; + + /// Behaves like `Option::expect`: if self is `Some` yield the contained value, + /// otherwise abort macro execution via `abort_call_site!`. + /// If it aborts the `message` will be used for [`compile_error!`][compl_err] invocation. + /// + /// [compl_err]: https://doc.rust-lang.org/std/macro.compile_error.html + fn expect_or_abort(self, msg: &str) -> Self::Some; +} + +impl Diagnostic { + /// Create a new diagnostic message that points to `Span::call_site()` + pub fn new(level: Level, message: String) -> Self { + Diagnostic::spanned(Span::call_site(), level, message) + } + + /// Create a new diagnostic message that points to the `span` + pub fn spanned(span: Span, level: Level, message: String) -> Self { + Diagnostic { + level, + span, + msg: message, + suggestions: vec![], + } + } + + /// Attach a "help" note to your main message, note will have it's own span on nightly. + /// + /// # Span + /// + /// The span is ignored on stable, the note effectively inherits its parent's (main message) span + pub fn span_help(mut self, span: Span, msg: String) -> Self { + self.suggestions + .push((SuggestionKind::Help, msg, Some(span))); + self + } + + /// Attach a "help" note to your main message, + pub fn help(mut self, msg: String) -> Self { + self.suggestions.push((SuggestionKind::Help, msg, None)); + self + } + + /// Attach a note to your main message, note will have it's own span on nightly. + /// + /// # Span + /// + /// The span is ignored on stable, the note effectively inherits its parent's (main message) span + pub fn span_note(mut self, span: Span, msg: String) -> Self { + self.suggestions + .push((SuggestionKind::Note, msg, Some(span))); + self + } + + /// Attach a note to your main message + pub fn note(mut self, msg: String) -> Self { + self.suggestions.push((SuggestionKind::Note, msg, None)); + self + } + + /// The message of main warning/error (no notes attached) + pub fn message(&self) -> &str { + &self.msg + } + + /// Abort the proc-macro's execution and display the diagnostic. + /// + /// # Warnings + /// + /// Warnings do not get emitted on stable/beta but this function will abort anyway. + pub fn abort(self) -> ! { + self.emit(); + abort_now() + } + + /// Display the diagnostic while not aborting macro execution. + /// + /// # Warnings + /// + /// Warnings are ignored on stable/beta + pub fn emit(self) { + imp::emit_diagnostic(self); + } +} + +/// Abort macro execution and display all the emitted errors, if any. +/// +/// Does nothing if no errors were emitted (warnings do not count). +pub fn abort_if_dirty() { + imp::abort_if_dirty(); +} + +#[doc(hidden)] +impl Diagnostic { + pub fn span_suggestion(self, span: Span, suggestion: &str, msg: String) -> Self { + match suggestion { + "help" | "hint" => self.span_help(span, msg), + _ => self.span_note(span, msg), + } + } + + pub fn suggestion(self, suggestion: &str, msg: String) -> Self { + match suggestion { + "help" | "hint" => self.help(msg), + _ => self.note(msg), + } + } +} + +impl ToTokens for Diagnostic { + fn to_tokens(&self, ts: &mut TokenStream) { + use std::borrow::Cow; + + fn ensure_lf(buf: &mut String, s: &str) { + if s.ends_with('\n') { + buf.push_str(s); + } else { + buf.push_str(s); + buf.push('\n'); + } + } + + let Diagnostic { + ref msg, + ref suggestions, + ref level, + .. + } = *self; + + if *level == Level::Warning { + return; + } + + let message = if suggestions.is_empty() { + Cow::Borrowed(msg) + } else { + let mut message = String::new(); + ensure_lf(&mut message, msg); + message.push('\n'); + + for (kind, note, _span) in suggestions { + message.push_str(" = "); + message.push_str(kind.name()); + message.push_str(": "); + ensure_lf(&mut message, note); + } + message.push('\n'); + + Cow::Owned(message) + }; + + let span = &self.span; + let msg = syn::LitStr::new(&*message, *span); + ts.extend(quote_spanned!(*span=> compile_error!(#msg); )); + } +} + +impl> ResultExt for Result { + type Ok = T; + + fn unwrap_or_abort(self) -> T { + match self { + Ok(res) => res, + Err(e) => e.into().abort(), + } + } + + fn expect_or_abort(self, message: &str) -> T { + match self { + Ok(res) => res, + Err(e) => { + let mut e = e.into(); + e.msg = format!("{}: {}", message, e.msg); + e.abort() + } + } + } +} + +impl OptionExt for Option { + type Some = T; + + fn expect_or_abort(self, message: &str) -> T { + match self { + Some(res) => res, + None => abort_call_site!(message), + } + } +} + +#[derive(Debug)] +enum SuggestionKind { + Help, + Note, +} + +impl SuggestionKind { + fn name(&self) -> &'static str { + match self { + SuggestionKind::Note => "note", + SuggestionKind::Help => "help", + } + } +} + +impl From for Diagnostic { + fn from(e: syn::Error) -> Self { + Diagnostic::spanned(e.span(), Level::Error, e.to_string()) + } +} + +/// This is the entry point for a proc-macro. +/// +/// **NOT PUBLIC API, SUBJECT TO CHANGE WITHOUT ANY NOTICE** +#[doc(hidden)] +pub fn entry_point(f: F, proc_macro_hack: bool) -> proc_macro::TokenStream +where + F: FnOnce() -> proc_macro::TokenStream + UnwindSafe, +{ + ENTERED_ENTRY_POINT.with(|flag| flag.set(true)); + let caught = catch_unwind(f); + let dummy = dummy::cleanup(); + let err_storage = imp::cleanup(); + ENTERED_ENTRY_POINT.with(|flag| flag.set(false)); + + let mut appendix = TokenStream::new(); + if proc_macro_hack { + appendix.extend(quote! { + #[allow(unused)] + macro_rules! proc_macro_call { + () => ( unimplemented!() ) + } + }); + } + + match caught { + Ok(ts) => { + if err_storage.is_empty() { + ts + } else { + quote!( #(#err_storage)* #dummy #appendix ).into() + } + } + + Err(boxed) => match boxed.downcast::() { + Ok(_) => quote!( #(#err_storage)* #dummy #appendix ).into(), + Err(boxed) => resume_unwind(boxed), + }, + } +} + +fn abort_now() -> ! { + check_correctness(); + panic!(AbortNow) +} + +thread_local! { + static ENTERED_ENTRY_POINT: Cell = Cell::new(false); +} + +struct AbortNow; + +fn check_correctness() { + if !ENTERED_ENTRY_POINT.with(|flag| flag.get()) { + panic!( + "proc-macro-error API cannot be used outside of `entry_point` invocation, \ + perhaps you forgot to annotate your #[proc_macro] function with `#[proc_macro_error]" + ); + } +} diff --git a/proc-macro-error/proc-macro-error/src/macros.rs b/proc-macro-error/proc-macro-error/src/macros.rs new file mode 100644 index 0000000..117612c --- /dev/null +++ b/proc-macro-error/proc-macro-error/src/macros.rs @@ -0,0 +1,257 @@ +// FIXME: this can be greatly simplified via $()? +// as soon as MRSV hits 1.32 + +/// Build [`Diagnostic`](struct.Diagnostic.html) instance from provided arguments. +/// +/// # Syntax +/// +/// See [the guide](index.html#guide). +/// +#[macro_export] +macro_rules! diagnostic { + // from alias + ($err:expr) => { $crate::Diagnostic::from($err) }; + + // span, message, help + ($span:expr, $level:expr, $fmt:expr, $($args:expr),+ ; $($rest:tt)+) => {{ + let diag = $crate::Diagnostic::spanned( + $span.into(), + $level, + format!($fmt, $($args),*) + ); + $crate::__pme__suggestions!(diag $($rest)*); + diag + }}; + + ($span:expr, $level:expr, $msg:expr ; $($rest:tt)+) => {{ + let diag = $crate::Diagnostic::spanned($span.into(), $level, $msg.to_string()); + $crate::__pme__suggestions!(diag $($rest)*); + diag + }}; + + // span, message, no help + ($span:expr, $level:expr, $fmt:expr, $($args:expr),+) => {{ + $crate::Diagnostic::spanned( + $span.into(), + $level, + format!($fmt, $($args),*) + ) + }}; + + ($span:expr, $level:expr, $msg:expr) => {{ + $crate::Diagnostic::spanned($span.into(), $level, $msg.to_string()) + }}; + + + // trailing commas + + ($span:expr, $level:expr, $fmt:expr, $($args:expr),+, ; $($rest:tt)+) => { + $crate::diagnostic!($span, $level, $fmt, $($args),* ; $($rest)*) + }; + ($span:expr, $level:expr, $msg:expr, ; $($rest:tt)+) => { + $crate::diagnostic!($span, $level, $msg ; $($rest)*) + }; + ($span:expr, $level:expr, $fmt:expr, $($args:expr),+,) => { + $crate::diagnostic!($span, $level, $fmt, $($args),*) + }; + ($span:expr, $level:expr, $msg:expr,) => { + $crate::diagnostic!($span, $level, $msg) + }; + // ($err:expr,) => { $crate::diagnostic!($err) }; +} + +/// Abort proc-macro execution right now and display the error. +/// +/// # Syntax +/// +/// See [the guide](index.html#guide). +#[macro_export] +macro_rules! abort { + ($err:expr) => { + $crate::diagnostic!($err).abort() + }; + + ($span:expr, $($tts:tt)*) => { + $crate::diagnostic!($span, $crate::Level::Error, $($tts)*).abort() + }; +} + +/// Shortcut for `abort!(Span::call_site(), msg...)`. This macro +/// is still preferable over plain panic, panics are not for error reporting. +/// +/// # Syntax +/// +/// See [the guide](index.html#guide). +/// +#[macro_export] +macro_rules! abort_call_site { + ($($tts:tt)*) => { + $crate::diagnostic!( + $crate::proc_macro2::Span::call_site(), + $crate::Level::Error, + $($tts)* + ).abort() + }; +} + +/// Emit an error while not aborting the proc-macro right away. +/// +/// # Syntax +/// +/// See [the guide](index.html#guide). +/// +#[macro_export] +macro_rules! emit_error { + ($err:expr) => { + $crate::diagnostic!($err).emit() + }; + + ($span:expr, $($tts:tt)*) => {{ + let level = $crate::Level::Error; + $crate::diagnostic!($span, level, $($tts)*).emit() + }}; +} + +/// Shortcut for `emit_error!(Span::call_site(), ...)`. This macro +/// is still preferable over plain panic, panics are not for error reporting.. +/// +/// # Syntax +/// +/// See [the guide](index.html#guide). +/// +#[macro_export] +macro_rules! emit_call_site_error { + ($($tts:tt)*) => { + $crate::diagnostic!( + $crate::proc_macro2::Span()::call_site(), + $crate::Level::Error, + $($tts)* + ).emit() + }; +} + +/// Emit a warning. Warnings are not errors and compilation won't fail because of them. +/// +/// **Does nothing on stable** +/// +/// # Syntax +/// +/// See [the guide](index.html#guide). +/// +#[macro_export] +macro_rules! emit_warning { + ($span:expr, $($tts:tt)*) => { + $crate::diagnostic!($span, $crate::Level::Warning, $($tts)*).emit() + }; +} + +/// Shortcut for `emit_warning!(Span::call_site(), ...)`. +/// +/// **Does nothing on stable** +/// +/// # Syntax +/// +/// See [the guide](index.html#guide). +/// +#[macro_export] +macro_rules! emit_call_site_warning { + ($($tts:tt)*) => {{ + let span = $crate::proc_macro2::Span()::call_site(); + $crate::diagnostic!(span, $crate::Level::Warning, $($tts)*).emit() + }}; +} + +#[doc(hidden)] +#[macro_export] +macro_rules! __pme__suggestions { + ($var:ident) => (); + + ($var:ident $help:ident =? $msg:expr) => { + let $var = if let Some(msg) = $msg { + $var.suggestion(stringify!($help), msg.to_string()) + } else { + $var + }; + }; + ($var:ident $help:ident =? $span:expr => $msg:expr) => { + let $var = if let Some(msg) = $msg { + $var.span_suggestion($span.into(), stringify!($help), msg.to_string()) + } else { + $var + }; + }; + + ($var:ident $help:ident =? $msg:expr ; $($rest:tt)*) => { + $crate::__pme__suggestions!($var $help =? $msg); + $crate::__pme__suggestions!($var $($rest)*); + }; + ($var:ident $help:ident =? $span:expr => $msg:expr ; $($rest:tt)*) => { + $crate::__pme__suggestions!($var $help =? $span => $msg); + $crate::__pme__suggestions!($var $($rest)*); + }; + + + ($var:ident $help:ident = $msg:expr) => { + let $var = $var.suggestion(stringify!($help), $msg.to_string()); + }; + ($var:ident $help:ident = $fmt:expr, $($args:expr),+) => { + let $var = $var.suggestion( + stringify!($help), + format!($fmt, $($args),*) + ); + }; + ($var:ident $help:ident = $span:expr => $msg:expr) => { + let $var = $var.span_suggestion($span.into(), stringify!($help), $msg.to_string()); + }; + ($var:ident $help:ident = $span:expr => $fmt:expr, $($args:expr),+) => { + let $var = $var.span_suggestion( + $span.into(), + stringify!($help), + format!($fmt, $($args),*) + ); + }; + + ($var:ident $help:ident = $msg:expr ; $($rest:tt)*) => { + $crate::__pme__suggestions!($var $help = $msg); + $crate::__pme__suggestions!($var $($rest)*); + }; + ($var:ident $help:ident = $fmt:expr, $($args:expr),+ ; $($rest:tt)*) => { + $crate::__pme__suggestions!($var $help = $fmt, $($args),*); + $crate::__pme__suggestions!($var $($rest)*); + }; + ($var:ident $help:ident = $span:expr => $msg:expr ; $($rest:tt)*) => { + $crate::__pme__suggestions!($var $help = $span => $msg); + $crate::__pme__suggestions!($var $($rest)*); + }; + ($var:ident $help:ident = $span:expr => $fmt:expr, $($args:expr),+ ; $($rest:tt)*) => { + $crate::__pme__suggestions!($var $help = $span => $fmt, $($args),*); + $crate::__pme__suggestions!($var $($rest)*); + }; + + // trailing commas + + ($var:ident $help:ident = $msg:expr,) => { + $crate::__pme__suggestions!($var $help = $msg) + }; + ($var:ident $help:ident = $fmt:expr, $($args:expr),+,) => { + $crate::__pme__suggestions!($var $help = $fmt, $($args)*) + }; + ($var:ident $help:ident = $span:expr => $msg:expr,) => { + $crate::__pme__suggestions!($var $help = $span => $msg) + }; + ($var:ident $help:ident = $span:expr => $fmt:expr, $($args:expr),*,) => { + $crate::__pme__suggestions!($var $help = $span => $fmt, $($args)*) + }; + ($var:ident $help:ident = $msg:expr, ; $($rest:tt)*) => { + $crate::__pme__suggestions!($var $help = $msg; $($rest)*) + }; + ($var:ident $help:ident = $fmt:expr, $($args:expr),+, ; $($rest:tt)*) => { + $crate::__pme__suggestions!($var $help = $fmt, $($args),*; $($rest)*) + }; + ($var:ident $help:ident = $span:expr => $msg:expr, ; $($rest:tt)*) => { + $crate::__pme__suggestions!($var $help = $span => $msg; $($rest)*) + }; + ($var:ident $help:ident = $span:expr => $fmt:expr, $($args:expr),+, ; $($rest:tt)*) => { + $crate::__pme__suggestions!($var $help = $span => $fmt, $($args),*; $($rest)*) + }; +} diff --git a/proc-macro-error/proc-macro-error/src/nightly.rs b/proc-macro-error/proc-macro-error/src/nightly.rs new file mode 100644 index 0000000..d053c2f --- /dev/null +++ b/proc-macro-error/proc-macro-error/src/nightly.rs @@ -0,0 +1,49 @@ +use std::sync::atomic::{AtomicBool, Ordering}; + +use proc_macro::{Diagnostic as PDiag, Level as PLevel}; + +use crate::{abort_now, check_correctness, Diagnostic, Level, SuggestionKind}; + +pub fn abort_if_dirty() { + check_correctness(); + if IS_DIRTY.load(Ordering::SeqCst) { + abort_now() + } +} + +pub(crate) fn cleanup() -> Vec { + vec![] +} + +pub(crate) fn emit_diagnostic(diag: Diagnostic) { + let Diagnostic { + level, + span, + msg, + suggestions, + } = diag; + + let level = match level { + Level::Warning => PLevel::Warning, + Level::Error => { + IS_DIRTY.store(true, Ordering::SeqCst); + PLevel::Error + } + _ => unreachable!(), + }; + + let mut res = PDiag::spanned(span.unwrap(), level, msg); + + for (kind, msg, span) in suggestions { + res = match (kind, span) { + (SuggestionKind::Note, Some(span)) => res.span_note(span.unwrap(), msg), + (SuggestionKind::Help, Some(span)) => res.span_help(span.unwrap(), msg), + (SuggestionKind::Note, None) => res.note(msg), + (SuggestionKind::Help, None) => res.help(msg), + } + } + + res.emit() +} + +static IS_DIRTY: AtomicBool = AtomicBool::new(false); diff --git a/proc-macro-error/proc-macro-error/src/stable.rs b/proc-macro-error/proc-macro-error/src/stable.rs new file mode 100644 index 0000000..07042d3 --- /dev/null +++ b/proc-macro-error/proc-macro-error/src/stable.rs @@ -0,0 +1,26 @@ +use std::cell::RefCell; + +use crate::{abort_now, check_correctness, Diagnostic, Level}; + +pub fn abort_if_dirty() { + check_correctness(); + ERR_STORAGE.with(|storage| { + if !storage.borrow().is_empty() { + abort_now() + } + }); +} + +pub(crate) fn cleanup() -> Vec { + ERR_STORAGE.with(|storage| storage.replace(Vec::new())) +} + +pub(crate) fn emit_diagnostic(diag: Diagnostic) { + if diag.level == Level::Error { + ERR_STORAGE.with(|storage| storage.borrow_mut().push(diag)); + } +} + +thread_local! { + static ERR_STORAGE: RefCell> = RefCell::new(Vec::new()); +} diff --git a/proc-macro-error/test-crate/Cargo.toml b/proc-macro-error/test-crate/Cargo.toml new file mode 100644 index 0000000..96dadc3 --- /dev/null +++ b/proc-macro-error/test-crate/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "test-crate" +version = "0.0.0" +authors = ["CreepySkeleton "] +edition = "2018" +publish = false + +[lib] +proc-macro = true + +[dependencies] +proc-macro-error = { path = "../proc-macro-error"} +quote = "1" +proc-macro2 = "1" +syn = {version = "1", default-features = false } + +[dev-dependencies] +trybuild = "1.0" +rustversion = "1.0" +toml = "=0.5.2" # DO NOT BUMP diff --git a/proc-macro-error/test-crate/src/lib.rs b/proc-macro-error/test-crate/src/lib.rs new file mode 100644 index 0000000..2ea9175 --- /dev/null +++ b/proc-macro-error/test-crate/src/lib.rs @@ -0,0 +1,123 @@ +#[macro_use] +extern crate proc_macro_error; +#[macro_use] +extern crate syn; +extern crate proc_macro; + +use proc_macro2::Span; +use proc_macro_error::{set_dummy, Level, OptionExt, ResultExt}; +use syn::{ + parse::{Parse, ParseStream}, + punctuated::Punctuated, + spanned::Spanned, + Ident, +}; + +struct IdentOrUnderscore { + span: Span, + part: String, +} + +impl IdentOrUnderscore { + fn new(span: Span, part: String) -> Self { + IdentOrUnderscore { span, part } + } +} + +impl Parse for IdentOrUnderscore { + fn parse(input: ParseStream) -> syn::Result { + let la = input.lookahead1(); + + if la.peek(Ident) { + let t = input.parse::().unwrap(); + Ok(IdentOrUnderscore::new(t.span(), t.to_string())) + } else if la.peek(Token![_]) { + let t = input.parse::().unwrap(); + Ok(IdentOrUnderscore::new(t.span(), "_".to_string())) + } else { + Err(la.error()) + } + } +} + +struct Args(Vec); + +impl Parse for Args { + fn parse(input: ParseStream) -> syn::Result { + let args = Punctuated::<_, Token![,]>::parse_terminated(input)?; + Ok(Args(args.into_iter().collect())) + } +} + +#[proc_macro] +#[proc_macro_error] +pub fn make_fn(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let mut name = String::new(); + let input = parse_macro_input!(input as Args); + + for arg in input.0 { + match &*arg.part { + "abort" => abort!( + arg.span, + "abort! 3{} args {}", "+", "test"; + hint = "help {} test", "message" + ), + + "abort_call_site" => abort_call_site!( + "abort_call_site! 2{} args {}", "+", "test"; + help = "help {} test", "message" + ), + + "direct_abort" => { + diagnostic!(arg.span, Level::Error, "direct MacroError::abort() test").abort() + } + + "result_expect" => { + let e = syn::Error::new(arg.span, "error"); + Err(e).expect_or_abort("Result::expect_or_abort() test") + } + + "result_unwrap" => { + let e = syn::Error::new(arg.span, "Result::unwrap_or_abort() test"); + Err(e).unwrap_or_abort() + } + + "option_expect" => None.expect_or_abort("Option::expect_or_abort() test"), + + "need_default" => { + set_dummy(quote! { + impl Default for NeedDefault { + fn default() -> Self { + NeedDefault::A + } + } + }); + + abort!(arg.span, "set_dummy test") + } + + part if part.starts_with("multi") => { + let no_help: Option = Option::None; + let help = Some("Option help test"); + emit_error!( + arg.span, + "multiple error part: {}", part; + note = "help {} test", "message"; + hint =? help; + wow = "I see what you did here..."; + help =? no_help + ) + } + + _ => name.push_str(&arg.part), + } + } + + // test that unrelated panics are not affected + if name.is_empty() { + panic!("unrelated panic test") + } + + let name = Ident::new(&name, Span::call_site()); + quote!( fn #name() {} ).into() +} diff --git a/proc-macro-error/test-crate/tests/macro-errors.rs b/proc-macro-error/test-crate/tests/macro-errors.rs new file mode 100644 index 0000000..8c672eb --- /dev/null +++ b/proc-macro-error/test-crate/tests/macro-errors.rs @@ -0,0 +1,6 @@ +#[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/proc-macro-error/test-crate/tests/ok.rs b/proc-macro-error/test-crate/tests/ok.rs new file mode 100644 index 0000000..9b6a3d1 --- /dev/null +++ b/proc-macro-error/test-crate/tests/ok.rs @@ -0,0 +1,9 @@ +extern crate test_crate; + +use test_crate::make_fn; + +make_fn!(it, _, works); + +fn main() { + it_works(); +} diff --git a/proc-macro-error/test-crate/tests/ui/abort.rs b/proc-macro-error/test-crate/tests/ui/abort.rs new file mode 100644 index 0000000..717d772 --- /dev/null +++ b/proc-macro-error/test-crate/tests/ui/abort.rs @@ -0,0 +1,6 @@ +extern crate test_crate; +use test_crate::make_fn; + +make_fn!(abort); + +fn main() {} diff --git a/proc-macro-error/test-crate/tests/ui/abort.stderr b/proc-macro-error/test-crate/tests/ui/abort.stderr new file mode 100644 index 0000000..7c4e6a0 --- /dev/null +++ b/proc-macro-error/test-crate/tests/ui/abort.stderr @@ -0,0 +1,8 @@ +error: abort! 3+ args test + + = help: help message test + + --> $DIR/abort.rs:4:10 + | +4 | make_fn!(abort); + | ^^^^^ diff --git a/proc-macro-error/test-crate/tests/ui/call_site.rs b/proc-macro-error/test-crate/tests/ui/call_site.rs new file mode 100644 index 0000000..7184cc4 --- /dev/null +++ b/proc-macro-error/test-crate/tests/ui/call_site.rs @@ -0,0 +1,6 @@ +extern crate test_crate; +use test_crate::make_fn; + +make_fn!(abort_call_site); + +fn main() {} diff --git a/proc-macro-error/test-crate/tests/ui/call_site.stderr b/proc-macro-error/test-crate/tests/ui/call_site.stderr new file mode 100644 index 0000000..d630a3a --- /dev/null +++ b/proc-macro-error/test-crate/tests/ui/call_site.stderr @@ -0,0 +1,8 @@ +error: abort_call_site! 2+ args test + + = help: help message test + + --> $DIR/call_site.rs:4:1 + | +4 | make_fn!(abort_call_site); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ in this macro invocation diff --git a/proc-macro-error/test-crate/tests/ui/direct_abort.rs b/proc-macro-error/test-crate/tests/ui/direct_abort.rs new file mode 100644 index 0000000..b5a4c97 --- /dev/null +++ b/proc-macro-error/test-crate/tests/ui/direct_abort.rs @@ -0,0 +1,6 @@ +extern crate test_crate; +use test_crate::make_fn; + +make_fn!(direct_abort); + +fn main() {} diff --git a/proc-macro-error/test-crate/tests/ui/direct_abort.stderr b/proc-macro-error/test-crate/tests/ui/direct_abort.stderr new file mode 100644 index 0000000..7cfbae8 --- /dev/null +++ b/proc-macro-error/test-crate/tests/ui/direct_abort.stderr @@ -0,0 +1,5 @@ +error: direct MacroError::abort() test + --> $DIR/direct_abort.rs:4:10 + | +4 | make_fn!(direct_abort); + | ^^^^^^^^^^^^ diff --git a/proc-macro-error/test-crate/tests/ui/dummy.rs b/proc-macro-error/test-crate/tests/ui/dummy.rs new file mode 100644 index 0000000..7514fe0 --- /dev/null +++ b/proc-macro-error/test-crate/tests/ui/dummy.rs @@ -0,0 +1,16 @@ +extern crate test_crate; +use test_crate::make_fn; + +enum NeedDefault { + A, + B +} + +make_fn!(need_default); + +fn main() { + let _ = NeedDefault::default(); +} + + + diff --git a/proc-macro-error/test-crate/tests/ui/dummy.stderr b/proc-macro-error/test-crate/tests/ui/dummy.stderr new file mode 100644 index 0000000..fd531be --- /dev/null +++ b/proc-macro-error/test-crate/tests/ui/dummy.stderr @@ -0,0 +1,5 @@ +error: set_dummy test + --> $DIR/dummy.rs:9:10 + | +9 | make_fn!(need_default); + | ^^^^^^^^^^^^ diff --git a/proc-macro-error/test-crate/tests/ui/multi-error.rs b/proc-macro-error/test-crate/tests/ui/multi-error.rs new file mode 100644 index 0000000..07fbb03 --- /dev/null +++ b/proc-macro-error/test-crate/tests/ui/multi-error.rs @@ -0,0 +1,6 @@ +extern crate test_crate; +use test_crate::make_fn; + +make_fn!(multi1, multi2, _, multi3); + +fn main() {} diff --git a/proc-macro-error/test-crate/tests/ui/multi-error.stderr b/proc-macro-error/test-crate/tests/ui/multi-error.stderr new file mode 100644 index 0000000..25174d5 --- /dev/null +++ b/proc-macro-error/test-crate/tests/ui/multi-error.stderr @@ -0,0 +1,32 @@ +error: multiple error part: multi1 + + = note: help message test + = help: Option help test + = note: I see what you did here... + + --> $DIR/multi-error.rs:4:10 + | +4 | make_fn!(multi1, multi2, _, multi3); + | ^^^^^^ + +error: multiple error part: multi2 + + = note: help message test + = help: Option help test + = note: I see what you did here... + + --> $DIR/multi-error.rs:4:18 + | +4 | make_fn!(multi1, multi2, _, multi3); + | ^^^^^^ + +error: multiple error part: multi3 + + = note: help message test + = help: Option help test + = note: I see what you did here... + + --> $DIR/multi-error.rs:4:29 + | +4 | make_fn!(multi1, multi2, _, multi3); + | ^^^^^^ diff --git a/proc-macro-error/test-crate/tests/ui/not_proc_macro.rs b/proc-macro-error/test-crate/tests/ui/not_proc_macro.rs new file mode 100644 index 0000000..e241c5c --- /dev/null +++ b/proc-macro-error/test-crate/tests/ui/not_proc_macro.rs @@ -0,0 +1,4 @@ +use proc_macro_error::proc_macro_error; + +#[proc_macro_error] +fn main() {} diff --git a/proc-macro-error/test-crate/tests/ui/not_proc_macro.stderr b/proc-macro-error/test-crate/tests/ui/not_proc_macro.stderr new file mode 100644 index 0000000..52d6a09 --- /dev/null +++ b/proc-macro-error/test-crate/tests/ui/not_proc_macro.stderr @@ -0,0 +1,8 @@ +error: #[proc_macro_error] attribute can be used only with a proc-macro + + hint: if you are really sure that #[proc_macro_error] should be applied to this exact function use #[proc_macro_error(allow_not_macro)] + + --> $DIR/not_proc_macro.rs:3:1 + | +3 | #[proc_macro_error] + | ^^^^^^^^^^^^^^^^^^^ diff --git a/proc-macro-error/test-crate/tests/ui/option_expect.rs b/proc-macro-error/test-crate/tests/ui/option_expect.rs new file mode 100644 index 0000000..20288ca --- /dev/null +++ b/proc-macro-error/test-crate/tests/ui/option_expect.rs @@ -0,0 +1,6 @@ +extern crate test_crate; +use test_crate::make_fn; + +make_fn!(option_expect); + +fn main() {} diff --git a/proc-macro-error/test-crate/tests/ui/option_expect.stderr b/proc-macro-error/test-crate/tests/ui/option_expect.stderr new file mode 100644 index 0000000..dd9ecd8 --- /dev/null +++ b/proc-macro-error/test-crate/tests/ui/option_expect.stderr @@ -0,0 +1,5 @@ +error: Option::expect_or_abort() test + --> $DIR/option_expect.rs:4:1 + | +4 | make_fn!(option_expect); + | ^^^^^^^^^^^^^^^^^^^^^^^^ in this macro invocation diff --git a/proc-macro-error/test-crate/tests/ui/result_expect.rs b/proc-macro-error/test-crate/tests/ui/result_expect.rs new file mode 100644 index 0000000..a42740b --- /dev/null +++ b/proc-macro-error/test-crate/tests/ui/result_expect.rs @@ -0,0 +1,6 @@ +extern crate test_crate; +use test_crate::make_fn; + +make_fn!(result_expect); + +fn main() {} diff --git a/proc-macro-error/test-crate/tests/ui/result_expect.stderr b/proc-macro-error/test-crate/tests/ui/result_expect.stderr new file mode 100644 index 0000000..c2dd81c --- /dev/null +++ b/proc-macro-error/test-crate/tests/ui/result_expect.stderr @@ -0,0 +1,5 @@ +error: Result::expect_or_abort() test: error + --> $DIR/result_expect.rs:4:10 + | +4 | make_fn!(result_expect); + | ^^^^^^^^^^^^^ diff --git a/proc-macro-error/test-crate/tests/ui/result_unwrap.rs b/proc-macro-error/test-crate/tests/ui/result_unwrap.rs new file mode 100644 index 0000000..9b7fb1c --- /dev/null +++ b/proc-macro-error/test-crate/tests/ui/result_unwrap.rs @@ -0,0 +1,6 @@ +extern crate test_crate; +use test_crate::make_fn; + +make_fn!(result_unwrap); + +fn main() {} diff --git a/proc-macro-error/test-crate/tests/ui/result_unwrap.stderr b/proc-macro-error/test-crate/tests/ui/result_unwrap.stderr new file mode 100644 index 0000000..2e614bd --- /dev/null +++ b/proc-macro-error/test-crate/tests/ui/result_unwrap.stderr @@ -0,0 +1,5 @@ +error: Result::unwrap_or_abort() test + --> $DIR/result_unwrap.rs:4:10 + | +4 | make_fn!(result_unwrap); + | ^^^^^^^^^^^^^ diff --git a/proc-macro-error/test-crate/tests/ui/unknown_setting.rs b/proc-macro-error/test-crate/tests/ui/unknown_setting.rs new file mode 100644 index 0000000..d8e58ea --- /dev/null +++ b/proc-macro-error/test-crate/tests/ui/unknown_setting.rs @@ -0,0 +1,4 @@ +use proc_macro_error::proc_macro_error; + +#[proc_macro_error(allow_not_macro, assert_unwind_safe, trololo)] +fn main() {} diff --git a/proc-macro-error/test-crate/tests/ui/unknown_setting.stderr b/proc-macro-error/test-crate/tests/ui/unknown_setting.stderr new file mode 100644 index 0000000..a55de0b --- /dev/null +++ b/proc-macro-error/test-crate/tests/ui/unknown_setting.stderr @@ -0,0 +1,5 @@ +error: unknown setting `trololo`, expected one of `assert_unwind_safe`, `allow_not_macro`, `proc_macro_hack` + --> $DIR/unknown_setting.rs:3:57 + | +3 | #[proc_macro_error(allow_not_macro, assert_unwind_safe, trololo)] + | ^^^^^^^ diff --git a/proc-macro-error/test-crate/tests/ui/unrelated_panic.rs b/proc-macro-error/test-crate/tests/ui/unrelated_panic.rs new file mode 100644 index 0000000..4863e5b --- /dev/null +++ b/proc-macro-error/test-crate/tests/ui/unrelated_panic.rs @@ -0,0 +1,6 @@ +extern crate test_crate; +use test_crate::make_fn; + +make_fn!(); + +fn main() {} diff --git a/proc-macro-error/test-crate/tests/ui/unrelated_panic.stderr b/proc-macro-error/test-crate/tests/ui/unrelated_panic.stderr new file mode 100644 index 0000000..b852cfd --- /dev/null +++ b/proc-macro-error/test-crate/tests/ui/unrelated_panic.stderr @@ -0,0 +1,7 @@ +error: proc macro panicked + --> $DIR/unrelated_panic.rs:4:1 + | +4 | make_fn!(); + | ^^^^^^^^^^^ + | + = help: message: unrelated panic test diff --git a/proc-macro2/.gitignore b/proc-macro2/.gitignore new file mode 100644 index 0000000..6936990 --- /dev/null +++ b/proc-macro2/.gitignore @@ -0,0 +1,3 @@ +/target +**/*.rs.bk +Cargo.lock diff --git a/proc-macro2/.travis.yml b/proc-macro2/.travis.yml new file mode 100644 index 0000000..acddb57 --- /dev/null +++ b/proc-macro2/.travis.yml @@ -0,0 +1,36 @@ +language: rust +sudo: false + +matrix: + include: + - rust: 1.31.0 + - rust: stable + - rust: beta + - rust: nightly + script: + - cargo test + - cargo test --no-default-features + - cargo test --no-default-features -- --ignored # run the ignored test to make sure the `proc-macro` feature is disabled + - cargo test --features span-locations + - RUSTFLAGS='--cfg procmacro2_semver_exempt' cargo test + - RUSTFLAGS='--cfg procmacro2_semver_exempt' cargo test --no-default-features + - RUSTFLAGS='-Z allow-features=' cargo test + - cargo update -Z minimal-versions && cargo build + - rust: nightly + name: WebAssembly + install: rustup target add wasm32-unknown-unknown + script: cargo test --target wasm32-unknown-unknown --no-run + +before_script: + - set -o errexit + +script: + - cargo test + - cargo test --no-default-features + - cargo test --features span-locations + - RUSTFLAGS='--cfg procmacro2_semver_exempt' cargo test + - RUSTFLAGS='--cfg procmacro2_semver_exempt' cargo test --no-default-features + +notifications: + email: + on_success: never diff --git a/proc-macro2/Cargo.toml b/proc-macro2/Cargo.toml new file mode 100644 index 0000000..a7d0865 --- /dev/null +++ b/proc-macro2/Cargo.toml @@ -0,0 +1,57 @@ +[package] +name = "proc-macro2" +version = "1.0.7" # remember to update html_root_url +authors = ["Alex Crichton "] +license = "MIT OR Apache-2.0" +readme = "README.md" +keywords = ["macros"] +repository = "https://github.com/alexcrichton/proc-macro2" +homepage = "https://github.com/alexcrichton/proc-macro2" +documentation = "https://docs.rs/proc-macro2" +edition = "2018" +description = """ +A stable implementation of the upcoming new `proc_macro` API. Comes with an +option, off by default, to also reimplement itself in terms of the upstream +unstable API. +""" + +[package.metadata.docs.rs] +rustc-args = ["--cfg", "procmacro2_semver_exempt"] +rustdoc-args = ["--cfg", "procmacro2_semver_exempt"] + +[dependencies] +unicode-xid = "0.2" + +[dev-dependencies] +quote = { version = "1.0", default_features = false } + +[features] +proc-macro = [] +default = ["proc-macro"] + +# Expose methods Span::start and Span::end which give the line/column location +# of a token. +span-locations = [] + +# This feature no longer means anything. +nightly = [] + +[badges] +travis-ci = { repository = "alexcrichton/proc-macro2" } + +[workspace] +members = ["benches/bench-libproc-macro"] + +[patch.crates-io] +# Our doc tests depend on quote which depends on proc-macro2. Without this line, +# the proc-macro2 dependency of quote would be the released version of +# proc-macro2. Quote would implement its traits for types from that proc-macro2, +# meaning impls would be missing when tested against types from the local +# proc-macro2. +# +# Travis builds that are in progress at the time that you publish may spuriously +# fail. This is because they'll be building a local proc-macro2 which carries +# the second-most-recent version number, pulling in quote which resolves to a +# dependency on the just-published most recent version number. Thus the patch +# will fail to apply because the version numbers are different. +proc-macro2 = { path = "." } diff --git a/proc-macro2/LICENSE-APACHE b/proc-macro2/LICENSE-APACHE new file mode 100644 index 0000000..16fe87b --- /dev/null +++ b/proc-macro2/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/proc-macro2/LICENSE-MIT b/proc-macro2/LICENSE-MIT new file mode 100644 index 0000000..39e0ed6 --- /dev/null +++ b/proc-macro2/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2014 Alex Crichton + +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/proc-macro2/README.md b/proc-macro2/README.md new file mode 100644 index 0000000..19b0c3b --- /dev/null +++ b/proc-macro2/README.md @@ -0,0 +1,93 @@ +# proc-macro2 + +[![Build Status](https://api.travis-ci.com/alexcrichton/proc-macro2.svg?branch=master)](https://travis-ci.com/alexcrichton/proc-macro2) +[![Latest Version](https://img.shields.io/crates/v/proc-macro2.svg)](https://crates.io/crates/proc-macro2) +[![Rust Documentation](https://img.shields.io/badge/api-rustdoc-blue.svg)](https://docs.rs/proc-macro2) + +A wrapper around the procedural macro API of the compiler's `proc_macro` crate. +This library serves two purposes: + +- **Bring proc-macro-like functionality to other contexts like build.rs and + main.rs.** Types from `proc_macro` are entirely specific to procedural macros + and cannot ever exist in code outside of a procedural macro. Meanwhile + `proc_macro2` types may exist anywhere including non-macro code. By developing + foundational libraries like [syn] and [quote] against `proc_macro2` rather + than `proc_macro`, the procedural macro ecosystem becomes easily applicable to + many other use cases and we avoid reimplementing non-macro equivalents of + those libraries. + +- **Make procedural macros unit testable.** As a consequence of being specific + to procedural macros, nothing that uses `proc_macro` can be executed from a + unit test. In order for helper libraries or components of a macro to be + testable in isolation, they must be implemented using `proc_macro2`. + +[syn]: https://github.com/dtolnay/syn +[quote]: https://github.com/dtolnay/quote + +## Usage + +```toml +[dependencies] +proc-macro2 = "1.0" +``` + +The skeleton of a typical procedural macro typically looks like this: + +```rust +extern crate proc_macro; + +#[proc_macro_derive(MyDerive)] +pub fn my_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = proc_macro2::TokenStream::from(input); + + let output: proc_macro2::TokenStream = { + /* transform input */ + }; + + proc_macro::TokenStream::from(output) +} +``` + +If parsing with [Syn], you'll use [`parse_macro_input!`] instead to propagate +parse errors correctly back to the compiler when parsing fails. + +[`parse_macro_input!`]: https://docs.rs/syn/1.0/syn/macro.parse_macro_input.html + +## Unstable features + +The default feature set of proc-macro2 tracks the most recent stable compiler +API. Functionality in `proc_macro` that is not yet stable is not exposed by +proc-macro2 by default. + +To opt into the additional APIs available in the most recent nightly compiler, +the `procmacro2_semver_exempt` config flag must be passed to rustc. We will +polyfill those nightly-only APIs back to Rust 1.31.0. As these are unstable APIs +that track the nightly compiler, minor versions of proc-macro2 may make breaking +changes to them at any time. + +``` +RUSTFLAGS='--cfg procmacro2_semver_exempt' cargo build +``` + +Note that this must not only be done for your crate, but for any crate that +depends on your crate. This infectious nature is intentional, as it serves as a +reminder that you are outside of the normal semver guarantees. + +Semver exempt methods are marked as such in the proc-macro2 documentation. + +
+ +#### License + + +Licensed under either of Apache License, Version +2.0 or MIT license at your option. + + +
+ + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in this crate 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/proc-macro2/benches/bench-libproc-macro/Cargo.toml b/proc-macro2/benches/bench-libproc-macro/Cargo.toml new file mode 100644 index 0000000..41d106d --- /dev/null +++ b/proc-macro2/benches/bench-libproc-macro/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "bench-libproc-macro" +version = "0.0.0" +edition = "2018" +publish = false + +[lib] +path = "lib.rs" +proc-macro = true + +[[bin]] +name = "bench-libproc-macro" +path = "main.rs" diff --git a/proc-macro2/benches/bench-libproc-macro/README.md b/proc-macro2/benches/bench-libproc-macro/README.md new file mode 100644 index 0000000..80e4939 --- /dev/null +++ b/proc-macro2/benches/bench-libproc-macro/README.md @@ -0,0 +1,10 @@ +Example output: + +```console +$ cargo check --release + + Compiling bench-libproc-macro v0.0.0 +STRING: 8 millis +TOKENSTREAM: 25721 millis + Finished release [optimized] target(s) in 26.15s +``` diff --git a/proc-macro2/benches/bench-libproc-macro/lib.rs b/proc-macro2/benches/bench-libproc-macro/lib.rs new file mode 100644 index 0000000..46b3711 --- /dev/null +++ b/proc-macro2/benches/bench-libproc-macro/lib.rs @@ -0,0 +1,49 @@ +extern crate proc_macro; + +use proc_macro::{Ident, Punct, Spacing, Span, TokenStream, TokenTree}; +use std::iter::once; +use std::time::Instant; + +const N: u32 = 7000; + +#[proc_macro] +pub fn bench(_input: TokenStream) -> TokenStream { + let start = Instant::now(); + let mut string = String::new(); + for _ in 0..N { + string += "core"; + string += ":"; + string += ":"; + string += "option"; + string += ":"; + string += ":"; + string += "Option"; + string += ":"; + string += ":"; + string += "None"; + string += ","; + } + string.parse::().unwrap(); + eprintln!("STRING: {} millis", start.elapsed().as_millis()); + + let start = Instant::now(); + let span = Span::call_site(); + let mut tokens = TokenStream::new(); + for _ in 0..N { + // Similar to what is emitted by quote. + tokens.extend(once(TokenTree::Ident(Ident::new("core", span)))); + tokens.extend(once(TokenTree::Punct(Punct::new(':', Spacing::Joint)))); + tokens.extend(once(TokenTree::Punct(Punct::new(':', Spacing::Alone)))); + tokens.extend(once(TokenTree::Ident(Ident::new("option", span)))); + tokens.extend(once(TokenTree::Punct(Punct::new(':', Spacing::Joint)))); + tokens.extend(once(TokenTree::Punct(Punct::new(':', Spacing::Alone)))); + tokens.extend(once(TokenTree::Ident(Ident::new("Option", span)))); + tokens.extend(once(TokenTree::Punct(Punct::new(':', Spacing::Joint)))); + tokens.extend(once(TokenTree::Punct(Punct::new(':', Spacing::Alone)))); + tokens.extend(once(TokenTree::Ident(Ident::new("None", span)))); + tokens.extend(once(TokenTree::Punct(Punct::new(',', Spacing::Joint)))); + } + eprintln!("TOKENSTREAM: {} millis", start.elapsed().as_millis()); + + TokenStream::new() +} diff --git a/proc-macro2/benches/bench-libproc-macro/main.rs b/proc-macro2/benches/bench-libproc-macro/main.rs new file mode 100644 index 0000000..34eedf6 --- /dev/null +++ b/proc-macro2/benches/bench-libproc-macro/main.rs @@ -0,0 +1,3 @@ +bench_libproc_macro::bench!(); + +fn main() {} diff --git a/proc-macro2/build.rs b/proc-macro2/build.rs new file mode 100644 index 0000000..deb9b92 --- /dev/null +++ b/proc-macro2/build.rs @@ -0,0 +1,129 @@ +// rustc-cfg emitted by the build script: +// +// "use_proc_macro" +// Link to extern crate proc_macro. Available on any compiler and any target +// except wasm32. Requires "proc-macro" Cargo cfg to be enabled (default is +// enabled). On wasm32 we never link to proc_macro even if "proc-macro" cfg +// is enabled. +// +// "wrap_proc_macro" +// Wrap types from libproc_macro rather than polyfilling the whole API. +// Enabled on rustc 1.29+ as long as procmacro2_semver_exempt is not set, +// because we can't emulate the unstable API without emulating everything +// else. Also enabled unconditionally on nightly, in which case the +// procmacro2_semver_exempt surface area is implemented by using the +// nightly-only proc_macro API. +// +// "proc_macro_span" +// Enable non-dummy behavior of Span::start and Span::end methods which +// requires an unstable compiler feature. Enabled when building with +// nightly, unless `-Z allow-feature` in RUSTFLAGS disallows unstable +// features. +// +// "super_unstable" +// Implement the semver exempt API in terms of the nightly-only proc_macro +// API. Enabled when using procmacro2_semver_exempt on a nightly compiler. +// +// "span_locations" +// Provide methods Span::start and Span::end which give the line/column +// location of a token. Enabled by procmacro2_semver_exempt or the +// "span-locations" Cargo cfg. This is behind a cfg because tracking +// location inside spans is a performance hit. + +use std::env; +use std::process::{self, Command}; +use std::str; + +fn main() { + println!("cargo:rerun-if-changed=build.rs"); + + let version = match rustc_version() { + Some(version) => version, + None => return, + }; + + if version.minor < 31 { + eprintln!("Minimum supported rustc version is 1.31"); + process::exit(1); + } + + let semver_exempt = cfg!(procmacro2_semver_exempt); + if semver_exempt { + // https://github.com/alexcrichton/proc-macro2/issues/147 + println!("cargo:rustc-cfg=procmacro2_semver_exempt"); + } + + if semver_exempt || cfg!(feature = "span-locations") { + println!("cargo:rustc-cfg=span_locations"); + } + + let target = env::var("TARGET").unwrap(); + if !enable_use_proc_macro(&target) { + return; + } + + println!("cargo:rustc-cfg=use_proc_macro"); + + if version.nightly || !semver_exempt { + println!("cargo:rustc-cfg=wrap_proc_macro"); + } + + if version.nightly && feature_allowed("proc_macro_span") { + println!("cargo:rustc-cfg=proc_macro_span"); + } + + if semver_exempt && version.nightly { + println!("cargo:rustc-cfg=super_unstable"); + } +} + +fn enable_use_proc_macro(target: &str) -> bool { + // wasm targets don't have the `proc_macro` crate, disable this feature. + if target.contains("wasm32") { + return false; + } + + // Otherwise, only enable it if our feature is actually enabled. + cfg!(feature = "proc-macro") +} + +struct RustcVersion { + minor: u32, + nightly: bool, +} + +fn rustc_version() -> Option { + let rustc = env::var_os("RUSTC")?; + let output = Command::new(rustc).arg("--version").output().ok()?; + let version = str::from_utf8(&output.stdout).ok()?; + let nightly = version.contains("nightly") || version.contains("dev"); + let mut pieces = version.split('.'); + if pieces.next() != Some("rustc 1") { + return None; + } + let minor = pieces.next()?.parse().ok()?; + Some(RustcVersion { minor, nightly }) +} + +fn feature_allowed(feature: &str) -> bool { + // Recognized formats: + // + // -Z allow-features=feature1,feature2 + // + // -Zallow-features=feature1,feature2 + + if let Some(rustflags) = env::var_os("RUSTFLAGS") { + for mut flag in rustflags.to_string_lossy().split(' ') { + if flag.starts_with("-Z") { + flag = &flag["-Z".len()..]; + } + if flag.starts_with("allow-features=") { + flag = &flag["allow-features=".len()..]; + return flag.split(',').any(|allowed| allowed == feature); + } + } + } + + // No allow-features= flag, allowed by default. + true +} diff --git a/proc-macro2/src/fallback.rs b/proc-macro2/src/fallback.rs new file mode 100644 index 0000000..9762d3b --- /dev/null +++ b/proc-macro2/src/fallback.rs @@ -0,0 +1,1458 @@ +#[cfg(span_locations)] +use std::cell::RefCell; +#[cfg(span_locations)] +use std::cmp; +use std::fmt; +use std::iter; +use std::ops::RangeBounds; +#[cfg(procmacro2_semver_exempt)] +use std::path::Path; +use std::path::PathBuf; +use std::str::FromStr; +use std::vec; + +use crate::strnom::{block_comment, skip_whitespace, whitespace, word_break, Cursor, PResult}; +use crate::{Delimiter, Punct, Spacing, TokenTree}; +use unicode_xid::UnicodeXID; + +#[derive(Clone)] +pub struct TokenStream { + inner: Vec, +} + +#[derive(Debug)] +pub struct LexError; + +impl TokenStream { + pub fn new() -> TokenStream { + TokenStream { inner: Vec::new() } + } + + pub fn is_empty(&self) -> bool { + self.inner.len() == 0 + } +} + +#[cfg(span_locations)] +fn get_cursor(src: &str) -> Cursor { + // Create a dummy file & add it to the source map + SOURCE_MAP.with(|cm| { + let mut cm = cm.borrow_mut(); + let name = format!("", cm.files.len()); + let span = cm.add_file(&name, src); + Cursor { + rest: src, + off: span.lo, + } + }) +} + +#[cfg(not(span_locations))] +fn get_cursor(src: &str) -> Cursor { + Cursor { rest: src } +} + +impl FromStr for TokenStream { + type Err = LexError; + + fn from_str(src: &str) -> Result { + // Create a dummy file & add it to the source map + let cursor = get_cursor(src); + + match token_stream(cursor) { + Ok((input, output)) => { + if skip_whitespace(input).len() != 0 { + Err(LexError) + } else { + Ok(output) + } + } + Err(LexError) => Err(LexError), + } + } +} + +impl fmt::Display for TokenStream { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut joint = false; + for (i, tt) in self.inner.iter().enumerate() { + if i != 0 && !joint { + write!(f, " ")?; + } + joint = false; + match *tt { + TokenTree::Group(ref tt) => { + let (start, end) = match tt.delimiter() { + Delimiter::Parenthesis => ("(", ")"), + Delimiter::Brace => ("{", "}"), + Delimiter::Bracket => ("[", "]"), + Delimiter::None => ("", ""), + }; + if tt.stream().into_iter().next().is_none() { + write!(f, "{} {}", start, end)? + } else { + write!(f, "{} {} {}", start, tt.stream(), end)? + } + } + TokenTree::Ident(ref tt) => write!(f, "{}", tt)?, + TokenTree::Punct(ref tt) => { + write!(f, "{}", tt.as_char())?; + match tt.spacing() { + Spacing::Alone => {} + Spacing::Joint => joint = true, + } + } + TokenTree::Literal(ref tt) => write!(f, "{}", tt)?, + } + } + + Ok(()) + } +} + +impl fmt::Debug for TokenStream { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("TokenStream ")?; + f.debug_list().entries(self.clone()).finish() + } +} + +#[cfg(use_proc_macro)] +impl From for TokenStream { + fn from(inner: proc_macro::TokenStream) -> TokenStream { + inner + .to_string() + .parse() + .expect("compiler token stream parse failed") + } +} + +#[cfg(use_proc_macro)] +impl From for proc_macro::TokenStream { + fn from(inner: TokenStream) -> proc_macro::TokenStream { + inner + .to_string() + .parse() + .expect("failed to parse to compiler tokens") + } +} + +impl From for TokenStream { + fn from(tree: TokenTree) -> TokenStream { + TokenStream { inner: vec![tree] } + } +} + +impl iter::FromIterator for TokenStream { + fn from_iter>(streams: I) -> Self { + let mut v = Vec::new(); + + for token in streams.into_iter() { + v.push(token); + } + + TokenStream { inner: v } + } +} + +impl iter::FromIterator for TokenStream { + fn from_iter>(streams: I) -> Self { + let mut v = Vec::new(); + + for stream in streams.into_iter() { + v.extend(stream.inner); + } + + TokenStream { inner: v } + } +} + +impl Extend for TokenStream { + fn extend>(&mut self, streams: I) { + self.inner.extend(streams); + } +} + +impl Extend for TokenStream { + fn extend>(&mut self, streams: I) { + self.inner + .extend(streams.into_iter().flat_map(|stream| stream)); + } +} + +pub type TokenTreeIter = vec::IntoIter; + +impl IntoIterator for TokenStream { + type Item = TokenTree; + type IntoIter = TokenTreeIter; + + fn into_iter(self) -> TokenTreeIter { + self.inner.into_iter() + } +} + +#[derive(Clone, PartialEq, Eq)] +pub struct SourceFile { + path: PathBuf, +} + +impl SourceFile { + /// Get the path to this source file as a string. + pub fn path(&self) -> PathBuf { + self.path.clone() + } + + pub fn is_real(&self) -> bool { + // XXX(nika): Support real files in the future? + false + } +} + +impl fmt::Debug for SourceFile { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("SourceFile") + .field("path", &self.path()) + .field("is_real", &self.is_real()) + .finish() + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct LineColumn { + pub line: usize, + pub column: usize, +} + +#[cfg(span_locations)] +thread_local! { + static SOURCE_MAP: RefCell = RefCell::new(SourceMap { + // NOTE: We start with a single dummy file which all call_site() and + // def_site() spans reference. + files: vec![{ + #[cfg(procmacro2_semver_exempt)] + { + FileInfo { + name: "".to_owned(), + span: Span { lo: 0, hi: 0 }, + lines: vec![0], + } + } + + #[cfg(not(procmacro2_semver_exempt))] + { + FileInfo { + span: Span { lo: 0, hi: 0 }, + lines: vec![0], + } + } + }], + }); +} + +#[cfg(span_locations)] +struct FileInfo { + #[cfg(procmacro2_semver_exempt)] + name: String, + span: Span, + lines: Vec, +} + +#[cfg(span_locations)] +impl FileInfo { + fn offset_line_column(&self, offset: usize) -> LineColumn { + assert!(self.span_within(Span { + lo: offset as u32, + hi: offset as u32 + })); + let offset = offset - self.span.lo as usize; + match self.lines.binary_search(&offset) { + Ok(found) => LineColumn { + line: found + 1, + column: 0, + }, + Err(idx) => LineColumn { + line: idx, + column: offset - self.lines[idx - 1], + }, + } + } + + fn span_within(&self, span: Span) -> bool { + span.lo >= self.span.lo && span.hi <= self.span.hi + } +} + +/// Computesthe offsets of each line in the given source string. +#[cfg(span_locations)] +fn lines_offsets(s: &str) -> Vec { + let mut lines = vec![0]; + let mut prev = 0; + while let Some(len) = s[prev..].find('\n') { + prev += len + 1; + lines.push(prev); + } + lines +} + +#[cfg(span_locations)] +struct SourceMap { + files: Vec, +} + +#[cfg(span_locations)] +impl SourceMap { + fn next_start_pos(&self) -> u32 { + // Add 1 so there's always space between files. + // + // We'll always have at least 1 file, as we initialize our files list + // with a dummy file. + self.files.last().unwrap().span.hi + 1 + } + + fn add_file(&mut self, name: &str, src: &str) -> Span { + let lines = lines_offsets(src); + let lo = self.next_start_pos(); + // XXX(nika): Shouild we bother doing a checked cast or checked add here? + let span = Span { + lo, + hi: lo + (src.len() as u32), + }; + + #[cfg(procmacro2_semver_exempt)] + self.files.push(FileInfo { + name: name.to_owned(), + span, + lines, + }); + + #[cfg(not(procmacro2_semver_exempt))] + self.files.push(FileInfo { span, lines }); + let _ = name; + + span + } + + fn fileinfo(&self, span: Span) -> &FileInfo { + for file in &self.files { + if file.span_within(span) { + return file; + } + } + panic!("Invalid span with no related FileInfo!"); + } +} + +#[derive(Clone, Copy, PartialEq, Eq)] +pub struct Span { + #[cfg(span_locations)] + lo: u32, + #[cfg(span_locations)] + hi: u32, +} + +impl Span { + #[cfg(not(span_locations))] + pub fn call_site() -> Span { + Span {} + } + + #[cfg(span_locations)] + pub fn call_site() -> Span { + Span { lo: 0, hi: 0 } + } + + #[cfg(procmacro2_semver_exempt)] + pub fn def_site() -> Span { + Span::call_site() + } + + #[cfg(procmacro2_semver_exempt)] + pub fn resolved_at(&self, _other: Span) -> Span { + // Stable spans consist only of line/column information, so + // `resolved_at` and `located_at` only select which span the + // caller wants line/column information from. + *self + } + + #[cfg(procmacro2_semver_exempt)] + pub fn located_at(&self, other: Span) -> Span { + other + } + + #[cfg(procmacro2_semver_exempt)] + pub fn source_file(&self) -> SourceFile { + SOURCE_MAP.with(|cm| { + let cm = cm.borrow(); + let fi = cm.fileinfo(*self); + SourceFile { + path: Path::new(&fi.name).to_owned(), + } + }) + } + + #[cfg(span_locations)] + pub fn start(&self) -> LineColumn { + SOURCE_MAP.with(|cm| { + let cm = cm.borrow(); + let fi = cm.fileinfo(*self); + fi.offset_line_column(self.lo as usize) + }) + } + + #[cfg(span_locations)] + pub fn end(&self) -> LineColumn { + SOURCE_MAP.with(|cm| { + let cm = cm.borrow(); + let fi = cm.fileinfo(*self); + fi.offset_line_column(self.hi as usize) + }) + } + + #[cfg(not(span_locations))] + pub fn join(&self, _other: Span) -> Option { + Some(Span {}) + } + + #[cfg(span_locations)] + pub fn join(&self, other: Span) -> Option { + SOURCE_MAP.with(|cm| { + let cm = cm.borrow(); + // If `other` is not within the same FileInfo as us, return None. + if !cm.fileinfo(*self).span_within(other) { + return None; + } + Some(Span { + lo: cmp::min(self.lo, other.lo), + hi: cmp::max(self.hi, other.hi), + }) + }) + } + + #[cfg(not(span_locations))] + fn first_byte(self) -> Self { + self + } + + #[cfg(span_locations)] + fn first_byte(self) -> Self { + Span { + lo: self.lo, + hi: cmp::min(self.lo.saturating_add(1), self.hi), + } + } + + #[cfg(not(span_locations))] + fn last_byte(self) -> Self { + self + } + + #[cfg(span_locations)] + fn last_byte(self) -> Self { + Span { + lo: cmp::max(self.hi.saturating_sub(1), self.lo), + hi: self.hi, + } + } +} + +impl fmt::Debug for Span { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + #[cfg(procmacro2_semver_exempt)] + return write!(f, "bytes({}..{})", self.lo, self.hi); + + #[cfg(not(procmacro2_semver_exempt))] + write!(f, "Span") + } +} + +pub fn debug_span_field_if_nontrivial(debug: &mut fmt::DebugStruct, span: Span) { + if cfg!(procmacro2_semver_exempt) { + debug.field("span", &span); + } +} + +#[derive(Clone)] +pub struct Group { + delimiter: Delimiter, + stream: TokenStream, + span: Span, +} + +impl Group { + pub fn new(delimiter: Delimiter, stream: TokenStream) -> Group { + Group { + delimiter, + stream, + span: Span::call_site(), + } + } + + pub fn delimiter(&self) -> Delimiter { + self.delimiter + } + + pub fn stream(&self) -> TokenStream { + self.stream.clone() + } + + pub fn span(&self) -> Span { + self.span + } + + pub fn span_open(&self) -> Span { + self.span.first_byte() + } + + pub fn span_close(&self) -> Span { + self.span.last_byte() + } + + pub fn set_span(&mut self, span: Span) { + self.span = span; + } +} + +impl fmt::Display for Group { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let (left, right) = match self.delimiter { + Delimiter::Parenthesis => ("(", ")"), + Delimiter::Brace => ("{", "}"), + Delimiter::Bracket => ("[", "]"), + Delimiter::None => ("", ""), + }; + + f.write_str(left)?; + self.stream.fmt(f)?; + f.write_str(right)?; + + Ok(()) + } +} + +impl fmt::Debug for Group { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + let mut debug = fmt.debug_struct("Group"); + debug.field("delimiter", &self.delimiter); + debug.field("stream", &self.stream); + #[cfg(procmacro2_semver_exempt)] + debug.field("span", &self.span); + debug.finish() + } +} + +#[derive(Clone)] +pub struct Ident { + sym: String, + span: Span, + raw: bool, +} + +impl Ident { + fn _new(string: &str, raw: bool, span: Span) -> Ident { + validate_ident(string); + + Ident { + sym: string.to_owned(), + span, + raw, + } + } + + pub fn new(string: &str, span: Span) -> Ident { + Ident::_new(string, false, span) + } + + pub fn new_raw(string: &str, span: Span) -> Ident { + Ident::_new(string, true, span) + } + + pub fn span(&self) -> Span { + self.span + } + + pub fn set_span(&mut self, span: Span) { + self.span = span; + } +} + +fn is_ident_start(c: char) -> bool { + ('a' <= c && c <= 'z') + || ('A' <= c && c <= 'Z') + || c == '_' + || (c > '\x7f' && UnicodeXID::is_xid_start(c)) +} + +fn is_ident_continue(c: char) -> bool { + ('a' <= c && c <= 'z') + || ('A' <= c && c <= 'Z') + || c == '_' + || ('0' <= c && c <= '9') + || (c > '\x7f' && UnicodeXID::is_xid_continue(c)) +} + +fn validate_ident(string: &str) { + let validate = string; + if validate.is_empty() { + panic!("Ident is not allowed to be empty; use Option"); + } + + if validate.bytes().all(|digit| digit >= b'0' && digit <= b'9') { + panic!("Ident cannot be a number; use Literal instead"); + } + + fn ident_ok(string: &str) -> bool { + let mut chars = string.chars(); + let first = chars.next().unwrap(); + if !is_ident_start(first) { + return false; + } + for ch in chars { + if !is_ident_continue(ch) { + return false; + } + } + true + } + + if !ident_ok(validate) { + panic!("{:?} is not a valid Ident", string); + } +} + +impl PartialEq for Ident { + fn eq(&self, other: &Ident) -> bool { + self.sym == other.sym && self.raw == other.raw + } +} + +impl PartialEq for Ident +where + T: ?Sized + AsRef, +{ + fn eq(&self, other: &T) -> bool { + let other = other.as_ref(); + if self.raw { + other.starts_with("r#") && self.sym == other[2..] + } else { + self.sym == other + } + } +} + +impl fmt::Display for Ident { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if self.raw { + "r#".fmt(f)?; + } + self.sym.fmt(f) + } +} + +impl fmt::Debug for Ident { + // Ident(proc_macro), Ident(r#union) + #[cfg(not(procmacro2_semver_exempt))] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut debug = f.debug_tuple("Ident"); + debug.field(&format_args!("{}", self)); + debug.finish() + } + + // Ident { + // sym: proc_macro, + // span: bytes(128..138) + // } + #[cfg(procmacro2_semver_exempt)] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut debug = f.debug_struct("Ident"); + debug.field("sym", &format_args!("{}", self)); + debug.field("span", &self.span); + debug.finish() + } +} + +#[derive(Clone)] +pub struct Literal { + text: String, + span: Span, +} + +macro_rules! suffixed_numbers { + ($($name:ident => $kind:ident,)*) => ($( + pub fn $name(n: $kind) -> Literal { + Literal::_new(format!(concat!("{}", stringify!($kind)), n)) + } + )*) +} + +macro_rules! unsuffixed_numbers { + ($($name:ident => $kind:ident,)*) => ($( + pub fn $name(n: $kind) -> Literal { + Literal::_new(n.to_string()) + } + )*) +} + +impl Literal { + fn _new(text: String) -> Literal { + Literal { + text, + span: Span::call_site(), + } + } + + suffixed_numbers! { + u8_suffixed => u8, + u16_suffixed => u16, + u32_suffixed => u32, + u64_suffixed => u64, + u128_suffixed => u128, + usize_suffixed => usize, + i8_suffixed => i8, + i16_suffixed => i16, + i32_suffixed => i32, + i64_suffixed => i64, + i128_suffixed => i128, + isize_suffixed => isize, + + f32_suffixed => f32, + f64_suffixed => f64, + } + + unsuffixed_numbers! { + u8_unsuffixed => u8, + u16_unsuffixed => u16, + u32_unsuffixed => u32, + u64_unsuffixed => u64, + u128_unsuffixed => u128, + usize_unsuffixed => usize, + i8_unsuffixed => i8, + i16_unsuffixed => i16, + i32_unsuffixed => i32, + i64_unsuffixed => i64, + i128_unsuffixed => i128, + isize_unsuffixed => isize, + } + + pub fn f32_unsuffixed(f: f32) -> Literal { + let mut s = f.to_string(); + if !s.contains(".") { + s.push_str(".0"); + } + Literal::_new(s) + } + + pub fn f64_unsuffixed(f: f64) -> Literal { + let mut s = f.to_string(); + if !s.contains(".") { + s.push_str(".0"); + } + Literal::_new(s) + } + + pub fn string(t: &str) -> Literal { + let mut text = String::with_capacity(t.len() + 2); + text.push('"'); + for c in t.chars() { + if c == '\'' { + // escape_default turns this into "\'" which is unnecessary. + text.push(c); + } else { + text.extend(c.escape_default()); + } + } + text.push('"'); + Literal::_new(text) + } + + pub fn character(t: char) -> Literal { + let mut text = String::new(); + text.push('\''); + if t == '"' { + // escape_default turns this into '\"' which is unnecessary. + text.push(t); + } else { + text.extend(t.escape_default()); + } + text.push('\''); + Literal::_new(text) + } + + pub fn byte_string(bytes: &[u8]) -> Literal { + let mut escaped = "b\"".to_string(); + for b in bytes { + match *b { + b'\0' => escaped.push_str(r"\0"), + b'\t' => escaped.push_str(r"\t"), + b'\n' => escaped.push_str(r"\n"), + b'\r' => escaped.push_str(r"\r"), + b'"' => escaped.push_str("\\\""), + b'\\' => escaped.push_str("\\\\"), + b'\x20'..=b'\x7E' => escaped.push(*b as char), + _ => escaped.push_str(&format!("\\x{:02X}", b)), + } + } + escaped.push('"'); + Literal::_new(escaped) + } + + pub fn span(&self) -> Span { + self.span + } + + pub fn set_span(&mut self, span: Span) { + self.span = span; + } + + pub fn subspan>(&self, _range: R) -> Option { + None + } +} + +impl fmt::Display for Literal { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.text.fmt(f) + } +} + +impl fmt::Debug for Literal { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + let mut debug = fmt.debug_struct("Literal"); + debug.field("lit", &format_args!("{}", self.text)); + #[cfg(procmacro2_semver_exempt)] + debug.field("span", &self.span); + debug.finish() + } +} + +fn token_stream(mut input: Cursor) -> PResult { + let mut trees = Vec::new(); + loop { + let input_no_ws = skip_whitespace(input); + if input_no_ws.rest.len() == 0 { + break; + } + if let Ok((a, tokens)) = doc_comment(input_no_ws) { + input = a; + trees.extend(tokens); + continue; + } + + let (a, tt) = match token_tree(input_no_ws) { + Ok(p) => p, + Err(_) => break, + }; + trees.push(tt); + input = a; + } + Ok((input, TokenStream { inner: trees })) +} + +#[cfg(not(span_locations))] +fn spanned<'a, T>( + input: Cursor<'a>, + f: fn(Cursor<'a>) -> PResult<'a, T>, +) -> PResult<'a, (T, crate::Span)> { + let (a, b) = f(skip_whitespace(input))?; + Ok((a, ((b, crate::Span::_new_stable(Span::call_site()))))) +} + +#[cfg(span_locations)] +fn spanned<'a, T>( + input: Cursor<'a>, + f: fn(Cursor<'a>) -> PResult<'a, T>, +) -> PResult<'a, (T, crate::Span)> { + let input = skip_whitespace(input); + let lo = input.off; + let (a, b) = f(input)?; + let hi = a.off; + let span = crate::Span::_new_stable(Span { lo, hi }); + Ok((a, (b, span))) +} + +fn token_tree(input: Cursor) -> PResult { + let (rest, (mut tt, span)) = spanned(input, token_kind)?; + tt.set_span(span); + Ok((rest, tt)) +} + +named!(token_kind -> TokenTree, alt!( + map!(group, |g| TokenTree::Group(crate::Group::_new_stable(g))) + | + map!(literal, |l| TokenTree::Literal(crate::Literal::_new_stable(l))) // must be before symbol + | + map!(op, TokenTree::Punct) + | + symbol_leading_ws +)); + +named!(group -> Group, alt!( + delimited!( + punct!("("), + token_stream, + punct!(")") + ) => { |ts| Group::new(Delimiter::Parenthesis, ts) } + | + delimited!( + punct!("["), + token_stream, + punct!("]") + ) => { |ts| Group::new(Delimiter::Bracket, ts) } + | + delimited!( + punct!("{"), + token_stream, + punct!("}") + ) => { |ts| Group::new(Delimiter::Brace, ts) } +)); + +fn symbol_leading_ws(input: Cursor) -> PResult { + symbol(skip_whitespace(input)) +} + +fn symbol(input: Cursor) -> PResult { + let raw = input.starts_with("r#"); + let rest = input.advance((raw as usize) << 1); + + let (rest, sym) = symbol_not_raw(rest)?; + + if !raw { + let ident = crate::Ident::new(sym, crate::Span::call_site()); + return Ok((rest, ident.into())); + } + + if sym == "_" { + return Err(LexError); + } + + let ident = crate::Ident::_new_raw(sym, crate::Span::call_site()); + Ok((rest, ident.into())) +} + +fn symbol_not_raw(input: Cursor) -> PResult<&str> { + let mut chars = input.char_indices(); + + match chars.next() { + Some((_, ch)) if is_ident_start(ch) => {} + _ => return Err(LexError), + } + + let mut end = input.len(); + for (i, ch) in chars { + if !is_ident_continue(ch) { + end = i; + break; + } + } + + Ok((input.advance(end), &input.rest[..end])) +} + +fn literal(input: Cursor) -> PResult { + let input_no_ws = skip_whitespace(input); + + match literal_nocapture(input_no_ws) { + Ok((a, ())) => { + let start = input.len() - input_no_ws.len(); + let len = input_no_ws.len() - a.len(); + let end = start + len; + Ok((a, Literal::_new(input.rest[start..end].to_string()))) + } + Err(LexError) => Err(LexError), + } +} + +named!(literal_nocapture -> (), alt!( + string + | + byte_string + | + byte + | + character + | + float + | + int +)); + +named!(string -> (), alt!( + quoted_string + | + preceded!( + punct!("r"), + raw_string + ) => { |_| () } +)); + +named!(quoted_string -> (), do_parse!( + punct!("\"") >> + cooked_string >> + tag!("\"") >> + option!(symbol_not_raw) >> + (()) +)); + +fn cooked_string(input: Cursor) -> PResult<()> { + let mut chars = input.char_indices().peekable(); + while let Some((byte_offset, ch)) = chars.next() { + match ch { + '"' => { + return Ok((input.advance(byte_offset), ())); + } + '\r' => { + if let Some((_, '\n')) = chars.next() { + // ... + } else { + break; + } + } + '\\' => match chars.next() { + Some((_, 'x')) => { + if !backslash_x_char(&mut chars) { + break; + } + } + Some((_, 'n')) | Some((_, 'r')) | Some((_, 't')) | Some((_, '\\')) + | Some((_, '\'')) | Some((_, '"')) | Some((_, '0')) => {} + Some((_, 'u')) => { + if !backslash_u(&mut chars) { + break; + } + } + Some((_, '\n')) | Some((_, '\r')) => { + while let Some(&(_, ch)) = chars.peek() { + if ch.is_whitespace() { + chars.next(); + } else { + break; + } + } + } + _ => break, + }, + _ch => {} + } + } + Err(LexError) +} + +named!(byte_string -> (), alt!( + delimited!( + punct!("b\""), + cooked_byte_string, + tag!("\"") + ) => { |_| () } + | + preceded!( + punct!("br"), + raw_string + ) => { |_| () } +)); + +fn cooked_byte_string(mut input: Cursor) -> PResult<()> { + let mut bytes = input.bytes().enumerate(); + 'outer: while let Some((offset, b)) = bytes.next() { + match b { + b'"' => { + return Ok((input.advance(offset), ())); + } + b'\r' => { + if let Some((_, b'\n')) = bytes.next() { + // ... + } else { + break; + } + } + b'\\' => match bytes.next() { + Some((_, b'x')) => { + if !backslash_x_byte(&mut bytes) { + break; + } + } + Some((_, b'n')) | Some((_, b'r')) | Some((_, b't')) | Some((_, b'\\')) + | Some((_, b'0')) | Some((_, b'\'')) | Some((_, b'"')) => {} + Some((newline, b'\n')) | Some((newline, b'\r')) => { + let rest = input.advance(newline + 1); + for (offset, ch) in rest.char_indices() { + if !ch.is_whitespace() { + input = rest.advance(offset); + bytes = input.bytes().enumerate(); + continue 'outer; + } + } + break; + } + _ => break, + }, + b if b < 0x80 => {} + _ => break, + } + } + Err(LexError) +} + +fn raw_string(input: Cursor) -> PResult<()> { + let mut chars = input.char_indices(); + let mut n = 0; + while let Some((byte_offset, ch)) = chars.next() { + match ch { + '"' => { + n = byte_offset; + break; + } + '#' => {} + _ => return Err(LexError), + } + } + for (byte_offset, ch) in chars { + match ch { + '"' if input.advance(byte_offset + 1).starts_with(&input.rest[..n]) => { + let rest = input.advance(byte_offset + 1 + n); + return Ok((rest, ())); + } + '\r' => {} + _ => {} + } + } + Err(LexError) +} + +named!(byte -> (), do_parse!( + punct!("b") >> + tag!("'") >> + cooked_byte >> + tag!("'") >> + (()) +)); + +fn cooked_byte(input: Cursor) -> PResult<()> { + let mut bytes = input.bytes().enumerate(); + let ok = match bytes.next().map(|(_, b)| b) { + Some(b'\\') => match bytes.next().map(|(_, b)| b) { + Some(b'x') => backslash_x_byte(&mut bytes), + Some(b'n') | Some(b'r') | Some(b't') | Some(b'\\') | Some(b'0') | Some(b'\'') + | Some(b'"') => true, + _ => false, + }, + b => b.is_some(), + }; + if ok { + match bytes.next() { + Some((offset, _)) => { + if input.chars().as_str().is_char_boundary(offset) { + Ok((input.advance(offset), ())) + } else { + Err(LexError) + } + } + None => Ok((input.advance(input.len()), ())), + } + } else { + Err(LexError) + } +} + +named!(character -> (), do_parse!( + punct!("'") >> + cooked_char >> + tag!("'") >> + (()) +)); + +fn cooked_char(input: Cursor) -> PResult<()> { + let mut chars = input.char_indices(); + let ok = match chars.next().map(|(_, ch)| ch) { + Some('\\') => match chars.next().map(|(_, ch)| ch) { + Some('x') => backslash_x_char(&mut chars), + Some('u') => backslash_u(&mut chars), + Some('n') | Some('r') | Some('t') | Some('\\') | Some('0') | Some('\'') | Some('"') => { + true + } + _ => false, + }, + ch => ch.is_some(), + }; + if ok { + match chars.next() { + Some((idx, _)) => Ok((input.advance(idx), ())), + None => Ok((input.advance(input.len()), ())), + } + } else { + Err(LexError) + } +} + +macro_rules! next_ch { + ($chars:ident @ $pat:pat $(| $rest:pat)*) => { + match $chars.next() { + Some((_, ch)) => match ch { + $pat $(| $rest)* => ch, + _ => return false, + }, + None => return false + } + }; +} + +fn backslash_x_char(chars: &mut I) -> bool +where + I: Iterator, +{ + next_ch!(chars @ '0'..='7'); + next_ch!(chars @ '0'..='9' | 'a'..='f' | 'A'..='F'); + true +} + +fn backslash_x_byte(chars: &mut I) -> bool +where + I: Iterator, +{ + next_ch!(chars @ b'0'..=b'9' | b'a'..=b'f' | b'A'..=b'F'); + next_ch!(chars @ b'0'..=b'9' | b'a'..=b'f' | b'A'..=b'F'); + true +} + +fn backslash_u(chars: &mut I) -> bool +where + I: Iterator, +{ + next_ch!(chars @ '{'); + next_ch!(chars @ '0'..='9' | 'a'..='f' | 'A'..='F'); + loop { + let c = next_ch!(chars @ '0'..='9' | 'a'..='f' | 'A'..='F' | '_' | '}'); + if c == '}' { + return true; + } + } +} + +fn float(input: Cursor) -> PResult<()> { + let (mut rest, ()) = float_digits(input)?; + if let Some(ch) = rest.chars().next() { + if is_ident_start(ch) { + rest = symbol_not_raw(rest)?.0; + } + } + word_break(rest) +} + +fn float_digits(input: Cursor) -> PResult<()> { + let mut chars = input.chars().peekable(); + match chars.next() { + Some(ch) if ch >= '0' && ch <= '9' => {} + _ => return Err(LexError), + } + + let mut len = 1; + let mut has_dot = false; + let mut has_exp = false; + while let Some(&ch) = chars.peek() { + match ch { + '0'..='9' | '_' => { + chars.next(); + len += 1; + } + '.' => { + if has_dot { + break; + } + chars.next(); + if chars + .peek() + .map(|&ch| ch == '.' || is_ident_start(ch)) + .unwrap_or(false) + { + return Err(LexError); + } + len += 1; + has_dot = true; + } + 'e' | 'E' => { + chars.next(); + len += 1; + has_exp = true; + break; + } + _ => break, + } + } + + let rest = input.advance(len); + if !(has_dot || has_exp || rest.starts_with("f32") || rest.starts_with("f64")) { + return Err(LexError); + } + + if has_exp { + let mut has_exp_value = false; + while let Some(&ch) = chars.peek() { + match ch { + '+' | '-' => { + if has_exp_value { + break; + } + chars.next(); + len += 1; + } + '0'..='9' => { + chars.next(); + len += 1; + has_exp_value = true; + } + '_' => { + chars.next(); + len += 1; + } + _ => break, + } + } + if !has_exp_value { + return Err(LexError); + } + } + + Ok((input.advance(len), ())) +} + +fn int(input: Cursor) -> PResult<()> { + let (mut rest, ()) = digits(input)?; + if let Some(ch) = rest.chars().next() { + if is_ident_start(ch) { + rest = symbol_not_raw(rest)?.0; + } + } + word_break(rest) +} + +fn digits(mut input: Cursor) -> PResult<()> { + let base = if input.starts_with("0x") { + input = input.advance(2); + 16 + } else if input.starts_with("0o") { + input = input.advance(2); + 8 + } else if input.starts_with("0b") { + input = input.advance(2); + 2 + } else { + 10 + }; + + let mut len = 0; + let mut empty = true; + for b in input.bytes() { + let digit = match b { + b'0'..=b'9' => (b - b'0') as u64, + b'a'..=b'f' => 10 + (b - b'a') as u64, + b'A'..=b'F' => 10 + (b - b'A') as u64, + b'_' => { + if empty && base == 10 { + return Err(LexError); + } + len += 1; + continue; + } + _ => break, + }; + if digit >= base { + return Err(LexError); + } + len += 1; + empty = false; + } + if empty { + Err(LexError) + } else { + Ok((input.advance(len), ())) + } +} + +fn op(input: Cursor) -> PResult { + let input = skip_whitespace(input); + match op_char(input) { + Ok((rest, '\'')) => { + symbol(rest)?; + Ok((rest, Punct::new('\'', Spacing::Joint))) + } + Ok((rest, ch)) => { + let kind = match op_char(rest) { + Ok(_) => Spacing::Joint, + Err(LexError) => Spacing::Alone, + }; + Ok((rest, Punct::new(ch, kind))) + } + Err(LexError) => Err(LexError), + } +} + +fn op_char(input: Cursor) -> PResult { + if input.starts_with("//") || input.starts_with("/*") { + // Do not accept `/` of a comment as an op. + return Err(LexError); + } + + let mut chars = input.chars(); + let first = match chars.next() { + Some(ch) => ch, + None => { + return Err(LexError); + } + }; + let recognized = "~!@#$%^&*-=+|;:,<.>/?'"; + if recognized.contains(first) { + Ok((input.advance(first.len_utf8()), first)) + } else { + Err(LexError) + } +} + +fn doc_comment(input: Cursor) -> PResult> { + let mut trees = Vec::new(); + let (rest, ((comment, inner), span)) = spanned(input, doc_comment_contents)?; + trees.push(TokenTree::Punct(Punct::new('#', Spacing::Alone))); + if inner { + trees.push(Punct::new('!', Spacing::Alone).into()); + } + let mut stream = vec![ + TokenTree::Ident(crate::Ident::new("doc", span)), + TokenTree::Punct(Punct::new('=', Spacing::Alone)), + TokenTree::Literal(crate::Literal::string(comment)), + ]; + for tt in stream.iter_mut() { + tt.set_span(span); + } + let group = Group::new(Delimiter::Bracket, stream.into_iter().collect()); + trees.push(crate::Group::_new_stable(group).into()); + for tt in trees.iter_mut() { + tt.set_span(span); + } + Ok((rest, trees)) +} + +named!(doc_comment_contents -> (&str, bool), alt!( + do_parse!( + punct!("//!") >> + s: take_until_newline_or_eof!() >> + ((s, true)) + ) + | + do_parse!( + option!(whitespace) >> + peek!(tag!("/*!")) >> + s: block_comment >> + ((s, true)) + ) + | + do_parse!( + punct!("///") >> + not!(tag!("/")) >> + s: take_until_newline_or_eof!() >> + ((s, false)) + ) + | + do_parse!( + option!(whitespace) >> + peek!(tuple!(tag!("/**"), not!(tag!("*")))) >> + s: block_comment >> + ((s, false)) + ) +)); diff --git a/proc-macro2/src/lib.rs b/proc-macro2/src/lib.rs new file mode 100644 index 0000000..bbfb375 --- /dev/null +++ b/proc-macro2/src/lib.rs @@ -0,0 +1,1199 @@ +//! A wrapper around the procedural macro API of the compiler's [`proc_macro`] +//! crate. This library serves two purposes: +//! +//! [`proc_macro`]: https://doc.rust-lang.org/proc_macro/ +//! +//! - **Bring proc-macro-like functionality to other contexts like build.rs and +//! main.rs.** Types from `proc_macro` are entirely specific to procedural +//! macros and cannot ever exist in code outside of a procedural macro. +//! Meanwhile `proc_macro2` types may exist anywhere including non-macro code. +//! By developing foundational libraries like [syn] and [quote] against +//! `proc_macro2` rather than `proc_macro`, the procedural macro ecosystem +//! becomes easily applicable to many other use cases and we avoid +//! reimplementing non-macro equivalents of those libraries. +//! +//! - **Make procedural macros unit testable.** As a consequence of being +//! specific to procedural macros, nothing that uses `proc_macro` can be +//! executed from a unit test. In order for helper libraries or components of +//! a macro to be testable in isolation, they must be implemented using +//! `proc_macro2`. +//! +//! [syn]: https://github.com/dtolnay/syn +//! [quote]: https://github.com/dtolnay/quote +//! +//! # Usage +//! +//! The skeleton of a typical procedural macro typically looks like this: +//! +//! ``` +//! extern crate proc_macro; +//! +//! # const IGNORE: &str = stringify! { +//! #[proc_macro_derive(MyDerive)] +//! # }; +//! # #[cfg(wrap_proc_macro)] +//! pub fn my_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { +//! let input = proc_macro2::TokenStream::from(input); +//! +//! let output: proc_macro2::TokenStream = { +//! /* transform input */ +//! # input +//! }; +//! +//! proc_macro::TokenStream::from(output) +//! } +//! ``` +//! +//! If parsing with [Syn], you'll use [`parse_macro_input!`] instead to +//! propagate parse errors correctly back to the compiler when parsing fails. +//! +//! [`parse_macro_input!`]: https://docs.rs/syn/1.0/syn/macro.parse_macro_input.html +//! +//! # Unstable features +//! +//! The default feature set of proc-macro2 tracks the most recent stable +//! compiler API. Functionality in `proc_macro` that is not yet stable is not +//! exposed by proc-macro2 by default. +//! +//! To opt into the additional APIs available in the most recent nightly +//! compiler, the `procmacro2_semver_exempt` config flag must be passed to +//! rustc. We will polyfill those nightly-only APIs back to Rust 1.31.0. As +//! these are unstable APIs that track the nightly compiler, minor versions of +//! proc-macro2 may make breaking changes to them at any time. +//! +//! ```sh +//! RUSTFLAGS='--cfg procmacro2_semver_exempt' cargo build +//! ``` +//! +//! Note that this must not only be done for your crate, but for any crate that +//! depends on your crate. This infectious nature is intentional, as it serves +//! as a reminder that you are outside of the normal semver guarantees. +//! +//! Semver exempt methods are marked as such in the proc-macro2 documentation. +//! +//! # Thread-Safety +//! +//! Most types in this crate are `!Sync` because the underlying compiler +//! types make use of thread-local memory, meaning they cannot be accessed from +//! a different thread. + +// Proc-macro2 types in rustdoc of other crates get linked to here. +#![doc(html_root_url = "https://docs.rs/proc-macro2/1.0.7")] +#![cfg_attr(any(proc_macro_span, super_unstable), feature(proc_macro_span))] +#![cfg_attr(super_unstable, feature(proc_macro_raw_ident, proc_macro_def_site))] + +#[cfg(use_proc_macro)] +extern crate proc_macro; + +use std::cmp::Ordering; +use std::fmt; +use std::hash::{Hash, Hasher}; +use std::iter::FromIterator; +use std::marker; +use std::ops::RangeBounds; +#[cfg(procmacro2_semver_exempt)] +use std::path::PathBuf; +use std::rc::Rc; +use std::str::FromStr; + +#[macro_use] +mod strnom; +mod fallback; + +#[cfg(not(wrap_proc_macro))] +use crate::fallback as imp; +#[path = "wrapper.rs"] +#[cfg(wrap_proc_macro)] +mod imp; + +/// An abstract stream of tokens, or more concretely a sequence of token trees. +/// +/// This type provides interfaces for iterating over token trees and for +/// collecting token trees into one stream. +/// +/// Token stream is both the input and output of `#[proc_macro]`, +/// `#[proc_macro_attribute]` and `#[proc_macro_derive]` definitions. +#[derive(Clone)] +pub struct TokenStream { + inner: imp::TokenStream, + _marker: marker::PhantomData>, +} + +/// Error returned from `TokenStream::from_str`. +pub struct LexError { + inner: imp::LexError, + _marker: marker::PhantomData>, +} + +impl TokenStream { + fn _new(inner: imp::TokenStream) -> TokenStream { + TokenStream { + inner, + _marker: marker::PhantomData, + } + } + + fn _new_stable(inner: fallback::TokenStream) -> TokenStream { + TokenStream { + inner: inner.into(), + _marker: marker::PhantomData, + } + } + + /// Returns an empty `TokenStream` containing no token trees. + pub fn new() -> TokenStream { + TokenStream::_new(imp::TokenStream::new()) + } + + /// Checks if this `TokenStream` is empty. + pub fn is_empty(&self) -> bool { + self.inner.is_empty() + } +} + +/// `TokenStream::default()` returns an empty stream, +/// i.e. this is equivalent with `TokenStream::new()`. +impl Default for TokenStream { + fn default() -> Self { + TokenStream::new() + } +} + +/// Attempts to break the string into tokens and parse those tokens into a token +/// stream. +/// +/// May fail for a number of reasons, for example, if the string contains +/// unbalanced delimiters or characters not existing in the language. +/// +/// NOTE: Some errors may cause panics instead of returning `LexError`. We +/// reserve the right to change these errors into `LexError`s later. +impl FromStr for TokenStream { + type Err = LexError; + + fn from_str(src: &str) -> Result { + let e = src.parse().map_err(|e| LexError { + inner: e, + _marker: marker::PhantomData, + })?; + Ok(TokenStream::_new(e)) + } +} + +#[cfg(use_proc_macro)] +impl From for TokenStream { + fn from(inner: proc_macro::TokenStream) -> TokenStream { + TokenStream::_new(inner.into()) + } +} + +#[cfg(use_proc_macro)] +impl From for proc_macro::TokenStream { + fn from(inner: TokenStream) -> proc_macro::TokenStream { + inner.inner.into() + } +} + +impl From for TokenStream { + fn from(token: TokenTree) -> Self { + TokenStream::_new(imp::TokenStream::from(token)) + } +} + +impl Extend for TokenStream { + fn extend>(&mut self, streams: I) { + self.inner.extend(streams) + } +} + +impl Extend for TokenStream { + fn extend>(&mut self, streams: I) { + self.inner + .extend(streams.into_iter().map(|stream| stream.inner)) + } +} + +/// Collects a number of token trees into a single stream. +impl FromIterator for TokenStream { + fn from_iter>(streams: I) -> Self { + TokenStream::_new(streams.into_iter().collect()) + } +} +impl FromIterator for TokenStream { + fn from_iter>(streams: I) -> Self { + TokenStream::_new(streams.into_iter().map(|i| i.inner).collect()) + } +} + +/// Prints the token stream as a string that is supposed to be losslessly +/// convertible back into the same token stream (modulo spans), except for +/// possibly `TokenTree::Group`s with `Delimiter::None` delimiters and negative +/// numeric literals. +impl fmt::Display for TokenStream { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.inner.fmt(f) + } +} + +/// Prints token in a form convenient for debugging. +impl fmt::Debug for TokenStream { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.inner.fmt(f) + } +} + +impl fmt::Debug for LexError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.inner.fmt(f) + } +} + +/// The source file of a given `Span`. +/// +/// This type is semver exempt and not exposed by default. +#[cfg(procmacro2_semver_exempt)] +#[derive(Clone, PartialEq, Eq)] +pub struct SourceFile { + inner: imp::SourceFile, + _marker: marker::PhantomData>, +} + +#[cfg(procmacro2_semver_exempt)] +impl SourceFile { + fn _new(inner: imp::SourceFile) -> Self { + SourceFile { + inner, + _marker: marker::PhantomData, + } + } + + /// Get the path to this source file. + /// + /// ### Note + /// + /// If the code span associated with this `SourceFile` was generated by an + /// external macro, this may not be an actual path on the filesystem. Use + /// [`is_real`] to check. + /// + /// Also note that even if `is_real` returns `true`, if + /// `--remap-path-prefix` was passed on the command line, the path as given + /// may not actually be valid. + /// + /// [`is_real`]: #method.is_real + pub fn path(&self) -> PathBuf { + self.inner.path() + } + + /// Returns `true` if this source file is a real source file, and not + /// generated by an external macro's expansion. + pub fn is_real(&self) -> bool { + self.inner.is_real() + } +} + +#[cfg(procmacro2_semver_exempt)] +impl fmt::Debug for SourceFile { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.inner.fmt(f) + } +} + +/// A line-column pair representing the start or end of a `Span`. +/// +/// This type is semver exempt and not exposed by default. +#[cfg(span_locations)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct LineColumn { + /// The 1-indexed line in the source file on which the span starts or ends + /// (inclusive). + pub line: usize, + /// The 0-indexed column (in UTF-8 characters) in the source file on which + /// the span starts or ends (inclusive). + pub column: usize, +} + +/// A region of source code, along with macro expansion information. +#[derive(Copy, Clone)] +pub struct Span { + inner: imp::Span, + _marker: marker::PhantomData>, +} + +impl Span { + fn _new(inner: imp::Span) -> Span { + Span { + inner, + _marker: marker::PhantomData, + } + } + + fn _new_stable(inner: fallback::Span) -> Span { + Span { + inner: inner.into(), + _marker: marker::PhantomData, + } + } + + /// The span of the invocation of the current procedural macro. + /// + /// Identifiers created with this span will be resolved as if they were + /// written directly at the macro call location (call-site hygiene) and + /// other code at the macro call site will be able to refer to them as well. + pub fn call_site() -> Span { + Span::_new(imp::Span::call_site()) + } + + /// A span that resolves at the macro definition site. + /// + /// This method is semver exempt and not exposed by default. + #[cfg(procmacro2_semver_exempt)] + pub fn def_site() -> Span { + Span::_new(imp::Span::def_site()) + } + + /// Creates a new span with the same line/column information as `self` but + /// that resolves symbols as though it were at `other`. + /// + /// This method is semver exempt and not exposed by default. + #[cfg(procmacro2_semver_exempt)] + pub fn resolved_at(&self, other: Span) -> Span { + Span::_new(self.inner.resolved_at(other.inner)) + } + + /// Creates a new span with the same name resolution behavior as `self` but + /// with the line/column information of `other`. + /// + /// This method is semver exempt and not exposed by default. + #[cfg(procmacro2_semver_exempt)] + pub fn located_at(&self, other: Span) -> Span { + Span::_new(self.inner.located_at(other.inner)) + } + + /// Convert `proc_macro2::Span` to `proc_macro::Span`. + /// + /// This method is available when building with a nightly compiler, or when + /// building with rustc 1.29+ *without* semver exempt features. + /// + /// # Panics + /// + /// Panics if called from outside of a procedural macro. Unlike + /// `proc_macro2::Span`, the `proc_macro::Span` type can only exist within + /// the context of a procedural macro invocation. + #[cfg(wrap_proc_macro)] + pub fn unwrap(self) -> proc_macro::Span { + self.inner.unwrap() + } + + // Soft deprecated. Please use Span::unwrap. + #[cfg(wrap_proc_macro)] + #[doc(hidden)] + pub fn unstable(self) -> proc_macro::Span { + self.unwrap() + } + + /// The original source file into which this span points. + /// + /// This method is semver exempt and not exposed by default. + #[cfg(procmacro2_semver_exempt)] + pub fn source_file(&self) -> SourceFile { + SourceFile::_new(self.inner.source_file()) + } + + /// Get the starting line/column in the source file for this span. + /// + /// This method requires the `"span-locations"` feature to be enabled. + #[cfg(span_locations)] + pub fn start(&self) -> LineColumn { + let imp::LineColumn { line, column } = self.inner.start(); + LineColumn { line, column } + } + + /// Get the ending line/column in the source file for this span. + /// + /// This method requires the `"span-locations"` feature to be enabled. + #[cfg(span_locations)] + pub fn end(&self) -> LineColumn { + let imp::LineColumn { line, column } = self.inner.end(); + LineColumn { line, column } + } + + /// Create a new span encompassing `self` and `other`. + /// + /// Returns `None` if `self` and `other` are from different files. + /// + /// Warning: the underlying [`proc_macro::Span::join`] method is + /// nightly-only. When called from within a procedural macro not using a + /// nightly compiler, this method will always return `None`. + /// + /// [`proc_macro::Span::join`]: https://doc.rust-lang.org/proc_macro/struct.Span.html#method.join + pub fn join(&self, other: Span) -> Option { + self.inner.join(other.inner).map(Span::_new) + } + + /// Compares two spans to see if they're equal. + /// + /// This method is semver exempt and not exposed by default. + #[cfg(procmacro2_semver_exempt)] + pub fn eq(&self, other: &Span) -> bool { + self.inner.eq(&other.inner) + } +} + +/// Prints a span in a form convenient for debugging. +impl fmt::Debug for Span { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.inner.fmt(f) + } +} + +/// A single token or a delimited sequence of token trees (e.g. `[1, (), ..]`). +#[derive(Clone)] +pub enum TokenTree { + /// A token stream surrounded by bracket delimiters. + Group(Group), + /// An identifier. + Ident(Ident), + /// A single punctuation character (`+`, `,`, `$`, etc.). + Punct(Punct), + /// A literal character (`'a'`), string (`"hello"`), number (`2.3`), etc. + Literal(Literal), +} + +impl TokenTree { + /// Returns the span of this tree, delegating to the `span` method of + /// the contained token or a delimited stream. + pub fn span(&self) -> Span { + match *self { + TokenTree::Group(ref t) => t.span(), + TokenTree::Ident(ref t) => t.span(), + TokenTree::Punct(ref t) => t.span(), + TokenTree::Literal(ref t) => t.span(), + } + } + + /// Configures the span for *only this token*. + /// + /// Note that if this token is a `Group` then this method will not configure + /// the span of each of the internal tokens, this will simply delegate to + /// the `set_span` method of each variant. + pub fn set_span(&mut self, span: Span) { + match *self { + TokenTree::Group(ref mut t) => t.set_span(span), + TokenTree::Ident(ref mut t) => t.set_span(span), + TokenTree::Punct(ref mut t) => t.set_span(span), + TokenTree::Literal(ref mut t) => t.set_span(span), + } + } +} + +impl From for TokenTree { + fn from(g: Group) -> TokenTree { + TokenTree::Group(g) + } +} + +impl From for TokenTree { + fn from(g: Ident) -> TokenTree { + TokenTree::Ident(g) + } +} + +impl From for TokenTree { + fn from(g: Punct) -> TokenTree { + TokenTree::Punct(g) + } +} + +impl From for TokenTree { + fn from(g: Literal) -> TokenTree { + TokenTree::Literal(g) + } +} + +/// Prints the token tree as a string that is supposed to be losslessly +/// convertible back into the same token tree (modulo spans), except for +/// possibly `TokenTree::Group`s with `Delimiter::None` delimiters and negative +/// numeric literals. +impl fmt::Display for TokenTree { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + TokenTree::Group(ref t) => t.fmt(f), + TokenTree::Ident(ref t) => t.fmt(f), + TokenTree::Punct(ref t) => t.fmt(f), + TokenTree::Literal(ref t) => t.fmt(f), + } + } +} + +/// Prints token tree in a form convenient for debugging. +impl fmt::Debug for TokenTree { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // Each of these has the name in the struct type in the derived debug, + // so don't bother with an extra layer of indirection + match *self { + TokenTree::Group(ref t) => t.fmt(f), + TokenTree::Ident(ref t) => { + let mut debug = f.debug_struct("Ident"); + debug.field("sym", &format_args!("{}", t)); + imp::debug_span_field_if_nontrivial(&mut debug, t.span().inner); + debug.finish() + } + TokenTree::Punct(ref t) => t.fmt(f), + TokenTree::Literal(ref t) => t.fmt(f), + } + } +} + +/// A delimited token stream. +/// +/// A `Group` internally contains a `TokenStream` which is surrounded by +/// `Delimiter`s. +#[derive(Clone)] +pub struct Group { + inner: imp::Group, +} + +/// Describes how a sequence of token trees is delimited. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum Delimiter { + /// `( ... )` + Parenthesis, + /// `{ ... }` + Brace, + /// `[ ... ]` + Bracket, + /// `Ø ... Ø` + /// + /// An implicit delimiter, that may, for example, appear around tokens + /// coming from a "macro variable" `$var`. It is important to preserve + /// operator priorities in cases like `$var * 3` where `$var` is `1 + 2`. + /// Implicit delimiters may not survive roundtrip of a token stream through + /// a string. + None, +} + +impl Group { + fn _new(inner: imp::Group) -> Self { + Group { inner } + } + + fn _new_stable(inner: fallback::Group) -> Self { + Group { + inner: inner.into(), + } + } + + /// Creates a new `Group` with the given delimiter and token stream. + /// + /// This constructor will set the span for this group to + /// `Span::call_site()`. To change the span you can use the `set_span` + /// method below. + pub fn new(delimiter: Delimiter, stream: TokenStream) -> Group { + Group { + inner: imp::Group::new(delimiter, stream.inner), + } + } + + /// Returns the delimiter of this `Group` + pub fn delimiter(&self) -> Delimiter { + self.inner.delimiter() + } + + /// Returns the `TokenStream` of tokens that are delimited in this `Group`. + /// + /// Note that the returned token stream does not include the delimiter + /// returned above. + pub fn stream(&self) -> TokenStream { + TokenStream::_new(self.inner.stream()) + } + + /// Returns the span for the delimiters of this token stream, spanning the + /// entire `Group`. + /// + /// ```text + /// pub fn span(&self) -> Span { + /// ^^^^^^^ + /// ``` + pub fn span(&self) -> Span { + Span::_new(self.inner.span()) + } + + /// Returns the span pointing to the opening delimiter of this group. + /// + /// ```text + /// pub fn span_open(&self) -> Span { + /// ^ + /// ``` + pub fn span_open(&self) -> Span { + Span::_new(self.inner.span_open()) + } + + /// Returns the span pointing to the closing delimiter of this group. + /// + /// ```text + /// pub fn span_close(&self) -> Span { + /// ^ + /// ``` + pub fn span_close(&self) -> Span { + Span::_new(self.inner.span_close()) + } + + /// Configures the span for this `Group`'s delimiters, but not its internal + /// tokens. + /// + /// This method will **not** set the span of all the internal tokens spanned + /// by this group, but rather it will only set the span of the delimiter + /// tokens at the level of the `Group`. + pub fn set_span(&mut self, span: Span) { + self.inner.set_span(span.inner) + } +} + +/// Prints the group as a string that should be losslessly convertible back +/// into the same group (modulo spans), except for possibly `TokenTree::Group`s +/// with `Delimiter::None` delimiters. +impl fmt::Display for Group { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.inner, formatter) + } +} + +impl fmt::Debug for Group { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(&self.inner, formatter) + } +} + +/// An `Punct` is an single punctuation character like `+`, `-` or `#`. +/// +/// Multicharacter operators like `+=` are represented as two instances of +/// `Punct` with different forms of `Spacing` returned. +#[derive(Clone)] +pub struct Punct { + op: char, + spacing: Spacing, + span: Span, +} + +/// Whether an `Punct` is followed immediately by another `Punct` or followed by +/// another token or whitespace. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum Spacing { + /// E.g. `+` is `Alone` in `+ =`, `+ident` or `+()`. + Alone, + /// E.g. `+` is `Joint` in `+=` or `'` is `Joint` in `'#`. + /// + /// Additionally, single quote `'` can join with identifiers to form + /// lifetimes `'ident`. + Joint, +} + +impl Punct { + /// Creates a new `Punct` from the given character and spacing. + /// + /// The `ch` argument must be a valid punctuation character permitted by the + /// language, otherwise the function will panic. + /// + /// The returned `Punct` will have the default span of `Span::call_site()` + /// which can be further configured with the `set_span` method below. + pub fn new(op: char, spacing: Spacing) -> Punct { + Punct { + op, + spacing, + span: Span::call_site(), + } + } + + /// Returns the value of this punctuation character as `char`. + pub fn as_char(&self) -> char { + self.op + } + + /// Returns the spacing of this punctuation character, indicating whether + /// it's immediately followed by another `Punct` in the token stream, so + /// they can potentially be combined into a multicharacter operator + /// (`Joint`), or it's followed by some other token or whitespace (`Alone`) + /// so the operator has certainly ended. + pub fn spacing(&self) -> Spacing { + self.spacing + } + + /// Returns the span for this punctuation character. + pub fn span(&self) -> Span { + self.span + } + + /// Configure the span for this punctuation character. + pub fn set_span(&mut self, span: Span) { + self.span = span; + } +} + +/// Prints the punctuation character as a string that should be losslessly +/// convertible back into the same character. +impl fmt::Display for Punct { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.op.fmt(f) + } +} + +impl fmt::Debug for Punct { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + let mut debug = fmt.debug_struct("Punct"); + debug.field("op", &self.op); + debug.field("spacing", &self.spacing); + imp::debug_span_field_if_nontrivial(&mut debug, self.span.inner); + debug.finish() + } +} + +/// A word of Rust code, which may be a keyword or legal variable name. +/// +/// An identifier consists of at least one Unicode code point, the first of +/// which has the XID_Start property and the rest of which have the XID_Continue +/// property. +/// +/// - The empty string is not an identifier. Use `Option`. +/// - A lifetime is not an identifier. Use `syn::Lifetime` instead. +/// +/// An identifier constructed with `Ident::new` is permitted to be a Rust +/// keyword, though parsing one through its [`Parse`] implementation rejects +/// Rust keywords. Use `input.call(Ident::parse_any)` when parsing to match the +/// behaviour of `Ident::new`. +/// +/// [`Parse`]: https://docs.rs/syn/1.0/syn/parse/trait.Parse.html +/// +/// # Examples +/// +/// A new ident can be created from a string using the `Ident::new` function. +/// A span must be provided explicitly which governs the name resolution +/// behavior of the resulting identifier. +/// +/// ``` +/// use proc_macro2::{Ident, Span}; +/// +/// fn main() { +/// let call_ident = Ident::new("calligraphy", Span::call_site()); +/// +/// println!("{}", call_ident); +/// } +/// ``` +/// +/// An ident can be interpolated into a token stream using the `quote!` macro. +/// +/// ``` +/// use proc_macro2::{Ident, Span}; +/// use quote::quote; +/// +/// fn main() { +/// let ident = Ident::new("demo", Span::call_site()); +/// +/// // Create a variable binding whose name is this ident. +/// let expanded = quote! { let #ident = 10; }; +/// +/// // Create a variable binding with a slightly different name. +/// let temp_ident = Ident::new(&format!("new_{}", ident), Span::call_site()); +/// let expanded = quote! { let #temp_ident = 10; }; +/// } +/// ``` +/// +/// A string representation of the ident is available through the `to_string()` +/// method. +/// +/// ``` +/// # use proc_macro2::{Ident, Span}; +/// # +/// # let ident = Ident::new("another_identifier", Span::call_site()); +/// # +/// // Examine the ident as a string. +/// let ident_string = ident.to_string(); +/// if ident_string.len() > 60 { +/// println!("Very long identifier: {}", ident_string) +/// } +/// ``` +#[derive(Clone)] +pub struct Ident { + inner: imp::Ident, + _marker: marker::PhantomData>, +} + +impl Ident { + fn _new(inner: imp::Ident) -> Ident { + Ident { + inner, + _marker: marker::PhantomData, + } + } + + /// Creates a new `Ident` with the given `string` as well as the specified + /// `span`. + /// + /// The `string` argument must be a valid identifier permitted by the + /// language, otherwise the function will panic. + /// + /// Note that `span`, currently in rustc, configures the hygiene information + /// for this identifier. + /// + /// As of this time `Span::call_site()` explicitly opts-in to "call-site" + /// hygiene meaning that identifiers created with this span will be resolved + /// as if they were written directly at the location of the macro call, and + /// other code at the macro call site will be able to refer to them as well. + /// + /// Later spans like `Span::def_site()` will allow to opt-in to + /// "definition-site" hygiene meaning that identifiers created with this + /// span will be resolved at the location of the macro definition and other + /// code at the macro call site will not be able to refer to them. + /// + /// Due to the current importance of hygiene this constructor, unlike other + /// tokens, requires a `Span` to be specified at construction. + /// + /// # Panics + /// + /// Panics if the input string is neither a keyword nor a legal variable + /// name. If you are not sure whether the string contains an identifier and + /// need to handle an error case, use + /// syn::parse_str::<Ident> + /// rather than `Ident::new`. + pub fn new(string: &str, span: Span) -> Ident { + Ident::_new(imp::Ident::new(string, span.inner)) + } + + /// Same as `Ident::new`, but creates a raw identifier (`r#ident`). + /// + /// This method is semver exempt and not exposed by default. + #[cfg(procmacro2_semver_exempt)] + pub fn new_raw(string: &str, span: Span) -> Ident { + Ident::_new_raw(string, span) + } + + fn _new_raw(string: &str, span: Span) -> Ident { + Ident::_new(imp::Ident::new_raw(string, span.inner)) + } + + /// Returns the span of this `Ident`. + pub fn span(&self) -> Span { + Span::_new(self.inner.span()) + } + + /// Configures the span of this `Ident`, possibly changing its hygiene + /// context. + pub fn set_span(&mut self, span: Span) { + self.inner.set_span(span.inner); + } +} + +impl PartialEq for Ident { + fn eq(&self, other: &Ident) -> bool { + self.inner == other.inner + } +} + +impl PartialEq for Ident +where + T: ?Sized + AsRef, +{ + fn eq(&self, other: &T) -> bool { + self.inner == other + } +} + +impl Eq for Ident {} + +impl PartialOrd for Ident { + fn partial_cmp(&self, other: &Ident) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Ident { + fn cmp(&self, other: &Ident) -> Ordering { + self.to_string().cmp(&other.to_string()) + } +} + +impl Hash for Ident { + fn hash(&self, hasher: &mut H) { + self.to_string().hash(hasher) + } +} + +/// Prints the identifier as a string that should be losslessly convertible back +/// into the same identifier. +impl fmt::Display for Ident { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.inner.fmt(f) + } +} + +impl fmt::Debug for Ident { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.inner.fmt(f) + } +} + +/// A literal string (`"hello"`), byte string (`b"hello"`), character (`'a'`), +/// byte character (`b'a'`), an integer or floating point number with or without +/// a suffix (`1`, `1u8`, `2.3`, `2.3f32`). +/// +/// Boolean literals like `true` and `false` do not belong here, they are +/// `Ident`s. +#[derive(Clone)] +pub struct Literal { + inner: imp::Literal, + _marker: marker::PhantomData>, +} + +macro_rules! suffixed_int_literals { + ($($name:ident => $kind:ident,)*) => ($( + /// Creates a new suffixed integer literal with the specified value. + /// + /// This function will create an integer like `1u32` where the integer + /// value specified is the first part of the token and the integral is + /// also suffixed at the end. Literals created from negative numbers may + /// not survive rountrips through `TokenStream` or strings and may be + /// broken into two tokens (`-` and positive literal). + /// + /// Literals created through this method have the `Span::call_site()` + /// span by default, which can be configured with the `set_span` method + /// below. + pub fn $name(n: $kind) -> Literal { + Literal::_new(imp::Literal::$name(n)) + } + )*) +} + +macro_rules! unsuffixed_int_literals { + ($($name:ident => $kind:ident,)*) => ($( + /// Creates a new unsuffixed integer literal with the specified value. + /// + /// This function will create an integer like `1` where the integer + /// value specified is the first part of the token. No suffix is + /// specified on this token, meaning that invocations like + /// `Literal::i8_unsuffixed(1)` are equivalent to + /// `Literal::u32_unsuffixed(1)`. Literals created from negative numbers + /// may not survive rountrips through `TokenStream` or strings and may + /// be broken into two tokens (`-` and positive literal). + /// + /// Literals created through this method have the `Span::call_site()` + /// span by default, which can be configured with the `set_span` method + /// below. + pub fn $name(n: $kind) -> Literal { + Literal::_new(imp::Literal::$name(n)) + } + )*) +} + +impl Literal { + fn _new(inner: imp::Literal) -> Literal { + Literal { + inner, + _marker: marker::PhantomData, + } + } + + fn _new_stable(inner: fallback::Literal) -> Literal { + Literal { + inner: inner.into(), + _marker: marker::PhantomData, + } + } + + suffixed_int_literals! { + u8_suffixed => u8, + u16_suffixed => u16, + u32_suffixed => u32, + u64_suffixed => u64, + u128_suffixed => u128, + usize_suffixed => usize, + i8_suffixed => i8, + i16_suffixed => i16, + i32_suffixed => i32, + i64_suffixed => i64, + i128_suffixed => i128, + isize_suffixed => isize, + } + + unsuffixed_int_literals! { + u8_unsuffixed => u8, + u16_unsuffixed => u16, + u32_unsuffixed => u32, + u64_unsuffixed => u64, + u128_unsuffixed => u128, + usize_unsuffixed => usize, + i8_unsuffixed => i8, + i16_unsuffixed => i16, + i32_unsuffixed => i32, + i64_unsuffixed => i64, + i128_unsuffixed => i128, + isize_unsuffixed => isize, + } + + /// Creates a new unsuffixed floating-point literal. + /// + /// This constructor is similar to those like `Literal::i8_unsuffixed` where + /// the float's value is emitted directly into the token but no suffix is + /// used, so it may be inferred to be a `f64` later in the compiler. + /// Literals created from negative numbers may not survive rountrips through + /// `TokenStream` or strings and may be broken into two tokens (`-` and + /// positive literal). + /// + /// # Panics + /// + /// This function requires that the specified float is finite, for example + /// if it is infinity or NaN this function will panic. + pub fn f64_unsuffixed(f: f64) -> Literal { + assert!(f.is_finite()); + Literal::_new(imp::Literal::f64_unsuffixed(f)) + } + + /// Creates a new suffixed floating-point literal. + /// + /// This constructor will create a literal like `1.0f64` where the value + /// specified is the preceding part of the token and `f64` is the suffix of + /// the token. This token will always be inferred to be an `f64` in the + /// compiler. Literals created from negative numbers may not survive + /// rountrips through `TokenStream` or strings and may be broken into two + /// tokens (`-` and positive literal). + /// + /// # Panics + /// + /// This function requires that the specified float is finite, for example + /// if it is infinity or NaN this function will panic. + pub fn f64_suffixed(f: f64) -> Literal { + assert!(f.is_finite()); + Literal::_new(imp::Literal::f64_suffixed(f)) + } + + /// Creates a new unsuffixed floating-point literal. + /// + /// This constructor is similar to those like `Literal::i8_unsuffixed` where + /// the float's value is emitted directly into the token but no suffix is + /// used, so it may be inferred to be a `f64` later in the compiler. + /// Literals created from negative numbers may not survive rountrips through + /// `TokenStream` or strings and may be broken into two tokens (`-` and + /// positive literal). + /// + /// # Panics + /// + /// This function requires that the specified float is finite, for example + /// if it is infinity or NaN this function will panic. + pub fn f32_unsuffixed(f: f32) -> Literal { + assert!(f.is_finite()); + Literal::_new(imp::Literal::f32_unsuffixed(f)) + } + + /// Creates a new suffixed floating-point literal. + /// + /// This constructor will create a literal like `1.0f32` where the value + /// specified is the preceding part of the token and `f32` is the suffix of + /// the token. This token will always be inferred to be an `f32` in the + /// compiler. Literals created from negative numbers may not survive + /// rountrips through `TokenStream` or strings and may be broken into two + /// tokens (`-` and positive literal). + /// + /// # Panics + /// + /// This function requires that the specified float is finite, for example + /// if it is infinity or NaN this function will panic. + pub fn f32_suffixed(f: f32) -> Literal { + assert!(f.is_finite()); + Literal::_new(imp::Literal::f32_suffixed(f)) + } + + /// String literal. + pub fn string(string: &str) -> Literal { + Literal::_new(imp::Literal::string(string)) + } + + /// Character literal. + pub fn character(ch: char) -> Literal { + Literal::_new(imp::Literal::character(ch)) + } + + /// Byte string literal. + pub fn byte_string(s: &[u8]) -> Literal { + Literal::_new(imp::Literal::byte_string(s)) + } + + /// Returns the span encompassing this literal. + pub fn span(&self) -> Span { + Span::_new(self.inner.span()) + } + + /// Configures the span associated for this literal. + pub fn set_span(&mut self, span: Span) { + self.inner.set_span(span.inner); + } + + /// Returns a `Span` that is a subset of `self.span()` containing only + /// the source bytes in range `range`. Returns `None` if the would-be + /// trimmed span is outside the bounds of `self`. + /// + /// Warning: the underlying [`proc_macro::Literal::subspan`] method is + /// nightly-only. When called from within a procedural macro not using a + /// nightly compiler, this method will always return `None`. + /// + /// [`proc_macro::Literal::subspan`]: https://doc.rust-lang.org/proc_macro/struct.Literal.html#method.subspan + pub fn subspan>(&self, range: R) -> Option { + self.inner.subspan(range).map(Span::_new) + } +} + +impl fmt::Debug for Literal { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.inner.fmt(f) + } +} + +impl fmt::Display for Literal { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.inner.fmt(f) + } +} + +/// Public implementation details for the `TokenStream` type, such as iterators. +pub mod token_stream { + use std::fmt; + use std::marker; + use std::rc::Rc; + + pub use crate::TokenStream; + use crate::{imp, TokenTree}; + + /// An iterator over `TokenStream`'s `TokenTree`s. + /// + /// The iteration is "shallow", e.g. the iterator doesn't recurse into + /// delimited groups, and returns whole groups as token trees. + #[derive(Clone)] + pub struct IntoIter { + inner: imp::TokenTreeIter, + _marker: marker::PhantomData>, + } + + impl Iterator for IntoIter { + type Item = TokenTree; + + fn next(&mut self) -> Option { + self.inner.next() + } + } + + impl fmt::Debug for IntoIter { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.inner.fmt(f) + } + } + + impl IntoIterator for TokenStream { + type Item = TokenTree; + type IntoIter = IntoIter; + + fn into_iter(self) -> IntoIter { + IntoIter { + inner: self.inner.into_iter(), + _marker: marker::PhantomData, + } + } + } +} diff --git a/proc-macro2/src/strnom.rs b/proc-macro2/src/strnom.rs new file mode 100644 index 0000000..eb7d0b8 --- /dev/null +++ b/proc-macro2/src/strnom.rs @@ -0,0 +1,391 @@ +//! Adapted from [`nom`](https://github.com/Geal/nom). + +use crate::fallback::LexError; +use std::str::{Bytes, CharIndices, Chars}; +use unicode_xid::UnicodeXID; + +#[derive(Copy, Clone, Eq, PartialEq)] +pub struct Cursor<'a> { + pub rest: &'a str, + #[cfg(span_locations)] + pub off: u32, +} + +impl<'a> Cursor<'a> { + #[cfg(not(span_locations))] + pub fn advance(&self, amt: usize) -> Cursor<'a> { + Cursor { + rest: &self.rest[amt..], + } + } + #[cfg(span_locations)] + pub fn advance(&self, amt: usize) -> Cursor<'a> { + Cursor { + rest: &self.rest[amt..], + off: self.off + (amt as u32), + } + } + + pub fn find(&self, p: char) -> Option { + self.rest.find(p) + } + + pub fn starts_with(&self, s: &str) -> bool { + self.rest.starts_with(s) + } + + pub fn is_empty(&self) -> bool { + self.rest.is_empty() + } + + pub fn len(&self) -> usize { + self.rest.len() + } + + pub fn as_bytes(&self) -> &'a [u8] { + self.rest.as_bytes() + } + + pub fn bytes(&self) -> Bytes<'a> { + self.rest.bytes() + } + + pub fn chars(&self) -> Chars<'a> { + self.rest.chars() + } + + pub fn char_indices(&self) -> CharIndices<'a> { + self.rest.char_indices() + } +} + +pub type PResult<'a, O> = Result<(Cursor<'a>, O), LexError>; + +pub fn whitespace(input: Cursor) -> PResult<()> { + if input.is_empty() { + return Err(LexError); + } + + let bytes = input.as_bytes(); + let mut i = 0; + while i < bytes.len() { + let s = input.advance(i); + if bytes[i] == b'/' { + if s.starts_with("//") + && (!s.starts_with("///") || s.starts_with("////")) + && !s.starts_with("//!") + { + if let Some(len) = s.find('\n') { + i += len + 1; + continue; + } + break; + } else if s.starts_with("/**/") { + i += 4; + continue; + } else if s.starts_with("/*") + && (!s.starts_with("/**") || s.starts_with("/***")) + && !s.starts_with("/*!") + { + let (_, com) = block_comment(s)?; + i += com.len(); + continue; + } + } + match bytes[i] { + b' ' | 0x09..=0x0d => { + i += 1; + continue; + } + b if b <= 0x7f => {} + _ => { + let ch = s.chars().next().unwrap(); + if is_whitespace(ch) { + i += ch.len_utf8(); + continue; + } + } + } + return if i > 0 { Ok((s, ())) } else { Err(LexError) }; + } + Ok((input.advance(input.len()), ())) +} + +pub fn block_comment(input: Cursor) -> PResult<&str> { + if !input.starts_with("/*") { + return Err(LexError); + } + + let mut depth = 0; + let bytes = input.as_bytes(); + let mut i = 0; + let upper = bytes.len() - 1; + while i < upper { + if bytes[i] == b'/' && bytes[i + 1] == b'*' { + depth += 1; + i += 1; // eat '*' + } else if bytes[i] == b'*' && bytes[i + 1] == b'/' { + depth -= 1; + if depth == 0 { + return Ok((input.advance(i + 2), &input.rest[..i + 2])); + } + i += 1; // eat '/' + } + i += 1; + } + Err(LexError) +} + +pub fn skip_whitespace(input: Cursor) -> Cursor { + match whitespace(input) { + Ok((rest, _)) => rest, + Err(LexError) => input, + } +} + +fn is_whitespace(ch: char) -> bool { + // Rust treats left-to-right mark and right-to-left mark as whitespace + ch.is_whitespace() || ch == '\u{200e}' || ch == '\u{200f}' +} + +pub fn word_break(input: Cursor) -> PResult<()> { + match input.chars().next() { + Some(ch) if UnicodeXID::is_xid_continue(ch) => Err(LexError), + Some(_) | None => Ok((input, ())), + } +} + +macro_rules! named { + ($name:ident -> $o:ty, $submac:ident!( $($args:tt)* )) => { + fn $name<'a>(i: Cursor<'a>) -> $crate::strnom::PResult<'a, $o> { + $submac!(i, $($args)*) + } + }; +} + +macro_rules! alt { + ($i:expr, $e:ident | $($rest:tt)*) => { + alt!($i, call!($e) | $($rest)*) + }; + + ($i:expr, $subrule:ident!( $($args:tt)*) | $($rest:tt)*) => { + match $subrule!($i, $($args)*) { + res @ Ok(_) => res, + _ => alt!($i, $($rest)*) + } + }; + + ($i:expr, $subrule:ident!( $($args:tt)* ) => { $gen:expr } | $($rest:tt)+) => { + match $subrule!($i, $($args)*) { + Ok((i, o)) => Ok((i, $gen(o))), + Err(LexError) => alt!($i, $($rest)*) + } + }; + + ($i:expr, $e:ident => { $gen:expr } | $($rest:tt)*) => { + alt!($i, call!($e) => { $gen } | $($rest)*) + }; + + ($i:expr, $e:ident => { $gen:expr }) => { + alt!($i, call!($e) => { $gen }) + }; + + ($i:expr, $subrule:ident!( $($args:tt)* ) => { $gen:expr }) => { + match $subrule!($i, $($args)*) { + Ok((i, o)) => Ok((i, $gen(o))), + Err(LexError) => Err(LexError), + } + }; + + ($i:expr, $e:ident) => { + alt!($i, call!($e)) + }; + + ($i:expr, $subrule:ident!( $($args:tt)*)) => { + $subrule!($i, $($args)*) + }; +} + +macro_rules! do_parse { + ($i:expr, ( $($rest:expr),* )) => { + Ok(($i, ( $($rest),* ))) + }; + + ($i:expr, $e:ident >> $($rest:tt)*) => { + do_parse!($i, call!($e) >> $($rest)*) + }; + + ($i:expr, $submac:ident!( $($args:tt)* ) >> $($rest:tt)*) => { + match $submac!($i, $($args)*) { + Err(LexError) => Err(LexError), + Ok((i, _)) => do_parse!(i, $($rest)*), + } + }; + + ($i:expr, $field:ident : $e:ident >> $($rest:tt)*) => { + do_parse!($i, $field: call!($e) >> $($rest)*) + }; + + ($i:expr, $field:ident : $submac:ident!( $($args:tt)* ) >> $($rest:tt)*) => { + match $submac!($i, $($args)*) { + Err(LexError) => Err(LexError), + Ok((i, o)) => { + let $field = o; + do_parse!(i, $($rest)*) + }, + } + }; +} + +macro_rules! peek { + ($i:expr, $submac:ident!( $($args:tt)* )) => { + match $submac!($i, $($args)*) { + Ok((_, o)) => Ok(($i, o)), + Err(LexError) => Err(LexError), + } + }; +} + +macro_rules! call { + ($i:expr, $fun:expr $(, $args:expr)*) => { + $fun($i $(, $args)*) + }; +} + +macro_rules! option { + ($i:expr, $f:expr) => { + match $f($i) { + Ok((i, o)) => Ok((i, Some(o))), + Err(LexError) => Ok(($i, None)), + } + }; +} + +macro_rules! take_until_newline_or_eof { + ($i:expr,) => {{ + if $i.len() == 0 { + Ok(($i, "")) + } else { + match $i.find('\n') { + Some(i) => Ok(($i.advance(i), &$i.rest[..i])), + None => Ok(($i.advance($i.len()), &$i.rest[..$i.len()])), + } + } + }}; +} + +macro_rules! tuple { + ($i:expr, $($rest:tt)*) => { + tuple_parser!($i, (), $($rest)*) + }; +} + +/// Do not use directly. Use `tuple!`. +macro_rules! tuple_parser { + ($i:expr, ($($parsed:tt),*), $e:ident, $($rest:tt)*) => { + tuple_parser!($i, ($($parsed),*), call!($e), $($rest)*) + }; + + ($i:expr, (), $submac:ident!( $($args:tt)* ), $($rest:tt)*) => { + match $submac!($i, $($args)*) { + Err(LexError) => Err(LexError), + Ok((i, o)) => tuple_parser!(i, (o), $($rest)*), + } + }; + + ($i:expr, ($($parsed:tt)*), $submac:ident!( $($args:tt)* ), $($rest:tt)*) => { + match $submac!($i, $($args)*) { + Err(LexError) => Err(LexError), + Ok((i, o)) => tuple_parser!(i, ($($parsed)* , o), $($rest)*), + } + }; + + ($i:expr, ($($parsed:tt),*), $e:ident) => { + tuple_parser!($i, ($($parsed),*), call!($e)) + }; + + ($i:expr, (), $submac:ident!( $($args:tt)* )) => { + $submac!($i, $($args)*) + }; + + ($i:expr, ($($parsed:expr),*), $submac:ident!( $($args:tt)* )) => { + match $submac!($i, $($args)*) { + Err(LexError) => Err(LexError), + Ok((i, o)) => Ok((i, ($($parsed),*, o))) + } + }; + + ($i:expr, ($($parsed:expr),*)) => { + Ok(($i, ($($parsed),*))) + }; +} + +macro_rules! not { + ($i:expr, $submac:ident!( $($args:tt)* )) => { + match $submac!($i, $($args)*) { + Ok((_, _)) => Err(LexError), + Err(LexError) => Ok(($i, ())), + } + }; +} + +macro_rules! tag { + ($i:expr, $tag:expr) => { + if $i.starts_with($tag) { + Ok(($i.advance($tag.len()), &$i.rest[..$tag.len()])) + } else { + Err(LexError) + } + }; +} + +macro_rules! punct { + ($i:expr, $punct:expr) => { + $crate::strnom::punct($i, $punct) + }; +} + +/// Do not use directly. Use `punct!`. +pub fn punct<'a>(input: Cursor<'a>, token: &'static str) -> PResult<'a, &'a str> { + let input = skip_whitespace(input); + if input.starts_with(token) { + Ok((input.advance(token.len()), token)) + } else { + Err(LexError) + } +} + +macro_rules! preceded { + ($i:expr, $submac:ident!( $($args:tt)* ), $submac2:ident!( $($args2:tt)* )) => { + match tuple!($i, $submac!($($args)*), $submac2!($($args2)*)) { + Ok((remaining, (_, o))) => Ok((remaining, o)), + Err(LexError) => Err(LexError), + } + }; + + ($i:expr, $submac:ident!( $($args:tt)* ), $g:expr) => { + preceded!($i, $submac!($($args)*), call!($g)) + }; +} + +macro_rules! delimited { + ($i:expr, $submac:ident!( $($args:tt)* ), $($rest:tt)+) => { + match tuple_parser!($i, (), $submac!($($args)*), $($rest)*) { + Err(LexError) => Err(LexError), + Ok((i1, (_, o, _))) => Ok((i1, o)) + } + }; +} + +macro_rules! map { + ($i:expr, $submac:ident!( $($args:tt)* ), $g:expr) => { + match $submac!($i, $($args)*) { + Err(LexError) => Err(LexError), + Ok((i, o)) => Ok((i, call!(o, $g))) + } + }; + + ($i:expr, $f:expr, $g:expr) => { + map!($i, call!($f), $g) + }; +} diff --git a/proc-macro2/src/wrapper.rs b/proc-macro2/src/wrapper.rs new file mode 100644 index 0000000..552b938 --- /dev/null +++ b/proc-macro2/src/wrapper.rs @@ -0,0 +1,927 @@ +use std::fmt; +use std::iter; +use std::ops::RangeBounds; +use std::panic::{self, PanicInfo}; +#[cfg(super_unstable)] +use std::path::PathBuf; +use std::str::FromStr; + +use crate::{fallback, Delimiter, Punct, Spacing, TokenTree}; + +#[derive(Clone)] +pub enum TokenStream { + Compiler(DeferredTokenStream), + Fallback(fallback::TokenStream), +} + +// Work around https://github.com/rust-lang/rust/issues/65080. +// In `impl Extend for TokenStream` which is used heavily by quote, +// we hold on to the appended tokens and do proc_macro::TokenStream::extend as +// late as possible to batch together consecutive uses of the Extend impl. +#[derive(Clone)] +pub struct DeferredTokenStream { + stream: proc_macro::TokenStream, + extra: Vec, +} + +pub enum LexError { + Compiler(proc_macro::LexError), + Fallback(fallback::LexError), +} + +fn nightly_works() -> bool { + use std::sync::atomic::*; + use std::sync::Once; + + static WORKS: AtomicUsize = AtomicUsize::new(0); + static INIT: Once = Once::new(); + + match WORKS.load(Ordering::SeqCst) { + 1 => return false, + 2 => return true, + _ => {} + } + + // Swap in a null panic hook to avoid printing "thread panicked" to stderr, + // then use catch_unwind to determine whether the compiler's proc_macro is + // working. When proc-macro2 is used from outside of a procedural macro all + // of the proc_macro crate's APIs currently panic. + // + // The Once is to prevent the possibility of this ordering: + // + // thread 1 calls take_hook, gets the user's original hook + // thread 1 calls set_hook with the null hook + // thread 2 calls take_hook, thinks null hook is the original hook + // thread 2 calls set_hook with the null hook + // thread 1 calls set_hook with the actual original hook + // thread 2 calls set_hook with what it thinks is the original hook + // + // in which the user's hook has been lost. + // + // There is still a race condition where a panic in a different thread can + // happen during the interval that the user's original panic hook is + // unregistered such that their hook is incorrectly not called. This is + // sufficiently unlikely and less bad than printing panic messages to stderr + // on correct use of this crate. Maybe there is a libstd feature request + // here. For now, if a user needs to guarantee that this failure mode does + // not occur, they need to call e.g. `proc_macro2::Span::call_site()` from + // the main thread before launching any other threads. + INIT.call_once(|| { + type PanicHook = dyn Fn(&PanicInfo) + Sync + Send + 'static; + + let null_hook: Box = Box::new(|_panic_info| { /* ignore */ }); + let sanity_check = &*null_hook as *const PanicHook; + let original_hook = panic::take_hook(); + panic::set_hook(null_hook); + + let works = panic::catch_unwind(|| proc_macro::Span::call_site()).is_ok(); + WORKS.store(works as usize + 1, Ordering::SeqCst); + + let hopefully_null_hook = panic::take_hook(); + panic::set_hook(original_hook); + if sanity_check != &*hopefully_null_hook { + panic!("observed race condition in proc_macro2::nightly_works"); + } + }); + nightly_works() +} + +fn mismatch() -> ! { + panic!("stable/nightly mismatch") +} + +impl DeferredTokenStream { + fn new(stream: proc_macro::TokenStream) -> Self { + DeferredTokenStream { + stream, + extra: Vec::new(), + } + } + + fn is_empty(&self) -> bool { + self.stream.is_empty() && self.extra.is_empty() + } + + fn evaluate_now(&mut self) { + self.stream.extend(self.extra.drain(..)); + } + + fn into_token_stream(mut self) -> proc_macro::TokenStream { + self.evaluate_now(); + self.stream + } +} + +impl TokenStream { + pub fn new() -> TokenStream { + if nightly_works() { + TokenStream::Compiler(DeferredTokenStream::new(proc_macro::TokenStream::new())) + } else { + TokenStream::Fallback(fallback::TokenStream::new()) + } + } + + pub fn is_empty(&self) -> bool { + match self { + TokenStream::Compiler(tts) => tts.is_empty(), + TokenStream::Fallback(tts) => tts.is_empty(), + } + } + + fn unwrap_nightly(self) -> proc_macro::TokenStream { + match self { + TokenStream::Compiler(s) => s.into_token_stream(), + TokenStream::Fallback(_) => mismatch(), + } + } + + fn unwrap_stable(self) -> fallback::TokenStream { + match self { + TokenStream::Compiler(_) => mismatch(), + TokenStream::Fallback(s) => s, + } + } +} + +impl FromStr for TokenStream { + type Err = LexError; + + fn from_str(src: &str) -> Result { + if nightly_works() { + Ok(TokenStream::Compiler(DeferredTokenStream::new( + src.parse()?, + ))) + } else { + Ok(TokenStream::Fallback(src.parse()?)) + } + } +} + +impl fmt::Display for TokenStream { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + TokenStream::Compiler(tts) => tts.clone().into_token_stream().fmt(f), + TokenStream::Fallback(tts) => tts.fmt(f), + } + } +} + +impl From for TokenStream { + fn from(inner: proc_macro::TokenStream) -> TokenStream { + TokenStream::Compiler(DeferredTokenStream::new(inner)) + } +} + +impl From for proc_macro::TokenStream { + fn from(inner: TokenStream) -> proc_macro::TokenStream { + match inner { + TokenStream::Compiler(inner) => inner.into_token_stream(), + TokenStream::Fallback(inner) => inner.to_string().parse().unwrap(), + } + } +} + +impl From for TokenStream { + fn from(inner: fallback::TokenStream) -> TokenStream { + TokenStream::Fallback(inner) + } +} + +// Assumes nightly_works(). +fn into_compiler_token(token: TokenTree) -> proc_macro::TokenTree { + match token { + TokenTree::Group(tt) => tt.inner.unwrap_nightly().into(), + TokenTree::Punct(tt) => { + let spacing = match tt.spacing() { + Spacing::Joint => proc_macro::Spacing::Joint, + Spacing::Alone => proc_macro::Spacing::Alone, + }; + let mut op = proc_macro::Punct::new(tt.as_char(), spacing); + op.set_span(tt.span().inner.unwrap_nightly()); + op.into() + } + TokenTree::Ident(tt) => tt.inner.unwrap_nightly().into(), + TokenTree::Literal(tt) => tt.inner.unwrap_nightly().into(), + } +} + +impl From for TokenStream { + fn from(token: TokenTree) -> TokenStream { + if nightly_works() { + TokenStream::Compiler(DeferredTokenStream::new(into_compiler_token(token).into())) + } else { + TokenStream::Fallback(token.into()) + } + } +} + +impl iter::FromIterator for TokenStream { + fn from_iter>(trees: I) -> Self { + if nightly_works() { + TokenStream::Compiler(DeferredTokenStream::new( + trees.into_iter().map(into_compiler_token).collect(), + )) + } else { + TokenStream::Fallback(trees.into_iter().collect()) + } + } +} + +impl iter::FromIterator for TokenStream { + fn from_iter>(streams: I) -> Self { + let mut streams = streams.into_iter(); + match streams.next() { + Some(TokenStream::Compiler(mut first)) => { + first.evaluate_now(); + first.stream.extend(streams.map(|s| match s { + TokenStream::Compiler(s) => s.into_token_stream(), + TokenStream::Fallback(_) => mismatch(), + })); + TokenStream::Compiler(first) + } + Some(TokenStream::Fallback(mut first)) => { + first.extend(streams.map(|s| match s { + TokenStream::Fallback(s) => s, + TokenStream::Compiler(_) => mismatch(), + })); + TokenStream::Fallback(first) + } + None => TokenStream::new(), + } + } +} + +impl Extend for TokenStream { + fn extend>(&mut self, streams: I) { + match self { + TokenStream::Compiler(tts) => { + // Here is the reason for DeferredTokenStream. + tts.extra + .extend(streams.into_iter().map(into_compiler_token)); + } + TokenStream::Fallback(tts) => tts.extend(streams), + } + } +} + +impl Extend for TokenStream { + fn extend>(&mut self, streams: I) { + match self { + TokenStream::Compiler(tts) => { + tts.evaluate_now(); + tts.stream + .extend(streams.into_iter().map(|stream| stream.unwrap_nightly())); + } + TokenStream::Fallback(tts) => { + tts.extend(streams.into_iter().map(|stream| stream.unwrap_stable())); + } + } + } +} + +impl fmt::Debug for TokenStream { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + TokenStream::Compiler(tts) => tts.clone().into_token_stream().fmt(f), + TokenStream::Fallback(tts) => tts.fmt(f), + } + } +} + +impl From for LexError { + fn from(e: proc_macro::LexError) -> LexError { + LexError::Compiler(e) + } +} + +impl From for LexError { + fn from(e: fallback::LexError) -> LexError { + LexError::Fallback(e) + } +} + +impl fmt::Debug for LexError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + LexError::Compiler(e) => e.fmt(f), + LexError::Fallback(e) => e.fmt(f), + } + } +} + +#[derive(Clone)] +pub enum TokenTreeIter { + Compiler(proc_macro::token_stream::IntoIter), + Fallback(fallback::TokenTreeIter), +} + +impl IntoIterator for TokenStream { + type Item = TokenTree; + type IntoIter = TokenTreeIter; + + fn into_iter(self) -> TokenTreeIter { + match self { + TokenStream::Compiler(tts) => { + TokenTreeIter::Compiler(tts.into_token_stream().into_iter()) + } + TokenStream::Fallback(tts) => TokenTreeIter::Fallback(tts.into_iter()), + } + } +} + +impl Iterator for TokenTreeIter { + type Item = TokenTree; + + fn next(&mut self) -> Option { + let token = match self { + TokenTreeIter::Compiler(iter) => iter.next()?, + TokenTreeIter::Fallback(iter) => return iter.next(), + }; + Some(match token { + proc_macro::TokenTree::Group(tt) => crate::Group::_new(Group::Compiler(tt)).into(), + proc_macro::TokenTree::Punct(tt) => { + let spacing = match tt.spacing() { + proc_macro::Spacing::Joint => Spacing::Joint, + proc_macro::Spacing::Alone => Spacing::Alone, + }; + let mut o = Punct::new(tt.as_char(), spacing); + o.set_span(crate::Span::_new(Span::Compiler(tt.span()))); + o.into() + } + proc_macro::TokenTree::Ident(s) => crate::Ident::_new(Ident::Compiler(s)).into(), + proc_macro::TokenTree::Literal(l) => crate::Literal::_new(Literal::Compiler(l)).into(), + }) + } + + fn size_hint(&self) -> (usize, Option) { + match self { + TokenTreeIter::Compiler(tts) => tts.size_hint(), + TokenTreeIter::Fallback(tts) => tts.size_hint(), + } + } +} + +impl fmt::Debug for TokenTreeIter { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("TokenTreeIter").finish() + } +} + +#[derive(Clone, PartialEq, Eq)] +#[cfg(super_unstable)] +pub enum SourceFile { + Compiler(proc_macro::SourceFile), + Fallback(fallback::SourceFile), +} + +#[cfg(super_unstable)] +impl SourceFile { + fn nightly(sf: proc_macro::SourceFile) -> Self { + SourceFile::Compiler(sf) + } + + /// Get the path to this source file as a string. + pub fn path(&self) -> PathBuf { + match self { + SourceFile::Compiler(a) => a.path(), + SourceFile::Fallback(a) => a.path(), + } + } + + pub fn is_real(&self) -> bool { + match self { + SourceFile::Compiler(a) => a.is_real(), + SourceFile::Fallback(a) => a.is_real(), + } + } +} + +#[cfg(super_unstable)] +impl fmt::Debug for SourceFile { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + SourceFile::Compiler(a) => a.fmt(f), + SourceFile::Fallback(a) => a.fmt(f), + } + } +} + +#[cfg(any(super_unstable, feature = "span-locations"))] +pub struct LineColumn { + pub line: usize, + pub column: usize, +} + +#[derive(Copy, Clone)] +pub enum Span { + Compiler(proc_macro::Span), + Fallback(fallback::Span), +} + +impl Span { + pub fn call_site() -> Span { + if nightly_works() { + Span::Compiler(proc_macro::Span::call_site()) + } else { + Span::Fallback(fallback::Span::call_site()) + } + } + + #[cfg(super_unstable)] + pub fn def_site() -> Span { + if nightly_works() { + Span::Compiler(proc_macro::Span::def_site()) + } else { + Span::Fallback(fallback::Span::def_site()) + } + } + + #[cfg(super_unstable)] + pub fn resolved_at(&self, other: Span) -> Span { + match (self, other) { + (Span::Compiler(a), Span::Compiler(b)) => Span::Compiler(a.resolved_at(b)), + (Span::Fallback(a), Span::Fallback(b)) => Span::Fallback(a.resolved_at(b)), + _ => mismatch(), + } + } + + #[cfg(super_unstable)] + pub fn located_at(&self, other: Span) -> Span { + match (self, other) { + (Span::Compiler(a), Span::Compiler(b)) => Span::Compiler(a.located_at(b)), + (Span::Fallback(a), Span::Fallback(b)) => Span::Fallback(a.located_at(b)), + _ => mismatch(), + } + } + + pub fn unwrap(self) -> proc_macro::Span { + match self { + Span::Compiler(s) => s, + Span::Fallback(_) => panic!("proc_macro::Span is only available in procedural macros"), + } + } + + #[cfg(super_unstable)] + pub fn source_file(&self) -> SourceFile { + match self { + Span::Compiler(s) => SourceFile::nightly(s.source_file()), + Span::Fallback(s) => SourceFile::Fallback(s.source_file()), + } + } + + #[cfg(any(super_unstable, feature = "span-locations"))] + pub fn start(&self) -> LineColumn { + match self { + #[cfg(proc_macro_span)] + Span::Compiler(s) => { + let proc_macro::LineColumn { line, column } = s.start(); + LineColumn { line, column } + } + #[cfg(not(proc_macro_span))] + Span::Compiler(_) => LineColumn { line: 0, column: 0 }, + Span::Fallback(s) => { + let fallback::LineColumn { line, column } = s.start(); + LineColumn { line, column } + } + } + } + + #[cfg(any(super_unstable, feature = "span-locations"))] + pub fn end(&self) -> LineColumn { + match self { + #[cfg(proc_macro_span)] + Span::Compiler(s) => { + let proc_macro::LineColumn { line, column } = s.end(); + LineColumn { line, column } + } + #[cfg(not(proc_macro_span))] + Span::Compiler(_) => LineColumn { line: 0, column: 0 }, + Span::Fallback(s) => { + let fallback::LineColumn { line, column } = s.end(); + LineColumn { line, column } + } + } + } + + pub fn join(&self, other: Span) -> Option { + let ret = match (self, other) { + #[cfg(proc_macro_span)] + (Span::Compiler(a), Span::Compiler(b)) => Span::Compiler(a.join(b)?), + (Span::Fallback(a), Span::Fallback(b)) => Span::Fallback(a.join(b)?), + _ => return None, + }; + Some(ret) + } + + #[cfg(super_unstable)] + pub fn eq(&self, other: &Span) -> bool { + match (self, other) { + (Span::Compiler(a), Span::Compiler(b)) => a.eq(b), + (Span::Fallback(a), Span::Fallback(b)) => a.eq(b), + _ => false, + } + } + + fn unwrap_nightly(self) -> proc_macro::Span { + match self { + Span::Compiler(s) => s, + Span::Fallback(_) => mismatch(), + } + } +} + +impl From for crate::Span { + fn from(proc_span: proc_macro::Span) -> crate::Span { + crate::Span::_new(Span::Compiler(proc_span)) + } +} + +impl From for Span { + fn from(inner: fallback::Span) -> Span { + Span::Fallback(inner) + } +} + +impl fmt::Debug for Span { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Span::Compiler(s) => s.fmt(f), + Span::Fallback(s) => s.fmt(f), + } + } +} + +pub fn debug_span_field_if_nontrivial(debug: &mut fmt::DebugStruct, span: Span) { + match span { + Span::Compiler(s) => { + debug.field("span", &s); + } + Span::Fallback(s) => fallback::debug_span_field_if_nontrivial(debug, s), + } +} + +#[derive(Clone)] +pub enum Group { + Compiler(proc_macro::Group), + Fallback(fallback::Group), +} + +impl Group { + pub fn new(delimiter: Delimiter, stream: TokenStream) -> Group { + match stream { + TokenStream::Compiler(tts) => { + let delimiter = match delimiter { + Delimiter::Parenthesis => proc_macro::Delimiter::Parenthesis, + Delimiter::Bracket => proc_macro::Delimiter::Bracket, + Delimiter::Brace => proc_macro::Delimiter::Brace, + Delimiter::None => proc_macro::Delimiter::None, + }; + Group::Compiler(proc_macro::Group::new(delimiter, tts.into_token_stream())) + } + TokenStream::Fallback(stream) => { + Group::Fallback(fallback::Group::new(delimiter, stream)) + } + } + } + + pub fn delimiter(&self) -> Delimiter { + match self { + Group::Compiler(g) => match g.delimiter() { + proc_macro::Delimiter::Parenthesis => Delimiter::Parenthesis, + proc_macro::Delimiter::Bracket => Delimiter::Bracket, + proc_macro::Delimiter::Brace => Delimiter::Brace, + proc_macro::Delimiter::None => Delimiter::None, + }, + Group::Fallback(g) => g.delimiter(), + } + } + + pub fn stream(&self) -> TokenStream { + match self { + Group::Compiler(g) => TokenStream::Compiler(DeferredTokenStream::new(g.stream())), + Group::Fallback(g) => TokenStream::Fallback(g.stream()), + } + } + + pub fn span(&self) -> Span { + match self { + Group::Compiler(g) => Span::Compiler(g.span()), + Group::Fallback(g) => Span::Fallback(g.span()), + } + } + + pub fn span_open(&self) -> Span { + match self { + #[cfg(proc_macro_span)] + Group::Compiler(g) => Span::Compiler(g.span_open()), + #[cfg(not(proc_macro_span))] + Group::Compiler(g) => Span::Compiler(g.span()), + Group::Fallback(g) => Span::Fallback(g.span_open()), + } + } + + pub fn span_close(&self) -> Span { + match self { + #[cfg(proc_macro_span)] + Group::Compiler(g) => Span::Compiler(g.span_close()), + #[cfg(not(proc_macro_span))] + Group::Compiler(g) => Span::Compiler(g.span()), + Group::Fallback(g) => Span::Fallback(g.span_close()), + } + } + + pub fn set_span(&mut self, span: Span) { + match (self, span) { + (Group::Compiler(g), Span::Compiler(s)) => g.set_span(s), + (Group::Fallback(g), Span::Fallback(s)) => g.set_span(s), + _ => mismatch(), + } + } + + fn unwrap_nightly(self) -> proc_macro::Group { + match self { + Group::Compiler(g) => g, + Group::Fallback(_) => mismatch(), + } + } +} + +impl From for Group { + fn from(g: fallback::Group) -> Self { + Group::Fallback(g) + } +} + +impl fmt::Display for Group { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + match self { + Group::Compiler(group) => group.fmt(formatter), + Group::Fallback(group) => group.fmt(formatter), + } + } +} + +impl fmt::Debug for Group { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + match self { + Group::Compiler(group) => group.fmt(formatter), + Group::Fallback(group) => group.fmt(formatter), + } + } +} + +#[derive(Clone)] +pub enum Ident { + Compiler(proc_macro::Ident), + Fallback(fallback::Ident), +} + +impl Ident { + pub fn new(string: &str, span: Span) -> Ident { + match span { + Span::Compiler(s) => Ident::Compiler(proc_macro::Ident::new(string, s)), + Span::Fallback(s) => Ident::Fallback(fallback::Ident::new(string, s)), + } + } + + pub fn new_raw(string: &str, span: Span) -> Ident { + match span { + Span::Compiler(s) => { + let p: proc_macro::TokenStream = string.parse().unwrap(); + let ident = match p.into_iter().next() { + Some(proc_macro::TokenTree::Ident(mut i)) => { + i.set_span(s); + i + } + _ => panic!(), + }; + Ident::Compiler(ident) + } + Span::Fallback(s) => Ident::Fallback(fallback::Ident::new_raw(string, s)), + } + } + + pub fn span(&self) -> Span { + match self { + Ident::Compiler(t) => Span::Compiler(t.span()), + Ident::Fallback(t) => Span::Fallback(t.span()), + } + } + + pub fn set_span(&mut self, span: Span) { + match (self, span) { + (Ident::Compiler(t), Span::Compiler(s)) => t.set_span(s), + (Ident::Fallback(t), Span::Fallback(s)) => t.set_span(s), + _ => mismatch(), + } + } + + fn unwrap_nightly(self) -> proc_macro::Ident { + match self { + Ident::Compiler(s) => s, + Ident::Fallback(_) => mismatch(), + } + } +} + +impl PartialEq for Ident { + fn eq(&self, other: &Ident) -> bool { + match (self, other) { + (Ident::Compiler(t), Ident::Compiler(o)) => t.to_string() == o.to_string(), + (Ident::Fallback(t), Ident::Fallback(o)) => t == o, + _ => mismatch(), + } + } +} + +impl PartialEq for Ident +where + T: ?Sized + AsRef, +{ + fn eq(&self, other: &T) -> bool { + let other = other.as_ref(); + match self { + Ident::Compiler(t) => t.to_string() == other, + Ident::Fallback(t) => t == other, + } + } +} + +impl fmt::Display for Ident { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Ident::Compiler(t) => t.fmt(f), + Ident::Fallback(t) => t.fmt(f), + } + } +} + +impl fmt::Debug for Ident { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Ident::Compiler(t) => t.fmt(f), + Ident::Fallback(t) => t.fmt(f), + } + } +} + +#[derive(Clone)] +pub enum Literal { + Compiler(proc_macro::Literal), + Fallback(fallback::Literal), +} + +macro_rules! suffixed_numbers { + ($($name:ident => $kind:ident,)*) => ($( + pub fn $name(n: $kind) -> Literal { + if nightly_works() { + Literal::Compiler(proc_macro::Literal::$name(n)) + } else { + Literal::Fallback(fallback::Literal::$name(n)) + } + } + )*) +} + +macro_rules! unsuffixed_integers { + ($($name:ident => $kind:ident,)*) => ($( + pub fn $name(n: $kind) -> Literal { + if nightly_works() { + Literal::Compiler(proc_macro::Literal::$name(n)) + } else { + Literal::Fallback(fallback::Literal::$name(n)) + } + } + )*) +} + +impl Literal { + suffixed_numbers! { + u8_suffixed => u8, + u16_suffixed => u16, + u32_suffixed => u32, + u64_suffixed => u64, + u128_suffixed => u128, + usize_suffixed => usize, + i8_suffixed => i8, + i16_suffixed => i16, + i32_suffixed => i32, + i64_suffixed => i64, + i128_suffixed => i128, + isize_suffixed => isize, + + f32_suffixed => f32, + f64_suffixed => f64, + } + + unsuffixed_integers! { + u8_unsuffixed => u8, + u16_unsuffixed => u16, + u32_unsuffixed => u32, + u64_unsuffixed => u64, + u128_unsuffixed => u128, + usize_unsuffixed => usize, + i8_unsuffixed => i8, + i16_unsuffixed => i16, + i32_unsuffixed => i32, + i64_unsuffixed => i64, + i128_unsuffixed => i128, + isize_unsuffixed => isize, + } + + pub fn f32_unsuffixed(f: f32) -> Literal { + if nightly_works() { + Literal::Compiler(proc_macro::Literal::f32_unsuffixed(f)) + } else { + Literal::Fallback(fallback::Literal::f32_unsuffixed(f)) + } + } + + pub fn f64_unsuffixed(f: f64) -> Literal { + if nightly_works() { + Literal::Compiler(proc_macro::Literal::f64_unsuffixed(f)) + } else { + Literal::Fallback(fallback::Literal::f64_unsuffixed(f)) + } + } + + pub fn string(t: &str) -> Literal { + if nightly_works() { + Literal::Compiler(proc_macro::Literal::string(t)) + } else { + Literal::Fallback(fallback::Literal::string(t)) + } + } + + pub fn character(t: char) -> Literal { + if nightly_works() { + Literal::Compiler(proc_macro::Literal::character(t)) + } else { + Literal::Fallback(fallback::Literal::character(t)) + } + } + + pub fn byte_string(bytes: &[u8]) -> Literal { + if nightly_works() { + Literal::Compiler(proc_macro::Literal::byte_string(bytes)) + } else { + Literal::Fallback(fallback::Literal::byte_string(bytes)) + } + } + + pub fn span(&self) -> Span { + match self { + Literal::Compiler(lit) => Span::Compiler(lit.span()), + Literal::Fallback(lit) => Span::Fallback(lit.span()), + } + } + + pub fn set_span(&mut self, span: Span) { + match (self, span) { + (Literal::Compiler(lit), Span::Compiler(s)) => lit.set_span(s), + (Literal::Fallback(lit), Span::Fallback(s)) => lit.set_span(s), + _ => mismatch(), + } + } + + pub fn subspan>(&self, range: R) -> Option { + match self { + #[cfg(proc_macro_span)] + Literal::Compiler(lit) => lit.subspan(range).map(Span::Compiler), + #[cfg(not(proc_macro_span))] + Literal::Compiler(_lit) => None, + Literal::Fallback(lit) => lit.subspan(range).map(Span::Fallback), + } + } + + fn unwrap_nightly(self) -> proc_macro::Literal { + match self { + Literal::Compiler(s) => s, + Literal::Fallback(_) => mismatch(), + } + } +} + +impl From for Literal { + fn from(s: fallback::Literal) -> Literal { + Literal::Fallback(s) + } +} + +impl fmt::Display for Literal { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Literal::Compiler(t) => t.fmt(f), + Literal::Fallback(t) => t.fmt(f), + } + } +} + +impl fmt::Debug for Literal { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Literal::Compiler(t) => t.fmt(f), + Literal::Fallback(t) => t.fmt(f), + } + } +} diff --git a/proc-macro2/tests/features.rs b/proc-macro2/tests/features.rs new file mode 100644 index 0000000..073f6e6 --- /dev/null +++ b/proc-macro2/tests/features.rs @@ -0,0 +1,8 @@ +#[test] +#[ignore] +fn make_sure_no_proc_macro() { + assert!( + !cfg!(feature = "proc-macro"), + "still compiled with proc_macro?" + ); +} diff --git a/proc-macro2/tests/marker.rs b/proc-macro2/tests/marker.rs new file mode 100644 index 0000000..7af2539 --- /dev/null +++ b/proc-macro2/tests/marker.rs @@ -0,0 +1,59 @@ +use proc_macro2::*; + +macro_rules! assert_impl { + ($ty:ident is $($marker:ident) and +) => { + #[test] + #[allow(non_snake_case)] + fn $ty() { + fn assert_implemented() {} + assert_implemented::<$ty>(); + } + }; + + ($ty:ident is not $($marker:ident) or +) => { + #[test] + #[allow(non_snake_case)] + fn $ty() { + $( + { + // Implemented for types that implement $marker. + trait IsNotImplemented { + fn assert_not_implemented() {} + } + impl IsNotImplemented for T {} + + // Implemented for the type being tested. + trait IsImplemented { + fn assert_not_implemented() {} + } + impl IsImplemented for $ty {} + + // If $ty does not implement $marker, there is no ambiguity + // in the following trait method call. + <$ty>::assert_not_implemented(); + } + )+ + } + }; +} + +assert_impl!(Delimiter is Send and Sync); +assert_impl!(Spacing is Send and Sync); + +assert_impl!(Group is not Send or Sync); +assert_impl!(Ident is not Send or Sync); +assert_impl!(LexError is not Send or Sync); +assert_impl!(Literal is not Send or Sync); +assert_impl!(Punct is not Send or Sync); +assert_impl!(Span is not Send or Sync); +assert_impl!(TokenStream is not Send or Sync); +assert_impl!(TokenTree is not Send or Sync); + +#[cfg(procmacro2_semver_exempt)] +mod semver_exempt { + use super::*; + + assert_impl!(LineColumn is Send and Sync); + + assert_impl!(SourceFile is not Send or Sync); +} diff --git a/proc-macro2/tests/test.rs b/proc-macro2/tests/test.rs new file mode 100644 index 0000000..7528388 --- /dev/null +++ b/proc-macro2/tests/test.rs @@ -0,0 +1,466 @@ +use std::str::{self, FromStr}; + +use proc_macro2::{Ident, Literal, Spacing, Span, TokenStream, TokenTree}; + +#[test] +fn idents() { + assert_eq!( + Ident::new("String", Span::call_site()).to_string(), + "String" + ); + assert_eq!(Ident::new("fn", Span::call_site()).to_string(), "fn"); + assert_eq!(Ident::new("_", Span::call_site()).to_string(), "_"); +} + +#[test] +#[cfg(procmacro2_semver_exempt)] +fn raw_idents() { + assert_eq!( + Ident::new_raw("String", Span::call_site()).to_string(), + "r#String" + ); + assert_eq!(Ident::new_raw("fn", Span::call_site()).to_string(), "r#fn"); + assert_eq!(Ident::new_raw("_", Span::call_site()).to_string(), "r#_"); +} + +#[test] +#[should_panic(expected = "Ident is not allowed to be empty; use Option")] +fn ident_empty() { + Ident::new("", Span::call_site()); +} + +#[test] +#[should_panic(expected = "Ident cannot be a number; use Literal instead")] +fn ident_number() { + Ident::new("255", Span::call_site()); +} + +#[test] +#[should_panic(expected = "\"a#\" is not a valid Ident")] +fn ident_invalid() { + Ident::new("a#", Span::call_site()); +} + +#[test] +#[should_panic(expected = "not a valid Ident")] +fn raw_ident_empty() { + Ident::new("r#", Span::call_site()); +} + +#[test] +#[should_panic(expected = "not a valid Ident")] +fn raw_ident_number() { + Ident::new("r#255", Span::call_site()); +} + +#[test] +#[should_panic(expected = "\"r#a#\" is not a valid Ident")] +fn raw_ident_invalid() { + Ident::new("r#a#", Span::call_site()); +} + +#[test] +#[should_panic(expected = "not a valid Ident")] +fn lifetime_empty() { + Ident::new("'", Span::call_site()); +} + +#[test] +#[should_panic(expected = "not a valid Ident")] +fn lifetime_number() { + Ident::new("'255", Span::call_site()); +} + +#[test] +#[should_panic(expected = r#""\'a#" is not a valid Ident"#)] +fn lifetime_invalid() { + Ident::new("'a#", Span::call_site()); +} + +#[test] +fn literal_string() { + assert_eq!(Literal::string("foo").to_string(), "\"foo\""); + assert_eq!(Literal::string("\"").to_string(), "\"\\\"\""); + assert_eq!(Literal::string("didn't").to_string(), "\"didn't\""); +} + +#[test] +fn literal_character() { + assert_eq!(Literal::character('x').to_string(), "'x'"); + assert_eq!(Literal::character('\'').to_string(), "'\\''"); + assert_eq!(Literal::character('"').to_string(), "'\"'"); +} + +#[test] +fn literal_float() { + assert_eq!(Literal::f32_unsuffixed(10.0).to_string(), "10.0"); +} + +#[test] +fn literal_suffix() { + fn token_count(p: &str) -> usize { + p.parse::().unwrap().into_iter().count() + } + + assert_eq!(token_count("999u256"), 1); + assert_eq!(token_count("999r#u256"), 3); + assert_eq!(token_count("1."), 1); + assert_eq!(token_count("1.f32"), 3); + assert_eq!(token_count("1.0_0"), 1); + assert_eq!(token_count("1._0"), 3); + assert_eq!(token_count("1._m"), 3); + assert_eq!(token_count("\"\"s"), 1); +} + +#[test] +fn roundtrip() { + fn roundtrip(p: &str) { + println!("parse: {}", p); + let s = p.parse::().unwrap().to_string(); + println!("first: {}", s); + let s2 = s.to_string().parse::().unwrap().to_string(); + assert_eq!(s, s2); + } + roundtrip("a"); + roundtrip("<<"); + roundtrip("<<="); + roundtrip( + " + 1 + 1.0 + 1f32 + 2f64 + 1usize + 4isize + 4e10 + 1_000 + 1_0i32 + 8u8 + 9 + 0 + 0xffffffffffffffffffffffffffffffff + 1x + 1u80 + 1f320 + ", + ); + roundtrip("'a"); + roundtrip("'_"); + roundtrip("'static"); + roundtrip("'\\u{10__FFFF}'"); + roundtrip("\"\\u{10_F0FF__}foo\\u{1_0_0_0__}\""); +} + +#[test] +fn fail() { + fn fail(p: &str) { + if let Ok(s) = p.parse::() { + panic!("should have failed to parse: {}\n{:#?}", p, s); + } + } + fail("' static"); + fail("r#1"); + fail("r#_"); +} + +#[cfg(span_locations)] +#[test] +fn span_test() { + use proc_macro2::TokenTree; + + fn check_spans(p: &str, mut lines: &[(usize, usize, usize, usize)]) { + let ts = p.parse::().unwrap(); + check_spans_internal(ts, &mut lines); + } + + fn check_spans_internal(ts: TokenStream, lines: &mut &[(usize, usize, usize, usize)]) { + for i in ts { + if let Some((&(sline, scol, eline, ecol), rest)) = lines.split_first() { + *lines = rest; + + let start = i.span().start(); + assert_eq!(start.line, sline, "sline did not match for {}", i); + assert_eq!(start.column, scol, "scol did not match for {}", i); + + let end = i.span().end(); + assert_eq!(end.line, eline, "eline did not match for {}", i); + assert_eq!(end.column, ecol, "ecol did not match for {}", i); + + match i { + TokenTree::Group(ref g) => { + check_spans_internal(g.stream().clone(), lines); + } + _ => {} + } + } + } + } + + check_spans( + "\ +/// This is a document comment +testing 123 +{ + testing 234 +}", + &[ + (1, 0, 1, 30), // # + (1, 0, 1, 30), // [ ... ] + (1, 0, 1, 30), // doc + (1, 0, 1, 30), // = + (1, 0, 1, 30), // "This is..." + (2, 0, 2, 7), // testing + (2, 8, 2, 11), // 123 + (3, 0, 5, 1), // { ... } + (4, 2, 4, 9), // testing + (4, 10, 4, 13), // 234 + ], + ); +} + +#[cfg(procmacro2_semver_exempt)] +#[cfg(not(nightly))] +#[test] +fn default_span() { + let start = Span::call_site().start(); + assert_eq!(start.line, 1); + assert_eq!(start.column, 0); + let end = Span::call_site().end(); + assert_eq!(end.line, 1); + assert_eq!(end.column, 0); + let source_file = Span::call_site().source_file(); + assert_eq!(source_file.path().to_string_lossy(), ""); + assert!(!source_file.is_real()); +} + +#[cfg(procmacro2_semver_exempt)] +#[test] +fn span_join() { + let source1 = "aaa\nbbb" + .parse::() + .unwrap() + .into_iter() + .collect::>(); + let source2 = "ccc\nddd" + .parse::() + .unwrap() + .into_iter() + .collect::>(); + + assert!(source1[0].span().source_file() != source2[0].span().source_file()); + assert_eq!( + source1[0].span().source_file(), + source1[1].span().source_file() + ); + + let joined1 = source1[0].span().join(source1[1].span()); + let joined2 = source1[0].span().join(source2[0].span()); + assert!(joined1.is_some()); + assert!(joined2.is_none()); + + let start = joined1.unwrap().start(); + let end = joined1.unwrap().end(); + assert_eq!(start.line, 1); + assert_eq!(start.column, 0); + assert_eq!(end.line, 2); + assert_eq!(end.column, 3); + + assert_eq!( + joined1.unwrap().source_file(), + source1[0].span().source_file() + ); +} + +#[test] +fn no_panic() { + let s = str::from_utf8(b"b\'\xc2\x86 \x00\x00\x00^\"").unwrap(); + assert!(s.parse::().is_err()); +} + +#[test] +fn tricky_doc_comment() { + let stream = "/**/".parse::().unwrap(); + let tokens = stream.into_iter().collect::>(); + assert!(tokens.is_empty(), "not empty -- {:?}", tokens); + + let stream = "/// doc".parse::().unwrap(); + let tokens = stream.into_iter().collect::>(); + assert!(tokens.len() == 2, "not length 2 -- {:?}", tokens); + match tokens[0] { + proc_macro2::TokenTree::Punct(ref tt) => assert_eq!(tt.as_char(), '#'), + _ => panic!("wrong token {:?}", tokens[0]), + } + let mut tokens = match tokens[1] { + proc_macro2::TokenTree::Group(ref tt) => { + assert_eq!(tt.delimiter(), proc_macro2::Delimiter::Bracket); + tt.stream().into_iter() + } + _ => panic!("wrong token {:?}", tokens[0]), + }; + + match tokens.next().unwrap() { + proc_macro2::TokenTree::Ident(ref tt) => assert_eq!(tt.to_string(), "doc"), + t => panic!("wrong token {:?}", t), + } + match tokens.next().unwrap() { + proc_macro2::TokenTree::Punct(ref tt) => assert_eq!(tt.as_char(), '='), + t => panic!("wrong token {:?}", t), + } + match tokens.next().unwrap() { + proc_macro2::TokenTree::Literal(ref tt) => { + assert_eq!(tt.to_string(), "\" doc\""); + } + t => panic!("wrong token {:?}", t), + } + assert!(tokens.next().is_none()); + + let stream = "//! doc".parse::().unwrap(); + let tokens = stream.into_iter().collect::>(); + assert!(tokens.len() == 3, "not length 3 -- {:?}", tokens); +} + +#[test] +fn op_before_comment() { + let mut tts = TokenStream::from_str("~// comment").unwrap().into_iter(); + match tts.next().unwrap() { + TokenTree::Punct(tt) => { + assert_eq!(tt.as_char(), '~'); + assert_eq!(tt.spacing(), Spacing::Alone); + } + wrong => panic!("wrong token {:?}", wrong), + } +} + +#[test] +fn raw_identifier() { + let mut tts = TokenStream::from_str("r#dyn").unwrap().into_iter(); + match tts.next().unwrap() { + TokenTree::Ident(raw) => assert_eq!("r#dyn", raw.to_string()), + wrong => panic!("wrong token {:?}", wrong), + } + assert!(tts.next().is_none()); +} + +#[test] +fn test_debug_ident() { + let ident = Ident::new("proc_macro", Span::call_site()); + + #[cfg(not(procmacro2_semver_exempt))] + let expected = "Ident(proc_macro)"; + + #[cfg(procmacro2_semver_exempt)] + let expected = "Ident { sym: proc_macro, span: bytes(0..0) }"; + + assert_eq!(expected, format!("{:?}", ident)); +} + +#[test] +fn test_debug_tokenstream() { + let tts = TokenStream::from_str("[a + 1]").unwrap(); + + #[cfg(not(procmacro2_semver_exempt))] + let expected = "\ +TokenStream [ + Group { + delimiter: Bracket, + stream: TokenStream [ + Ident { + sym: a, + }, + Punct { + op: '+', + spacing: Alone, + }, + Literal { + lit: 1, + }, + ], + }, +]\ + "; + + #[cfg(not(procmacro2_semver_exempt))] + let expected_before_trailing_commas = "\ +TokenStream [ + Group { + delimiter: Bracket, + stream: TokenStream [ + Ident { + sym: a + }, + Punct { + op: '+', + spacing: Alone + }, + Literal { + lit: 1 + } + ] + } +]\ + "; + + #[cfg(procmacro2_semver_exempt)] + let expected = "\ +TokenStream [ + Group { + delimiter: Bracket, + stream: TokenStream [ + Ident { + sym: a, + span: bytes(2..3), + }, + Punct { + op: '+', + spacing: Alone, + span: bytes(4..5), + }, + Literal { + lit: 1, + span: bytes(6..7), + }, + ], + span: bytes(1..8), + }, +]\ + "; + + #[cfg(procmacro2_semver_exempt)] + let expected_before_trailing_commas = "\ +TokenStream [ + Group { + delimiter: Bracket, + stream: TokenStream [ + Ident { + sym: a, + span: bytes(2..3) + }, + Punct { + op: '+', + spacing: Alone, + span: bytes(4..5) + }, + Literal { + lit: 1, + span: bytes(6..7) + } + ], + span: bytes(1..8) + } +]\ + "; + + let actual = format!("{:#?}", tts); + if actual.ends_with(",\n]") { + assert_eq!(expected, actual); + } else { + assert_eq!(expected_before_trailing_commas, actual); + } +} + +#[test] +fn default_tokenstream_is_empty() { + let default_token_stream: TokenStream = Default::default(); + + assert!(default_token_stream.is_empty()); +} diff --git a/quote/.gitignore b/quote/.gitignore new file mode 100644 index 0000000..4fffb2f --- /dev/null +++ b/quote/.gitignore @@ -0,0 +1,2 @@ +/target +/Cargo.lock diff --git a/quote/.travis.yml b/quote/.travis.yml new file mode 100644 index 0000000..7182d9b --- /dev/null +++ b/quote/.travis.yml @@ -0,0 +1,18 @@ +sudo: false + +language: rust + +rust: + - stable + - 1.31.0 + - beta + +script: + - cargo test + +matrix: + include: + - rust: nightly + script: + - cargo test + - cargo update -Z minimal-versions && cargo build diff --git a/quote/Cargo.toml b/quote/Cargo.toml new file mode 100644 index 0000000..c052022 --- /dev/null +++ b/quote/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "quote" +version = "1.0.2" # don't forget to update html_root_url, version in readme for breaking changes +authors = ["David Tolnay "] +license = "MIT OR Apache-2.0" +description = "Quasi-quoting macro quote!(...)" +repository = "https://github.com/dtolnay/quote" +documentation = "https://docs.rs/quote/" +keywords = ["syn"] +categories = ["development-tools::procedural-macro-helpers"] +readme = "README.md" +include = ["Cargo.toml", "src/**/*.rs", "tests/**/*.rs", "README.md", "LICENSE-APACHE", "LICENSE-MIT"] +edition = "2018" + +[lib] +name = "quote" + +[dependencies] +proc-macro2 = { version = "1.0", default-features = false } + +[dev-dependencies] +rustversion = "0.1" +trybuild = "1.0" + +[features] +default = ["proc-macro"] +# Disabling the proc-macro feature removes the dynamic library dependency on +# libproc_macro in the rustc compiler. +proc-macro = ["proc-macro2/proc-macro"] + +[badges] +travis-ci = { repository = "dtolnay/quote" } diff --git a/quote/LICENSE-APACHE b/quote/LICENSE-APACHE new file mode 100644 index 0000000..16fe87b --- /dev/null +++ b/quote/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/quote/LICENSE-MIT b/quote/LICENSE-MIT new file mode 100644 index 0000000..40b8817 --- /dev/null +++ b/quote/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2016 The Rust Project Developers + +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/quote/README.md b/quote/README.md new file mode 100644 index 0000000..7c7f743 --- /dev/null +++ b/quote/README.md @@ -0,0 +1,237 @@ +Rust Quasi-Quoting +================== + +[![Build Status](https://api.travis-ci.org/dtolnay/quote.svg?branch=master)](https://travis-ci.org/dtolnay/quote) +[![Latest Version](https://img.shields.io/crates/v/quote.svg)](https://crates.io/crates/quote) +[![Rust Documentation](https://img.shields.io/badge/api-rustdoc-blue.svg)](https://docs.rs/quote/) + +This crate provides the [`quote!`] macro for turning Rust syntax tree data +structures into tokens of source code. + +[`quote!`]: https://docs.rs/quote/1.0/quote/macro.quote.html + +Procedural macros in Rust receive a stream of tokens as input, execute arbitrary +Rust code to determine how to manipulate those tokens, and produce a stream of +tokens to hand back to the compiler to compile into the caller's crate. +Quasi-quoting is a solution to one piece of that — producing tokens to +return to the compiler. + +The idea of quasi-quoting is that we write *code* that we treat as *data*. +Within the `quote!` macro, we can write what looks like code to our text editor +or IDE. We get all the benefits of the editor's brace matching, syntax +highlighting, indentation, and maybe autocompletion. But rather than compiling +that as code into the current crate, we can treat it as data, pass it around, +mutate it, and eventually hand it back to the compiler as tokens to compile into +the macro caller's crate. + +This crate is motivated by the procedural macro use case, but is a +general-purpose Rust quasi-quoting library and is not specific to procedural +macros. + +*Version requirement: Quote supports any compiler version back to Rust's very +first support for procedural macros in Rust 1.15.0.* + +[*Release notes*](https://github.com/dtolnay/quote/releases) + +```toml +[dependencies] +quote = "1.0" +``` + +## Syntax + +The quote crate provides a [`quote!`] macro within which you can write Rust code +that gets packaged into a [`TokenStream`] and can be treated as data. You should +think of `TokenStream` as representing a fragment of Rust source code. + +[`TokenStream`]: https://docs.rs/proc-macro2/1.0/proc_macro2/struct.TokenStream.html + +Within the `quote!` macro, interpolation is done with `#var`. Any type +implementing the [`quote::ToTokens`] trait can be interpolated. This includes +most Rust primitive types as well as most of the syntax tree types from [`syn`]. + +[`quote::ToTokens`]: https://docs.rs/quote/1.0/quote/trait.ToTokens.html +[`syn`]: https://github.com/dtolnay/syn + +```rust +let tokens = quote! { + struct SerializeWith #generics #where_clause { + value: &'a #field_ty, + phantom: core::marker::PhantomData<#item_ty>, + } + + impl #generics serde::Serialize for SerializeWith #generics #where_clause { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + #path(self.value, serializer) + } + } + + SerializeWith { + value: #value, + phantom: core::marker::PhantomData::<#item_ty>, + } +}; +``` + +## Repetition + +Repetition is done using `#(...)*` or `#(...),*` similar to `macro_rules!`. This +iterates through the elements of any variable interpolated within the repetition +and inserts a copy of the repetition body for each one. The variables in an +interpolation may be anything that implements `IntoIterator`, including `Vec` or +a pre-existing iterator. + +- `#(#var)*` — no separators +- `#(#var),*` — the character before the asterisk is used as a separator +- `#( struct #var; )*` — the repetition can contain other things +- `#( #k => println!("{}", #v), )*` — even multiple interpolations + +Note that there is a difference between `#(#var ,)*` and `#(#var),*`—the latter +does not produce a trailing comma. This matches the behavior of delimiters in +`macro_rules!`. + +## Returning tokens to the compiler + +The `quote!` macro evaluates to an expression of type +`proc_macro2::TokenStream`. Meanwhile Rust procedural macros are expected to +return the type `proc_macro::TokenStream`. + +The difference between the two types is that `proc_macro` types are entirely +specific to procedural macros and cannot ever exist in code outside of a +procedural macro, while `proc_macro2` types may exist anywhere including tests +and non-macro code like main.rs and build.rs. This is why even the procedural +macro ecosystem is largely built around `proc_macro2`, because that ensures the +libraries are unit testable and accessible in non-macro contexts. + +There is a [`From`]-conversion in both directions so returning the output of +`quote!` from a procedural macro usually looks like `tokens.into()` or +`proc_macro::TokenStream::from(tokens)`. + +[`From`]: https://doc.rust-lang.org/std/convert/trait.From.html + +## Examples + +### Combining quoted fragments + +Usually you don't end up constructing an entire final `TokenStream` in one +piece. Different parts may come from different helper functions. The tokens +produced by `quote!` themselves implement `ToTokens` and so can be interpolated +into later `quote!` invocations to build up a final result. + +```rust +let type_definition = quote! {...}; +let methods = quote! {...}; + +let tokens = quote! { + #type_definition + #methods +}; +``` + +### Constructing identifiers + +Suppose we have an identifier `ident` which came from somewhere in a macro +input and we need to modify it in some way for the macro output. Let's consider +prepending the identifier with an underscore. + +Simply interpolating the identifier next to an underscore will not have the +behavior of concatenating them. The underscore and the identifier will continue +to be two separate tokens as if you had written `_ x`. + +```rust +// incorrect +quote! { + let mut _#ident = 0; +} +``` + +The solution is to build a new identifier token with the correct value. As this +is such a common case, the `format_ident!` macro provides a convenient utility +for doing so correctly. + +```rust +let varname = format_ident!("_{}", ident); +quote! { + let mut #varname = 0; +} +``` + +Alternatively, the APIs provided by Syn and proc-macro2 can be used to directly +build the identifier. This is roughly equivalent to the above, but will not +handle `ident` being a raw identifier. + +```rust +let concatenated = format!("_{}", ident); +let varname = syn::Ident::new(&concatenated, ident.span()); +quote! { + let mut #varname = 0; +} +``` + +### Making method calls + +Let's say our macro requires some type specified in the macro input to have a +constructor called `new`. We have the type in a variable called `field_type` of +type `syn::Type` and want to invoke the constructor. + +```rust +// incorrect +quote! { + let value = #field_type::new(); +} +``` + +This works only sometimes. If `field_type` is `String`, the expanded code +contains `String::new()` which is fine. But if `field_type` is something like +`Vec` then the expanded code is `Vec::new()` which is invalid syntax. +Ordinarily in handwritten Rust we would write `Vec::::new()` but for macros +often the following is more convenient. + +```rust +quote! { + let value = <#field_type>::new(); +} +``` + +This expands to `>::new()` which behaves correctly. + +A similar pattern is appropriate for trait methods. + +```rust +quote! { + let value = <#field_type as core::default::Default>::default(); +} +``` + +## Hygiene + +Any interpolated tokens preserve the `Span` information provided by their +`ToTokens` implementation. Tokens that originate within a `quote!` invocation +are spanned with [`Span::call_site()`]. + +[`Span::call_site()`]: https://docs.rs/proc-macro2/1.0/proc_macro2/struct.Span.html#method.call_site + +A different span can be provided explicitly through the [`quote_spanned!`] +macro. + +[`quote_spanned!`]: https://docs.rs/quote/1.0/quote/macro.quote_spanned.html + +
+ +#### License + + +Licensed under either of Apache License, Version +2.0 or MIT license at your option. + + +
+ + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in this crate 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/quote/benches/bench.rs b/quote/benches/bench.rs new file mode 100644 index 0000000..c76a638 --- /dev/null +++ b/quote/benches/bench.rs @@ -0,0 +1,193 @@ +#![feature(test)] +#![recursion_limit = "512"] + +extern crate test; + +use quote::quote; +use test::Bencher; + +#[bench] +fn bench_impl(b: &mut Bencher) { + b.iter(|| { + quote! { + impl<'de> _serde::Deserialize<'de> for Response { + fn deserialize<__D>(__deserializer: __D) -> _serde::export::Result + where + __D: _serde::Deserializer<'de>, + { + #[allow(non_camel_case_types)] + enum __Field { + __field0, + __field1, + __ignore, + } + struct __FieldVisitor; + impl<'de> _serde::de::Visitor<'de> for __FieldVisitor { + type Value = __Field; + fn expecting( + &self, + __formatter: &mut _serde::export::Formatter, + ) -> _serde::export::fmt::Result { + _serde::export::Formatter::write_str(__formatter, "field identifier") + } + fn visit_u64<__E>(self, __value: u64) -> _serde::export::Result + where + __E: _serde::de::Error, + { + match __value { + 0u64 => _serde::export::Ok(__Field::__field0), + 1u64 => _serde::export::Ok(__Field::__field1), + _ => _serde::export::Err(_serde::de::Error::invalid_value( + _serde::de::Unexpected::Unsigned(__value), + &"field index 0 <= i < 2", + )), + } + } + fn visit_str<__E>(self, __value: &str) -> _serde::export::Result + where + __E: _serde::de::Error, + { + match __value { + "id" => _serde::export::Ok(__Field::__field0), + "s" => _serde::export::Ok(__Field::__field1), + _ => _serde::export::Ok(__Field::__ignore), + } + } + fn visit_bytes<__E>( + self, + __value: &[u8], + ) -> _serde::export::Result + where + __E: _serde::de::Error, + { + match __value { + b"id" => _serde::export::Ok(__Field::__field0), + b"s" => _serde::export::Ok(__Field::__field1), + _ => _serde::export::Ok(__Field::__ignore), + } + } + } + impl<'de> _serde::Deserialize<'de> for __Field { + #[inline] + fn deserialize<__D>(__deserializer: __D) -> _serde::export::Result + where + __D: _serde::Deserializer<'de>, + { + _serde::Deserializer::deserialize_identifier(__deserializer, __FieldVisitor) + } + } + struct __Visitor<'de> { + marker: _serde::export::PhantomData, + lifetime: _serde::export::PhantomData<&'de ()>, + } + impl<'de> _serde::de::Visitor<'de> for __Visitor<'de> { + type Value = Response; + fn expecting( + &self, + __formatter: &mut _serde::export::Formatter, + ) -> _serde::export::fmt::Result { + _serde::export::Formatter::write_str(__formatter, "struct Response") + } + #[inline] + fn visit_seq<__A>( + self, + mut __seq: __A, + ) -> _serde::export::Result + where + __A: _serde::de::SeqAccess<'de>, + { + let __field0 = + match try!(_serde::de::SeqAccess::next_element::(&mut __seq)) { + _serde::export::Some(__value) => __value, + _serde::export::None => { + return _serde::export::Err(_serde::de::Error::invalid_length( + 0usize, + &"struct Response with 2 elements", + )); + } + }; + let __field1 = + match try!(_serde::de::SeqAccess::next_element::(&mut __seq)) { + _serde::export::Some(__value) => __value, + _serde::export::None => { + return _serde::export::Err(_serde::de::Error::invalid_length( + 1usize, + &"struct Response with 2 elements", + )); + } + }; + _serde::export::Ok(Response { + id: __field0, + s: __field1, + }) + } + #[inline] + fn visit_map<__A>( + self, + mut __map: __A, + ) -> _serde::export::Result + where + __A: _serde::de::MapAccess<'de>, + { + let mut __field0: _serde::export::Option = _serde::export::None; + let mut __field1: _serde::export::Option = _serde::export::None; + while let _serde::export::Some(__key) = + try!(_serde::de::MapAccess::next_key::<__Field>(&mut __map)) + { + match __key { + __Field::__field0 => { + if _serde::export::Option::is_some(&__field0) { + return _serde::export::Err( + <__A::Error as _serde::de::Error>::duplicate_field("id"), + ); + } + __field0 = _serde::export::Some( + try!(_serde::de::MapAccess::next_value::(&mut __map)), + ); + } + __Field::__field1 => { + if _serde::export::Option::is_some(&__field1) { + return _serde::export::Err( + <__A::Error as _serde::de::Error>::duplicate_field("s"), + ); + } + __field1 = _serde::export::Some( + try!(_serde::de::MapAccess::next_value::(&mut __map)), + ); + } + _ => { + let _ = try!(_serde::de::MapAccess::next_value::< + _serde::de::IgnoredAny, + >(&mut __map)); + } + } + } + let __field0 = match __field0 { + _serde::export::Some(__field0) => __field0, + _serde::export::None => try!(_serde::private::de::missing_field("id")), + }; + let __field1 = match __field1 { + _serde::export::Some(__field1) => __field1, + _serde::export::None => try!(_serde::private::de::missing_field("s")), + }; + _serde::export::Ok(Response { + id: __field0, + s: __field1, + }) + } + } + const FIELDS: &'static [&'static str] = &["id", "s"]; + _serde::Deserializer::deserialize_struct( + __deserializer, + "Response", + FIELDS, + __Visitor { + marker: _serde::export::PhantomData::, + lifetime: _serde::export::PhantomData, + }, + ) + } + } + } + }); +} diff --git a/quote/src/ext.rs b/quote/src/ext.rs new file mode 100644 index 0000000..9e9b4a5 --- /dev/null +++ b/quote/src/ext.rs @@ -0,0 +1,112 @@ +use super::ToTokens; + +use std::iter; + +use proc_macro2::{TokenStream, TokenTree}; + +/// TokenStream extension trait with methods for appending tokens. +/// +/// This trait is sealed and cannot be implemented outside of the `quote` crate. +pub trait TokenStreamExt: private::Sealed { + /// For use by `ToTokens` implementations. + /// + /// Appends the token specified to this list of tokens. + fn append(&mut self, token: U) + where + U: Into; + + /// For use by `ToTokens` implementations. + /// + /// ``` + /// # use quote::{quote, TokenStreamExt, ToTokens}; + /// # use proc_macro2::TokenStream; + /// # + /// struct X; + /// + /// impl ToTokens for X { + /// fn to_tokens(&self, tokens: &mut TokenStream) { + /// tokens.append_all(&[true, false]); + /// } + /// } + /// + /// let tokens = quote!(#X); + /// assert_eq!(tokens.to_string(), "true false"); + /// ``` + fn append_all(&mut self, iter: I) + where + I: IntoIterator, + I::Item: ToTokens; + + /// For use by `ToTokens` implementations. + /// + /// Appends all of the items in the iterator `I`, separated by the tokens + /// `U`. + fn append_separated(&mut self, iter: I, op: U) + where + I: IntoIterator, + I::Item: ToTokens, + U: ToTokens; + + /// For use by `ToTokens` implementations. + /// + /// Appends all tokens in the iterator `I`, appending `U` after each + /// element, including after the last element of the iterator. + fn append_terminated(&mut self, iter: I, term: U) + where + I: IntoIterator, + I::Item: ToTokens, + U: ToTokens; +} + +impl TokenStreamExt for TokenStream { + fn append(&mut self, token: U) + where + U: Into, + { + self.extend(iter::once(token.into())); + } + + fn append_all(&mut self, iter: I) + where + I: IntoIterator, + I::Item: ToTokens, + { + for token in iter { + token.to_tokens(self); + } + } + + fn append_separated(&mut self, iter: I, op: U) + where + I: IntoIterator, + I::Item: ToTokens, + U: ToTokens, + { + for (i, token) in iter.into_iter().enumerate() { + if i > 0 { + op.to_tokens(self); + } + token.to_tokens(self); + } + } + + fn append_terminated(&mut self, iter: I, term: U) + where + I: IntoIterator, + I::Item: ToTokens, + U: ToTokens, + { + for token in iter { + token.to_tokens(self); + term.to_tokens(self); + } + } +} + +mod private { + use proc_macro2::TokenStream; + + pub trait Sealed {} + + impl Sealed for TokenStream {} +} diff --git a/quote/src/format.rs b/quote/src/format.rs new file mode 100644 index 0000000..13c8811 --- /dev/null +++ b/quote/src/format.rs @@ -0,0 +1,164 @@ +/// Formatting macro for constructing `Ident`s. +/// +///
+/// +/// # Syntax +/// +/// Syntax is copied from the [`format!`] macro, supporting both positional and +/// named arguments. +/// +/// Only a limited set of formatting traits are supported. The current mapping +/// of format types to traits is: +/// +/// * `{}` ⇒ [`IdentFragment`] +/// * `{:o}` ⇒ [`Octal`](`std::fmt::Octal`) +/// * `{:x}` ⇒ [`LowerHex`](`std::fmt::LowerHex`) +/// * `{:X}` ⇒ [`UpperHex`](`std::fmt::UpperHex`) +/// * `{:b}` ⇒ [`Binary`](`std::fmt::Binary`) +/// +/// See [`std::fmt`] for more information. +/// +///
+/// +/// # IdentFragment +/// +/// Unlike `format!`, this macro uses the [`IdentFragment`] formatting trait by +/// default. This trait is like `Display`, with a few differences: +/// +/// * `IdentFragment` is only implemented for a limited set of types, such as +/// unsigned integers and strings. +/// * [`Ident`] arguments will have their `r#` prefixes stripped, if present. +/// +/// [`Ident`]: `proc_macro2::Ident` +/// +///
+/// +/// # Hygiene +/// +/// The [`Span`] of the first `Ident` argument is used as the span of the final +/// identifier, falling back to [`Span::call_site`] when no identifiers are +/// provided. +/// +/// ``` +/// # use quote::format_ident; +/// # let ident = format_ident!("Ident"); +/// // If `ident` is an Ident, the span of `my_ident` will be inherited from it. +/// let my_ident = format_ident!("My{}{}", ident, "IsCool"); +/// assert_eq!(my_ident, "MyIdentIsCool"); +/// ``` +/// +/// Alternatively, the span can be overridden by passing the `span` named +/// argument. +/// +/// ``` +/// # use quote::format_ident; +/// # const IGNORE_TOKENS: &'static str = stringify! { +/// let my_span = /* ... */; +/// # }; +/// # let my_span = proc_macro2::Span::call_site(); +/// format_ident!("MyIdent", span = my_span); +/// ``` +/// +/// [`Span`]: `proc_macro2::Span` +/// [`Span::call_site`]: `proc_macro2::Span::call_site` +/// +///


+/// +/// # Panics +/// +/// This method will panic if the resulting formatted string is not a valid +/// identifier. +/// +///
+/// +/// # Examples +/// +/// Composing raw and non-raw identifiers: +/// ``` +/// # use quote::format_ident; +/// let my_ident = format_ident!("My{}", "Ident"); +/// assert_eq!(my_ident, "MyIdent"); +/// +/// let raw = format_ident!("r#Raw"); +/// assert_eq!(raw, "r#Raw"); +/// +/// let my_ident_raw = format_ident!("{}Is{}", my_ident, raw); +/// assert_eq!(my_ident_raw, "MyIdentIsRaw"); +/// ``` +/// +/// Integer formatting options: +/// ``` +/// # use quote::format_ident; +/// let num: u32 = 10; +/// +/// let decimal = format_ident!("Id_{}", num); +/// assert_eq!(decimal, "Id_10"); +/// +/// let octal = format_ident!("Id_{:o}", num); +/// assert_eq!(octal, "Id_12"); +/// +/// let binary = format_ident!("Id_{:b}", num); +/// assert_eq!(binary, "Id_1010"); +/// +/// let lower_hex = format_ident!("Id_{:x}", num); +/// assert_eq!(lower_hex, "Id_a"); +/// +/// let upper_hex = format_ident!("Id_{:X}", num); +/// assert_eq!(upper_hex, "Id_A"); +/// ``` +#[macro_export] +macro_rules! format_ident { + ($fmt:expr) => { + $crate::format_ident_impl!([ + ::std::option::Option::None, + $fmt + ]) + }; + + ($fmt:expr, $($rest:tt)*) => { + $crate::format_ident_impl!([ + ::std::option::Option::None, + $fmt + ] $($rest)*) + }; +} + +#[macro_export] +#[doc(hidden)] +macro_rules! format_ident_impl { + // Final state + ([$span:expr, $($fmt:tt)*]) => { + $crate::__rt::mk_ident(&format!($($fmt)*), $span) + }; + + // Span argument + ([$old:expr, $($fmt:tt)*] span = $span:expr) => { + $crate::format_ident_impl!([$old, $($fmt)*] span = $span,) + }; + ([$old:expr, $($fmt:tt)*] span = $span:expr, $($rest:tt)*) => { + $crate::format_ident_impl!([ + ::std::option::Option::Some::<$crate::__rt::Span>($span), + $($fmt)* + ] $($rest)*) + }; + + // Named argument + ([$span:expr, $($fmt:tt)*] $name:ident = $arg:expr) => { + $crate::format_ident_impl!([$span, $($fmt)*] $name = $arg,) + }; + ([$span:expr, $($fmt:tt)*] $name:ident = $arg:expr, $($rest:tt)*) => { + match $crate::__rt::IdentFragmentAdapter(&$arg) { + arg => $crate::format_ident_impl!([$span.or(arg.span()), $($fmt)*, $name = arg] $($rest)*), + } + }; + + // Positional argument + ([$span:expr, $($fmt:tt)*] $arg:expr) => { + $crate::format_ident_impl!([$span, $($fmt)*] $arg,) + }; + ([$span:expr, $($fmt:tt)*] $arg:expr, $($rest:tt)*) => { + match $crate::__rt::IdentFragmentAdapter(&$arg) { + arg => $crate::format_ident_impl!([$span.or(arg.span()), $($fmt)*, arg] $($rest)*), + } + }; +} diff --git a/quote/src/ident_fragment.rs b/quote/src/ident_fragment.rs new file mode 100644 index 0000000..09ead65 --- /dev/null +++ b/quote/src/ident_fragment.rs @@ -0,0 +1,72 @@ +use proc_macro2::{Ident, Span}; +use std::fmt; + +/// Specialized formatting trait used by `format_ident!`. +/// +/// [`Ident`] arguments formatted using this trait will have their `r#` prefix +/// stripped, if present. +/// +/// See [`format_ident!`] for more information. +pub trait IdentFragment { + /// Format this value as an identifier fragment. + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result; + + /// Span associated with this `IdentFragment`. + /// + /// If non-`None`, may be inherited by formatted identifiers. + fn span(&self) -> Option { + None + } +} + +impl<'a, T: IdentFragment + ?Sized> IdentFragment for &'a T { + fn span(&self) -> Option { + ::span(*self) + } + + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + IdentFragment::fmt(*self, f) + } +} + +impl<'a, T: IdentFragment + ?Sized> IdentFragment for &'a mut T { + fn span(&self) -> Option { + ::span(*self) + } + + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + IdentFragment::fmt(*self, f) + } +} + +impl IdentFragment for Ident { + fn span(&self) -> Option { + Some(self.span()) + } + + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let id = self.to_string(); + if id.starts_with("r#") { + fmt::Display::fmt(&id[2..], f) + } else { + fmt::Display::fmt(&id[..], f) + } + } +} + +// Limited set of types which this is implemented for, as we want to avoid types +// which will often include non-identifier characters in their `Display` impl. +macro_rules! ident_fragment_display { + ($($T:ty),*) => { + $( + impl IdentFragment for $T { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(self, f) + } + } + )* + } +} + +ident_fragment_display!(bool, str, String); +ident_fragment_display!(u8, u16, u32, u64, u128, usize); diff --git a/quote/src/lib.rs b/quote/src/lib.rs new file mode 100644 index 0000000..3341a16 --- /dev/null +++ b/quote/src/lib.rs @@ -0,0 +1,948 @@ +//! This crate provides the [`quote!`] macro for turning Rust syntax tree data +//! structures into tokens of source code. +//! +//! [`quote!`]: macro.quote.html +//! +//! Procedural macros in Rust receive a stream of tokens as input, execute +//! arbitrary Rust code to determine how to manipulate those tokens, and produce +//! a stream of tokens to hand back to the compiler to compile into the caller's +//! crate. Quasi-quoting is a solution to one piece of that — producing +//! tokens to return to the compiler. +//! +//! The idea of quasi-quoting is that we write *code* that we treat as *data*. +//! Within the `quote!` macro, we can write what looks like code to our text +//! editor or IDE. We get all the benefits of the editor's brace matching, +//! syntax highlighting, indentation, and maybe autocompletion. But rather than +//! compiling that as code into the current crate, we can treat it as data, pass +//! it around, mutate it, and eventually hand it back to the compiler as tokens +//! to compile into the macro caller's crate. +//! +//! This crate is motivated by the procedural macro use case, but is a +//! general-purpose Rust quasi-quoting library and is not specific to procedural +//! macros. +//! +//! ```toml +//! [dependencies] +//! quote = "1.0" +//! ``` +//! +//!
+//! +//! # Example +//! +//! The following quasi-quoted block of code is something you might find in [a] +//! procedural macro having to do with data structure serialization. The `#var` +//! syntax performs interpolation of runtime variables into the quoted tokens. +//! Check out the documentation of the [`quote!`] macro for more detail about +//! the syntax. See also the [`quote_spanned!`] macro which is important for +//! implementing hygienic procedural macros. +//! +//! [a]: https://serde.rs/ +//! [`quote_spanned!`]: macro.quote_spanned.html +//! +//! ``` +//! # use quote::quote; +//! # +//! # let generics = ""; +//! # let where_clause = ""; +//! # let field_ty = ""; +//! # let item_ty = ""; +//! # let path = ""; +//! # let value = ""; +//! # +//! let tokens = quote! { +//! struct SerializeWith #generics #where_clause { +//! value: &'a #field_ty, +//! phantom: core::marker::PhantomData<#item_ty>, +//! } +//! +//! impl #generics serde::Serialize for SerializeWith #generics #where_clause { +//! fn serialize(&self, serializer: S) -> Result +//! where +//! S: serde::Serializer, +//! { +//! #path(self.value, serializer) +//! } +//! } +//! +//! SerializeWith { +//! value: #value, +//! phantom: core::marker::PhantomData::<#item_ty>, +//! } +//! }; +//! ``` + +// Quote types in rustdoc of other crates get linked to here. +#![doc(html_root_url = "https://docs.rs/quote/1.0.2")] + +#[cfg(all( + not(all(target_arch = "wasm32", target_os = "unknown")), + feature = "proc-macro" +))] +extern crate proc_macro; + +mod ext; +mod format; +mod ident_fragment; +mod to_tokens; + +// Not public API. +#[doc(hidden)] +#[path = "runtime.rs"] +pub mod __rt; + +pub use crate::ext::TokenStreamExt; +pub use crate::ident_fragment::IdentFragment; +pub use crate::to_tokens::ToTokens; + +// Not public API. +#[doc(hidden)] +pub mod spanned; + +/// The whole point. +/// +/// Performs variable interpolation against the input and produces it as +/// [`proc_macro2::TokenStream`]. +/// +/// Note: for returning tokens to the compiler in a procedural macro, use +/// `.into()` on the result to convert to [`proc_macro::TokenStream`]. +/// +/// [`TokenStream`]: https://docs.rs/proc-macro2/1.0/proc_macro2/struct.TokenStream.html +/// +///
+/// +/// # Interpolation +/// +/// Variable interpolation is done with `#var` (similar to `$var` in +/// `macro_rules!` macros). This grabs the `var` variable that is currently in +/// scope and inserts it in that location in the output tokens. Any type +/// implementing the [`ToTokens`] trait can be interpolated. This includes most +/// Rust primitive types as well as most of the syntax tree types from the [Syn] +/// crate. +/// +/// [`ToTokens`]: trait.ToTokens.html +/// [Syn]: https://github.com/dtolnay/syn +/// +/// Repetition is done using `#(...)*` or `#(...),*` again similar to +/// `macro_rules!`. This iterates through the elements of any variable +/// interpolated within the repetition and inserts a copy of the repetition body +/// for each one. The variables in an interpolation may be a `Vec`, slice, +/// `BTreeSet`, or any `Iterator`. +/// +/// - `#(#var)*` — no separators +/// - `#(#var),*` — the character before the asterisk is used as a separator +/// - `#( struct #var; )*` — the repetition can contain other tokens +/// - `#( #k => println!("{}", #v), )*` — even multiple interpolations +/// +///
+/// +/// # Hygiene +/// +/// Any interpolated tokens preserve the `Span` information provided by their +/// `ToTokens` implementation. Tokens that originate within the `quote!` +/// invocation are spanned with [`Span::call_site()`]. +/// +/// [`Span::call_site()`]: https://docs.rs/proc-macro2/1.0/proc_macro2/struct.Span.html#method.call_site +/// +/// A different span can be provided through the [`quote_spanned!`] macro. +/// +/// [`quote_spanned!`]: macro.quote_spanned.html +/// +///
+/// +/// # Return type +/// +/// The macro evaluates to an expression of type `proc_macro2::TokenStream`. +/// Meanwhile Rust procedural macros are expected to return the type +/// `proc_macro::TokenStream`. +/// +/// The difference between the two types is that `proc_macro` types are entirely +/// specific to procedural macros and cannot ever exist in code outside of a +/// procedural macro, while `proc_macro2` types may exist anywhere including +/// tests and non-macro code like main.rs and build.rs. This is why even the +/// procedural macro ecosystem is largely built around `proc_macro2`, because +/// that ensures the libraries are unit testable and accessible in non-macro +/// contexts. +/// +/// There is a [`From`]-conversion in both directions so returning the output of +/// `quote!` from a procedural macro usually looks like `tokens.into()` or +/// `proc_macro::TokenStream::from(tokens)`. +/// +/// [`From`]: https://doc.rust-lang.org/std/convert/trait.From.html +/// +///
+/// +/// # Examples +/// +/// ### Procedural macro +/// +/// The structure of a basic procedural macro is as follows. Refer to the [Syn] +/// crate for further useful guidance on using `quote!` as part of a procedural +/// macro. +/// +/// [Syn]: https://github.com/dtolnay/syn +/// +/// ``` +/// # #[cfg(any())] +/// extern crate proc_macro; +/// # extern crate proc_macro2; +/// +/// # #[cfg(any())] +/// use proc_macro::TokenStream; +/// # use proc_macro2::TokenStream; +/// use quote::quote; +/// +/// # const IGNORE_TOKENS: &'static str = stringify! { +/// #[proc_macro_derive(HeapSize)] +/// # }; +/// pub fn derive_heap_size(input: TokenStream) -> TokenStream { +/// // Parse the input and figure out what implementation to generate... +/// # const IGNORE_TOKENS: &'static str = stringify! { +/// let name = /* ... */; +/// let expr = /* ... */; +/// # }; +/// # +/// # let name = 0; +/// # let expr = 0; +/// +/// let expanded = quote! { +/// // The generated impl. +/// impl heapsize::HeapSize for #name { +/// fn heap_size_of_children(&self) -> usize { +/// #expr +/// } +/// } +/// }; +/// +/// // Hand the output tokens back to the compiler. +/// TokenStream::from(expanded) +/// } +/// ``` +/// +///


+/// +/// ### Combining quoted fragments +/// +/// Usually you don't end up constructing an entire final `TokenStream` in one +/// piece. Different parts may come from different helper functions. The tokens +/// produced by `quote!` themselves implement `ToTokens` and so can be +/// interpolated into later `quote!` invocations to build up a final result. +/// +/// ``` +/// # use quote::quote; +/// # +/// let type_definition = quote! {...}; +/// let methods = quote! {...}; +/// +/// let tokens = quote! { +/// #type_definition +/// #methods +/// }; +/// ``` +/// +///


+/// +/// ### Constructing identifiers +/// +/// Suppose we have an identifier `ident` which came from somewhere in a macro +/// input and we need to modify it in some way for the macro output. Let's +/// consider prepending the identifier with an underscore. +/// +/// Simply interpolating the identifier next to an underscore will not have the +/// behavior of concatenating them. The underscore and the identifier will +/// continue to be two separate tokens as if you had written `_ x`. +/// +/// ``` +/// # use proc_macro2::{self as syn, Span}; +/// # use quote::quote; +/// # +/// # let ident = syn::Ident::new("i", Span::call_site()); +/// # +/// // incorrect +/// quote! { +/// let mut _#ident = 0; +/// } +/// # ; +/// ``` +/// +/// The solution is to build a new identifier token with the correct value. As +/// this is such a common case, the [`format_ident!`] macro provides a +/// convenient utility for doing so correctly. +/// +/// ``` +/// # use proc_macro2::{Ident, Span}; +/// # use quote::{format_ident, quote}; +/// # +/// # let ident = Ident::new("i", Span::call_site()); +/// # +/// let varname = format_ident!("_{}", ident); +/// quote! { +/// let mut #varname = 0; +/// } +/// # ; +/// ``` +/// +/// Alternatively, the APIs provided by Syn and proc-macro2 can be used to +/// directly build the identifier. This is roughly equivalent to the above, but +/// will not handle `ident` being a raw identifier. +/// +/// ``` +/// # use proc_macro2::{self as syn, Span}; +/// # use quote::quote; +/// # +/// # let ident = syn::Ident::new("i", Span::call_site()); +/// # +/// let concatenated = format!("_{}", ident); +/// let varname = syn::Ident::new(&concatenated, ident.span()); +/// quote! { +/// let mut #varname = 0; +/// } +/// # ; +/// ``` +/// +///


+/// +/// ### Making method calls +/// +/// Let's say our macro requires some type specified in the macro input to have +/// a constructor called `new`. We have the type in a variable called +/// `field_type` of type `syn::Type` and want to invoke the constructor. +/// +/// ``` +/// # use quote::quote; +/// # +/// # let field_type = quote!(...); +/// # +/// // incorrect +/// quote! { +/// let value = #field_type::new(); +/// } +/// # ; +/// ``` +/// +/// This works only sometimes. If `field_type` is `String`, the expanded code +/// contains `String::new()` which is fine. But if `field_type` is something +/// like `Vec` then the expanded code is `Vec::new()` which is invalid +/// syntax. Ordinarily in handwritten Rust we would write `Vec::::new()` +/// but for macros often the following is more convenient. +/// +/// ``` +/// # use quote::quote; +/// # +/// # let field_type = quote!(...); +/// # +/// quote! { +/// let value = <#field_type>::new(); +/// } +/// # ; +/// ``` +/// +/// This expands to `>::new()` which behaves correctly. +/// +/// A similar pattern is appropriate for trait methods. +/// +/// ``` +/// # use quote::quote; +/// # +/// # let field_type = quote!(...); +/// # +/// quote! { +/// let value = <#field_type as core::default::Default>::default(); +/// } +/// # ; +/// ``` +/// +///


+/// +/// ### Interpolating text inside of doc comments +/// +/// Neither doc comments nor string literals get interpolation behavior in +/// quote: +/// +/// ```compile_fail +/// quote! { +/// /// try to interpolate: #ident +/// /// +/// /// ... +/// } +/// ``` +/// +/// ```compile_fail +/// quote! { +/// #[doc = "try to interpolate: #ident"] +/// } +/// ``` +/// +/// Macro calls in a doc attribute are not valid syntax: +/// +/// ```compile_fail +/// quote! { +/// #[doc = concat!("try to interpolate: ", stringify!(#ident))] +/// } +/// ``` +/// +/// Instead the best way to build doc comments that involve variables is by +/// formatting the doc string literal outside of quote. +/// +/// ```rust +/// # use proc_macro2::{Ident, Span}; +/// # use quote::quote; +/// # +/// # const IGNORE: &str = stringify! { +/// let msg = format!(...); +/// # }; +/// # +/// # let ident = Ident::new("var", Span::call_site()); +/// # let msg = format!("try to interpolate: {}", ident); +/// quote! { +/// #[doc = #msg] +/// /// +/// /// ... +/// } +/// # ; +/// ``` +/// +///


+/// +/// ### Indexing into a tuple struct +/// +/// When interpolating indices of a tuple or tuple struct, we need them not to +/// appears suffixed as integer literals by interpolating them as [`syn::Index`] +/// instead. +/// +/// [`syn::Index`]: https://docs.rs/syn/1.0/syn/struct.Index.html +/// +/// ```compile_fail +/// let i = 0usize..self.fields.len(); +/// +/// // expands to 0 + self.0usize.heap_size() + self.1usize.heap_size() + ... +/// // which is not valid syntax +/// quote! { +/// 0 #( + self.#i.heap_size() )* +/// } +/// ``` +/// +/// ``` +/// # use proc_macro2::{Ident, TokenStream}; +/// # use quote::quote; +/// # +/// # mod syn { +/// # use proc_macro2::{Literal, TokenStream}; +/// # use quote::{ToTokens, TokenStreamExt}; +/// # +/// # pub struct Index(usize); +/// # +/// # impl From for Index { +/// # fn from(i: usize) -> Self { +/// # Index(i) +/// # } +/// # } +/// # +/// # impl ToTokens for Index { +/// # fn to_tokens(&self, tokens: &mut TokenStream) { +/// # tokens.append(Literal::usize_unsuffixed(self.0)); +/// # } +/// # } +/// # } +/// # +/// # struct Struct { +/// # fields: Vec, +/// # } +/// # +/// # impl Struct { +/// # fn example(&self) -> TokenStream { +/// let i = (0..self.fields.len()).map(syn::Index::from); +/// +/// // expands to 0 + self.0.heap_size() + self.1.heap_size() + ... +/// quote! { +/// 0 #( + self.#i.heap_size() )* +/// } +/// # } +/// # } +/// ``` +#[macro_export] +macro_rules! quote { + ($($tt:tt)*) => { + $crate::quote_spanned!($crate::__rt::Span::call_site()=> $($tt)*) + }; +} + +/// Same as `quote!`, but applies a given span to all tokens originating within +/// the macro invocation. +/// +///
+/// +/// # Syntax +/// +/// A span expression of type [`Span`], followed by `=>`, followed by the tokens +/// to quote. The span expression should be brief — use a variable for +/// anything more than a few characters. There should be no space before the +/// `=>` token. +/// +/// [`Span`]: https://docs.rs/proc-macro2/1.0/proc_macro2/struct.Span.html +/// +/// ``` +/// # use proc_macro2::Span; +/// # use quote::quote_spanned; +/// # +/// # const IGNORE_TOKENS: &'static str = stringify! { +/// let span = /* ... */; +/// # }; +/// # let span = Span::call_site(); +/// # let init = 0; +/// +/// // On one line, use parentheses. +/// let tokens = quote_spanned!(span=> Box::into_raw(Box::new(#init))); +/// +/// // On multiple lines, place the span at the top and use braces. +/// let tokens = quote_spanned! {span=> +/// Box::into_raw(Box::new(#init)) +/// }; +/// ``` +/// +/// The lack of space before the `=>` should look jarring to Rust programmers +/// and this is intentional. The formatting is designed to be visibly +/// off-balance and draw the eye a particular way, due to the span expression +/// being evaluated in the context of the procedural macro and the remaining +/// tokens being evaluated in the generated code. +/// +///
+/// +/// # Hygiene +/// +/// Any interpolated tokens preserve the `Span` information provided by their +/// `ToTokens` implementation. Tokens that originate within the `quote_spanned!` +/// invocation are spanned with the given span argument. +/// +///
+/// +/// # Example +/// +/// The following procedural macro code uses `quote_spanned!` to assert that a +/// particular Rust type implements the [`Sync`] trait so that references can be +/// safely shared between threads. +/// +/// [`Sync`]: https://doc.rust-lang.org/std/marker/trait.Sync.html +/// +/// ``` +/// # use quote::{quote_spanned, TokenStreamExt, ToTokens}; +/// # use proc_macro2::{Span, TokenStream}; +/// # +/// # struct Type; +/// # +/// # impl Type { +/// # fn span(&self) -> Span { +/// # Span::call_site() +/// # } +/// # } +/// # +/// # impl ToTokens for Type { +/// # fn to_tokens(&self, _tokens: &mut TokenStream) {} +/// # } +/// # +/// # let ty = Type; +/// # let call_site = Span::call_site(); +/// # +/// let ty_span = ty.span(); +/// let assert_sync = quote_spanned! {ty_span=> +/// struct _AssertSync where #ty: Sync; +/// }; +/// ``` +/// +/// If the assertion fails, the user will see an error like the following. The +/// input span of their type is hightlighted in the error. +/// +/// ```text +/// error[E0277]: the trait bound `*const (): std::marker::Sync` is not satisfied +/// --> src/main.rs:10:21 +/// | +/// 10 | static ref PTR: *const () = &(); +/// | ^^^^^^^^^ `*const ()` cannot be shared between threads safely +/// ``` +/// +/// In this example it is important for the where-clause to be spanned with the +/// line/column information of the user's input type so that error messages are +/// placed appropriately by the compiler. +#[macro_export] +macro_rules! quote_spanned { + ($span:expr=> $($tt:tt)*) => {{ + let mut _s = $crate::__rt::TokenStream::new(); + let _span: $crate::__rt::Span = $span; + $crate::quote_each_token!(_s _span $($tt)*); + _s + }}; +} + +// Extract the names of all #metavariables and pass them to the $call macro. +// +// in: pounded_var_names!(then!(...) a #b c #( #d )* #e) +// out: then!(... b); +// then!(... d); +// then!(... e); +#[macro_export] +#[doc(hidden)] +macro_rules! pounded_var_names { + ($call:ident! $extra:tt $($tts:tt)*) => { + $crate::pounded_var_names_with_context!($call! $extra + (@ $($tts)*) + ($($tts)* @) + ) + }; +} + +#[macro_export] +#[doc(hidden)] +macro_rules! pounded_var_names_with_context { + ($call:ident! $extra:tt ($($b1:tt)*) ($($curr:tt)*)) => { + $( + $crate::pounded_var_with_context!($call! $extra $b1 $curr); + )* + }; +} + +#[macro_export] +#[doc(hidden)] +macro_rules! pounded_var_with_context { + ($call:ident! $extra:tt $b1:tt ( $($inner:tt)* )) => { + $crate::pounded_var_names!($call! $extra $($inner)*); + }; + + ($call:ident! $extra:tt $b1:tt [ $($inner:tt)* ]) => { + $crate::pounded_var_names!($call! $extra $($inner)*); + }; + + ($call:ident! $extra:tt $b1:tt { $($inner:tt)* }) => { + $crate::pounded_var_names!($call! $extra $($inner)*); + }; + + ($call:ident!($($extra:tt)*) # $var:ident) => { + $crate::$call!($($extra)* $var); + }; + + ($call:ident! $extra:tt $b1:tt $curr:tt) => {}; +} + +#[macro_export] +#[doc(hidden)] +macro_rules! quote_bind_into_iter { + ($has_iter:ident $var:ident) => { + // `mut` may be unused if $var occurs multiple times in the list. + #[allow(unused_mut)] + let (mut $var, i) = $var.quote_into_iter(); + let $has_iter = $has_iter | i; + }; +} + +#[macro_export] +#[doc(hidden)] +macro_rules! quote_bind_next_or_break { + ($var:ident) => { + let $var = match $var.next() { + Some(_x) => $crate::__rt::RepInterp(_x), + None => break, + }; + }; +} + +#[macro_export] +#[doc(hidden)] +macro_rules! quote_each_token { + ($tokens:ident $span:ident $($tts:tt)*) => { + $crate::quote_tokens_with_context!($tokens $span + (@ @ @ @ @ @ $($tts)*) + (@ @ @ @ @ $($tts)* @) + (@ @ @ @ $($tts)* @ @) + (@ @ @ $(($tts))* @ @ @) + (@ @ $($tts)* @ @ @ @) + (@ $($tts)* @ @ @ @ @) + ($($tts)* @ @ @ @ @ @) + ); + }; +} + +#[macro_export] +#[doc(hidden)] +macro_rules! quote_tokens_with_context { + ($tokens:ident $span:ident + ($($b3:tt)*) ($($b2:tt)*) ($($b1:tt)*) + ($($curr:tt)*) + ($($a1:tt)*) ($($a2:tt)*) ($($a3:tt)*) + ) => { + $( + $crate::quote_token_with_context!($tokens $span $b3 $b2 $b1 $curr $a1 $a2 $a3); + )* + }; +} + +#[macro_export] +#[doc(hidden)] +macro_rules! quote_token_with_context { + ($tokens:ident $span:ident $b3:tt $b2:tt $b1:tt @ $a1:tt $a2:tt $a3:tt) => {}; + + ($tokens:ident $span:ident $b3:tt $b2:tt $b1:tt (#) ( $($inner:tt)* ) * $a3:tt) => {{ + use $crate::__rt::ext::*; + let has_iter = $crate::__rt::ThereIsNoIteratorInRepetition; + $crate::pounded_var_names!(quote_bind_into_iter!(has_iter) () $($inner)*); + let _: $crate::__rt::HasIterator = has_iter; + // This is `while true` instead of `loop` because if there are no + // iterators used inside of this repetition then the body would not + // contain any `break`, so the compiler would emit unreachable code + // warnings on anything below the loop. We use has_iter to detect and + // fail to compile when there are no iterators, so here we just work + // around the unneeded extra warning. + while true { + $crate::pounded_var_names!(quote_bind_next_or_break!() () $($inner)*); + $crate::quote_each_token!($tokens $span $($inner)*); + } + }}; + ($tokens:ident $span:ident $b3:tt $b2:tt # (( $($inner:tt)* )) * $a2:tt $a3:tt) => {}; + ($tokens:ident $span:ident $b3:tt # ( $($inner:tt)* ) (*) $a1:tt $a2:tt $a3:tt) => {}; + + ($tokens:ident $span:ident $b3:tt $b2:tt $b1:tt (#) ( $($inner:tt)* ) $sep:tt *) => {{ + use $crate::__rt::ext::*; + let mut _i = 0usize; + let has_iter = $crate::__rt::ThereIsNoIteratorInRepetition; + $crate::pounded_var_names!(quote_bind_into_iter!(has_iter) () $($inner)*); + let _: $crate::__rt::HasIterator = has_iter; + while true { + $crate::pounded_var_names!(quote_bind_next_or_break!() () $($inner)*); + if _i > 0 { + $crate::quote_token!($tokens $span $sep); + } + _i += 1; + $crate::quote_each_token!($tokens $span $($inner)*); + } + }}; + ($tokens:ident $span:ident $b3:tt $b2:tt # (( $($inner:tt)* )) $sep:tt * $a3:tt) => {}; + ($tokens:ident $span:ident $b3:tt # ( $($inner:tt)* ) ($sep:tt) * $a2:tt $a3:tt) => {}; + ($tokens:ident $span:ident # ( $($inner:tt)* ) * (*) $a1:tt $a2:tt $a3:tt) => { + // https://github.com/dtolnay/quote/issues/130 + $crate::quote_token!($tokens $span *); + }; + ($tokens:ident $span:ident # ( $($inner:tt)* ) $sep:tt (*) $a1:tt $a2:tt $a3:tt) => {}; + + ($tokens:ident $span:ident $b3:tt $b2:tt $b1:tt (#) $var:ident $a2:tt $a3:tt) => { + $crate::ToTokens::to_tokens(&$var, &mut $tokens); + }; + ($tokens:ident $span:ident $b3:tt $b2:tt # ($var:ident) $a1:tt $a2:tt $a3:tt) => {}; + ($tokens:ident $span:ident $b3:tt $b2:tt $b1:tt ($curr:tt) $a1:tt $a2:tt $a3:tt) => { + $crate::quote_token!($tokens $span $curr); + }; +} + +#[macro_export] +#[doc(hidden)] +macro_rules! quote_token { + ($tokens:ident $span:ident ( $($inner:tt)* )) => { + $tokens.extend({ + let mut g = $crate::__rt::Group::new( + $crate::__rt::Delimiter::Parenthesis, + $crate::quote_spanned!($span=> $($inner)*), + ); + g.set_span($span); + Some($crate::__rt::TokenTree::from(g)) + }); + }; + + ($tokens:ident $span:ident [ $($inner:tt)* ]) => { + $tokens.extend({ + let mut g = $crate::__rt::Group::new( + $crate::__rt::Delimiter::Bracket, + $crate::quote_spanned!($span=> $($inner)*), + ); + g.set_span($span); + Some($crate::__rt::TokenTree::from(g)) + }); + }; + + ($tokens:ident $span:ident { $($inner:tt)* }) => { + $tokens.extend({ + let mut g = $crate::__rt::Group::new( + $crate::__rt::Delimiter::Brace, + $crate::quote_spanned!($span=> $($inner)*), + ); + g.set_span($span); + Some($crate::__rt::TokenTree::from(g)) + }); + }; + + ($tokens:ident $span:ident +) => { + $crate::__rt::push_add(&mut $tokens, $span); + }; + + ($tokens:ident $span:ident +=) => { + $crate::__rt::push_add_eq(&mut $tokens, $span); + }; + + ($tokens:ident $span:ident &) => { + $crate::__rt::push_and(&mut $tokens, $span); + }; + + ($tokens:ident $span:ident &&) => { + $crate::__rt::push_and_and(&mut $tokens, $span); + }; + + ($tokens:ident $span:ident &=) => { + $crate::__rt::push_and_eq(&mut $tokens, $span); + }; + + ($tokens:ident $span:ident @) => { + $crate::__rt::push_at(&mut $tokens, $span); + }; + + ($tokens:ident $span:ident !) => { + $crate::__rt::push_bang(&mut $tokens, $span); + }; + + ($tokens:ident $span:ident ^) => { + $crate::__rt::push_caret(&mut $tokens, $span); + }; + + ($tokens:ident $span:ident ^=) => { + $crate::__rt::push_caret_eq(&mut $tokens, $span); + }; + + ($tokens:ident $span:ident :) => { + $crate::__rt::push_colon(&mut $tokens, $span); + }; + + ($tokens:ident $span:ident ::) => { + $crate::__rt::push_colon2(&mut $tokens, $span); + }; + + ($tokens:ident $span:ident ,) => { + $crate::__rt::push_comma(&mut $tokens, $span); + }; + + ($tokens:ident $span:ident /) => { + $crate::__rt::push_div(&mut $tokens, $span); + }; + + ($tokens:ident $span:ident /=) => { + $crate::__rt::push_div_eq(&mut $tokens, $span); + }; + + ($tokens:ident $span:ident .) => { + $crate::__rt::push_dot(&mut $tokens, $span); + }; + + ($tokens:ident $span:ident ..) => { + $crate::__rt::push_dot2(&mut $tokens, $span); + }; + + ($tokens:ident $span:ident ...) => { + $crate::__rt::push_dot3(&mut $tokens, $span); + }; + + ($tokens:ident $span:ident ..=) => { + $crate::__rt::push_dot_dot_eq(&mut $tokens, $span); + }; + + ($tokens:ident $span:ident =) => { + $crate::__rt::push_eq(&mut $tokens, $span); + }; + + ($tokens:ident $span:ident ==) => { + $crate::__rt::push_eq_eq(&mut $tokens, $span); + }; + + ($tokens:ident $span:ident >=) => { + $crate::__rt::push_ge(&mut $tokens, $span); + }; + + ($tokens:ident $span:ident >) => { + $crate::__rt::push_gt(&mut $tokens, $span); + }; + + ($tokens:ident $span:ident <=) => { + $crate::__rt::push_le(&mut $tokens, $span); + }; + + ($tokens:ident $span:ident <) => { + $crate::__rt::push_lt(&mut $tokens, $span); + }; + + ($tokens:ident $span:ident *=) => { + $crate::__rt::push_mul_eq(&mut $tokens, $span); + }; + + ($tokens:ident $span:ident !=) => { + $crate::__rt::push_ne(&mut $tokens, $span); + }; + + ($tokens:ident $span:ident |) => { + $crate::__rt::push_or(&mut $tokens, $span); + }; + + ($tokens:ident $span:ident |=) => { + $crate::__rt::push_or_eq(&mut $tokens, $span); + }; + + ($tokens:ident $span:ident ||) => { + $crate::__rt::push_or_or(&mut $tokens, $span); + }; + + ($tokens:ident $span:ident #) => { + $crate::__rt::push_pound(&mut $tokens, $span); + }; + + ($tokens:ident $span:ident ?) => { + $crate::__rt::push_question(&mut $tokens, $span); + }; + + ($tokens:ident $span:ident ->) => { + $crate::__rt::push_rarrow(&mut $tokens, $span); + }; + + ($tokens:ident $span:ident <-) => { + $crate::__rt::push_larrow(&mut $tokens, $span); + }; + + ($tokens:ident $span:ident %) => { + $crate::__rt::push_rem(&mut $tokens, $span); + }; + + ($tokens:ident $span:ident %=) => { + $crate::__rt::push_rem_eq(&mut $tokens, $span); + }; + + ($tokens:ident $span:ident =>) => { + $crate::__rt::push_fat_arrow(&mut $tokens, $span); + }; + + ($tokens:ident $span:ident ;) => { + $crate::__rt::push_semi(&mut $tokens, $span); + }; + + ($tokens:ident $span:ident <<) => { + $crate::__rt::push_shl(&mut $tokens, $span); + }; + + ($tokens:ident $span:ident <<=) => { + $crate::__rt::push_shl_eq(&mut $tokens, $span); + }; + + ($tokens:ident $span:ident >>) => { + $crate::__rt::push_shr(&mut $tokens, $span); + }; + + ($tokens:ident $span:ident >>=) => { + $crate::__rt::push_shr_eq(&mut $tokens, $span); + }; + + ($tokens:ident $span:ident *) => { + $crate::__rt::push_star(&mut $tokens, $span); + }; + + ($tokens:ident $span:ident -) => { + $crate::__rt::push_sub(&mut $tokens, $span); + }; + + ($tokens:ident $span:ident -=) => { + $crate::__rt::push_sub_eq(&mut $tokens, $span); + }; + + ($tokens:ident $span:ident $other:tt) => { + $crate::__rt::parse(&mut $tokens, $span, stringify!($other)); + }; +} diff --git a/quote/src/runtime.rs b/quote/src/runtime.rs new file mode 100644 index 0000000..4a1c14c --- /dev/null +++ b/quote/src/runtime.rs @@ -0,0 +1,373 @@ +use crate::{IdentFragment, ToTokens, TokenStreamExt}; +use std::fmt; +use std::ops::BitOr; + +pub use proc_macro2::*; + +pub struct HasIterator; // True +pub struct ThereIsNoIteratorInRepetition; // False + +impl BitOr for ThereIsNoIteratorInRepetition { + type Output = ThereIsNoIteratorInRepetition; + fn bitor(self, _rhs: ThereIsNoIteratorInRepetition) -> ThereIsNoIteratorInRepetition { + ThereIsNoIteratorInRepetition + } +} + +impl BitOr for HasIterator { + type Output = HasIterator; + fn bitor(self, _rhs: ThereIsNoIteratorInRepetition) -> HasIterator { + HasIterator + } +} + +impl BitOr for ThereIsNoIteratorInRepetition { + type Output = HasIterator; + fn bitor(self, _rhs: HasIterator) -> HasIterator { + HasIterator + } +} + +impl BitOr for HasIterator { + type Output = HasIterator; + fn bitor(self, _rhs: HasIterator) -> HasIterator { + HasIterator + } +} + +/// Extension traits used by the implementation of `quote!`. These are defined +/// in separate traits, rather than as a single trait due to ambiguity issues. +/// +/// These traits expose a `quote_into_iter` method which should allow calling +/// whichever impl happens to be applicable. Calling that method repeatedly on +/// the returned value should be idempotent. +pub mod ext { + use super::RepInterp; + use super::{HasIterator as HasIter, ThereIsNoIteratorInRepetition as DoesNotHaveIter}; + use crate::ToTokens; + use std::collections::btree_set::{self, BTreeSet}; + use std::slice; + + /// Extension trait providing the `quote_into_iter` method on iterators. + pub trait RepIteratorExt: Iterator + Sized { + fn quote_into_iter(self) -> (Self, HasIter) { + (self, HasIter) + } + } + + impl RepIteratorExt for T {} + + /// Extension trait providing the `quote_into_iter` method for + /// non-iterable types. These types interpolate the same value in each + /// iteration of the repetition. + pub trait RepToTokensExt { + /// Pretend to be an iterator for the purposes of `quote_into_iter`. + /// This allows repeated calls to `quote_into_iter` to continue + /// correctly returning DoesNotHaveIter. + fn next(&self) -> Option<&Self> { + Some(self) + } + + fn quote_into_iter(&self) -> (&Self, DoesNotHaveIter) { + (self, DoesNotHaveIter) + } + } + + impl RepToTokensExt for T {} + + /// Extension trait providing the `quote_into_iter` method for types that + /// can be referenced as an iterator. + pub trait RepAsIteratorExt<'q> { + type Iter: Iterator; + + fn quote_into_iter(&'q self) -> (Self::Iter, HasIter); + } + + impl<'q, 'a, T: RepAsIteratorExt<'q> + ?Sized> RepAsIteratorExt<'q> for &'a T { + type Iter = T::Iter; + + fn quote_into_iter(&'q self) -> (Self::Iter, HasIter) { + ::quote_into_iter(*self) + } + } + + impl<'q, 'a, T: RepAsIteratorExt<'q> + ?Sized> RepAsIteratorExt<'q> for &'a mut T { + type Iter = T::Iter; + + fn quote_into_iter(&'q self) -> (Self::Iter, HasIter) { + ::quote_into_iter(*self) + } + } + + impl<'q, T: 'q> RepAsIteratorExt<'q> for [T] { + type Iter = slice::Iter<'q, T>; + + fn quote_into_iter(&'q self) -> (Self::Iter, HasIter) { + (self.iter(), HasIter) + } + } + + impl<'q, T: 'q> RepAsIteratorExt<'q> for Vec { + type Iter = slice::Iter<'q, T>; + + fn quote_into_iter(&'q self) -> (Self::Iter, HasIter) { + (self.iter(), HasIter) + } + } + + impl<'q, T: 'q> RepAsIteratorExt<'q> for BTreeSet { + type Iter = btree_set::Iter<'q, T>; + + fn quote_into_iter(&'q self) -> (Self::Iter, HasIter) { + (self.iter(), HasIter) + } + } + + macro_rules! array_rep_slice { + ($($l:tt)*) => { + $( + impl<'q, T: 'q> RepAsIteratorExt<'q> for [T; $l] { + type Iter = slice::Iter<'q, T>; + + fn quote_into_iter(&'q self) -> (Self::Iter, HasIter) { + (self.iter(), HasIter) + } + } + )* + } + } + + array_rep_slice!( + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 + 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 + ); + + impl<'q, T: RepAsIteratorExt<'q>> RepAsIteratorExt<'q> for RepInterp { + type Iter = T::Iter; + + fn quote_into_iter(&'q self) -> (Self::Iter, HasIter) { + self.0.quote_into_iter() + } + } +} + +// Helper type used within interpolations to allow for repeated binding names. +// Implements the relevant traits, and exports a dummy `next()` method. +#[derive(Copy, Clone)] +pub struct RepInterp(pub T); + +impl RepInterp { + // This method is intended to look like `Iterator::next`, and is called when + // a name is bound multiple times, as the previous binding will shadow the + // original `Iterator` object. This allows us to avoid advancing the + // iterator multiple times per iteration. + pub fn next(self) -> Option { + Some(self.0) + } +} + +impl Iterator for RepInterp { + type Item = T::Item; + + fn next(&mut self) -> Option { + self.0.next() + } +} + +impl ToTokens for RepInterp { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.0.to_tokens(tokens); + } +} + +fn is_ident_start(c: u8) -> bool { + (b'a' <= c && c <= b'z') || (b'A' <= c && c <= b'Z') || c == b'_' +} + +fn is_ident_continue(c: u8) -> bool { + (b'a' <= c && c <= b'z') || (b'A' <= c && c <= b'Z') || c == b'_' || (b'0' <= c && c <= b'9') +} + +fn is_ident(token: &str) -> bool { + let mut iter = token.bytes(); + let first_ok = iter.next().map(is_ident_start).unwrap_or(false); + + first_ok && iter.all(is_ident_continue) +} + +pub fn parse(tokens: &mut TokenStream, span: Span, s: &str) { + if is_ident(s) { + // Fast path, since idents are the most common token. + tokens.append(Ident::new(s, span)); + } else { + let s: TokenStream = s.parse().expect("invalid token stream"); + tokens.extend(s.into_iter().map(|mut t| { + t.set_span(span); + t + })); + } +} + +macro_rules! push_punct { + ($name:ident $char1:tt) => { + pub fn $name(tokens: &mut TokenStream, span: Span) { + let mut punct = Punct::new($char1, Spacing::Alone); + punct.set_span(span); + tokens.append(punct); + } + }; + ($name:ident $char1:tt $char2:tt) => { + pub fn $name(tokens: &mut TokenStream, span: Span) { + let mut punct = Punct::new($char1, Spacing::Joint); + punct.set_span(span); + tokens.append(punct); + let mut punct = Punct::new($char2, Spacing::Alone); + punct.set_span(span); + tokens.append(punct); + } + }; + ($name:ident $char1:tt $char2:tt $char3:tt) => { + pub fn $name(tokens: &mut TokenStream, span: Span) { + let mut punct = Punct::new($char1, Spacing::Joint); + punct.set_span(span); + tokens.append(punct); + let mut punct = Punct::new($char2, Spacing::Joint); + punct.set_span(span); + tokens.append(punct); + let mut punct = Punct::new($char3, Spacing::Alone); + punct.set_span(span); + tokens.append(punct); + } + }; +} + +push_punct!(push_add '+'); +push_punct!(push_add_eq '+' '='); +push_punct!(push_and '&'); +push_punct!(push_and_and '&' '&'); +push_punct!(push_and_eq '&' '='); +push_punct!(push_at '@'); +push_punct!(push_bang '!'); +push_punct!(push_caret '^'); +push_punct!(push_caret_eq '^' '='); +push_punct!(push_colon ':'); +push_punct!(push_colon2 ':' ':'); +push_punct!(push_comma ','); +push_punct!(push_div '/'); +push_punct!(push_div_eq '/' '='); +push_punct!(push_dot '.'); +push_punct!(push_dot2 '.' '.'); +push_punct!(push_dot3 '.' '.' '.'); +push_punct!(push_dot_dot_eq '.' '.' '='); +push_punct!(push_eq '='); +push_punct!(push_eq_eq '=' '='); +push_punct!(push_ge '>' '='); +push_punct!(push_gt '>'); +push_punct!(push_le '<' '='); +push_punct!(push_lt '<'); +push_punct!(push_mul_eq '*' '='); +push_punct!(push_ne '!' '='); +push_punct!(push_or '|'); +push_punct!(push_or_eq '|' '='); +push_punct!(push_or_or '|' '|'); +push_punct!(push_pound '#'); +push_punct!(push_question '?'); +push_punct!(push_rarrow '-' '>'); +push_punct!(push_larrow '<' '-'); +push_punct!(push_rem '%'); +push_punct!(push_rem_eq '%' '='); +push_punct!(push_fat_arrow '=' '>'); +push_punct!(push_semi ';'); +push_punct!(push_shl '<' '<'); +push_punct!(push_shl_eq '<' '<' '='); +push_punct!(push_shr '>' '>'); +push_punct!(push_shr_eq '>' '>' '='); +push_punct!(push_star '*'); +push_punct!(push_sub '-'); +push_punct!(push_sub_eq '-' '='); + +// Helper method for constructing identifiers from the `format_ident!` macro, +// handling `r#` prefixes. +// +// Directly parsing the input string may produce a valid identifier, +// although the input string was invalid, due to ignored characters such as +// whitespace and comments. Instead, we always create a non-raw identifier +// to validate that the string is OK, and only parse again if needed. +// +// The `is_ident` method defined above is insufficient for validation, as it +// will reject non-ASCII identifiers. +pub fn mk_ident(id: &str, span: Option) -> Ident { + let span = span.unwrap_or_else(Span::call_site); + + let is_raw = id.starts_with("r#"); + let unraw = Ident::new(if is_raw { &id[2..] } else { id }, span); + if !is_raw { + return unraw; + } + + // At this point, the identifier is raw, and the unraw-ed version of it was + // successfully converted into an identifier. Try to produce a valid raw + // identifier by running the `TokenStream` parser, and unwrapping the first + // token as an `Ident`. + // + // FIXME: When `Ident::new_raw` becomes stable, this method should be + // updated to call it when available. + match id.parse::() { + Ok(ts) => { + let mut iter = ts.into_iter(); + match (iter.next(), iter.next()) { + (Some(TokenTree::Ident(mut id)), None) => { + id.set_span(span); + id + } + _ => unreachable!("valid raw ident fails to parse"), + } + } + Err(_) => unreachable!("valid raw ident fails to parse"), + } +} + +// Adapts from `IdentFragment` to `fmt::Display` for use by the `format_ident!` +// macro, and exposes span information from these fragments. +// +// This struct also has forwarding implementations of the formatting traits +// `Octal`, `LowerHex`, `UpperHex`, and `Binary` to allow for their use within +// `format_ident!`. +#[derive(Copy, Clone)] +pub struct IdentFragmentAdapter(pub T); + +impl IdentFragmentAdapter { + pub fn span(&self) -> Option { + self.0.span() + } +} + +impl fmt::Display for IdentFragmentAdapter { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + IdentFragment::fmt(&self.0, f) + } +} + +impl fmt::Octal for IdentFragmentAdapter { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Octal::fmt(&self.0, f) + } +} + +impl fmt::LowerHex for IdentFragmentAdapter { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::LowerHex::fmt(&self.0, f) + } +} + +impl fmt::UpperHex for IdentFragmentAdapter { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::UpperHex::fmt(&self.0, f) + } +} + +impl fmt::Binary for IdentFragmentAdapter { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Binary::fmt(&self.0, f) + } +} diff --git a/quote/src/spanned.rs b/quote/src/spanned.rs new file mode 100644 index 0000000..55168bd --- /dev/null +++ b/quote/src/spanned.rs @@ -0,0 +1,42 @@ +use crate::ToTokens; +use proc_macro2::{Span, TokenStream}; + +pub trait Spanned { + fn __span(&self) -> Span; +} + +impl Spanned for Span { + fn __span(&self) -> Span { + *self + } +} + +impl Spanned for T { + fn __span(&self) -> Span { + join_spans(self.into_token_stream()) + } +} + +fn join_spans(tokens: TokenStream) -> Span { + let mut iter = tokens.into_iter().filter_map(|tt| { + // FIXME: This shouldn't be required, since optimally spans should + // never be invalid. This filter_map can probably be removed when + // https://github.com/rust-lang/rust/issues/43081 is resolved. + let span = tt.span(); + let debug = format!("{:?}", span); + if debug.ends_with("bytes(0..0)") { + None + } else { + Some(span) + } + }); + + let first = match iter.next() { + Some(span) => span, + None => return Span::call_site(), + }; + + iter.fold(None, |_prev, next| Some(next)) + .and_then(|last| first.join(last)) + .unwrap_or(first) +} diff --git a/quote/src/to_tokens.rs b/quote/src/to_tokens.rs new file mode 100644 index 0000000..7f98083 --- /dev/null +++ b/quote/src/to_tokens.rs @@ -0,0 +1,209 @@ +use super::TokenStreamExt; + +use std::borrow::Cow; +use std::iter; +use std::rc::Rc; + +use proc_macro2::{Group, Ident, Literal, Punct, Span, TokenStream, TokenTree}; + +/// Types that can be interpolated inside a `quote!` invocation. +/// +/// [`quote!`]: macro.quote.html +pub trait ToTokens { + /// Write `self` to the given `TokenStream`. + /// + /// The token append methods provided by the [`TokenStreamExt`] extension + /// trait may be useful for implementing `ToTokens`. + /// + /// [`TokenStreamExt`]: trait.TokenStreamExt.html + /// + /// # Example + /// + /// Example implementation for a struct representing Rust paths like + /// `std::cmp::PartialEq`: + /// + /// ``` + /// use proc_macro2::{TokenTree, Spacing, Span, Punct, TokenStream}; + /// use quote::{TokenStreamExt, ToTokens}; + /// + /// pub struct Path { + /// pub global: bool, + /// pub segments: Vec, + /// } + /// + /// impl ToTokens for Path { + /// fn to_tokens(&self, tokens: &mut TokenStream) { + /// for (i, segment) in self.segments.iter().enumerate() { + /// if i > 0 || self.global { + /// // Double colon `::` + /// tokens.append(Punct::new(':', Spacing::Joint)); + /// tokens.append(Punct::new(':', Spacing::Alone)); + /// } + /// segment.to_tokens(tokens); + /// } + /// } + /// } + /// # + /// # pub struct PathSegment; + /// # + /// # impl ToTokens for PathSegment { + /// # fn to_tokens(&self, tokens: &mut TokenStream) { + /// # unimplemented!() + /// # } + /// # } + /// ``` + fn to_tokens(&self, tokens: &mut TokenStream); + + /// Convert `self` directly into a `TokenStream` object. + /// + /// This method is implicitly implemented using `to_tokens`, and acts as a + /// convenience method for consumers of the `ToTokens` trait. + fn to_token_stream(&self) -> TokenStream { + let mut tokens = TokenStream::new(); + self.to_tokens(&mut tokens); + tokens + } + + /// Convert `self` directly into a `TokenStream` object. + /// + /// This method is implicitly implemented using `to_tokens`, and acts as a + /// convenience method for consumers of the `ToTokens` trait. + fn into_token_stream(self) -> TokenStream + where + Self: Sized, + { + self.to_token_stream() + } +} + +impl<'a, T: ?Sized + ToTokens> ToTokens for &'a T { + fn to_tokens(&self, tokens: &mut TokenStream) { + (**self).to_tokens(tokens); + } +} + +impl<'a, T: ?Sized + ToTokens> ToTokens for &'a mut T { + fn to_tokens(&self, tokens: &mut TokenStream) { + (**self).to_tokens(tokens); + } +} + +impl<'a, T: ?Sized + ToOwned + ToTokens> ToTokens for Cow<'a, T> { + fn to_tokens(&self, tokens: &mut TokenStream) { + (**self).to_tokens(tokens); + } +} + +impl ToTokens for Box { + fn to_tokens(&self, tokens: &mut TokenStream) { + (**self).to_tokens(tokens); + } +} + +impl ToTokens for Rc { + fn to_tokens(&self, tokens: &mut TokenStream) { + (**self).to_tokens(tokens); + } +} + +impl ToTokens for Option { + fn to_tokens(&self, tokens: &mut TokenStream) { + if let Some(ref t) = *self { + t.to_tokens(tokens); + } + } +} + +impl ToTokens for str { + fn to_tokens(&self, tokens: &mut TokenStream) { + tokens.append(Literal::string(self)); + } +} + +impl ToTokens for String { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.as_str().to_tokens(tokens); + } +} + +macro_rules! primitive { + ($($t:ident => $name:ident)*) => ($( + impl ToTokens for $t { + fn to_tokens(&self, tokens: &mut TokenStream) { + tokens.append(Literal::$name(*self)); + } + } + )*) +} + +primitive! { + i8 => i8_suffixed + i16 => i16_suffixed + i32 => i32_suffixed + i64 => i64_suffixed + i128 => i128_suffixed + isize => isize_suffixed + + u8 => u8_suffixed + u16 => u16_suffixed + u32 => u32_suffixed + u64 => u64_suffixed + u128 => u128_suffixed + usize => usize_suffixed + + f32 => f32_suffixed + f64 => f64_suffixed +} + +impl ToTokens for char { + fn to_tokens(&self, tokens: &mut TokenStream) { + tokens.append(Literal::character(*self)); + } +} + +impl ToTokens for bool { + fn to_tokens(&self, tokens: &mut TokenStream) { + let word = if *self { "true" } else { "false" }; + tokens.append(Ident::new(word, Span::call_site())); + } +} + +impl ToTokens for Group { + fn to_tokens(&self, tokens: &mut TokenStream) { + tokens.append(self.clone()); + } +} + +impl ToTokens for Ident { + fn to_tokens(&self, tokens: &mut TokenStream) { + tokens.append(self.clone()); + } +} + +impl ToTokens for Punct { + fn to_tokens(&self, tokens: &mut TokenStream) { + tokens.append(self.clone()); + } +} + +impl ToTokens for Literal { + fn to_tokens(&self, tokens: &mut TokenStream) { + tokens.append(self.clone()); + } +} + +impl ToTokens for TokenTree { + fn to_tokens(&self, dst: &mut TokenStream) { + dst.append(self.clone()); + } +} + +impl ToTokens for TokenStream { + fn to_tokens(&self, dst: &mut TokenStream) { + dst.extend(iter::once(self.clone())); + } + + fn into_token_stream(self) -> TokenStream { + self + } +} diff --git a/quote/tests/compiletest.rs b/quote/tests/compiletest.rs new file mode 100644 index 0000000..f9aea23 --- /dev/null +++ b/quote/tests/compiletest.rs @@ -0,0 +1,6 @@ +#[rustversion::attr(not(nightly), ignore)] +#[test] +fn ui() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/ui/*.rs"); +} diff --git a/quote/tests/test.rs b/quote/tests/test.rs new file mode 100644 index 0000000..957d470 --- /dev/null +++ b/quote/tests/test.rs @@ -0,0 +1,429 @@ +#![cfg_attr(feature = "cargo-clippy", allow(blacklisted_name))] + +use std::borrow::Cow; +use std::collections::BTreeSet; + +use proc_macro2::{Ident, Span, TokenStream}; +use quote::{format_ident, quote, TokenStreamExt}; + +struct X; + +impl quote::ToTokens for X { + fn to_tokens(&self, tokens: &mut TokenStream) { + tokens.append(Ident::new("X", Span::call_site())); + } +} + +#[test] +fn test_quote_impl() { + let tokens = quote! { + impl<'a, T: ToTokens> ToTokens for &'a T { + fn to_tokens(&self, tokens: &mut TokenStream) { + (**self).to_tokens(tokens) + } + } + }; + + let expected = concat!( + "impl < 'a , T : ToTokens > ToTokens for & 'a T { ", + "fn to_tokens ( & self , tokens : & mut TokenStream ) { ", + "( * * self ) . to_tokens ( tokens ) ", + "} ", + "}" + ); + + assert_eq!(expected, tokens.to_string()); +} + +#[test] +fn test_substitution() { + let x = X; + let tokens = quote!(#x <#x> (#x) [#x] {#x}); + + let expected = "X < X > ( X ) [ X ] { X }"; + + assert_eq!(expected, tokens.to_string()); +} + +#[test] +fn test_iter() { + let primes = &[X, X, X, X]; + + assert_eq!("X X X X", quote!(#(#primes)*).to_string()); + + assert_eq!("X , X , X , X ,", quote!(#(#primes,)*).to_string()); + + assert_eq!("X , X , X , X", quote!(#(#primes),*).to_string()); +} + +#[test] +fn test_advanced() { + let generics = quote!( <'a, T> ); + + let where_clause = quote!( where T: Serialize ); + + let field_ty = quote!(String); + + let item_ty = quote!(Cow<'a, str>); + + let path = quote!(SomeTrait::serialize_with); + + let value = quote!(self.x); + + let tokens = quote! { + struct SerializeWith #generics #where_clause { + value: &'a #field_ty, + phantom: ::std::marker::PhantomData<#item_ty>, + } + + impl #generics ::serde::Serialize for SerializeWith #generics #where_clause { + fn serialize(&self, s: &mut S) -> Result<(), S::Error> + where S: ::serde::Serializer + { + #path(self.value, s) + } + } + + SerializeWith { + value: #value, + phantom: ::std::marker::PhantomData::<#item_ty>, + } + }; + + let expected = concat!( + "struct SerializeWith < 'a , T > where T : Serialize { ", + "value : & 'a String , ", + "phantom : :: std :: marker :: PhantomData < Cow < 'a , str > > , ", + "} ", + "impl < 'a , T > :: serde :: Serialize for SerializeWith < 'a , T > where T : Serialize { ", + "fn serialize < S > ( & self , s : & mut S ) -> Result < ( ) , S :: Error > ", + "where S : :: serde :: Serializer ", + "{ ", + "SomeTrait :: serialize_with ( self . value , s ) ", + "} ", + "} ", + "SerializeWith { ", + "value : self . x , ", + "phantom : :: std :: marker :: PhantomData :: < Cow < 'a , str > > , ", + "}" + ); + + assert_eq!(expected, tokens.to_string()); +} + +#[test] +fn test_integer() { + let ii8 = -1i8; + let ii16 = -1i16; + let ii32 = -1i32; + let ii64 = -1i64; + let ii128 = -1i128; + let iisize = -1isize; + let uu8 = 1u8; + let uu16 = 1u16; + let uu32 = 1u32; + let uu64 = 1u64; + let uu128 = 1u128; + let uusize = 1usize; + + let tokens = quote! { + #ii8 #ii16 #ii32 #ii64 #ii128 #iisize + #uu8 #uu16 #uu32 #uu64 #uu128 #uusize + }; + let expected = "-1i8 -1i16 -1i32 -1i64 -1i128 -1isize 1u8 1u16 1u32 1u64 1u128 1usize"; + assert_eq!(expected, tokens.to_string()); +} + +#[test] +fn test_floating() { + let e32 = 2.345f32; + + let e64 = 2.345f64; + + let tokens = quote! { + #e32 + #e64 + }; + let expected = concat!("2.345f32 2.345f64"); + assert_eq!(expected, tokens.to_string()); +} + +#[test] +fn test_char() { + let zero = '\0'; + let pound = '#'; + let quote = '"'; + let apost = '\''; + let newline = '\n'; + let heart = '\u{2764}'; + + let tokens = quote! { + #zero #pound #quote #apost #newline #heart + }; + let expected = "'\\u{0}' '#' '\"' '\\'' '\\n' '\\u{2764}'"; + assert_eq!(expected, tokens.to_string()); +} + +#[test] +fn test_str() { + let s = "\0 a 'b \" c"; + let tokens = quote!(#s); + let expected = "\"\\u{0} a 'b \\\" c\""; + assert_eq!(expected, tokens.to_string()); +} + +#[test] +fn test_string() { + let s = "\0 a 'b \" c".to_string(); + let tokens = quote!(#s); + let expected = "\"\\u{0} a 'b \\\" c\""; + assert_eq!(expected, tokens.to_string()); +} + +#[test] +fn test_ident() { + let foo = Ident::new("Foo", Span::call_site()); + let bar = Ident::new(&format!("Bar{}", 7), Span::call_site()); + let tokens = quote!(struct #foo; enum #bar {}); + let expected = "struct Foo ; enum Bar7 { }"; + assert_eq!(expected, tokens.to_string()); +} + +#[test] +fn test_duplicate() { + let ch = 'x'; + + let tokens = quote!(#ch #ch); + + let expected = "'x' 'x'"; + assert_eq!(expected, tokens.to_string()); +} + +#[test] +fn test_fancy_repetition() { + let foo = vec!["a", "b"]; + let bar = vec![true, false]; + + let tokens = quote! { + #(#foo: #bar),* + }; + + let expected = r#""a" : true , "b" : false"#; + assert_eq!(expected, tokens.to_string()); +} + +#[test] +fn test_nested_fancy_repetition() { + let nested = vec![vec!['a', 'b', 'c'], vec!['x', 'y', 'z']]; + + let tokens = quote! { + #( + #(#nested)* + ),* + }; + + let expected = "'a' 'b' 'c' , 'x' 'y' 'z'"; + assert_eq!(expected, tokens.to_string()); +} + +#[test] +fn test_duplicate_name_repetition() { + let foo = &["a", "b"]; + + let tokens = quote! { + #(#foo: #foo),* + #(#foo: #foo),* + }; + + let expected = r#""a" : "a" , "b" : "b" "a" : "a" , "b" : "b""#; + assert_eq!(expected, tokens.to_string()); +} + +#[test] +fn test_duplicate_name_repetition_no_copy() { + let foo = vec!["a".to_owned(), "b".to_owned()]; + + let tokens = quote! { + #(#foo: #foo),* + }; + + let expected = r#""a" : "a" , "b" : "b""#; + assert_eq!(expected, tokens.to_string()); +} + +#[test] +fn test_btreeset_repetition() { + let mut set = BTreeSet::new(); + set.insert("a".to_owned()); + set.insert("b".to_owned()); + + let tokens = quote! { + #(#set: #set),* + }; + + let expected = r#""a" : "a" , "b" : "b""#; + assert_eq!(expected, tokens.to_string()); +} + +#[test] +fn test_variable_name_conflict() { + // The implementation of `#(...),*` uses the variable `_i` but it should be + // fine, if a little confusing when debugging. + let _i = vec!['a', 'b']; + let tokens = quote! { #(#_i),* }; + let expected = "'a' , 'b'"; + assert_eq!(expected, tokens.to_string()); +} + +#[test] +fn test_nonrep_in_repetition() { + let rep = vec!["a", "b"]; + let nonrep = "c"; + + let tokens = quote! { + #(#rep #rep : #nonrep #nonrep),* + }; + + let expected = r#""a" "a" : "c" "c" , "b" "b" : "c" "c""#; + assert_eq!(expected, tokens.to_string()); +} + +#[test] +fn test_empty_quote() { + let tokens = quote!(); + assert_eq!("", tokens.to_string()); +} + +#[test] +fn test_box_str() { + let b = "str".to_owned().into_boxed_str(); + let tokens = quote! { #b }; + assert_eq!("\"str\"", tokens.to_string()); +} + +#[test] +fn test_cow() { + let owned: Cow = Cow::Owned(Ident::new("owned", Span::call_site())); + + let ident = Ident::new("borrowed", Span::call_site()); + let borrowed = Cow::Borrowed(&ident); + + let tokens = quote! { #owned #borrowed }; + assert_eq!("owned borrowed", tokens.to_string()); +} + +#[test] +fn test_closure() { + fn field_i(i: usize) -> Ident { + format_ident!("__field{}", i) + } + + let fields = (0usize..3) + .map(field_i as fn(_) -> _) + .map(|var| quote! { #var }); + + let tokens = quote! { #(#fields)* }; + assert_eq!("__field0 __field1 __field2", tokens.to_string()); +} + +#[test] +fn test_append_tokens() { + let mut a = quote!(a); + let b = quote!(b); + a.append_all(b); + assert_eq!("a b", a.to_string()); +} + +#[test] +fn test_format_ident() { + let id0 = format_ident!("Aa"); + let id1 = format_ident!("Hello{x}", x = id0); + let id2 = format_ident!("Hello{x}", x = 5usize); + let id3 = format_ident!("Hello{}_{x}", id0, x = 10usize); + let id4 = format_ident!("Aa", span = Span::call_site()); + + assert_eq!(id0, "Aa"); + assert_eq!(id1, "HelloAa"); + assert_eq!(id2, "Hello5"); + assert_eq!(id3, "HelloAa_10"); + assert_eq!(id4, "Aa"); +} + +#[test] +fn test_format_ident_strip_raw() { + let id = format_ident!("r#struct"); + let my_id = format_ident!("MyId{}", id); + let raw_my_id = format_ident!("r#MyId{}", id); + + assert_eq!(id, "r#struct"); + assert_eq!(my_id, "MyIdstruct"); + assert_eq!(raw_my_id, "r#MyIdstruct"); +} + +#[test] +fn test_outer_line_comment() { + let tokens = quote! { + /// doc + }; + let expected = "# [ doc = r\" doc\" ]"; + assert_eq!(expected, tokens.to_string()); +} + +#[test] +fn test_inner_line_comment() { + let tokens = quote! { + //! doc + }; + let expected = "# ! [ doc = r\" doc\" ]"; + assert_eq!(expected, tokens.to_string()); +} + +#[test] +fn test_outer_block_comment() { + let tokens = quote! { + /** doc */ + }; + let expected = "# [ doc = r\" doc \" ]"; + assert_eq!(expected, tokens.to_string()); +} + +#[test] +fn test_inner_block_comment() { + let tokens = quote! { + /*! doc */ + }; + let expected = "# ! [ doc = r\" doc \" ]"; + assert_eq!(expected, tokens.to_string()); +} + +#[test] +fn test_outer_attr() { + let tokens = quote! { + #[inline] + }; + let expected = "# [ inline ]"; + assert_eq!(expected, tokens.to_string()); +} + +#[test] +fn test_inner_attr() { + let tokens = quote! { + #![no_std] + }; + let expected = "# ! [ no_std ]"; + assert_eq!(expected, tokens.to_string()); +} + +// https://github.com/dtolnay/quote/issues/130 +#[test] +fn test_star_after_repetition() { + let c = vec!['0', '1']; + let tokens = quote! { + #( + f(#c); + )* + *out = None; + }; + let expected = "f ( '0' ) ; f ( '1' ) ; * out = None ;"; + assert_eq!(expected, tokens.to_string()); +} diff --git a/quote/tests/ui/does-not-have-iter-interpolated-dup.rs b/quote/tests/ui/does-not-have-iter-interpolated-dup.rs new file mode 100644 index 0000000..0a39f41 --- /dev/null +++ b/quote/tests/ui/does-not-have-iter-interpolated-dup.rs @@ -0,0 +1,9 @@ +use quote::quote; + +fn main() { + let nonrep = ""; + + // Without some protection against repetitions with no iterator somewhere + // inside, this would loop infinitely. + quote!(#(#nonrep #nonrep)*); +} diff --git a/quote/tests/ui/does-not-have-iter-interpolated-dup.stderr b/quote/tests/ui/does-not-have-iter-interpolated-dup.stderr new file mode 100644 index 0000000..6ee6fdf --- /dev/null +++ b/quote/tests/ui/does-not-have-iter-interpolated-dup.stderr @@ -0,0 +1,11 @@ +error[E0308]: mismatched types + --> $DIR/does-not-have-iter-interpolated-dup.rs:8:5 + | +8 | quote!(#(#nonrep #nonrep)*); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `quote::__rt::HasIterator`, found struct `quote::__rt::ThereIsNoIteratorInRepetition` + | + = note: expected type `quote::__rt::HasIterator` + found type `quote::__rt::ThereIsNoIteratorInRepetition` + = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) + +For more information about this error, try `rustc --explain E0308`. diff --git a/quote/tests/ui/does-not-have-iter-interpolated.rs b/quote/tests/ui/does-not-have-iter-interpolated.rs new file mode 100644 index 0000000..2c740cc --- /dev/null +++ b/quote/tests/ui/does-not-have-iter-interpolated.rs @@ -0,0 +1,9 @@ +use quote::quote; + +fn main() { + let nonrep = ""; + + // Without some protection against repetitions with no iterator somewhere + // inside, this would loop infinitely. + quote!(#(#nonrep)*); +} diff --git a/quote/tests/ui/does-not-have-iter-interpolated.stderr b/quote/tests/ui/does-not-have-iter-interpolated.stderr new file mode 100644 index 0000000..8d6c990 --- /dev/null +++ b/quote/tests/ui/does-not-have-iter-interpolated.stderr @@ -0,0 +1,11 @@ +error[E0308]: mismatched types + --> $DIR/does-not-have-iter-interpolated.rs:8:5 + | +8 | quote!(#(#nonrep)*); + | ^^^^^^^^^^^^^^^^^^^^ expected struct `quote::__rt::HasIterator`, found struct `quote::__rt::ThereIsNoIteratorInRepetition` + | + = note: expected type `quote::__rt::HasIterator` + found type `quote::__rt::ThereIsNoIteratorInRepetition` + = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) + +For more information about this error, try `rustc --explain E0308`. diff --git a/quote/tests/ui/does-not-have-iter-separated.rs b/quote/tests/ui/does-not-have-iter-separated.rs new file mode 100644 index 0000000..c027243 --- /dev/null +++ b/quote/tests/ui/does-not-have-iter-separated.rs @@ -0,0 +1,5 @@ +use quote::quote; + +fn main() { + quote!(#(a b),*); +} diff --git a/quote/tests/ui/does-not-have-iter-separated.stderr b/quote/tests/ui/does-not-have-iter-separated.stderr new file mode 100644 index 0000000..c1fd0ad --- /dev/null +++ b/quote/tests/ui/does-not-have-iter-separated.stderr @@ -0,0 +1,11 @@ +error[E0308]: mismatched types + --> $DIR/does-not-have-iter-separated.rs:4:5 + | +4 | quote!(#(a b),*); + | ^^^^^^^^^^^^^^^^^ expected struct `quote::__rt::HasIterator`, found struct `quote::__rt::ThereIsNoIteratorInRepetition` + | + = note: expected type `quote::__rt::HasIterator` + found type `quote::__rt::ThereIsNoIteratorInRepetition` + = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) + +For more information about this error, try `rustc --explain E0308`. diff --git a/quote/tests/ui/does-not-have-iter.rs b/quote/tests/ui/does-not-have-iter.rs new file mode 100644 index 0000000..8908353 --- /dev/null +++ b/quote/tests/ui/does-not-have-iter.rs @@ -0,0 +1,5 @@ +use quote::quote; + +fn main() { + quote!(#(a b)*); +} diff --git a/quote/tests/ui/does-not-have-iter.stderr b/quote/tests/ui/does-not-have-iter.stderr new file mode 100644 index 0000000..3b87705 --- /dev/null +++ b/quote/tests/ui/does-not-have-iter.stderr @@ -0,0 +1,11 @@ +error[E0308]: mismatched types + --> $DIR/does-not-have-iter.rs:4:5 + | +4 | quote!(#(a b)*); + | ^^^^^^^^^^^^^^^^ expected struct `quote::__rt::HasIterator`, found struct `quote::__rt::ThereIsNoIteratorInRepetition` + | + = note: expected type `quote::__rt::HasIterator` + found type `quote::__rt::ThereIsNoIteratorInRepetition` + = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) + +For more information about this error, try `rustc --explain E0308`. diff --git a/quote/tests/ui/not-quotable.rs b/quote/tests/ui/not-quotable.rs new file mode 100644 index 0000000..f991c18 --- /dev/null +++ b/quote/tests/ui/not-quotable.rs @@ -0,0 +1,7 @@ +use quote::quote; +use std::net::Ipv4Addr; + +fn main() { + let ip = Ipv4Addr::LOCALHOST; + let _ = quote! { #ip }; +} diff --git a/quote/tests/ui/not-quotable.stderr b/quote/tests/ui/not-quotable.stderr new file mode 100644 index 0000000..f51f85f --- /dev/null +++ b/quote/tests/ui/not-quotable.stderr @@ -0,0 +1,10 @@ +error[E0277]: the trait bound `std::net::Ipv4Addr: quote::to_tokens::ToTokens` is not satisfied + --> $DIR/not-quotable.rs:6:13 + | +6 | let _ = quote! { #ip }; + | ^^^^^^^^^^^^^^ the trait `quote::to_tokens::ToTokens` is not implemented for `std::net::Ipv4Addr` + | + = note: required by `quote::to_tokens::ToTokens::to_tokens` + = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) + +For more information about this error, try `rustc --explain E0277`. diff --git a/quote/tests/ui/not-repeatable.rs b/quote/tests/ui/not-repeatable.rs new file mode 100644 index 0000000..ff18060 --- /dev/null +++ b/quote/tests/ui/not-repeatable.rs @@ -0,0 +1,7 @@ +use quote::quote; +use std::net::Ipv4Addr; + +fn main() { + let ip = Ipv4Addr::LOCALHOST; + let _ = quote! { #(#ip)* }; +} diff --git a/quote/tests/ui/not-repeatable.stderr b/quote/tests/ui/not-repeatable.stderr new file mode 100644 index 0000000..ddcac05 --- /dev/null +++ b/quote/tests/ui/not-repeatable.stderr @@ -0,0 +1,14 @@ +error[E0599]: no method named `quote_into_iter` found for type `std::net::Ipv4Addr` in the current scope + --> $DIR/not-repeatable.rs:6:13 + | +6 | let _ = quote! { #(#ip)* }; + | ^^^^^^^^^^^^^^^^^^ + | + = note: the method `quote_into_iter` exists but the following trait bounds were not satisfied: + `&mut std::net::Ipv4Addr : quote::__rt::ext::RepIteratorExt` + `&std::net::Ipv4Addr : quote::__rt::ext::RepIteratorExt` + `std::net::Ipv4Addr : quote::__rt::ext::RepIteratorExt` + `std::net::Ipv4Addr : quote::__rt::ext::RepToTokensExt` + = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) + +For more information about this error, try `rustc --explain E0599`. diff --git a/quote/tests/ui/wrong-type-span.rs b/quote/tests/ui/wrong-type-span.rs new file mode 100644 index 0000000..1ce391c --- /dev/null +++ b/quote/tests/ui/wrong-type-span.rs @@ -0,0 +1,7 @@ +use quote::quote_spanned; + +fn main() { + let span = ""; + let x = 0; + quote_spanned!(span=> #x); +} diff --git a/quote/tests/ui/wrong-type-span.stderr b/quote/tests/ui/wrong-type-span.stderr new file mode 100644 index 0000000..a6ae8ef --- /dev/null +++ b/quote/tests/ui/wrong-type-span.stderr @@ -0,0 +1,10 @@ +error[E0308]: mismatched types + --> $DIR/wrong-type-span.rs:6:20 + | +6 | quote_spanned!(span=> #x); + | ^^^^ expected struct `proc_macro2::Span`, found &str + | + = note: expected type `proc_macro2::Span` + found type `&str` + +For more information about this error, try `rustc --explain E0308`. diff --git a/rustversion/.gitignore b/rustversion/.gitignore new file mode 100644 index 0000000..6936990 --- /dev/null +++ b/rustversion/.gitignore @@ -0,0 +1,3 @@ +/target +**/*.rs.bk +Cargo.lock diff --git a/rustversion/.travis.yml b/rustversion/.travis.yml new file mode 100644 index 0000000..1a615ba --- /dev/null +++ b/rustversion/.travis.yml @@ -0,0 +1,17 @@ +language: rust + +rust: + - nightly + - beta + - stable + - 1.31.0 + +script: + - cargo test + +matrix: + include: + - rust: nightly + name: Minimal versions + before_script: + - cargo update -Z minimal-versions diff --git a/rustversion/Cargo.toml b/rustversion/Cargo.toml new file mode 100644 index 0000000..789c5f4 --- /dev/null +++ b/rustversion/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "rustversion" +version = "1.0.1" +authors = ["David Tolnay "] +edition = "2018" +license = "MIT OR Apache-2.0" +description = "Conditional compilation according to rustc compiler version" +repository = "https://github.com/dtolnay/rustversion" +documentation = "https://docs.rs/rustversion" +readme = "README.md" + +[lib] +proc-macro = true + +[badges] +travis-ci = { repository = "dtolnay/rustversion" } + +[dependencies] +proc-macro2 = "1.0" +quote = "1.0" +syn = { version = "1.0.1", features = ["full"] } diff --git a/rustversion/LICENSE-APACHE b/rustversion/LICENSE-APACHE new file mode 100644 index 0000000..16fe87b --- /dev/null +++ b/rustversion/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/rustversion/LICENSE-MIT b/rustversion/LICENSE-MIT new file mode 100644 index 0000000..31aa793 --- /dev/null +++ b/rustversion/LICENSE-MIT @@ -0,0 +1,23 @@ +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/rustversion/README.md b/rustversion/README.md new file mode 100644 index 0000000..f7fdab1 --- /dev/null +++ b/rustversion/README.md @@ -0,0 +1,138 @@ +Compiler version cfg +==================== + +[![Build Status](https://api.travis-ci.com/dtolnay/rustversion.svg?branch=master)](https://travis-ci.com/dtolnay/rustversion) +[![Latest Version](https://img.shields.io/crates/v/rustversion.svg)](https://crates.io/crates/rustversion) +[![Rust Documentation](https://img.shields.io/badge/api-rustdoc-blue.svg)](https://docs.rs/rustversion) + +This crate provides macros for conditional compilation according to rustc +compiler version, analogous to [`#[cfg(...)]`][cfg] and +[`#[cfg_attr(...)]`][cfg_attr]. + +[cfg]: https://doc.rust-lang.org/reference/conditional-compilation.html#the-cfg-attribute +[cfg_attr]: https://doc.rust-lang.org/reference/conditional-compilation.html#the-cfg_attr-attribute + +```toml +[dependencies] +rustversion = "1.0" +``` + +
+ +## Selectors + +- `#[rustversion::stable]` + —
+ True on any stable compiler. + +- `#[rustversion::stable(1.34)]` + —
+ True on exactly the specified stable compiler. + +- `#[rustversion::beta]` + —
+ True on any beta compiler. + +- `#[rustversion::nightly]` + —
+ True on any nightly compiler or dev build. + +- `#[rustversion::nightly(2019-01-01)]` + —
+ True on exactly one nightly. + +- `#[rustversion::since(1.34)]` + —
+ True on that stable release and any later compiler, including beta and + nightly. + +- `#[rustversion::since(2019-01-01)]` + —
+ True on that nightly and all newer ones. + +- `#[rustversion::before(`version or date`)]` + —
+ Negative of *#[rustversion::since(...)]*. + +- `#[rustversion::not(`selector`)]` + —
+ Negative of any selector; for example *#[rustversion::not(nightly)]*. + +- `#[rustversion::any(`selectors...`)]` + —
+ True if any of the comma-separated selectors is true; for example + *#[rustversion::any(stable, beta)]*. + +- `#[rustversion::all(`selectors...`)]` + —
+ True if all of the comma-separated selectors are true; for example + *#[rustversion::all(since(1.31), before(1.34))]*. + +- `#[rustversion::attr(`selector`, `attribute`)]` + —
+ For conditional inclusion of attributes; analogous to `cfg_attr`. + +
+ +## Use cases + +Providing additional trait impls as types are stabilized in the standard library +without breaking compatibility with older compilers; in this case Pin\ +stabilized in [Rust 1.33][pin]: + +[pin]: https://blog.rust-lang.org/2019/02/28/Rust-1.33.0.html#pinning + +```rust +#[rustversion::since(1.33)] +use std::pin::Pin; + +#[rustversion::since(1.33)] +impl MyTrait for Pin

{ + /* ... */ +} +``` + +Similar but for language features; the ability to control alignment greater than +1 of packed structs was stabilized in [Rust 1.33][packed]. + +[packed]: https://github.com/rust-lang/rust/blob/master/RELEASES.md#version-1330-2019-02-28 + +```rust +#[rustversion::attr(before(1.33), repr(packed))] +#[rustversion::attr(since(1.33), repr(packed(2)))] +struct Six(i16, i32); + +fn main() { + println!("{}", std::mem::align_of::()); +} +``` + +Augmenting code with `const` as const impls are stabilized in the standard +library. This use of `const` as an attribute is recognized as a special case by +the rustversion::attr macro. + +```rust +use std::time::Duration; + +#[rustversion::attr(since(1.32), const)] +fn duration_as_days(dur: Duration) -> u64 { + dur.as_secs() / 60 / 60 / 24 +} +``` + +
+ +#### License + + +Licensed under either of Apache License, Version +2.0 or MIT license at your option. + + +
+ + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in this crate 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/rustversion/src/attr.rs b/rustversion/src/attr.rs new file mode 100644 index 0000000..591b2c0 --- /dev/null +++ b/rustversion/src/attr.rs @@ -0,0 +1,35 @@ +use crate::expr::Expr; +use proc_macro2::TokenStream; +use syn::parse::{Parse, ParseStream, Result}; +use syn::Token; + +pub struct Args { + pub condition: Expr, + pub then: Then, +} + +pub enum Then { + Const(Token![const]), + Attribute(TokenStream), +} + +impl Parse for Args { + fn parse(input: ParseStream) -> Result { + let condition: Expr = input.parse()?; + + input.parse::()?; + if input.is_empty() { + return Err(input.error("expected one or more attrs")); + } + + let const_token: Option = input.parse()?; + let then = if let Some(const_token) = const_token { + input.parse::>()?; + Then::Const(const_token) + } else { + input.parse().map(Then::Attribute)? + }; + + Ok(Args { condition, then }) + } +} diff --git a/rustversion/src/bound.rs b/rustversion/src/bound.rs new file mode 100644 index 0000000..2546637 --- /dev/null +++ b/rustversion/src/bound.rs @@ -0,0 +1,84 @@ +use crate::date::Date; +use crate::version::{Channel::*, Version}; +use quote::quote; +use std::cmp::Ordering; +use syn::parse::{Error, Parse, ParseStream, Result}; +use syn::{LitFloat, LitInt, Token}; + +pub enum Bound { + Nightly(Date), + Stable(Release), +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct Release { + pub minor: u16, + pub patch: Option, +} + +impl Parse for Bound { + fn parse(input: ParseStream) -> Result { + if input.peek2(Token![-]) { + input.parse().map(Bound::Nightly) + } else { + input.parse().map(Bound::Stable) + } + } +} + +impl Parse for Release { + fn parse(input: ParseStream) -> Result { + let span = input.cursor().token_stream(); + let error = || Error::new_spanned(&span, "expected rustc release number, like 1.31"); + + let major_minor: LitFloat = input.parse().map_err(|_| error())?; + let string = quote!(#major_minor).to_string(); + + if !string.starts_with("1.") { + return Err(error()); + } + + let minor: u16 = string[2..].parse().map_err(|_| error())?; + + let patch = if input.parse::>()?.is_some() { + let int: LitInt = input.parse().map_err(|_| error())?; + Some(int.base10_parse().map_err(|_| error())?) + } else { + None + }; + + Ok(Release { minor, patch }) + } +} + +impl PartialEq for Version { + fn eq(&self, rhs: &Bound) -> bool { + match rhs { + Bound::Nightly(date) => match self.channel { + Stable | Beta | Dev => false, + Nightly(nightly) => nightly == *date, + }, + Bound::Stable(release) => { + self.minor == release.minor + && release.patch.map_or(true, |patch| self.patch == patch) + } + } + } +} + +impl PartialOrd for Version { + fn partial_cmp(&self, rhs: &Bound) -> Option { + match rhs { + Bound::Nightly(date) => match self.channel { + Stable | Beta => Some(Ordering::Less), + Nightly(nightly) => Some(nightly.cmp(date)), + Dev => Some(Ordering::Greater), + }, + Bound::Stable(release) => { + let version = (self.minor, self.patch); + let bound = (release.minor, release.patch.unwrap_or(0)); + Some(version.cmp(&bound)) + } + } + } +} diff --git a/rustversion/src/date.rs b/rustversion/src/date.rs new file mode 100644 index 0000000..631b762 --- /dev/null +++ b/rustversion/src/date.rs @@ -0,0 +1,77 @@ +use crate::time; +use std::fmt::{self, Display}; +use std::num::ParseIntError; +use std::str::FromStr; +use syn::parse::{Error, Parse, ParseStream}; +use syn::{LitInt, Token}; + +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct Date { + pub year: u16, + pub month: u8, + pub day: u8, +} + +impl Display for Date { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + write!( + formatter, + "{:04}-{:02}-{:02}", + self.year, self.month, self.day, + ) + } +} + +pub struct ParseDateError; + +impl From for ParseDateError { + fn from(_err: ParseIntError) -> Self { + ParseDateError + } +} + +impl FromStr for Date { + type Err = ParseDateError; + + fn from_str(s: &str) -> Result { + let mut date = s.split('-'); + let year = date.next().ok_or(ParseDateError)?.parse()?; + let month = date.next().ok_or(ParseDateError)?.parse()?; + let day = date.next().ok_or(ParseDateError)?.parse()?; + match date.next() { + None => Ok(Date { year, month, day }), + Some(_) => Err(ParseDateError), + } + } +} + +impl Parse for Date { + fn parse(input: ParseStream) -> syn::Result { + let span = input.cursor().token_stream(); + let error = || { + Error::new_spanned( + &span, + format!("expected nightly date, like {}", time::today()), + ) + }; + + let year: LitInt = input.parse().map_err(|_| error())?; + input.parse::()?; + let month: LitInt = input.parse().map_err(|_| error())?; + input.parse::()?; + let day: LitInt = input.parse().map_err(|_| error())?; + + let year = year.base10_parse::().map_err(|_| error())?; + let month = month.base10_parse::().map_err(|_| error())?; + let day = day.base10_parse::().map_err(|_| error())?; + if year >= 3000 || month > 12 || day > 31 { + return Err(error()); + } + + Ok(Date { + year: year as u16, + month: month as u8, + day: day as u8, + }) + } +} diff --git a/rustversion/src/expr.rs b/rustversion/src/expr.rs new file mode 100644 index 0000000..2ea91af --- /dev/null +++ b/rustversion/src/expr.rs @@ -0,0 +1,177 @@ +use crate::bound::{Bound, Release}; +use crate::date::Date; +use crate::version::{Channel, Version}; +use syn::parse::{Parse, ParseStream, Result}; +use syn::punctuated::Punctuated; +use syn::{parenthesized, token, Token}; + +pub enum Expr { + Stable, + Beta, + Nightly, + Date(Date), + Since(Bound), + Before(Bound), + Release(Release), + Not(Box), + Any(Vec), + All(Vec), +} + +impl Expr { + pub fn eval(&self, rustc: Version) -> bool { + use self::Expr::*; + + match self { + Stable => rustc.channel == Channel::Stable, + Beta => rustc.channel == Channel::Beta, + Nightly => match rustc.channel { + Channel::Nightly(_) | Channel::Dev => true, + Channel::Stable | Channel::Beta => false, + }, + Date(date) => match rustc.channel { + Channel::Nightly(rustc) => rustc == *date, + Channel::Stable | Channel::Beta | Channel::Dev => false, + }, + Since(bound) => rustc >= *bound, + Before(bound) => rustc < *bound, + Release(release) => { + rustc.channel == Channel::Stable + && rustc.minor == release.minor + && release.patch.map_or(true, |patch| rustc.patch == patch) + } + Not(expr) => !expr.eval(rustc), + Any(exprs) => exprs.iter().any(|e| e.eval(rustc)), + All(exprs) => exprs.iter().all(|e| e.eval(rustc)), + } + } +} + +type Exprs = Punctuated; + +mod keyword { + syn::custom_keyword!(stable); + syn::custom_keyword!(beta); + syn::custom_keyword!(nightly); + syn::custom_keyword!(since); + syn::custom_keyword!(before); + syn::custom_keyword!(not); + syn::custom_keyword!(any); + syn::custom_keyword!(all); +} + +impl Parse for Expr { + fn parse(input: ParseStream) -> Result { + let lookahead = input.lookahead1(); + if lookahead.peek(keyword::stable) { + Self::parse_stable(input) + } else if lookahead.peek(keyword::beta) { + Self::parse_beta(input) + } else if lookahead.peek(keyword::nightly) { + Self::parse_nightly(input) + } else if lookahead.peek(keyword::since) { + Self::parse_since(input) + } else if lookahead.peek(keyword::before) { + Self::parse_before(input) + } else if lookahead.peek(keyword::not) { + Self::parse_not(input) + } else if lookahead.peek(keyword::any) { + Self::parse_any(input) + } else if lookahead.peek(keyword::all) { + Self::parse_all(input) + } else { + Err(lookahead.error()) + } + } +} + +impl Expr { + fn parse_nightly(input: ParseStream) -> Result { + input.parse::()?; + + if !input.peek(token::Paren) { + return Ok(Expr::Nightly); + } + + let paren; + parenthesized!(paren in input); + let date: Date = paren.parse()?; + paren.parse::>()?; + + Ok(Expr::Date(date)) + } + + fn parse_beta(input: ParseStream) -> Result { + input.parse::()?; + + Ok(Expr::Beta) + } + + fn parse_stable(input: ParseStream) -> Result { + input.parse::()?; + + if !input.peek(token::Paren) { + return Ok(Expr::Stable); + } + + let paren; + parenthesized!(paren in input); + let release: Release = paren.parse()?; + paren.parse::>()?; + + Ok(Expr::Release(release)) + } + + fn parse_since(input: ParseStream) -> Result { + input.parse::()?; + + let paren; + parenthesized!(paren in input); + let bound: Bound = paren.parse()?; + paren.parse::>()?; + + Ok(Expr::Since(bound)) + } + + fn parse_before(input: ParseStream) -> Result { + input.parse::()?; + + let paren; + parenthesized!(paren in input); + let bound: Bound = paren.parse()?; + paren.parse::>()?; + + Ok(Expr::Before(bound)) + } + + fn parse_not(input: ParseStream) -> Result { + input.parse::()?; + + let paren; + parenthesized!(paren in input); + let expr: Expr = paren.parse()?; + paren.parse::>()?; + + Ok(Expr::Not(Box::new(expr))) + } + + fn parse_any(input: ParseStream) -> Result { + input.parse::()?; + + let paren; + parenthesized!(paren in input); + let exprs: Exprs = paren.parse_terminated(Expr::parse)?; + + Ok(Expr::Any(exprs.into_iter().collect())) + } + + fn parse_all(input: ParseStream) -> Result { + input.parse::()?; + + let paren; + parenthesized!(paren in input); + let exprs: Exprs = paren.parse_terminated(Expr::parse)?; + + Ok(Expr::All(exprs.into_iter().collect())) + } +} diff --git a/rustversion/src/lib.rs b/rustversion/src/lib.rs new file mode 100644 index 0000000..cf8ed21 --- /dev/null +++ b/rustversion/src/lib.rs @@ -0,0 +1,254 @@ +//! This crate provides macros for conditional compilation according to rustc +//! compiler version, analogous to [`#[cfg(...)]`][cfg] and +//! [`#[cfg_attr(...)]`][cfg_attr]. +//! +//! [cfg]: https://doc.rust-lang.org/reference/conditional-compilation.html#the-cfg-attribute +//! [cfg_attr]: https://doc.rust-lang.org/reference/conditional-compilation.html#the-cfg_attr-attribute +//! +//!
+//! +//! # Selectors +//! +//! -

+//! #[rustversion::stable] +//! —
+//! True on any stable compiler. +//!

+//! +//! -

+//! #[rustversion::stable(1.34)] +//! —
+//! True on exactly the specified stable compiler. +//!

+//! +//! -

+//! #[rustversion::beta] +//! —
+//! True on any beta compiler. +//!

+//! +//! -

+//! #[rustversion::nightly] +//! —
+//! True on any nightly compiler or dev build. +//!

+//! +//! -

+//! #[rustversion::nightly(2019-01-01)] +//! —
+//! True on exactly one nightly. +//!

+//! +//! -

+//! #[rustversion::since(1.34)] +//! —
+//! True on that stable release and any later compiler, including beta and +//! nightly. +//!

+//! +//! -

+//! #[rustversion::since(2019-01-01)] +//! —
+//! True on that nightly and all newer ones. +//!

+//! +//! -

+//! #[rustversion::before(version or date)] +//! —
+//! Negative of #[rustversion::since(...)]. +//!

+//! +//! -

+//! #[rustversion::not(selector)] +//! —
+//! Negative of any selector; for example #[rustversion::not(nightly)]. +//!

+//! +//! -

+//! #[rustversion::any(selectors...)] +//! —
+//! True if any of the comma-separated selectors is true; for example +//! #[rustversion::any(stable, beta)]. +//!

+//! +//! -

+//! #[rustversion::all(selectors...)] +//! —
+//! True if all of the comma-separated selectors are true; for example +//! #[rustversion::all(since(1.31), before(1.34))]. +//!

+//! +//! -

+//! #[rustversion::attr(selector, attribute)] +//! —
+//! For conditional inclusion of attributes; analogous to +//! cfg_attr. +//!

+//! +//!
+//! +//! # Use cases +//! +//! Providing additional trait impls as types are stabilized in the standard library +//! without breaking compatibility with older compilers; in this case Pin\ +//! stabilized in [Rust 1.33][pin]: +//! +//! [pin]: https://blog.rust-lang.org/2019/02/28/Rust-1.33.0.html#pinning +//! +//! ``` +//! # trait MyTrait {} +//! # +//! #[rustversion::since(1.33)] +//! use std::pin::Pin; +//! +//! #[rustversion::since(1.33)] +//! impl MyTrait for Pin

{ +//! /* ... */ +//! } +//! ``` +//! +//! Similar but for language features; the ability to control alignment greater than +//! 1 of packed structs was stabilized in [Rust 1.33][packed]. +//! +//! [packed]: https://github.com/rust-lang/rust/blob/master/RELEASES.md#version-1330-2019-02-28 +//! +//! ``` +//! #[rustversion::attr(before(1.33), repr(packed))] +//! #[rustversion::attr(since(1.33), repr(packed(2)))] +//! struct Six(i16, i32); +//! +//! fn main() { +//! println!("{}", std::mem::align_of::()); +//! } +//! ``` +//! +//! Augmenting code with `const` as const impls are stabilized in the standard +//! library. This use of `const` as an attribute is recognized as a special case +//! by the rustversion::attr macro. +//! +//! ``` +//! use std::time::Duration; +//! +//! #[rustversion::attr(since(1.32), const)] +//! fn duration_as_days(dur: Duration) -> u64 { +//! dur.as_secs() / 60 / 60 / 24 +//! } +//! ``` +//! +//!
+ +extern crate proc_macro; + +mod attr; +mod bound; +mod date; +mod expr; +mod rustc; +mod time; +mod version; + +use crate::attr::Then; +use crate::expr::Expr; +use proc_macro::TokenStream; +use proc_macro2::{Ident, Span, TokenStream as TokenStream2}; +use quote::quote; +use syn::{parse_macro_input, ItemFn, Result}; + +#[proc_macro_attribute] +pub fn stable(args: TokenStream, input: TokenStream) -> TokenStream { + cfg("stable", args, input) +} + +#[proc_macro_attribute] +pub fn beta(args: TokenStream, input: TokenStream) -> TokenStream { + cfg("beta", args, input) +} + +#[proc_macro_attribute] +pub fn nightly(args: TokenStream, input: TokenStream) -> TokenStream { + cfg("nightly", args, input) +} + +#[proc_macro_attribute] +pub fn since(args: TokenStream, input: TokenStream) -> TokenStream { + cfg("since", args, input) +} + +#[proc_macro_attribute] +pub fn before(args: TokenStream, input: TokenStream) -> TokenStream { + cfg("before", args, input) +} + +#[proc_macro_attribute] +pub fn not(args: TokenStream, input: TokenStream) -> TokenStream { + cfg("not", args, input) +} + +#[proc_macro_attribute] +pub fn any(args: TokenStream, input: TokenStream) -> TokenStream { + cfg("any", args, input) +} + +#[proc_macro_attribute] +pub fn all(args: TokenStream, input: TokenStream) -> TokenStream { + cfg("all", args, input) +} + +fn cfg(top: &str, args: TokenStream, input: TokenStream) -> TokenStream { + match try_cfg(top, args, input) { + Ok(tokens) => tokens, + Err(err) => TokenStream::from(err.to_compile_error()), + } +} + +fn try_cfg(top: &str, args: TokenStream, input: TokenStream) -> Result { + let args = TokenStream2::from(args); + let top = Ident::new(top, Span::call_site()); + + let mut full_args = quote!(#top); + if !args.is_empty() { + full_args.extend(quote!((#args))); + } + + let expr: Expr = syn::parse2(full_args)?; + let version = rustc::version()?; + + if expr.eval(version) { + Ok(input) + } else { + Ok(TokenStream::new()) + } +} + +#[proc_macro_attribute] +pub fn attr(args: TokenStream, input: TokenStream) -> TokenStream { + let args = parse_macro_input!(args as attr::Args); + + match try_attr(args, input) { + Ok(tokens) => tokens, + Err(err) => TokenStream::from(err.to_compile_error()), + } +} + +fn try_attr(args: attr::Args, input: TokenStream) -> Result { + let version = rustc::version()?; + + if !args.condition.eval(version) { + return Ok(input); + } + + match args.then { + Then::Const(const_token) => { + let mut input: ItemFn = syn::parse(input)?; + input.sig.constness = Some(const_token); + Ok(TokenStream::from(quote!(#input))) + } + Then::Attribute(then) => { + let input = TokenStream2::from(input); + Ok(TokenStream::from(quote! { + #[cfg_attr(all(), #then)] + #input + })) + } + } +} diff --git a/rustversion/src/rustc.rs b/rustversion/src/rustc.rs new file mode 100644 index 0000000..4e7699d --- /dev/null +++ b/rustversion/src/rustc.rs @@ -0,0 +1,195 @@ +use std::env; +use std::ffi::OsString; +use std::fmt::{self, Display}; +use std::io; +use std::process::Command; +use std::str::FromStr; +use std::string::FromUtf8Error; + +use crate::date::Date; +use crate::version::{Channel::*, Version}; +use proc_macro2::Span; + +#[derive(Debug)] +pub enum Error { + Exec(io::Error), + Utf8(FromUtf8Error), + Parse(String), +} + +pub type Result = std::result::Result; + +impl Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use self::Error::*; + + match self { + Exec(e) => write!(f, "failed to run `rustc --version`: {}", e), + Utf8(e) => write!(f, "failed to parse output of `rustc --version`: {}", e), + Parse(string) => write!( + f, + "unexpected output from `rustc --version`, please file an issue: {:?}", + string, + ), + } + } +} + +impl From for Error { + fn from(err: FromUtf8Error) -> Self { + Error::Utf8(err) + } +} + +impl From for syn::Error { + fn from(err: Error) -> Self { + syn::Error::new(Span::call_site(), err) + } +} + +pub fn version() -> Result { + let rustc = env::var_os("RUSTC").unwrap_or_else(|| OsString::from("rustc")); + let output = Command::new(rustc) + .arg("--version") + .output() + .map_err(Error::Exec)?; + let string = String::from_utf8(output.stdout)?; + + match parse(&string) { + Some(version) => Ok(version), + None => Err(Error::Parse(string)), + } +} + +fn parse(string: &str) -> Option { + let last_line = string.lines().last().unwrap_or(&string); + let mut words = last_line.trim().split(' '); + + if words.next()? != "rustc" { + return None; + } + + let mut version_channel = words.next()?.split('-'); + let version = version_channel.next()?; + let channel = version_channel.next(); + + let mut digits = version.split('.'); + let major = digits.next()?; + if major != "1" { + return None; + } + let minor = digits.next()?.parse().ok()?; + let patch = digits.next().unwrap_or("0").parse().ok()?; + + let channel = match channel { + None => Stable, + Some(channel) if channel == "dev" => Dev, + Some(channel) if channel.starts_with("beta") => Beta, + Some(channel) if channel == "nightly" => { + match words.next() { + Some(hash) => { + if !hash.starts_with('(') { + return None; + } + let date = words.next()?; + if !date.ends_with(')') { + return None; + } + let date = Date::from_str(&date[..date.len() - 1]).ok()?; + Nightly(date) + } + None => Dev, + } + } + Some(_) => return None, + }; + + Some(Version { + minor, + patch, + channel, + }) +} + +#[test] +fn test_parse() { + let cases = &[ + ( + "rustc 1.0.0 (a59de37e9 2015-05-13) (built 2015-05-14)", + Version { + minor: 0, + patch: 0, + channel: Stable, + }, + ), + ( + "rustc 1.18.0", + Version { + minor: 18, + patch: 0, + channel: Stable, + }, + ), + ( + "rustc 1.24.1 (d3ae9a9e0 2018-02-27)", + Version { + minor: 24, + patch: 1, + channel: Stable, + }, + ), + ( + "rustc 1.35.0-beta.3 (c13114dc8 2019-04-27)", + Version { + minor: 35, + patch: 0, + channel: Beta, + }, + ), + ( + "rustc 1.36.0-nightly (938d4ffe1 2019-04-27)", + Version { + minor: 36, + patch: 0, + channel: Nightly(Date { + year: 2019, + month: 4, + day: 27, + }), + }, + ), + ( + "rustc 1.36.0-dev", + Version { + minor: 36, + patch: 0, + channel: Dev, + }, + ), + ( + "rustc 1.36.0-nightly", + Version { + minor: 36, + patch: 0, + channel: Dev, + }, + ), + ( + "warning: invalid logging spec 'warning', ignoring it + rustc 1.30.0-nightly (3bc2ca7e4 2018-09-20)", + Version { + minor: 30, + patch: 0, + channel: Nightly(Date { + year: 2018, + month: 9, + day: 20, + }), + }, + ), + ]; + + for (string, expected) in cases { + assert_eq!(parse(string).unwrap(), *expected); + } +} diff --git a/rustversion/src/time.rs b/rustversion/src/time.rs new file mode 100644 index 0000000..1e6dd90 --- /dev/null +++ b/rustversion/src/time.rs @@ -0,0 +1,44 @@ +use crate::date::Date; +use std::time::{SystemTime, UNIX_EPOCH}; + +// Timestamp of 2016-03-01 00:00:00 in UTC. +const BASE: u64 = 1456790400; +const BASE_YEAR: u16 = 2016; +const BASE_MONTH: u8 = 3; + +// Days between leap days. +const CYCLE: u64 = 365 * 4 + 1; + +const DAYS_BY_MONTH: [u8; 12] = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + +pub fn today() -> Date { + let default = Date { + year: 2019, + month: 1, + day: 1, + }; + try_today().unwrap_or(default) +} + +fn try_today() -> Option { + let now = SystemTime::now(); + let since_epoch = now.duration_since(UNIX_EPOCH).ok()?; + let secs = since_epoch.as_secs(); + + let approx_days = secs.checked_sub(BASE)? / 60 / 60 / 24; + let cycle = approx_days / CYCLE; + let mut rem = approx_days % CYCLE; + + let mut year = BASE_YEAR + cycle as u16 * 4; + let mut month = BASE_MONTH; + loop { + let days_in_month = DAYS_BY_MONTH[month as usize - 1]; + if rem < days_in_month as u64 { + let day = rem as u8 + 1; + return Some(Date { year, month, day }); + } + rem -= days_in_month as u64; + year += (month == 12) as u16; + month = month % 12 + 1; + } +} diff --git a/rustversion/src/version.rs b/rustversion/src/version.rs new file mode 100644 index 0000000..ab3992f --- /dev/null +++ b/rustversion/src/version.rs @@ -0,0 +1,16 @@ +use crate::date::Date; + +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct Version { + pub minor: u16, + pub patch: u16, + pub channel: Channel, +} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum Channel { + Stable, + Beta, + Nightly(Date), + Dev, +} 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 +} diff --git a/syn-mid/.editorconfig b/syn-mid/.editorconfig new file mode 100644 index 0000000..920ea40 --- /dev/null +++ b/syn-mid/.editorconfig @@ -0,0 +1,22 @@ +# EditorConfig configuration +# http://EditorConfig.org + +# Top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file, utf-8 charset +[*] +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +charset = utf-8 + +# Match rust/toml, set 4 space indentation +[*.{rs,toml}] +indent_style = space +indent_size = 4 + +# Match yaml/markdown, set 2 space indentation +[*.{yml,md}] +indent_style = space +indent_size = 2 diff --git a/syn-mid/.gitignore b/syn-mid/.gitignore new file mode 100644 index 0000000..2e6c307 --- /dev/null +++ b/syn-mid/.gitignore @@ -0,0 +1,6 @@ +.idea/ +.vscode/ +target/ +**/*.rs.bk +Cargo.lock +___* diff --git a/syn-mid/.rustfmt.toml b/syn-mid/.rustfmt.toml new file mode 100644 index 0000000..a27c0b7 --- /dev/null +++ b/syn-mid/.rustfmt.toml @@ -0,0 +1,4 @@ +# Set the default settings again to always apply the proper formatting without being affected by the editor settings. +# https://github.com/rust-lang/rls/issues/501#issuecomment-333717736 +edition = "2018" +tab_spaces = 4 diff --git a/syn-mid/CHANGELOG.md b/syn-mid/CHANGELOG.md new file mode 100644 index 0000000..25c17c1 --- /dev/null +++ b/syn-mid/CHANGELOG.md @@ -0,0 +1,33 @@ +# Unreleased + +# 0.4.0 - 2019-08-15 + +* Updated all data structures based on `syn` 1.0. + +* Updated `proc-macro2`, `syn`, and `quote` to 1.0. + +* Bumped the minimum required version from Rust 1.30 to Rust 1.31. + +# 0.3.0 - 2019-02-18 + +* Removed support for unneeded syntax. + +* Removed unneeded types and fields. + +* Implemented `Parse` for `Block`. + +* Changed `clone-impls` feature to "disabled by default". + +* Removed `extra-traits` feature. + +* Bumped the minimum required version from Rust 1.15 to Rust 1.30. + +# 0.2.0 - 2019-02-15 + +* Reduced features. + +* Fixed bugs. + +# 0.1.0 - 2019-02-14 + +Initial release diff --git a/syn-mid/Cargo.toml b/syn-mid/Cargo.toml new file mode 100644 index 0000000..f92e7a0 --- /dev/null +++ b/syn-mid/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "syn-mid" +version = "0.4.0" +authors = ["Taiki Endo "] +edition = "2018" +license = "Apache-2.0/MIT" +repository = "https://github.com/taiki-e/syn-mid" +documentation = "https://docs.rs/syn-mid/" +readme = "README.md" +keywords = ["syn", "macros"] +categories = ["development-tools::procedural-macro-helpers"] +description = "Providing the features between \"full\" and \"derive\" of syn." + +[workspace] +members = ["examples/const_fn", "examples/const_fn_test"] + +[features] +clone-impls = ["syn/clone-impls"] + +[dependencies] +proc-macro2 = "1.0" +quote = "1.0" +syn = { version = "1.0", default-features = false, features = ["parsing", "printing", "derive"] } diff --git a/syn-mid/LICENSE-APACHE b/syn-mid/LICENSE-APACHE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/syn-mid/LICENSE-APACHE @@ -0,0 +1,202 @@ + + 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/syn-mid/LICENSE-MIT b/syn-mid/LICENSE-MIT new file mode 100644 index 0000000..31aa793 --- /dev/null +++ b/syn-mid/LICENSE-MIT @@ -0,0 +1,23 @@ +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/syn-mid/README.md b/syn-mid/README.md new file mode 100644 index 0000000..b9b1c75 --- /dev/null +++ b/syn-mid/README.md @@ -0,0 +1,72 @@ +# syn-mid + +[![Build Status][azure-badge]][azure-url] +[![Crates.io][crates-version-badge]][crates-url] +[![Docs.rs][docs-badge]][docs-url] +[![License][crates-license-badge]][crates-url] +[![Minimum supported Rust version][rustc-badge]][rustc-url] + +[azure-badge]: https://dev.azure.com/taiki-e/taiki-e/_apis/build/status/taiki-e.syn-mid?branchName=master +[azure-url]: https://dev.azure.com/taiki-e/taiki-e/_build/latest?definitionId=11&branchName=master +[crates-version-badge]: https://img.shields.io/crates/v/syn-mid.svg +[crates-license-badge]: https://img.shields.io/crates/l/syn-mid.svg +[crates-badge]: https://img.shields.io/crates/v/syn-mid.svg +[crates-url]: https://crates.io/crates/syn-mid/ +[docs-badge]: https://docs.rs/syn-mid/badge.svg +[docs-url]: https://docs.rs/syn-mid/ +[rustc-badge]: https://img.shields.io/badge/rustc-1.31+-lightgray.svg +[rustc-url]: https://blog.rust-lang.org/2018/12/06/Rust-1.31-and-rust-2018.html + +Providing the features between "full" and "derive" of syn. + +This crate provides the following two unique data structures. + +* `syn_mid::ItemFn` -- A function whose body is not parsed. + + ```text + fn process(n: usize) -> Result<()> { ... } + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^ ^ + ``` + +* `syn_mid::Block` -- A block whose body is not parsed. + + ```text + { ... } + ^ ^ + ``` + +Other data structures are the same as data structures of [syn]. These are defined in this crate because they cannot be used in [syn] without "full" feature. + +[syn]: https://github.com/dtolnay/syn + +## Usage + +Add this to your `Cargo.toml`: + +```toml +[dependencies] +syn-mid = "0.4" +``` + +The current syn-mid requires Rust 1.31 or later. + +[**Examples**](examples) + +[**Documentation**](https://docs.rs/syn-mid/) + +## Optional features + +* **`clone-impls`** — Clone impls for all syntax tree types. + +## 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/syn-mid/azure-pipelines.yml b/syn-mid/azure-pipelines.yml new file mode 100644 index 0000000..6f0c968 --- /dev/null +++ b/syn-mid/azure-pipelines.yml @@ -0,0 +1,49 @@ +trigger: +- master +- staging +- trying + +variables: + RUSTFLAGS: -Dwarnings + +jobs: +# This is the minimum Rust version supported by syn-mid. +# When updating this, the reminder to update the minimum supported +# Rust version in README.md. +# +# Tests are not run as tests may require newer versions of rust. +- template: ci/azure-test.yml + parameters: + name: minrust + rust: 1.31.0 + +- template: ci/azure-test.yml + parameters: + name: stable + rust: stable + +- template: ci/azure-test.yml + parameters: + name: beta + rust: beta + +- template: ci/azure-test.yml + parameters: + name: nightly + rust: nightly + cmd: test + +- template: ci/azure-clippy.yml + parameters: + name: clippy + rust: nightly + +- template: ci/azure-rustfmt.yml + parameters: + name: rustfmt + rust: stable + +- template: ci/azure-rustdoc.yml + parameters: + name: rustdoc + rust: nightly diff --git a/syn-mid/bors.toml b/syn-mid/bors.toml new file mode 100644 index 0000000..b477894 --- /dev/null +++ b/syn-mid/bors.toml @@ -0,0 +1 @@ +status = ["taiki-e.syn-mid"] diff --git a/syn-mid/ci/azure-clippy.yml b/syn-mid/ci/azure-clippy.yml new file mode 100644 index 0000000..22165c6 --- /dev/null +++ b/syn-mid/ci/azure-clippy.yml @@ -0,0 +1,31 @@ +jobs: +- job: ${{ parameters.name }} + displayName: Clippy + pool: + vmImage: ubuntu-16.04 + + steps: + - template: azure-install-rust.yml + parameters: + rust: ${{ parameters.rust }} + + - script: | + set +e + if rustup component add clippy; then + set -e + else + set -e + target=`curl https://rust-lang.github.io/rustup-components-history/x86_64-unknown-linux-gnu/clippy` + echo "'clippy' is unavailable on the toolchain '${{ parameters.rust }}', use the toolchain 'nightly-$target' instead" + rustup toolchain install nightly-$target + rustup default nightly-$target + rustup component add clippy + rustup toolchain list + rustc -Vv + cargo -V + fi + cargo clippy --version + displayName: rustup component add clippy + + - script: cargo clippy --all --all-features + displayName: cargo clippy --all-features diff --git a/syn-mid/ci/azure-install-rust.yml b/syn-mid/ci/azure-install-rust.yml new file mode 100644 index 0000000..6b008c6 --- /dev/null +++ b/syn-mid/ci/azure-install-rust.yml @@ -0,0 +1,33 @@ +steps: + # Linux and macOS. + - script: | + set -e + curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain none + export PATH=$PATH:$HOME/.cargo/bin + rustup toolchain install $RUSTUP_TOOLCHAIN + rustup default $RUSTUP_TOOLCHAIN + echo "##vso[task.setvariable variable=PATH;]$PATH:$HOME/.cargo/bin" + env: + RUSTUP_TOOLCHAIN: ${{ parameters.rust }} + displayName: Install rust (*nix) + condition: not(eq(variables['Agent.OS'], 'Windows_NT')) + + # Windows. + - script: | + curl -sSf -o rustup-init.exe https://win.rustup.rs + rustup-init.exe -y --default-toolchain none + set PATH=%PATH%;%USERPROFILE%\.cargo\bin + rustup toolchain install %RUSTUP_TOOLCHAIN% + rustup default %RUSTUP_TOOLCHAIN% + echo "##vso[task.setvariable variable=PATH;]%PATH%;%USERPROFILE%\.cargo\bin" + env: + RUSTUP_TOOLCHAIN: ${{ parameters.rust }} + displayName: Install rust (windows) + condition: eq(variables['Agent.OS'], 'Windows_NT') + + # All platforms. + - script: | + rustup toolchain list + rustc -Vv + cargo -V + displayName: Query rust and cargo versions diff --git a/syn-mid/ci/azure-rustdoc.yml b/syn-mid/ci/azure-rustdoc.yml new file mode 100644 index 0000000..99a43ff --- /dev/null +++ b/syn-mid/ci/azure-rustdoc.yml @@ -0,0 +1,13 @@ +jobs: +- job: ${{ parameters.name }} + displayName: Rustdoc + pool: + vmImage: ubuntu-16.04 + + steps: + - template: azure-install-rust.yml + parameters: + rust: ${{ parameters.rust }} + + - script: RUSTDOCFLAGS=-Dwarnings cargo doc --no-deps --all --all-features + displayName: cargo doc --all-features diff --git a/syn-mid/ci/azure-rustfmt.yml b/syn-mid/ci/azure-rustfmt.yml new file mode 100644 index 0000000..0b20da3 --- /dev/null +++ b/syn-mid/ci/azure-rustfmt.yml @@ -0,0 +1,18 @@ +jobs: +- job: ${{ parameters.name }} + displayName: Rustfmt + pool: + vmImage: ubuntu-16.04 + + steps: + - template: azure-install-rust.yml + parameters: + rust: ${{ parameters.rust }} + + - script: | + rustup component add rustfmt + cargo fmt --version + displayName: rustup component add rustfmt + + - script: cargo fmt --all -- --check + displayName: cargo fmt -- --check diff --git a/syn-mid/ci/azure-test.yml b/syn-mid/ci/azure-test.yml new file mode 100644 index 0000000..32a56ed --- /dev/null +++ b/syn-mid/ci/azure-test.yml @@ -0,0 +1,34 @@ +parameters: + cmd: check + +jobs: +- job: ${{ parameters.name }} + displayName: ${{ parameters.displayName }} ${{ parameters.rust }} + strategy: + matrix: + Linux: + vmImage: ubuntu-16.04 + + ${{ if parameters.cross }}: + MacOS: + vmImage: macOS-10.13 + Windows: + vmImage: vs2017-win2016 + pool: + vmImage: $(vmImage) + + steps: + - template: azure-install-rust.yml + parameters: + rust: ${{ parameters.rust }} + + - script: | + cargo ${{ parameters.cmd }} + cargo ${{ parameters.cmd }} --all-features + displayName: cargo ${{ parameters.cmd }} + + - ${{ if eq(parameters.rust, 'nightly') }}: + - script: | + cargo update -Zminimal-versions + cargo check --all-features + displayName: cargo check -Zminimal-versions diff --git a/syn-mid/examples/const_fn/Cargo.toml b/syn-mid/examples/const_fn/Cargo.toml new file mode 100644 index 0000000..d823e76 --- /dev/null +++ b/syn-mid/examples/const_fn/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "const_fn" +version = "0.0.0" +authors = ["Taiki Endo "] +edition = "2018" +publish = false + +[lib] +proc-macro = true +path = "lib.rs" + +[dependencies] +proc-macro2 = "1.0" +quote = "1.0" +syn = "1.0" +syn-mid = { version = "0.4", path = "../..", features = ["clone-impls"] } diff --git a/syn-mid/examples/const_fn/lib.rs b/syn-mid/examples/const_fn/lib.rs new file mode 100644 index 0000000..29255f2 --- /dev/null +++ b/syn-mid/examples/const_fn/lib.rs @@ -0,0 +1,31 @@ +#![warn(rust_2018_idioms)] + +extern crate proc_macro; + +use proc_macro::TokenStream; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use syn_mid::ItemFn; + +/// An attribute for easy generation of a const function with conditional compilations. +#[proc_macro_attribute] +pub fn const_fn(args: TokenStream, function: TokenStream) -> TokenStream { + assert!(!args.is_empty(), "requires an argument"); + + let mut function = syn::parse_macro_input!(function as ItemFn); + let mut const_function = function.clone(); + + if function.constness.is_some() { + function.constness = None; + } else { + const_function.constness = Some(Default::default()); + } + + let args = TokenStream2::from(args); + TokenStream::from(quote! { + #[cfg(not(#args))] + #function + #[cfg(#args)] + #const_function + }) +} diff --git a/syn-mid/examples/const_fn_test/Cargo.toml b/syn-mid/examples/const_fn_test/Cargo.toml new file mode 100644 index 0000000..b3e2807 --- /dev/null +++ b/syn-mid/examples/const_fn_test/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "const_fn_test" +version = "0.0.0" +authors = ["Taiki Endo "] +edition = "2018" +publish = false + +[dependencies] +const_fn = { path = "../const_fn" } diff --git a/syn-mid/examples/const_fn_test/build.rs b/syn-mid/examples/const_fn_test/build.rs new file mode 100644 index 0000000..bebf234 --- /dev/null +++ b/syn-mid/examples/const_fn_test/build.rs @@ -0,0 +1,16 @@ +use std::{env, process::Command}; + +fn main() { + println!("cargo:rerun-if-changed=build.rs"); + + if is_nightly() { + println!("cargo:rustc-cfg=nightly"); + } +} + +fn is_nightly() -> bool { + env::var_os("RUSTC") + .and_then(|rustc| Command::new(rustc).arg("--version").output().ok()) + .and_then(|output| String::from_utf8(output.stdout).ok()) + .map_or(false, |version| version.contains("nightly")) +} diff --git a/syn-mid/examples/const_fn_test/tests/test.rs b/syn-mid/examples/const_fn_test/tests/test.rs new file mode 100644 index 0000000..1b2c742 --- /dev/null +++ b/syn-mid/examples/const_fn_test/tests/test.rs @@ -0,0 +1,25 @@ +#![cfg_attr(nightly, feature(const_fn, const_vec_new))] +#![warn(rust_2018_idioms)] +#![allow(dead_code)] + +use const_fn::const_fn; + +#[const_fn(nightly)] +fn const_vec_new() -> Vec { + let vec = Vec::new(); + vec +} + +#[test] +fn test_stable() { + assert_eq!(const_vec_new::(), Vec::new()); +} + +#[cfg(nightly)] +const CONST_UNSTABLE: Vec = const_vec_new(); + +#[cfg(nightly)] +#[test] +fn test_unstable() { + assert_eq!(CONST_UNSTABLE, Vec::new()); +} diff --git a/syn-mid/src/arg.rs b/syn-mid/src/arg.rs new file mode 100644 index 0000000..593a1ac --- /dev/null +++ b/syn-mid/src/arg.rs @@ -0,0 +1,99 @@ +use syn::{Attribute, Lifetime, Token}; + +use super::PatType; + +ast_enum_of_structs! { + /// An argument in a function signature: the `n: usize` in `fn f(n: usize)`. + pub enum FnArg { + /// The `self` argument of an associated method, whether taken by value + /// or by reference. + Receiver(Receiver), + + /// A function argument accepted by pattern and type. + Typed(PatType), + } +} + +ast_struct! { + /// The `self` argument of an associated method, whether taken by value + /// or by reference. + pub struct Receiver { + pub attrs: Vec, + pub reference: Option<(Token![&], Option)>, + pub mutability: Option, + pub self_token: Token![self], + } +} + +mod parsing { + use syn::{ + parse::{discouraged::Speculative, Parse, ParseStream, Result}, + Attribute, Token, + }; + + use super::{FnArg, PatType, Receiver}; + + impl Parse for FnArg { + fn parse(input: ParseStream<'_>) -> Result { + let attrs = input.call(Attribute::parse_outer)?; + + let ahead = input.fork(); + if let Ok(mut receiver) = ahead.parse::() { + if !ahead.peek(Token![:]) { + input.advance_to(&ahead); + receiver.attrs = attrs; + return Ok(FnArg::Receiver(receiver)); + } + } + + let mut typed = input.call(fn_arg_typed)?; + typed.attrs = attrs; + Ok(FnArg::Typed(typed)) + } + } + + impl Parse for Receiver { + fn parse(input: ParseStream<'_>) -> Result { + Ok(Self { + attrs: Vec::new(), + reference: { + if input.peek(Token![&]) { + Some((input.parse()?, input.parse()?)) + } else { + None + } + }, + mutability: input.parse()?, + self_token: input.parse()?, + }) + } + } + + fn fn_arg_typed(input: ParseStream<'_>) -> Result { + Ok(PatType { + attrs: Vec::new(), + pat: input.parse()?, + colon_token: input.parse()?, + ty: Box::new(input.parse()?), + }) + } +} + +mod printing { + use proc_macro2::TokenStream; + use quote::{ToTokens, TokenStreamExt}; + + use super::Receiver; + + impl ToTokens for Receiver { + fn to_tokens(&self, tokens: &mut TokenStream) { + tokens.append_all(&self.attrs); + if let Some((ampersand, lifetime)) = &self.reference { + ampersand.to_tokens(tokens); + lifetime.to_tokens(tokens); + } + self.mutability.to_tokens(tokens); + self.self_token.to_tokens(tokens); + } + } +} diff --git a/syn-mid/src/lib.rs b/syn-mid/src/lib.rs new file mode 100644 index 0000000..69bdec9 --- /dev/null +++ b/syn-mid/src/lib.rs @@ -0,0 +1,190 @@ +//! Providing the features between "full" and "derive" of syn. +//! +//! This crate provides the following two unique data structures. +//! +//! * [`syn_mid::ItemFn`] -- A function whose body is not parsed. +//! +//! ```text +//! fn process(n: usize) -> Result<()> { ... } +//! ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^ ^ +//! ``` +//! +//! * [`syn_mid::Block`] -- A block whose body is not parsed. +//! +//! ```text +//! { ... } +//! ^ ^ +//! ``` +//! +//! Other data structures are the same as data structures of [syn]. These are defined in this crate +//! because they cannot be used in [syn] without "full" feature. +//! +//! ## Optional features +//! +//! syn-mid in the default features aims to provide the features between "full" +//! and "derive" of [syn]. +//! +//! * **`clone-impls`** — Clone impls for all syntax tree types. +//! +//! [`syn_mid::ItemFn`]: struct.ItemFn.html +//! [`syn_mid::Block`]: struct.Block.html +//! [syn]: https://github.com/dtolnay/syn +//! + +#![doc(html_root_url = "https://docs.rs/syn-mid/0.4.0")] +#![doc(test(attr(deny(warnings), allow(dead_code, unused_assignments, unused_variables))))] +#![warn(unsafe_code)] +#![warn(rust_2018_idioms, unreachable_pub)] +#![warn(single_use_lifetimes)] +#![warn(clippy::all, clippy::pedantic)] +#![allow( + clippy::eval_order_dependence, + clippy::large_enum_variant, + clippy::module_name_repetitions, + clippy::use_self +)] + +// Many of the code contained in this crate are copies from https://github.com/dtolnay/syn. + +#[macro_use] +mod macros; + +mod arg; +mod pat; +mod path; + +pub use self::arg::*; +pub use self::pat::*; + +use proc_macro2::TokenStream; +use syn::{ + punctuated::Punctuated, token, Abi, Attribute, Generics, Ident, ReturnType, Token, Visibility, +}; + +ast_struct! { + /// A braced block containing Rust statements. + pub struct Block { + pub brace_token: token::Brace, + /// Statements in a block + pub stmts: TokenStream, + } +} + +ast_struct! { + /// A free-standing function: `fn process(n: usize) -> Result<()> { ... + /// }`. + pub struct ItemFn { + pub attrs: Vec, + pub vis: Visibility, + pub constness: Option, + pub asyncness: Option, + pub unsafety: Option, + pub abi: Option, + pub fn_token: Token![fn], + pub ident: Ident, + pub generics: Generics, + pub paren_token: token::Paren, + pub inputs: Punctuated, + pub output: ReturnType, + pub block: Block, + } +} + +mod parsing { + use syn::{ + braced, parenthesized, + parse::{Parse, ParseStream, Result}, + Abi, Attribute, Generics, Ident, ReturnType, Token, Visibility, WhereClause, + }; + + use super::{Block, FnArg, ItemFn}; + + impl Parse for Block { + fn parse(input: ParseStream<'_>) -> Result { + let content; + Ok(Self { + brace_token: braced!(content in input), + stmts: content.parse()?, + }) + } + } + + impl Parse for ItemFn { + fn parse(input: ParseStream<'_>) -> Result { + let attrs = input.call(Attribute::parse_outer)?; + let vis: Visibility = input.parse()?; + let constness: Option = input.parse()?; + let asyncness: Option = input.parse()?; + let unsafety: Option = input.parse()?; + let abi: Option = input.parse()?; + let fn_token: Token![fn] = input.parse()?; + let ident: Ident = input.parse()?; + let generics: Generics = input.parse()?; + + let content; + let paren_token = parenthesized!(content in input); + let inputs = content.parse_terminated(FnArg::parse)?; + + let output: ReturnType = input.parse()?; + let where_clause: Option = input.parse()?; + + let block = input.parse()?; + + Ok(Self { + attrs, + vis, + constness, + asyncness, + unsafety, + abi, + fn_token, + ident, + generics: Generics { + where_clause, + ..generics + }, + paren_token, + inputs, + output, + block, + }) + } + } +} + +mod printing { + use proc_macro2::TokenStream; + use quote::{ToTokens, TokenStreamExt}; + + use super::{Block, ItemFn}; + + impl ToTokens for Block { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.brace_token.surround(tokens, |tokens| { + tokens.append_all(self.stmts.clone()); + }); + } + } + + impl ToTokens for ItemFn { + fn to_tokens(&self, tokens: &mut TokenStream) { + tokens.append_all(&self.attrs); + self.vis.to_tokens(tokens); + self.constness.to_tokens(tokens); + self.asyncness.to_tokens(tokens); + self.unsafety.to_tokens(tokens); + self.abi.to_tokens(tokens); + self.fn_token.to_tokens(tokens); + self.ident.to_tokens(tokens); + self.generics.to_tokens(tokens); + self.paren_token.surround(tokens, |tokens| { + self.inputs.to_tokens(tokens); + }); + self.output.to_tokens(tokens); + self.generics.where_clause.to_tokens(tokens); + self.block.brace_token.surround(tokens, |tokens| { + tokens.append_all(self.block.stmts.clone()); + }); + } + } +} diff --git a/syn-mid/src/macros.rs b/syn-mid/src/macros.rs new file mode 100644 index 0000000..87be7b4 --- /dev/null +++ b/syn-mid/src/macros.rs @@ -0,0 +1,107 @@ +macro_rules! ast_struct { + ( + [$($attrs_pub:tt)*] + struct $name:ident $($rest:tt)* + ) => { + #[cfg_attr(feature = "clone-impls", derive(Clone))] + $($attrs_pub)* struct $name $($rest)* + }; + + ($($t:tt)*) => { + strip_attrs_pub!(ast_struct!($($t)*)); + }; +} + +macro_rules! ast_enum { + ( + [$($attrs_pub:tt)*] + enum $name:ident $($rest:tt)* + ) => ( + #[cfg_attr(feature = "clone-impls", derive(Clone))] + $($attrs_pub)* enum $name $($rest)* + ); + + ($($t:tt)*) => { + strip_attrs_pub!(ast_enum!($($t)*)); + }; +} + +macro_rules! ast_enum_of_structs { + ( + $(#[$enum_attr:meta])* + $pub:ident $enum:ident $name:ident $body:tt + ) => { + ast_enum!($(#[$enum_attr])* $pub $enum $name $body); + ast_enum_of_structs_impl!($pub $enum $name $body); + }; +} + +macro_rules! ast_enum_of_structs_impl { + ( + $pub:ident $enum:ident $name:ident { + $( + $(#[$variant_attr:meta])* + $variant:ident $( ($member:ident) )*, + )* + } + ) => { + check_keyword_matches!(pub $pub); + check_keyword_matches!(enum $enum); + + $( + $( + impl From<$member> for $name { + fn from(e: $member) -> $name { + $name::$variant(e) + } + } + )* + )* + + generate_to_tokens! { + () + tokens + $name { $($variant $($member)*,)* } + } + }; +} + +macro_rules! generate_to_tokens { + (($($arms:tt)*) $tokens:ident $name:ident { $variant:ident, $($next:tt)*}) => { + generate_to_tokens!( + ($($arms)* $name::$variant => {}) + $tokens $name { $($next)* } + ); + }; + + (($($arms:tt)*) $tokens:ident $name:ident { $variant:ident $member:ident, $($next:tt)*}) => { + generate_to_tokens!( + ($($arms)* $name::$variant(_e) => quote::ToTokens::to_tokens(_e, $tokens),) + $tokens $name { $($next)* } + ); + }; + + (($($arms:tt)*) $tokens:ident $name:ident {}) => { + impl quote::ToTokens for $name { + fn to_tokens(&self, $tokens: &mut proc_macro2::TokenStream) { + match self { + $($arms)* + } + } + } + }; +} + +macro_rules! strip_attrs_pub { + ($mac:ident!($(#[$m:meta])* $pub:ident $($t:tt)*)) => { + check_keyword_matches!(pub $pub); + + $mac!([$(#[$m])* $pub] $($t)*); + }; +} + +macro_rules! check_keyword_matches { + (struct struct) => {}; + (enum enum) => {}; + (pub pub) => {}; +} diff --git a/syn-mid/src/pat.rs b/syn-mid/src/pat.rs new file mode 100644 index 0000000..8f95381 --- /dev/null +++ b/syn-mid/src/pat.rs @@ -0,0 +1,413 @@ +use syn::{punctuated::Punctuated, token, Attribute, Ident, Member, Path, Token, Type}; + +ast_enum_of_structs! { + /// A pattern in a local binding, function signature, match expression, or + /// various other places. + /// + /// # Syntax tree enum + /// + /// This type is a [syntax tree enum]. + /// + /// [syntax tree enum]: enum.Expr.html#syntax-tree-enums + pub enum Pat { + /// A pattern that binds a new variable: `ref mut binding @ SUBPATTERN`. + Ident(PatIdent), + + /// A path pattern like `Color::Red`. + Path(PatPath), + + /// A reference pattern: `&mut var`. + Reference(PatReference), + + /// A struct or struct variant pattern: `Variant { x, y, .. }`. + Struct(PatStruct), + + /// A tuple pattern: `(a, b)`. + Tuple(PatTuple), + + /// A tuple struct or tuple variant pattern: `Variant(x, y, .., z)`. + TupleStruct(PatTupleStruct), + + /// A type ascription pattern: `foo: f64`. + Type(PatType), + + /// A pattern that matches any value: `_`. + Wild(PatWild), + + #[doc(hidden)] + __Nonexhaustive, + } +} + +ast_struct! { + /// A pattern that binds a new variable: `ref mut binding @ SUBPATTERN`. + pub struct PatIdent { + pub attrs: Vec, + pub by_ref: Option, + pub mutability: Option, + pub ident: Ident, + } +} + +ast_struct! { + /// A path pattern like `Color::Red`. + pub struct PatPath { + pub attrs: Vec, + pub path: Path, + } +} + +ast_struct! { + /// A reference pattern: `&mut var`. + pub struct PatReference { + pub attrs: Vec, + pub and_token: Token![&], + pub mutability: Option, + pub pat: Box, + } +} + +ast_struct! { + /// A struct or struct variant pattern: `Variant { x, y, .. }`. + pub struct PatStruct { + pub attrs: Vec, + pub path: Path, + pub brace_token: token::Brace, + pub fields: Punctuated, + pub dot2_token: Option, + } +} + +ast_struct! { + /// A tuple pattern: `(a, b)`. + pub struct PatTuple { + pub attrs: Vec, + pub paren_token: token::Paren, + pub elems: Punctuated, + } +} + +ast_struct! { + /// A tuple struct or tuple variant pattern: `Variant(x, y, .., z)`. + pub struct PatTupleStruct { + pub attrs: Vec, + pub path: Path, + pub pat: PatTuple, + } +} + +ast_struct! { + /// A type ascription pattern: `foo: f64`. + pub struct PatType { + pub attrs: Vec, + pub pat: Box, + pub colon_token: Token![:], + pub ty: Box, + } +} + +ast_struct! { + /// A pattern that matches any value: `_`. + pub struct PatWild { + pub attrs: Vec, + pub underscore_token: Token![_], + } +} + +ast_struct! { + /// A single field in a struct pattern. + /// + /// Patterns like the fields of Foo `{ x, ref y, ref mut z }` are treated + /// the same as `x: x, y: ref y, z: ref mut z` but there is no colon token. + pub struct FieldPat { + pub attrs: Vec, + pub member: Member, + pub colon_token: Option, + pub pat: Box, + } +} + +mod parsing { + use syn::{ + braced, + ext::IdentExt, + parenthesized, + parse::{Parse, ParseStream, Result}, + punctuated::Punctuated, + token, Ident, Member, Path, Token, + }; + + use crate::path; + + use super::{ + FieldPat, Pat, PatIdent, PatPath, PatReference, PatStruct, PatTuple, PatTupleStruct, + PatWild, + }; + + impl Parse for Pat { + fn parse(input: ParseStream<'_>) -> Result { + let lookahead = input.lookahead1(); + if lookahead.peek(Ident) + && ({ + input.peek2(Token![::]) + || input.peek2(token::Brace) + || input.peek2(token::Paren) + }) + || input.peek(Token![self]) && input.peek2(Token![::]) + || lookahead.peek(Token![::]) + || lookahead.peek(Token![<]) + || input.peek(Token![Self]) + || input.peek(Token![super]) + || input.peek(Token![extern]) + || input.peek(Token![crate]) + { + pat_path_or_struct(input) + } else if lookahead.peek(Token![_]) { + input.call(pat_wild).map(Pat::Wild) + } else if lookahead.peek(Token![ref]) + || lookahead.peek(Token![mut]) + || input.peek(Token![self]) + || input.peek(Ident) + { + input.call(pat_ident).map(Pat::Ident) + } else if lookahead.peek(Token![&]) { + input.call(pat_reference).map(Pat::Reference) + } else if lookahead.peek(token::Paren) { + input.call(pat_tuple).map(Pat::Tuple) + } else { + Err(lookahead.error()) + } + } + } + + fn pat_path_or_struct(input: ParseStream<'_>) -> Result { + let path = path::parse_path(input)?; + + if input.peek(token::Brace) { + pat_struct(input, path).map(Pat::Struct) + } else if input.peek(token::Paren) { + pat_tuple_struct(input, path).map(Pat::TupleStruct) + } else { + Ok(Pat::Path(PatPath { + attrs: Vec::new(), + path, + })) + } + } + + fn pat_wild(input: ParseStream<'_>) -> Result { + Ok(PatWild { + attrs: Vec::new(), + underscore_token: input.parse()?, + }) + } + + fn pat_ident(input: ParseStream<'_>) -> Result { + Ok(PatIdent { + attrs: Vec::new(), + by_ref: input.parse()?, + mutability: input.parse()?, + ident: input.call(Ident::parse_any)?, + }) + } + + fn pat_tuple_struct(input: ParseStream<'_>, path: Path) -> Result { + Ok(PatTupleStruct { + attrs: Vec::new(), + path, + pat: input.call(pat_tuple)?, + }) + } + + fn pat_struct(input: ParseStream<'_>, path: Path) -> Result { + let content; + let brace_token = braced!(content in input); + + let mut fields = Punctuated::new(); + while !content.is_empty() && !content.peek(Token![..]) { + let value = content.call(field_pat)?; + fields.push_value(value); + if !content.peek(Token![,]) { + break; + } + let punct: Token![,] = content.parse()?; + fields.push_punct(punct); + } + + let dot2_token = if fields.empty_or_trailing() && content.peek(Token![..]) { + Some(content.parse()?) + } else { + None + }; + + Ok(PatStruct { + attrs: Vec::new(), + path, + brace_token, + fields, + dot2_token, + }) + } + + fn field_pat(input: ParseStream<'_>) -> Result { + let boxed: Option = input.parse()?; + let by_ref: Option = input.parse()?; + let mutability: Option = input.parse()?; + let member: Member = input.parse()?; + + if boxed.is_none() && by_ref.is_none() && mutability.is_none() && input.peek(Token![:]) + || is_unnamed(&member) + { + return Ok(FieldPat { + attrs: Vec::new(), + member, + colon_token: input.parse()?, + pat: input.parse()?, + }); + } + + let ident = match member { + Member::Named(ident) => ident, + Member::Unnamed(_) => unreachable!(), + }; + + let pat = Pat::Ident(PatIdent { + attrs: Vec::new(), + by_ref, + mutability, + ident: ident.clone(), + }); + + Ok(FieldPat { + member: Member::Named(ident), + pat: Box::new(pat), + attrs: Vec::new(), + colon_token: None, + }) + } + + fn pat_tuple(input: ParseStream<'_>) -> Result { + let content; + let paren_token = parenthesized!(content in input); + + let mut elems = Punctuated::new(); + while !content.is_empty() { + let value: Pat = content.parse()?; + elems.push_value(value); + if content.is_empty() { + break; + } + let punct = content.parse()?; + elems.push_punct(punct); + } + + Ok(PatTuple { + attrs: Vec::new(), + paren_token, + elems, + }) + } + + fn pat_reference(input: ParseStream<'_>) -> Result { + Ok(PatReference { + attrs: Vec::new(), + and_token: input.parse()?, + mutability: input.parse()?, + pat: input.parse()?, + }) + } + + fn is_unnamed(member: &Member) -> bool { + match member { + Member::Named(_) => false, + Member::Unnamed(_) => true, + } + } +} + +mod printing { + use proc_macro2::TokenStream; + use quote::{ToTokens, TokenStreamExt}; + use syn::Token; + + use super::{ + FieldPat, PatIdent, PatPath, PatReference, PatStruct, PatTuple, PatTupleStruct, PatType, + PatWild, + }; + + impl ToTokens for PatWild { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.underscore_token.to_tokens(tokens); + } + } + + impl ToTokens for PatIdent { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.by_ref.to_tokens(tokens); + self.mutability.to_tokens(tokens); + self.ident.to_tokens(tokens); + } + } + + impl ToTokens for PatStruct { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.path.to_tokens(tokens); + self.brace_token.surround(tokens, |tokens| { + self.fields.to_tokens(tokens); + // NOTE: We need a comma before the dot2 token if it is present. + if !self.fields.empty_or_trailing() && self.dot2_token.is_some() { + ::default().to_tokens(tokens); + } + self.dot2_token.to_tokens(tokens); + }); + } + } + + impl ToTokens for PatTupleStruct { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.path.to_tokens(tokens); + self.pat.to_tokens(tokens); + } + } + + impl ToTokens for PatType { + fn to_tokens(&self, tokens: &mut TokenStream) { + tokens.append_all(&self.attrs); + self.pat.to_tokens(tokens); + self.colon_token.to_tokens(tokens); + self.ty.to_tokens(tokens); + } + } + + impl ToTokens for PatPath { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.path.to_tokens(tokens) + } + } + + impl ToTokens for PatTuple { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.paren_token.surround(tokens, |tokens| { + self.elems.to_tokens(tokens); + }); + } + } + + impl ToTokens for PatReference { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.and_token.to_tokens(tokens); + self.mutability.to_tokens(tokens); + self.pat.to_tokens(tokens); + } + } + + impl ToTokens for FieldPat { + fn to_tokens(&self, tokens: &mut TokenStream) { + if let Some(colon_token) = &self.colon_token { + self.member.to_tokens(tokens); + colon_token.to_tokens(tokens); + } + self.pat.to_tokens(tokens); + } + } +} diff --git a/syn-mid/src/path.rs b/syn-mid/src/path.rs new file mode 100644 index 0000000..8093b53 --- /dev/null +++ b/syn-mid/src/path.rs @@ -0,0 +1,50 @@ +use syn::{ + ext::IdentExt, + parse::{ParseStream, Result}, + punctuated::Punctuated, + Ident, Path, PathArguments, PathSegment, Token, +}; + +fn parse_path_segment(input: ParseStream<'_>) -> Result { + if input.peek(Token![super]) + || input.peek(Token![self]) + || input.peek(Token![crate]) + || input.peek(Token![extern]) + { + let ident = input.call(Ident::parse_any)?; + return Ok(PathSegment::from(ident)); + } + + let ident = if input.peek(Token![Self]) { + input.call(Ident::parse_any)? + } else { + input.parse()? + }; + + if input.peek(Token![::]) && input.peek3(Token![<]) { + Ok(PathSegment { + ident, + arguments: PathArguments::AngleBracketed(input.parse()?), + }) + } else { + Ok(PathSegment::from(ident)) + } +} + +pub(crate) fn parse_path(input: ParseStream<'_>) -> Result { + Ok(Path { + leading_colon: input.parse()?, + segments: { + let mut segments = Punctuated::new(); + let value = parse_path_segment(input)?; + segments.push_value(value); + while input.peek(Token![::]) { + let punct: Token![::] = input.parse()?; + segments.push_punct(punct); + let value = parse_path_segment(input)?; + segments.push_value(value); + } + segments + }, + }) +} diff --git a/syn/.gitignore b/syn/.gitignore new file mode 100644 index 0000000..9fa6883 --- /dev/null +++ b/syn/.gitignore @@ -0,0 +1,3 @@ +target +Cargo.lock +tests/rust/* diff --git a/syn/.travis.yml b/syn/.travis.yml new file mode 100644 index 0000000..a8719cc --- /dev/null +++ b/syn/.travis.yml @@ -0,0 +1,76 @@ +language: rust + +before_script: + - set -o errexit + +script: + - shopt -s expand_aliases + - alias build="cargo build ${TARGET+--target=$TARGET}" + - build --no-default-features + - build + - build --features full + - build --features 'fold visit visit-mut' + - build --features 'full fold visit visit-mut' + - build --no-default-features --features derive + - build --no-default-features --features 'derive parsing' + - build --no-default-features --features 'derive printing' + - build --no-default-features --features 'proc-macro parsing printing' + - build --no-default-features --features full + - build --no-default-features --features 'full parsing' + - build --no-default-features --features 'full printing' + - build --no-default-features --features 'full parsing printing' + - build --no-default-features --features 'fold visit visit-mut parsing printing' + - build --no-default-features --features 'full fold visit visit-mut parsing printing' + +matrix: + include: + - rust: nightly + name: Tests + install: + - rustup component add rustc-dev + script: + - cargo test --all-features --release + - rust: nightly + - rust: stable + - rust: beta + - rust: 1.31.0 + - rust: nightly + name: Examples + script: + - cargo check --manifest-path examples/dump-syntax/Cargo.toml + - cargo check --manifest-path examples/heapsize/example/Cargo.toml + - cargo check --manifest-path examples/lazy-static/example/Cargo.toml + - cargo check --manifest-path examples/trace-var/example/Cargo.toml + - rust: nightly + name: Codegen + script: + - (cd codegen && cargo run) + - git diff --exit-code + - rust: nightly + name: Minimal versions + script: + - cargo update -Z minimal-versions + - cargo build --all-features + - rust: nightly + name: Clippy + script: + - rustup component add clippy || travis_terminate 0 + - cargo clippy --all-features + - rust: nightly + name: WebAssembly + env: TARGET=wasm32-unknown-unknown + install: + - rustup target add "${TARGET}" + - rust: nightly + name: WASI + env: TARGET=wasm32-wasi + install: + - rustup target add "${TARGET}" + allow_failures: + - rust: nightly + name: Clippy + fast_finish: true + +env: + global: + - RUST_MIN_STACK=20000000 diff --git a/syn/Cargo.toml b/syn/Cargo.toml new file mode 100644 index 0000000..11ad921 --- /dev/null +++ b/syn/Cargo.toml @@ -0,0 +1,72 @@ +[package] +name = "syn" +version = "1.0.12" # don't forget to update html_root_url and syn.json +authors = ["David Tolnay "] +license = "MIT OR Apache-2.0" +description = "Parser for Rust source code" +repository = "https://github.com/dtolnay/syn" +documentation = "https://docs.rs/syn" +categories = ["development-tools::procedural-macro-helpers"] +readme = "README.md" +include = [ + "/benches/**", + "/build.rs", + "/Cargo.toml", + "/LICENSE-APACHE", + "/LICENSE-MIT", + "/README.md", + "/src/**", + "/tests/**", +] +edition = "2018" + +[features] +default = ["derive", "parsing", "printing", "clone-impls", "proc-macro"] +derive = [] +full = [] +parsing = [] +printing = ["quote"] +visit = [] +visit-mut = [] +fold = [] +clone-impls = [] +extra-traits = [] +proc-macro = ["proc-macro2/proc-macro", "quote/proc-macro"] + +[dependencies] +proc-macro2 = { version = "1.0.7", default-features = false } +quote = { version = "1.0", optional = true, default-features = false } +unicode-xid = "0.2" + +[dev-dependencies] +anyhow = "1.0" +flate2 = "1.0" +insta = "0.12" +rayon = "1.0" +ref-cast = "1.0" +regex = "1.0" +reqwest = { version = "0.10", features = ["blocking"] } +tar = "0.4" +termcolor = "1.0" +walkdir = "2.1" + +[[bench]] +name = "rust" +harness = false +required-features = ["full", "parsing"] + +[[bench]] +name = "file" +required-features = ["full", "parsing"] + +[package.metadata.docs.rs] +all-features = true + +[package.metadata.playground] +all-features = true + +[badges] +travis-ci = { repository = "dtolnay/syn" } + +[workspace] +members = ["dev", "json"] diff --git a/syn/LICENSE-APACHE b/syn/LICENSE-APACHE new file mode 100644 index 0000000..16fe87b --- /dev/null +++ b/syn/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/syn/LICENSE-MIT b/syn/LICENSE-MIT new file mode 100644 index 0000000..31aa793 --- /dev/null +++ b/syn/LICENSE-MIT @@ -0,0 +1,23 @@ +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/syn/README.md b/syn/README.md new file mode 100644 index 0000000..29a7f32 --- /dev/null +++ b/syn/README.md @@ -0,0 +1,291 @@ +Parser for Rust source code +=========================== + +[![Build Status](https://api.travis-ci.org/dtolnay/syn.svg?branch=master)](https://travis-ci.org/dtolnay/syn) +[![Latest Version](https://img.shields.io/crates/v/syn.svg)](https://crates.io/crates/syn) +[![Rust Documentation](https://img.shields.io/badge/api-rustdoc-blue.svg)](https://docs.rs/syn/1.0/syn/) +[![Rustc Version 1.31+](https://img.shields.io/badge/rustc-1.31+-lightgray.svg)](https://blog.rust-lang.org/2018/12/06/Rust-1.31-and-rust-2018.html) + +Syn is a parsing library for parsing a stream of Rust tokens into a syntax tree +of Rust source code. + +Currently this library is geared toward use in Rust procedural macros, but +contains some APIs that may be useful more generally. + +- **Data structures** — Syn provides a complete syntax tree that can represent + any valid Rust source code. The syntax tree is rooted at [`syn::File`] which + represents a full source file, but there are other entry points that may be + useful to procedural macros including [`syn::Item`], [`syn::Expr`] and + [`syn::Type`]. + +- **Derives** — Of particular interest to derive macros is [`syn::DeriveInput`] + which is any of the three legal input items to a derive macro. An example + below shows using this type in a library that can derive implementations of a + user-defined trait. + +- **Parsing** — Parsing in Syn is built around [parser functions] with the + signature `fn(ParseStream) -> Result`. Every syntax tree node defined by + Syn is individually parsable and may be used as a building block for custom + syntaxes, or you may dream up your own brand new syntax without involving any + of our syntax tree types. + +- **Location information** — Every token parsed by Syn is associated with a + `Span` that tracks line and column information back to the source of that + token. These spans allow a procedural macro to display detailed error messages + pointing to all the right places in the user's code. There is an example of + this below. + +- **Feature flags** — Functionality is aggressively feature gated so your + procedural macros enable only what they need, and do not pay in compile time + for all the rest. + +[`syn::File`]: https://docs.rs/syn/1.0/syn/struct.File.html +[`syn::Item`]: https://docs.rs/syn/1.0/syn/enum.Item.html +[`syn::Expr`]: https://docs.rs/syn/1.0/syn/enum.Expr.html +[`syn::Type`]: https://docs.rs/syn/1.0/syn/enum.Type.html +[`syn::DeriveInput`]: https://docs.rs/syn/1.0/syn/struct.DeriveInput.html +[parser functions]: https://docs.rs/syn/1.0/syn/parse/index.html + +If you get stuck with anything involving procedural macros in Rust I am happy to +provide help even if the issue is not related to Syn. Please file a ticket in +this repo. + +*Version requirement: Syn supports rustc 1.31 and up.* + +[*Release notes*](https://github.com/dtolnay/syn/releases) + +
+ +## Resources + +The best way to learn about procedural macros is by writing some. Consider +working through [this procedural macro workshop][workshop] to get familiar with +the different types of procedural macros. The workshop contains relevant links +into the Syn documentation as you work through each project. + +[workshop]: https://github.com/dtolnay/proc-macro-workshop + +
+ +## Example of a derive macro + +The canonical derive macro using Syn looks like this. We write an ordinary Rust +function tagged with a `proc_macro_derive` attribute and the name of the trait +we are deriving. Any time that derive appears in the user's code, the Rust +compiler passes their data structure as tokens into our macro. We get to execute +arbitrary Rust code to figure out what to do with those tokens, then hand some +tokens back to the compiler to compile into the user's crate. + +[`TokenStream`]: https://doc.rust-lang.org/proc_macro/struct.TokenStream.html + +```toml +[dependencies] +syn = "1.0" +quote = "1.0" + +[lib] +proc-macro = true +``` + +```rust +extern crate proc_macro; + +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, DeriveInput}; + +#[proc_macro_derive(MyMacro)] +pub fn my_macro(input: TokenStream) -> TokenStream { + // Parse the input tokens into a syntax tree + let input = parse_macro_input!(input as DeriveInput); + + // Build the output, possibly using quasi-quotation + let expanded = quote! { + // ... + }; + + // Hand the output tokens back to the compiler + TokenStream::from(expanded) +} +``` + +The [`heapsize`] example directory shows a complete working implementation of a +derive macro. It works on any Rust compiler 1.31+. The example derives a +`HeapSize` trait which computes an estimate of the amount of heap memory owned +by a value. + +[`heapsize`]: examples/heapsize + +```rust +pub trait HeapSize { + /// Total number of bytes of heap memory owned by `self`. + fn heap_size_of_children(&self) -> usize; +} +``` + +The derive macro allows users to write `#[derive(HeapSize)]` on data structures +in their program. + +```rust +#[derive(HeapSize)] +struct Demo<'a, T: ?Sized> { + a: Box, + b: u8, + c: &'a str, + d: String, +} +``` + +
+ +## Spans and error reporting + +The token-based procedural macro API provides great control over where the +compiler's error messages are displayed in user code. Consider the error the +user sees if one of their field types does not implement `HeapSize`. + +```rust +#[derive(HeapSize)] +struct Broken { + ok: String, + bad: std::thread::Thread, +} +``` + +By tracking span information all the way through the expansion of a procedural +macro as shown in the `heapsize` example, token-based macros in Syn are able to +trigger errors that directly pinpoint the source of the problem. + +``` +error[E0277]: the trait bound `std::thread::Thread: HeapSize` is not satisfied + --> src/main.rs:7:5 + | +7 | bad: std::thread::Thread, + | ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `HeapSize` is not implemented for `std::thread::Thread` +``` + +
+ +## Parsing a custom syntax + +The [`lazy-static`] example directory shows the implementation of a +`functionlike!(...)` procedural macro in which the input tokens are parsed using +Syn's parsing API. + +[`lazy-static`]: examples/lazy-static + +The example reimplements the popular `lazy_static` crate from crates.io as a +procedural macro. + +``` +lazy_static! { + static ref USERNAME: Regex = Regex::new("^[a-z0-9_-]{3,16}$").unwrap(); +} +``` + +The implementation shows how to trigger custom warnings and error messages on +the macro input. + +``` +warning: come on, pick a more creative name + --> src/main.rs:10:16 + | +10 | static ref FOO: String = "lazy_static".to_owned(); + | ^^^ +``` + +
+ +## Testing + +When testing macros, we often care not just that the macro can be used +successfully but also that when the macro is provided with invalid input it +produces maximally helpful error messages. Consider using the [`trybuild`] crate +to write tests for errors that are emitted by your macro or errors detected by +the Rust compiler in the expanded code following misuse of the macro. Such tests +help avoid regressions from later refactors that mistakenly make an error no +longer trigger or be less helpful than it used to be. + +[`trybuild`]: https://github.com/dtolnay/trybuild + +
+ +## Debugging + +When developing a procedural macro it can be helpful to look at what the +generated code looks like. Use `cargo rustc -- -Zunstable-options +--pretty=expanded` or the [`cargo expand`] subcommand. + +[`cargo expand`]: https://github.com/dtolnay/cargo-expand + +To show the expanded code for some crate that uses your procedural macro, run +`cargo expand` from that crate. To show the expanded code for one of your own +test cases, run `cargo expand --test the_test_case` where the last argument is +the name of the test file without the `.rs` extension. + +This write-up by Brandon W Maister discusses debugging in more detail: +[Debugging Rust's new Custom Derive system][debugging]. + +[debugging]: https://quodlibetor.github.io/posts/debugging-rusts-new-custom-derive-system/ + +
+ +## Optional features + +Syn puts a lot of functionality behind optional features in order to optimize +compile time for the most common use cases. The following features are +available. + +- **`derive`** *(enabled by default)* — Data structures for representing the + possible input to a derive macro, including structs and enums and types. +- **`full`** — Data structures for representing the syntax tree of all valid + Rust source code, including items and expressions. +- **`parsing`** *(enabled by default)* — Ability to parse input tokens into a + syntax tree node of a chosen type. +- **`printing`** *(enabled by default)* — Ability to print a syntax tree node as + tokens of Rust source code. +- **`visit`** — Trait for traversing a syntax tree. +- **`visit-mut`** — Trait for traversing and mutating in place a syntax tree. +- **`fold`** — Trait for transforming an owned syntax tree. +- **`clone-impls`** *(enabled by default)* — Clone impls for all syntax tree + types. +- **`extra-traits`** — Debug, Eq, PartialEq, Hash impls for all syntax tree + types. +- **`proc-macro`** *(enabled by default)* — Runtime dependency on the dynamic + library libproc_macro from rustc toolchain. + +
+ +## Proc macro shim + +Syn operates on the token representation provided by the [proc-macro2] crate +from crates.io rather than using the compiler's built in proc-macro crate +directly. This enables code using Syn to execute outside of the context of a +procedural macro, such as in unit tests or build.rs, and we avoid needing +incompatible ecosystems for proc macros vs non-macro use cases. + +In general all of your code should be written against proc-macro2 rather than +proc-macro. The one exception is in the signatures of procedural macro entry +points, which are required by the language to use `proc_macro::TokenStream`. + +The proc-macro2 crate will automatically detect and use the compiler's data +structures when a procedural macro is active. + +[proc-macro2]: https://docs.rs/proc-macro2/1.0.0/proc_macro2/ + +
+ +#### License + + +Licensed under either of
Apache License, Version +2.0 or MIT license at your option. + + +
+ + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in this crate 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/syn/appveyor.yml b/syn/appveyor.yml new file mode 100644 index 0000000..020c8ac --- /dev/null +++ b/syn/appveyor.yml @@ -0,0 +1,16 @@ +environment: + matrix: + - TARGET: x86_64-pc-windows-msvc + +install: + - ps: Start-FileDownload "https://static.rust-lang.org/dist/rust-nightly-${env:TARGET}.exe" + - rust-nightly-%TARGET%.exe /VERYSILENT /NORESTART /DIR="C:\Program Files (x86)\Rust" + - SET PATH=%PATH%;C:\Program Files (x86)\Rust\bin + - SET PATH=%PATH%;C:\MinGW\bin + - rustc -V + - cargo -V + +build: false + +test_script: + - cargo build --all-features diff --git a/syn/benches/file.rs b/syn/benches/file.rs new file mode 100644 index 0000000..58ab8df --- /dev/null +++ b/syn/benches/file.rs @@ -0,0 +1,30 @@ +// $ cargo bench --features full --bench file + +#![feature(rustc_private, test)] +#![recursion_limit = "1024"] + +extern crate test; + +#[macro_use] +#[path = "../tests/macros/mod.rs"] +mod macros; + +#[path = "../tests/common/mod.rs"] +mod common; +#[path = "../tests/repo/mod.rs"] +pub mod repo; + +use proc_macro2::TokenStream; +use std::fs; +use std::str::FromStr; +use test::Bencher; + +const FILE: &str = "tests/rust/src/libcore/str/mod.rs"; + +#[bench] +fn parse_file(b: &mut Bencher) { + repo::clone_rust(); + let content = fs::read_to_string(FILE).unwrap(); + let tokens = TokenStream::from_str(&content).unwrap(); + b.iter(|| syn::parse2::(tokens.clone())); +} diff --git a/syn/benches/rust.rs b/syn/benches/rust.rs new file mode 100644 index 0000000..941ecb9 --- /dev/null +++ b/syn/benches/rust.rs @@ -0,0 +1,158 @@ +// $ cargo bench --features full --bench rust +// +// Syn only, useful for profiling: +// $ RUSTFLAGS='--cfg syn_only' cargo build --release --features full --bench rust + +#![cfg_attr(not(syn_only), feature(rustc_private))] +#![recursion_limit = "1024"] + +#[macro_use] +#[path = "../tests/macros/mod.rs"] +mod macros; + +#[path = "../tests/common/mod.rs"] +mod common; +#[path = "../tests/repo/mod.rs"] +mod repo; + +use std::fs; +use std::time::{Duration, Instant}; + +#[cfg(not(syn_only))] +mod tokenstream_parse { + use proc_macro2::TokenStream; + use std::str::FromStr; + + pub fn bench(content: &str) -> Result<(), ()> { + TokenStream::from_str(content).map(drop).map_err(drop) + } +} + +mod syn_parse { + pub fn bench(content: &str) -> Result<(), ()> { + syn::parse_file(content).map(drop).map_err(drop) + } +} + +#[cfg(not(syn_only))] +mod libsyntax_parse { + extern crate rustc_data_structures; + extern crate rustc_parse; + extern crate rustc_span; + extern crate syntax; + + use rustc_data_structures::sync::Lrc; + use rustc_span::FileName; + use syntax::edition::Edition; + use syntax::errors::{emitter::Emitter, Diagnostic, Handler}; + use syntax::sess::ParseSess; + use syntax::source_map::{FilePathMapping, SourceMap}; + + pub fn bench(content: &str) -> Result<(), ()> { + struct SilentEmitter; + + impl Emitter for SilentEmitter { + fn emit_diagnostic(&mut self, _diag: &Diagnostic) {} + fn source_map(&self) -> Option<&Lrc> { + None + } + } + + syntax::with_globals(Edition::Edition2018, || { + let cm = Lrc::new(SourceMap::new(FilePathMapping::empty())); + let emitter = Box::new(SilentEmitter); + let handler = Handler::with_emitter(false, None, emitter); + let sess = ParseSess::with_span_handler(handler, cm); + if let Err(mut diagnostic) = rustc_parse::parse_crate_from_source_str( + FileName::Custom("bench".to_owned()), + content.to_owned(), + &sess, + ) { + diagnostic.cancel(); + return Err(()); + }; + Ok(()) + }) + } +} + +#[cfg(not(syn_only))] +mod read_from_disk { + pub fn bench(content: &str) -> Result<(), ()> { + let _ = content; + Ok(()) + } +} + +fn exec(mut codepath: impl FnMut(&str) -> Result<(), ()>) -> Duration { + let begin = Instant::now(); + let mut success = 0; + let mut total = 0; + + walkdir::WalkDir::new("tests/rust/src") + .into_iter() + .filter_entry(repo::base_dir_filter) + .for_each(|entry| { + let entry = entry.unwrap(); + let path = entry.path(); + if path.is_dir() { + return; + } + let content = fs::read_to_string(path).unwrap(); + let ok = codepath(&content).is_ok(); + success += ok as usize; + total += 1; + if !ok { + eprintln!("FAIL {}", path.display()); + } + }); + + assert_eq!(success, total); + begin.elapsed() +} + +fn main() { + repo::clone_rust(); + + macro_rules! testcases { + ($($(#[$cfg:meta])* $name:path,)*) => { + vec![ + $( + $(#[$cfg])* + (stringify!($name), $name as fn(&str) -> Result<(), ()>), + )* + ] + }; + } + + #[cfg(not(syn_only))] + { + let mut lines = 0; + let mut files = 0; + exec(|content| { + lines += content.lines().count(); + files += 1; + Ok(()) + }); + eprintln!("\n{} lines in {} files", lines, files); + } + + for (name, f) in testcases!( + #[cfg(not(syn_only))] + read_from_disk::bench, + #[cfg(not(syn_only))] + tokenstream_parse::bench, + syn_parse::bench, + #[cfg(not(syn_only))] + libsyntax_parse::bench, + ) { + eprint!("{:20}", format!("{}:", name)); + let elapsed = exec(f); + eprintln!( + "elapsed={}.{:03}s", + elapsed.as_secs(), + elapsed.subsec_millis(), + ); + } + eprintln!(); +} diff --git a/syn/build.rs b/syn/build.rs new file mode 100644 index 0000000..c6040c1 --- /dev/null +++ b/syn/build.rs @@ -0,0 +1,63 @@ +use std::env; +use std::process::Command; +use std::str::{self, FromStr}; + +// The rustc-cfg strings below are *not* public API. Please let us know by +// opening a GitHub issue if your build environment requires some way to enable +// these cfgs other than by executing our build script. +fn main() { + let compiler = match rustc_version() { + Some(compiler) => compiler, + None => return, + }; + + if compiler.minor < 36 { + println!("cargo:rustc-cfg=syn_omit_await_from_token_macro"); + } + + if !compiler.nightly { + println!("cargo:rustc-cfg=syn_disable_nightly_tests"); + } +} + +struct Compiler { + minor: u32, + nightly: bool, +} + +fn rustc_version() -> Option { + let rustc = match env::var_os("RUSTC") { + Some(rustc) => rustc, + None => return None, + }; + + let output = match Command::new(rustc).arg("--version").output() { + Ok(output) => output, + Err(_) => return None, + }; + + let version = match str::from_utf8(&output.stdout) { + Ok(version) => version, + Err(_) => return None, + }; + + let mut pieces = version.split('.'); + if pieces.next() != Some("rustc 1") { + return None; + } + + let next = match pieces.next() { + Some(next) => next, + None => return None, + }; + + let minor = match u32::from_str(next) { + Ok(minor) => minor, + Err(_) => return None, + }; + + Some(Compiler { + minor, + nightly: version.contains("nightly"), + }) +} diff --git a/syn/codegen/Cargo.toml b/syn/codegen/Cargo.toml new file mode 100644 index 0000000..44d890b --- /dev/null +++ b/syn/codegen/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "syn-internal-codegen" +version = "0.0.0" +authors = ["David Tolnay ", "Nika Layzell "] +edition = "2018" + +publish = false # this is an internal crate which should never be published + +[dependencies] +anyhow = "1.0" +color-backtrace = "0.2" +indexmap = { version = "1.0", features = ["serde-1"] } +inflections = "1.1" +proc-macro2 = { version = "1.0", features = ["span-locations"] } +quote = "1.0" +rustfmt = { package = "rustfmt-nightly", git = "https://github.com/rust-lang-nursery/rustfmt" } +semver = { version = "0.9", features = ["serde"] } +serde = { version = "1.0.88", features = ["derive"] } +serde_json = "1.0.38" +syn-codegen = { path = "../json" } +syn = { path = "..", features = ["full", "extra-traits"] } +thiserror = "1.0" +toml = "0.4.10" + +# work around https://github.com/crossbeam-rs/crossbeam/issues/435 +# until https://github.com/BurntSushi/ripgrep/pull/1427 is released +crossbeam-utils = "=0.6.5" + +[workspace] +# Prefer that `cargo clean` in syn's directory does not require a rebuild of +# rustfmt in the codegen directory. diff --git a/syn/codegen/README.md b/syn/codegen/README.md new file mode 100644 index 0000000..df46bd2 --- /dev/null +++ b/syn/codegen/README.md @@ -0,0 +1,12 @@ +# syn_codegen + +This is an internal (not published on crates.io) crate which is used to generate +the files in the `gen/` directory of `syn`. It is used to ensure that the +implementations for `Fold`, `Visit`, and `VisitMut` remain in sync with the +actual AST. + +To run this program, run `cargo run` in this directory, and the `gen/` folder +will be re-generated. + +This program is slow, and is therefore not run when building `syn` as part of +the build script to save on compile time. diff --git a/syn/codegen/src/debug.rs b/syn/codegen/src/debug.rs new file mode 100644 index 0000000..9193881 --- /dev/null +++ b/syn/codegen/src/debug.rs @@ -0,0 +1,308 @@ +use crate::file; +use anyhow::Result; +use proc_macro2::{Ident, Span, TokenStream}; +use quote::quote; +use syn::Index; +use syn_codegen::{Data, Definitions, Node, Type}; + +const DEBUG_SRC: &str = "../tests/debug/gen.rs"; + +fn rust_type(ty: &Type) -> TokenStream { + match ty { + Type::Syn(ty) => { + let ident = Ident::new(ty, Span::call_site()); + quote!(syn::#ident) + } + Type::Std(ty) => { + let ident = Ident::new(ty, Span::call_site()); + quote!(#ident) + } + Type::Ext(ty) => { + let ident = Ident::new(ty, Span::call_site()); + quote!(proc_macro2::#ident) + } + Type::Token(ty) | Type::Group(ty) => { + let ident = Ident::new(ty, Span::call_site()); + quote!(syn::token::#ident) + } + Type::Punctuated(ty) => { + let element = rust_type(&ty.element); + let punct = Ident::new(&ty.punct, Span::call_site()); + quote!(syn::punctuated::Punctuated<#element, #punct>) + } + Type::Option(ty) => { + let inner = rust_type(ty); + quote!(Option<#inner>) + } + Type::Box(ty) => { + let inner = rust_type(ty); + quote!(Box<#inner>) + } + Type::Vec(ty) => { + let inner = rust_type(ty); + quote!(Vec<#inner>) + } + Type::Tuple(ty) => { + let inner = ty.iter().map(rust_type); + quote!((#(#inner,)*)) + } + } +} + +fn is_printable(ty: &Type) -> bool { + match ty { + Type::Ext(name) => name != "Span", + Type::Box(ty) => is_printable(ty), + Type::Tuple(ty) => ty.iter().any(is_printable), + Type::Token(_) | Type::Group(_) => false, + Type::Syn(name) => name != "Reserved", + Type::Std(_) | Type::Punctuated(_) | Type::Option(_) | Type::Vec(_) => true, + } +} + +fn format_field(val: &TokenStream, ty: &Type) -> Option { + if !is_printable(ty) { + return None; + } + let format = match ty { + Type::Option(ty) => { + let inner = quote!(_val); + let format = format_field(&inner, ty).map(|format| { + quote! { + formatter.write_str("(")?; + Debug::fmt(#format, formatter)?; + formatter.write_str(")")?; + } + }); + let ty = rust_type(ty); + quote!({ + #[derive(RefCast)] + #[repr(transparent)] + struct Print(Option<#ty>); + impl Debug for Print { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + match &self.0 { + Some(#inner) => { + formatter.write_str("Some")?; + #format + Ok(()) + } + None => formatter.write_str("None"), + } + } + } + Print::ref_cast(#val) + }) + } + Type::Tuple(ty) => { + let printable: Vec = ty + .iter() + .enumerate() + .filter_map(|(i, ty)| { + let index = Index::from(i); + let val = quote!(&#val.#index); + format_field(&val, ty) + }) + .collect(); + if printable.len() == 1 { + printable.into_iter().next().unwrap() + } else { + quote! { + &(#(#printable),*) + } + } + } + _ => quote! { Lite(#val) }, + }; + Some(format) +} + +fn syntax_tree_enum<'a>(outer: &str, inner: &str, fields: &'a [Type]) -> Option<&'a str> { + if fields.len() != 1 { + return None; + } + const WHITELIST: &[&str] = &["PathArguments", "Visibility"]; + match &fields[0] { + Type::Syn(ty) if WHITELIST.contains(&outer) || outer.to_owned() + inner == *ty => Some(ty), + _ => None, + } +} + +fn lookup<'a>(defs: &'a Definitions, name: &str) -> &'a Node { + for node in &defs.types { + if node.ident == name { + return node; + } + } + panic!("not found: {}", name) +} + +fn expand_impl_body(defs: &Definitions, node: &Node, name: &str) -> TokenStream { + let ident = Ident::new(&node.ident, Span::call_site()); + + match &node.data { + Data::Enum(variants) => { + let arms = variants.iter().map(|(v, fields)| { + let variant = Ident::new(v, Span::call_site()); + if fields.is_empty() { + quote! { + syn::#ident::#variant => formatter.write_str(#v), + } + } else if let Some(inner) = syntax_tree_enum(name, v, fields) { + let path = format!("{}::{}", name, v); + let format = expand_impl_body(defs, lookup(defs, inner), &path); + quote! { + syn::#ident::#variant(_val) => { + #format + } + } + } else if fields.len() == 1 { + let ty = &fields[0]; + let val = quote!(_val); + let format = format_field(&val, ty).map(|format| { + quote! { + formatter.write_str("(")?; + Debug::fmt(#format, formatter)?; + formatter.write_str(")")?; + } + }); + quote! { + syn::#ident::#variant(_val) => { + formatter.write_str(#v)?; + #format + Ok(()) + } + } + } else { + let pats = (0..fields.len()) + .map(|i| Ident::new(&format!("_v{}", i), Span::call_site())); + let fields = fields.iter().enumerate().filter_map(|(i, ty)| { + let index = Ident::new(&format!("_v{}", i), Span::call_site()); + let val = quote!(#index); + let format = format_field(&val, ty)?; + Some(quote! { + formatter.field(#format); + }) + }); + quote! { + syn::#ident::#variant(#(#pats),*) => { + let mut formatter = formatter.debug_tuple(#v); + #(#fields)* + formatter.finish() + } + } + } + }); + let nonexhaustive = if node.exhaustive { + None + } else { + Some(quote!(_ => unreachable!())) + }; + quote! { + match _val { + #(#arms)* + #nonexhaustive + } + } + } + Data::Struct(fields) => { + let fields = fields.iter().filter_map(|(f, ty)| { + let ident = Ident::new(f, Span::call_site()); + if let Type::Option(ty) = ty { + let inner = quote!(_val); + let format = format_field(&inner, ty).map(|format| { + quote! { + let #inner = &self.0; + formatter.write_str("(")?; + Debug::fmt(#format, formatter)?; + formatter.write_str(")")?; + } + }); + let ty = rust_type(ty); + Some(quote! { + if let Some(val) = &_val.#ident { + #[derive(RefCast)] + #[repr(transparent)] + struct Print(#ty); + impl Debug for Print { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("Some")?; + #format + Ok(()) + } + } + formatter.field(#f, Print::ref_cast(val)); + } + }) + } else { + let val = quote!(&_val.#ident); + let format = format_field(&val, ty)?; + let mut call = quote! { + formatter.field(#f, #format); + }; + if let Type::Vec(_) | Type::Punctuated(_) = ty { + call = quote! { + if !_val.#ident.is_empty() { + #call + } + }; + } + Some(call) + } + }); + quote! { + let mut formatter = formatter.debug_struct(#name); + #(#fields)* + formatter.finish() + } + } + Data::Private => { + if node.ident == "LitInt" || node.ident == "LitFloat" { + quote! { + write!(formatter, "{}", _val) + } + } else { + quote! { + write!(formatter, "{:?}", _val.value()) + } + } + } + } +} + +fn expand_impl(defs: &Definitions, node: &Node) -> TokenStream { + if node.ident == "Reserved" { + return TokenStream::new(); + } + + let ident = Ident::new(&node.ident, Span::call_site()); + let body = expand_impl_body(defs, node, &node.ident); + + quote! { + impl Debug for Lite { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + let _val = &self.value; + #body + } + } + } +} + +pub fn generate(defs: &Definitions) -> Result<()> { + let mut impls = TokenStream::new(); + for node in &defs.types { + impls.extend(expand_impl(&defs, node)); + } + + file::write( + DEBUG_SRC, + quote! { + use super::{Lite, RefCast}; + use std::fmt::{self, Debug}; + + #impls + }, + )?; + + Ok(()) +} diff --git a/syn/codegen/src/file.rs b/syn/codegen/src/file.rs new file mode 100644 index 0000000..5521d75 --- /dev/null +++ b/syn/codegen/src/file.rs @@ -0,0 +1,32 @@ +use anyhow::Result; +use proc_macro2::TokenStream; +use std::fs; +use std::io::Write; +use std::path::Path; + +pub fn write>(path: P, content: TokenStream) -> Result<()> { + let mut formatted = Vec::new(); + writeln!( + formatted, + "// This file is @generated by syn-internal-codegen." + )?; + writeln!(formatted, "// It is not intended for manual editing.")?; + writeln!(formatted)?; + + let mut config = rustfmt::Config::default(); + config.set().emit_mode(rustfmt::EmitMode::Stdout); + config.set().verbose(rustfmt::Verbosity::Quiet); + config.set().format_macro_matchers(true); + config.set().normalize_doc_attributes(true); + + let mut session = rustfmt::Session::new(config, Some(&mut formatted)); + session.format(rustfmt::Input::Text(content.to_string()))?; + drop(session); + + if path.as_ref().is_file() && fs::read(&path)? == formatted { + return Ok(()); + } + + fs::write(path, formatted)?; + Ok(()) +} diff --git a/syn/codegen/src/fold.rs b/syn/codegen/src/fold.rs new file mode 100644 index 0000000..6914d76 --- /dev/null +++ b/syn/codegen/src/fold.rs @@ -0,0 +1,284 @@ +use crate::{file, full, gen}; +use anyhow::Result; +use proc_macro2::{Ident, Span, TokenStream}; +use quote::quote; +use syn::Index; +use syn_codegen::{Data, Definitions, Features, Node, Type}; + +const FOLD_SRC: &str = "../src/gen/fold.rs"; + +fn simple_visit(item: &str, name: &TokenStream) -> TokenStream { + let ident = gen::under_name(item); + let method = Ident::new(&format!("fold_{}", ident), Span::call_site()); + quote! { + f.#method(#name) + } +} + +fn visit( + ty: &Type, + features: &Features, + defs: &Definitions, + name: &TokenStream, +) -> Option { + match ty { + Type::Box(t) => { + let res = visit(t, features, defs, "e!(*#name))?; + Some(quote! { + Box::new(#res) + }) + } + Type::Vec(t) => { + let operand = quote!(it); + let val = visit(t, features, defs, &operand)?; + Some(quote! { + FoldHelper::lift(#name, |it| { #val }) + }) + } + Type::Punctuated(p) => { + let operand = quote!(it); + let val = visit(&p.element, features, defs, &operand)?; + Some(quote! { + FoldHelper::lift(#name, |it| { #val }) + }) + } + Type::Option(t) => { + let it = quote!(it); + let val = visit(t, features, defs, &it)?; + Some(quote! { + (#name).map(|it| { #val }) + }) + } + Type::Tuple(t) => { + let mut code = TokenStream::new(); + for (i, elem) in t.iter().enumerate() { + let i = Index::from(i); + let it = quote!((#name).#i); + let val = visit(elem, features, defs, &it).unwrap_or(it); + code.extend(val); + code.extend(quote!(,)); + } + Some(quote! { + (#code) + }) + } + Type::Token(t) => { + let repr = &defs.tokens[t]; + let is_keyword = repr.chars().next().unwrap().is_alphabetic(); + let spans = if is_keyword { + quote!(span) + } else { + quote!(spans) + }; + let ty = if repr == "await" { + quote!(crate::token::Await) + } else { + syn::parse_str(&format!("Token![{}]", repr)).unwrap() + }; + Some(quote! { + #ty(tokens_helper(f, &#name.#spans)) + }) + } + Type::Group(t) => { + let ty = Ident::new(t, Span::call_site()); + Some(quote! { + #ty(tokens_helper(f, &#name.span)) + }) + } + Type::Syn(t) => { + fn requires_full(features: &Features) -> bool { + features.any.contains("full") && features.any.len() == 1 + } + let mut res = simple_visit(t, name); + let target = defs.types.iter().find(|ty| ty.ident == *t).unwrap(); + if requires_full(&target.features) && !requires_full(features) { + res = quote!(full!(#res)); + } + Some(res) + } + Type::Ext(t) if gen::TERMINAL_TYPES.contains(&&t[..]) => Some(simple_visit(t, name)), + Type::Ext(_) | Type::Std(_) => None, + } +} + +fn node(traits: &mut TokenStream, impls: &mut TokenStream, s: &Node, defs: &Definitions) { + let under_name = gen::under_name(&s.ident); + let ty = Ident::new(&s.ident, Span::call_site()); + let fold_fn = Ident::new(&format!("fold_{}", under_name), Span::call_site()); + + let mut fold_impl = TokenStream::new(); + + match &s.data { + Data::Enum(variants) => { + let mut fold_variants = TokenStream::new(); + + for (variant, fields) in variants { + let variant_ident = Ident::new(variant, Span::call_site()); + + if fields.is_empty() { + fold_variants.extend(quote! { + #ty::#variant_ident => { + #ty::#variant_ident + } + }); + } else { + let mut bind_fold_fields = TokenStream::new(); + let mut fold_fields = TokenStream::new(); + + for (idx, ty) in fields.iter().enumerate() { + let name = format!("_binding_{}", idx); + let binding = Ident::new(&name, Span::call_site()); + + bind_fold_fields.extend(quote! { + #binding, + }); + + let owned_binding = quote!(#binding); + + fold_fields.extend( + visit(ty, &s.features, defs, &owned_binding).unwrap_or(owned_binding), + ); + + fold_fields.extend(quote!(,)); + } + + fold_variants.extend(quote! { + #ty::#variant_ident(#bind_fold_fields) => { + #ty::#variant_ident( + #fold_fields + ) + } + }); + } + } + + let nonexhaustive = if s.exhaustive { + None + } else { + Some(quote!(_ => unreachable!())) + }; + + fold_impl.extend(quote! { + match node { + #fold_variants + #nonexhaustive + } + }); + } + Data::Struct(fields) => { + let mut fold_fields = TokenStream::new(); + + for (field, ty) in fields { + let id = Ident::new(&field, Span::call_site()); + let ref_toks = quote!(node.#id); + + if let Type::Syn(ty) = ty { + if ty == "Reserved" { + fold_fields.extend(quote! { + #id: #ref_toks, + }); + continue; + } + } + + let fold = visit(&ty, &s.features, defs, &ref_toks).unwrap_or(ref_toks); + + fold_fields.extend(quote! { + #id: #fold, + }); + } + + if !fields.is_empty() { + fold_impl.extend(quote! { + #ty { + #fold_fields + } + }) + } else { + if ty == "Ident" { + fold_impl.extend(quote! { + let mut node = node; + let span = f.fold_span(node.span()); + node.set_span(span); + }); + } + fold_impl.extend(quote! { + node + }); + } + } + Data::Private => { + if ty == "Ident" { + fold_impl.extend(quote! { + let mut node = node; + let span = f.fold_span(node.span()); + node.set_span(span); + }); + } + fold_impl.extend(quote! { + node + }); + } + } + + let fold_span_only = + s.data == Data::Private && !gen::TERMINAL_TYPES.contains(&s.ident.as_str()); + if fold_span_only { + fold_impl = quote! { + let span = f.fold_span(node.span()); + let mut node = node; + node.set_span(span); + node + }; + } + + traits.extend(quote! { + fn #fold_fn(&mut self, i: #ty) -> #ty { + #fold_fn(self, i) + } + }); + + impls.extend(quote! { + pub fn #fold_fn(f: &mut F, node: #ty) -> #ty + where + F: Fold + ?Sized, + { + #fold_impl + } + }); +} + +pub fn generate(defs: &Definitions) -> Result<()> { + let (traits, impls) = gen::traverse(defs, node); + let full_macro = full::get_macro(); + file::write( + FOLD_SRC, + quote! { + // Unreachable code is generated sometimes without the full feature. + #![allow(unreachable_code, unused_variables)] + + use crate::*; + #[cfg(any(feature = "full", feature = "derive"))] + use crate::token::{Brace, Bracket, Paren, Group}; + use proc_macro2::Span; + #[cfg(any(feature = "full", feature = "derive"))] + use crate::gen::helper::fold::*; + + #full_macro + + /// Syntax tree traversal to transform the nodes of an owned syntax tree. + /// + /// See the [module documentation] for details. + /// + /// [module documentation]: self + /// + /// *This trait is available if Syn is built with the `"fold"` feature.* + pub trait Fold { + #traits + } + + #impls + }, + )?; + Ok(()) +} diff --git a/syn/codegen/src/full.rs b/syn/codegen/src/full.rs new file mode 100644 index 0000000..a410031 --- /dev/null +++ b/syn/codegen/src/full.rs @@ -0,0 +1,20 @@ +use proc_macro2::TokenStream; +use quote::quote; + +pub fn get_macro() -> TokenStream { + quote! { + #[cfg(feature = "full")] + macro_rules! full { + ($e:expr) => { + $e + }; + } + + #[cfg(all(feature = "derive", not(feature = "full")))] + macro_rules! full { + ($e:expr) => { + unreachable!() + }; + } + } +} diff --git a/syn/codegen/src/gen.rs b/syn/codegen/src/gen.rs new file mode 100644 index 0000000..ef43182 --- /dev/null +++ b/syn/codegen/src/gen.rs @@ -0,0 +1,45 @@ +use inflections::Inflect; +use proc_macro2::{Ident, Span, TokenStream}; +use quote::quote; +use syn_codegen::{Data, Definitions, Features, Node}; + +pub const TERMINAL_TYPES: &[&str] = &["Span", "Ident"]; + +pub fn under_name(name: &str) -> Ident { + Ident::new(&name.to_snake_case(), Span::call_site()) +} + +pub fn traverse( + defs: &Definitions, + node: fn(&mut TokenStream, &mut TokenStream, &Node, &Definitions), +) -> (TokenStream, TokenStream) { + let mut types = defs.types.clone(); + for terminal in TERMINAL_TYPES { + types.push(Node { + ident: terminal.to_string(), + features: Features::default(), + data: Data::Private, + exhaustive: true, + }); + } + types.sort_by(|a, b| a.ident.cmp(&b.ident)); + + let mut traits = TokenStream::new(); + let mut impls = TokenStream::new(); + for s in types { + if s.ident == "Reserved" { + continue; + } + let features = &s.features.any; + let features = match features.len() { + 0 => quote!(), + 1 => quote!(#[cfg(feature = #(#features)*)]), + _ => quote!(#[cfg(any(#(feature = #features),*))]), + }; + traits.extend(features.clone()); + impls.extend(features); + node(&mut traits, &mut impls, &s, defs); + } + + (traits, impls) +} diff --git a/syn/codegen/src/json.rs b/syn/codegen/src/json.rs new file mode 100644 index 0000000..53904bf --- /dev/null +++ b/syn/codegen/src/json.rs @@ -0,0 +1,18 @@ +use anyhow::Result; +use std::fs; +use std::path::Path; +use syn_codegen::Definitions; + +pub fn generate(defs: &Definitions) -> Result<()> { + let mut j = serde_json::to_string_pretty(&defs)?; + j.push('\n'); + + let check: Definitions = serde_json::from_str(&j)?; + assert_eq!(*defs, check); + + let codegen_root = Path::new(env!("CARGO_MANIFEST_DIR")); + let json_path = codegen_root.join("../syn.json"); + fs::write(json_path, j)?; + + Ok(()) +} diff --git a/syn/codegen/src/main.rs b/syn/codegen/src/main.rs new file mode 100644 index 0000000..9b1b5dd --- /dev/null +++ b/syn/codegen/src/main.rs @@ -0,0 +1,36 @@ +// This crate crawls the Syn source directory to find all structs and enums that +// form the Syn syntax tree. +// +// A machine-readable representation of the syntax tree is saved to syn.json in +// the repo root for other code generation tools to consume. The syn-codegen +// crate (https://docs.rs/syn-codegen/) provides the data structures for parsing +// and making use of syn.json from Rust code. +// +// Finally this crate generates the Visit, VisitMut, and Fold traits in Syn +// programmatically from the syntax tree description. + +#![recursion_limit = "128"] +#![allow(clippy::needless_pass_by_value)] + +mod debug; +mod file; +mod fold; +mod full; +mod gen; +mod json; +mod operand; +mod parse; +mod version; +mod visit; +mod visit_mut; + +fn main() -> anyhow::Result<()> { + color_backtrace::install(); + let defs = parse::parse()?; + json::generate(&defs)?; + fold::generate(&defs)?; + visit::generate(&defs)?; + visit_mut::generate(&defs)?; + debug::generate(&defs)?; + Ok(()) +} diff --git a/syn/codegen/src/operand.rs b/syn/codegen/src/operand.rs new file mode 100644 index 0000000..db3bd18 --- /dev/null +++ b/syn/codegen/src/operand.rs @@ -0,0 +1,38 @@ +use proc_macro2::TokenStream; +use quote::quote; + +pub enum Operand { + Borrowed(TokenStream), + Owned(TokenStream), +} + +pub use self::Operand::*; + +impl Operand { + pub fn tokens(&self) -> &TokenStream { + match self { + Borrowed(n) | Owned(n) => n, + } + } + + pub fn ref_tokens(&self) -> TokenStream { + match self { + Borrowed(n) => n.clone(), + Owned(n) => quote!(&#n), + } + } + + pub fn ref_mut_tokens(&self) -> TokenStream { + match self { + Borrowed(n) => n.clone(), + Owned(n) => quote!(&mut #n), + } + } + + pub fn owned_tokens(&self) -> TokenStream { + match self { + Borrowed(n) => quote!(*#n), + Owned(n) => n.clone(), + } + } +} diff --git a/syn/codegen/src/parse.rs b/syn/codegen/src/parse.rs new file mode 100644 index 0000000..cdd6085 --- /dev/null +++ b/syn/codegen/src/parse.rs @@ -0,0 +1,657 @@ +use crate::version; +use anyhow::{bail, Result}; +use indexmap::IndexMap; +use quote::quote; +use syn::parse::Parser; +use syn::{parse_quote, Data, DataStruct, DeriveInput, Ident, Item}; +use syn_codegen as types; +use thiserror::Error; + +use std::collections::BTreeMap; +use std::fs; +use std::path::{Path, PathBuf}; + +const SYN_CRATE_ROOT: &str = "../src/lib.rs"; +const TOKEN_SRC: &str = "../src/token.rs"; +const IGNORED_MODS: &[&str] = &["fold", "visit", "visit_mut"]; +const EXTRA_TYPES: &[&str] = &["Lifetime"]; +const NONEXHAUSTIVE: &str = "__Nonexhaustive"; + +// NOTE: BTreeMap is used here instead of HashMap to have deterministic output. +type ItemLookup = BTreeMap; +type TokenLookup = BTreeMap; + +/// Parse the contents of `src` and return a list of AST types. +pub fn parse() -> Result { + let mut item_lookup = BTreeMap::new(); + load_file(SYN_CRATE_ROOT, &[], &mut item_lookup)?; + + let token_lookup = load_token_file(TOKEN_SRC)?; + + let version = version::get()?; + + let types = item_lookup + .values() + .map(|item| introspect_item(item, &item_lookup, &token_lookup)) + .collect(); + + let tokens = token_lookup + .into_iter() + .map(|(name, ty)| (ty, name)) + .collect(); + + Ok(types::Definitions { + version, + types, + tokens, + }) +} + +/// Data extracted from syn source +#[derive(Clone)] +pub struct AstItem { + ast: DeriveInput, + features: Vec, +} + +fn introspect_item(item: &AstItem, items: &ItemLookup, tokens: &TokenLookup) -> types::Node { + let features = introspect_features(&item.features); + + match &item.ast.data { + Data::Enum(ref data) => types::Node { + ident: item.ast.ident.to_string(), + features, + data: types::Data::Enum(introspect_enum(data, items, tokens)), + exhaustive: data.variants.iter().all(|v| v.ident != NONEXHAUSTIVE), + }, + Data::Struct(ref data) => types::Node { + ident: item.ast.ident.to_string(), + features, + data: { + if data.fields.iter().all(|f| is_pub(&f.vis)) { + types::Data::Struct(introspect_struct(data, items, tokens)) + } else { + types::Data::Private + } + }, + exhaustive: true, + }, + Data::Union(..) => panic!("Union not supported"), + } +} + +fn introspect_enum( + item: &syn::DataEnum, + items: &ItemLookup, + tokens: &TokenLookup, +) -> types::Variants { + item.variants + .iter() + .filter_map(|variant| { + if variant.ident == NONEXHAUSTIVE { + return None; + } + let fields = match &variant.fields { + syn::Fields::Unnamed(fields) => fields + .unnamed + .iter() + .map(|field| introspect_type(&field.ty, items, tokens)) + .collect(), + syn::Fields::Unit => vec![], + _ => panic!("Enum representation not supported"), + }; + Some((variant.ident.to_string(), fields)) + }) + .collect() +} + +fn introspect_struct( + item: &syn::DataStruct, + items: &ItemLookup, + tokens: &TokenLookup, +) -> types::Fields { + match &item.fields { + syn::Fields::Named(fields) => fields + .named + .iter() + .map(|field| { + ( + field.ident.as_ref().unwrap().to_string(), + introspect_type(&field.ty, items, tokens), + ) + }) + .collect(), + syn::Fields::Unit => IndexMap::new(), + _ => panic!("Struct representation not supported"), + } +} + +fn introspect_type(item: &syn::Type, items: &ItemLookup, tokens: &TokenLookup) -> types::Type { + match item { + syn::Type::Path(syn::TypePath { + qself: None, + ref path, + }) => { + let last = path.segments.last().unwrap(); + let string = last.ident.to_string(); + + match string.as_str() { + "Option" => { + let nested = introspect_type(first_arg(&last.arguments), items, tokens); + types::Type::Option(Box::new(nested)) + } + "Punctuated" => { + let nested = introspect_type(first_arg(&last.arguments), items, tokens); + let punct = match introspect_type(last_arg(&last.arguments), items, tokens) { + types::Type::Token(s) => s, + _ => panic!(), + }; + + types::Type::Punctuated(types::Punctuated { + element: Box::new(nested), + punct, + }) + } + "Vec" => { + let nested = introspect_type(first_arg(&last.arguments), items, tokens); + types::Type::Vec(Box::new(nested)) + } + "Box" => { + let nested = introspect_type(first_arg(&last.arguments), items, tokens); + types::Type::Box(Box::new(nested)) + } + "Brace" | "Bracket" | "Paren" | "Group" => types::Type::Group(string), + "TokenStream" | "Literal" | "Ident" | "Span" => types::Type::Ext(string), + "String" | "u32" | "usize" | "bool" => types::Type::Std(string), + "Await" => types::Type::Token("Await".to_string()), + _ => { + if items.get(&last.ident).is_some() || last.ident == "Reserved" { + types::Type::Syn(string) + } else { + unimplemented!("{}", string); + } + } + } + } + syn::Type::Tuple(syn::TypeTuple { ref elems, .. }) => { + let tys = elems + .iter() + .map(|ty| introspect_type(&ty, items, tokens)) + .collect(); + types::Type::Tuple(tys) + } + syn::Type::Macro(syn::TypeMacro { ref mac }) + if mac.path.segments.last().unwrap().ident == "Token" => + { + let content = mac.tokens.to_string(); + let ty = tokens.get(&content).unwrap().to_string(); + + types::Type::Token(ty) + } + _ => panic!("{}", quote!(#item).to_string()), + } +} + +fn introspect_features(attrs: &[syn::Attribute]) -> types::Features { + let mut ret = types::Features::default(); + + for attr in attrs { + if !attr.path.is_ident("cfg") { + continue; + } + + let features = parsing::parse_features.parse2(attr.tokens.clone()).unwrap(); + + if ret.any.is_empty() { + ret = features; + } else if ret.any.len() < features.any.len() { + assert!(ret.any.iter().all(|f| features.any.contains(f))); + } else { + assert!(features.any.iter().all(|f| ret.any.contains(f))); + ret = features; + } + } + + ret +} + +fn is_pub(vis: &syn::Visibility) -> bool { + match vis { + syn::Visibility::Public(_) => true, + _ => false, + } +} + +fn first_arg(params: &syn::PathArguments) -> &syn::Type { + let data = match *params { + syn::PathArguments::AngleBracketed(ref data) => data, + _ => panic!("Expected at least 1 type argument here"), + }; + + match *data + .args + .first() + .expect("Expected at least 1 type argument here") + { + syn::GenericArgument::Type(ref ty) => ty, + _ => panic!("Expected at least 1 type argument here"), + } +} + +fn last_arg(params: &syn::PathArguments) -> &syn::Type { + let data = match *params { + syn::PathArguments::AngleBracketed(ref data) => data, + _ => panic!("Expected at least 1 type argument here"), + }; + + match *data + .args + .last() + .expect("Expected at least 1 type argument here") + { + syn::GenericArgument::Type(ref ty) => ty, + _ => panic!("Expected at least 1 type argument here"), + } +} + +mod parsing { + use super::{AstItem, TokenLookup}; + + use proc_macro2::{TokenStream, TokenTree}; + use quote::quote; + use syn; + use syn::parse::{ParseStream, Result}; + use syn::*; + use syn_codegen as types; + + use std::collections::{BTreeMap, BTreeSet}; + + fn peek_tag(input: ParseStream, tag: &str) -> bool { + let ahead = input.fork(); + ahead.parse::().is_ok() + && ahead + .parse::() + .map(|ident| ident == tag) + .unwrap_or(false) + } + + // Parses #full - returns #[cfg(feature = "full")] if it is present, and + // nothing otherwise. + fn full(input: ParseStream) -> Vec { + if peek_tag(input, "full") { + input.parse::().unwrap(); + input.parse::().unwrap(); + vec![parse_quote!(#[cfg(feature = "full")])] + } else { + vec![] + } + } + + fn skip_manual_extra_traits(input: ParseStream) { + if peek_tag(input, "manual_extra_traits") || peek_tag(input, "manual_extra_traits_debug") { + input.parse::().unwrap(); + input.parse::().unwrap(); + } + } + + // Parses a simple AstStruct without the `pub struct` prefix. + fn ast_struct_inner(input: ParseStream) -> Result { + let ident: Ident = input.parse()?; + let features = full(input); + skip_manual_extra_traits(input); + let rest: TokenStream = input.parse()?; + Ok(AstItem { + ast: syn::parse2(quote! { + pub struct #ident #rest + })?, + features, + }) + } + + pub fn ast_struct(input: ParseStream) -> Result { + input.call(Attribute::parse_outer)?; + input.parse::()?; + input.parse::()?; + let res = input.call(ast_struct_inner)?; + Ok(res) + } + + fn no_visit(input: ParseStream) -> bool { + if peek_tag(input, "no_visit") { + input.parse::().unwrap(); + input.parse::().unwrap(); + true + } else { + false + } + } + + pub fn ast_enum(input: ParseStream) -> Result> { + input.call(Attribute::parse_outer)?; + input.parse::()?; + input.parse::()?; + let ident: Ident = input.parse()?; + skip_manual_extra_traits(input); + let no_visit = no_visit(input); + let rest: TokenStream = input.parse()?; + Ok(if no_visit { + None + } else { + Some(AstItem { + ast: syn::parse2(quote! { + pub enum #ident #rest + })?, + features: vec![], + }) + }) + } + + // A single variant of an ast_enum_of_structs! + struct EosVariant { + name: Ident, + member: Option, + } + fn eos_variant(input: ParseStream) -> Result { + input.call(Attribute::parse_outer)?; + let variant: Ident = input.parse()?; + let member = if input.peek(token::Paren) { + let content; + parenthesized!(content in input); + let path: Path = content.parse()?; + Some(path) + } else { + None + }; + input.parse::()?; + Ok(EosVariant { + name: variant, + member, + }) + } + + pub fn ast_enum_of_structs(input: ParseStream) -> Result { + input.call(Attribute::parse_outer)?; + input.parse::()?; + input.parse::()?; + let ident: Ident = input.parse()?; + skip_manual_extra_traits(input); + + let content; + braced!(content in input); + let mut variants = Vec::new(); + while !content.is_empty() { + variants.push(content.call(eos_variant)?); + } + + if let Some(ident) = input.parse::>()? { + assert_eq!(ident, "do_not_generate_to_tokens"); + } + + let enum_item = { + let variants = variants.iter().map(|v| { + let name = v.name.clone(); + match v.member { + Some(ref member) => quote!(#name(#member)), + None => quote!(#name), + } + }); + parse_quote! { + pub enum #ident { + #(#variants),* + } + } + }; + Ok(AstItem { + ast: enum_item, + features: vec![], + }) + } + + mod kw { + syn::custom_keyword!(macro_rules); + syn::custom_keyword!(Token); + } + + pub fn parse_token_macro(input: ParseStream) -> Result { + input.parse::()?; + input.parse::]>()?; + + let definition; + braced!(definition in input); + definition.call(Attribute::parse_outer)?; + definition.parse::()?; + definition.parse::()?; + definition.parse::()?; + + let rules; + braced!(rules in definition); + input.parse::()?; + + let mut tokens = BTreeMap::new(); + while !rules.is_empty() { + if rules.peek(Token![$]) { + rules.parse::()?; + rules.parse::()?; + rules.parse::()?; + tokens.insert("await".to_owned(), "Await".to_owned()); + } else { + let pattern; + parenthesized!(pattern in rules); + let token = pattern.parse::()?.to_string(); + rules.parse::]>()?; + let expansion; + braced!(expansion in rules); + rules.parse::()?; + expansion.parse::()?; + let path: Path = expansion.parse()?; + let ty = path.segments.last().unwrap().ident.to_string(); + tokens.insert(token, ty.to_string()); + } + } + Ok(tokens) + } + + fn parse_feature(input: ParseStream) -> Result { + let i: syn::Ident = input.parse()?; + assert_eq!(i, "feature"); + + input.parse::()?; + let s = input.parse::()?; + + Ok(s.value()) + } + + pub fn parse_features(input: ParseStream) -> Result { + let mut features = BTreeSet::new(); + + let level_1; + parenthesized!(level_1 in input); + + let i: syn::Ident = level_1.fork().parse()?; + + if i == "any" { + level_1.parse::()?; + + let level_2; + parenthesized!(level_2 in level_1); + + while !level_2.is_empty() { + features.insert(parse_feature(&level_2)?); + + if !level_2.is_empty() { + level_2.parse::()?; + } + } + } else if i == "feature" { + features.insert(parse_feature(&level_1)?); + assert!(level_1.is_empty()); + } else { + panic!("{:?}", i); + } + + assert!(input.is_empty()); + + Ok(types::Features { any: features }) + } +} + +fn get_features(attrs: &[syn::Attribute], base: &[syn::Attribute]) -> Vec { + let mut ret = base.to_owned(); + + for attr in attrs { + if attr.path.is_ident("cfg") { + ret.push(attr.clone()); + } + } + + ret +} + +#[derive(Error, Debug)] +#[error("{path}:{line}:{column}: {error}")] +struct LoadFileError { + path: PathBuf, + line: usize, + column: usize, + error: syn::Error, +} + +fn load_file>( + name: P, + features: &[syn::Attribute], + lookup: &mut ItemLookup, +) -> Result<()> { + let error = match do_load_file(&name, features, lookup).err() { + None => return Ok(()), + Some(error) => error, + }; + + let error = error.downcast::()?; + let span = error.span().start(); + + bail!(LoadFileError { + path: name.as_ref().to_owned(), + line: span.line, + column: span.column + 1, + error, + }) +} + +fn do_load_file>( + name: P, + features: &[syn::Attribute], + lookup: &mut ItemLookup, +) -> Result<()> { + let name = name.as_ref(); + let parent = name.parent().expect("no parent path"); + + // Parse the file + let src = fs::read_to_string(name)?; + let file = syn::parse_file(&src)?; + + // Collect all of the interesting AstItems declared in this file or submodules. + 'items: for item in file.items { + match item { + Item::Mod(item) => { + // Don't inspect inline modules. + if item.content.is_some() { + continue; + } + + // We don't want to try to load the generated rust files and + // parse them, so we ignore them here. + for name in IGNORED_MODS { + if item.ident == name { + continue 'items; + } + } + + // Lookup any #[cfg()] attributes on the module and add them to + // the feature set. + // + // The derive module is weird because it is built with either + // `full` or `derive` but exported only under `derive`. + let features = if item.ident == "derive" { + vec![parse_quote!(#[cfg(feature = "derive")])] + } else { + get_features(&item.attrs, features) + }; + + // Look up the submodule file, and recursively parse it. + // Only handles same-directory .rs file submodules for now. + let path = parent.join(&format!("{}.rs", item.ident)); + load_file(path, &features, lookup)?; + } + Item::Macro(item) => { + // Lookip any #[cfg()] attributes directly on the macro + // invocation, and add them to the feature set. + let features = get_features(&item.attrs, features); + + // Try to parse the AstItem declaration out of the item. + let tts = item.mac.tokens.clone(); + let found = if item.mac.path.is_ident("ast_struct") { + Some(parsing::ast_struct.parse2(tts)?) + } else if item.mac.path.is_ident("ast_enum") { + parsing::ast_enum.parse2(tts)? + } else if item.mac.path.is_ident("ast_enum_of_structs") { + Some(parsing::ast_enum_of_structs.parse2(tts)?) + } else { + continue; + }; + + // Record our features on the parsed AstItems. + for mut item in found { + if item.ast.ident != "Reserved" { + item.features.extend(features.clone()); + lookup.insert(item.ast.ident.clone(), item); + } + } + } + Item::Struct(item) => { + let ident = item.ident; + if EXTRA_TYPES.contains(&&ident.to_string()[..]) { + lookup.insert( + ident.clone(), + AstItem { + ast: DeriveInput { + ident, + vis: item.vis, + attrs: item.attrs, + generics: item.generics, + data: Data::Struct(DataStruct { + fields: item.fields, + struct_token: item.struct_token, + semi_token: item.semi_token, + }), + }, + features: features.to_owned(), + }, + ); + } + } + _ => {} + } + } + Ok(()) +} + +fn load_token_file>(name: P) -> Result { + let name = name.as_ref(); + let src = fs::read_to_string(name)?; + let file = syn::parse_file(&src)?; + for item in file.items { + match item { + Item::Macro(item) => { + match item.ident { + Some(ref i) if i == "export_token_macro" => {} + _ => continue, + } + let tokens = item.mac.parse_body_with(parsing::parse_token_macro)?; + return Ok(tokens); + } + _ => {} + } + } + + panic!("failed to parse Token macro") +} diff --git a/syn/codegen/src/version.rs b/syn/codegen/src/version.rs new file mode 100644 index 0000000..9374624 --- /dev/null +++ b/syn/codegen/src/version.rs @@ -0,0 +1,24 @@ +use anyhow::Result; +use semver::Version; +use serde::Deserialize; + +use std::fs; +use std::path::Path; + +pub fn get() -> Result { + let codegen_root = Path::new(env!("CARGO_MANIFEST_DIR")); + let syn_cargo_toml = codegen_root.join("../Cargo.toml"); + let manifest = fs::read_to_string(syn_cargo_toml)?; + let parsed: Manifest = toml::from_str(&manifest)?; + Ok(parsed.package.version) +} + +#[derive(Debug, Deserialize)] +struct Manifest { + package: Package, +} + +#[derive(Debug, Deserialize)] +struct Package { + version: Version, +} diff --git a/syn/codegen/src/visit.rs b/syn/codegen/src/visit.rs new file mode 100644 index 0000000..41bc9e9 --- /dev/null +++ b/syn/codegen/src/visit.rs @@ -0,0 +1,265 @@ +use crate::operand::{Borrowed, Operand, Owned}; +use crate::{file, full, gen}; +use anyhow::Result; +use proc_macro2::{Ident, Span, TokenStream}; +use quote::quote; +use syn::Index; +use syn_codegen::{Data, Definitions, Features, Node, Type}; + +const VISIT_SRC: &str = "../src/gen/visit.rs"; + +fn simple_visit(item: &str, name: &Operand) -> TokenStream { + let ident = gen::under_name(item); + let method = Ident::new(&format!("visit_{}", ident), Span::call_site()); + let name = name.ref_tokens(); + quote! { + v.#method(#name) + } +} + +fn noop_visit(name: &Operand) -> TokenStream { + let name = name.tokens(); + quote! { + skip!(#name) + } +} + +fn visit( + ty: &Type, + features: &Features, + defs: &Definitions, + name: &Operand, +) -> Option { + match ty { + Type::Box(t) => { + let name = name.owned_tokens(); + visit(t, features, defs, &Owned(quote!(*#name))) + } + Type::Vec(t) => { + let operand = Borrowed(quote!(it)); + let val = visit(t, features, defs, &operand)?; + let name = name.ref_tokens(); + Some(quote! { + for it in #name { + #val + } + }) + } + Type::Punctuated(p) => { + let operand = Borrowed(quote!(it)); + let val = visit(&p.element, features, defs, &operand)?; + let name = name.ref_tokens(); + Some(quote! { + for el in Punctuated::pairs(#name) { + let (it, p) = el.into_tuple(); + #val; + if let Some(p) = p { + tokens_helper(v, &p.spans); + } + } + }) + } + Type::Option(t) => { + let it = Borrowed(quote!(it)); + let val = visit(t, features, defs, &it)?; + let name = name.owned_tokens(); + Some(quote! { + if let Some(it) = &#name { + #val + } + }) + } + Type::Tuple(t) => { + let mut code = TokenStream::new(); + for (i, elem) in t.iter().enumerate() { + let name = name.tokens(); + let i = Index::from(i); + let it = Owned(quote!((#name).#i)); + let val = visit(elem, features, defs, &it).unwrap_or_else(|| noop_visit(&it)); + code.extend(val); + code.extend(quote!(;)); + } + Some(code) + } + Type::Token(t) => { + let name = name.tokens(); + let repr = &defs.tokens[t]; + let is_keyword = repr.chars().next().unwrap().is_alphabetic(); + let spans = if is_keyword { + quote!(span) + } else { + quote!(spans) + }; + Some(quote! { + tokens_helper(v, &#name.#spans) + }) + } + Type::Group(_) => { + let name = name.tokens(); + Some(quote! { + tokens_helper(v, &#name.span) + }) + } + Type::Syn(t) => { + fn requires_full(features: &Features) -> bool { + features.any.contains("full") && features.any.len() == 1 + } + let mut res = simple_visit(t, name); + let target = defs.types.iter().find(|ty| ty.ident == *t).unwrap(); + if requires_full(&target.features) && !requires_full(features) { + res = quote!(full!(#res)); + } + Some(res) + } + Type::Ext(t) if gen::TERMINAL_TYPES.contains(&&t[..]) => Some(simple_visit(t, name)), + Type::Ext(_) | Type::Std(_) => None, + } +} + +fn node(traits: &mut TokenStream, impls: &mut TokenStream, s: &Node, defs: &Definitions) { + let under_name = gen::under_name(&s.ident); + let ty = Ident::new(&s.ident, Span::call_site()); + let visit_fn = Ident::new(&format!("visit_{}", under_name), Span::call_site()); + + let mut visit_impl = TokenStream::new(); + + match &s.data { + Data::Enum(variants) => { + let mut visit_variants = TokenStream::new(); + + for (variant, fields) in variants { + let variant_ident = Ident::new(variant, Span::call_site()); + + if fields.is_empty() { + visit_variants.extend(quote! { + #ty::#variant_ident => {} + }); + } else { + let mut bind_visit_fields = TokenStream::new(); + let mut visit_fields = TokenStream::new(); + + for (idx, ty) in fields.iter().enumerate() { + let name = format!("_binding_{}", idx); + let binding = Ident::new(&name, Span::call_site()); + + bind_visit_fields.extend(quote! { + #binding, + }); + + let borrowed_binding = Borrowed(quote!(#binding)); + + visit_fields.extend( + visit(ty, &s.features, defs, &borrowed_binding) + .unwrap_or_else(|| noop_visit(&borrowed_binding)), + ); + + visit_fields.extend(quote!(;)); + } + + visit_variants.extend(quote! { + #ty::#variant_ident(#bind_visit_fields) => { + #visit_fields + } + }); + } + } + + let nonexhaustive = if s.exhaustive { + None + } else { + Some(quote!(_ => unreachable!())) + }; + + visit_impl.extend(quote! { + match node { + #visit_variants + #nonexhaustive + } + }); + } + Data::Struct(fields) => { + for (field, ty) in fields { + if let Type::Syn(ty) = ty { + if ty == "Reserved" { + continue; + } + } + + let id = Ident::new(&field, Span::call_site()); + let ref_toks = Owned(quote!(node.#id)); + let visit_field = visit(&ty, &s.features, defs, &ref_toks) + .unwrap_or_else(|| noop_visit(&ref_toks)); + visit_impl.extend(quote! { + #visit_field; + }); + } + } + Data::Private => { + if ty == "Ident" { + visit_impl.extend(quote! { + v.visit_span(&node.span()); + }); + } + } + } + + let ast_lifetime = if s.ident == "Span" { + None + } else { + Some(quote!('ast)) + }; + + traits.extend(quote! { + fn #visit_fn(&mut self, i: &#ast_lifetime #ty) { + #visit_fn(self, i) + } + }); + + impls.extend(quote! { + pub fn #visit_fn<'ast, V>(v: &mut V, node: &#ast_lifetime #ty) + where + V: Visit<'ast> + ?Sized, + { + #visit_impl + } + }); +} + +pub fn generate(defs: &Definitions) -> Result<()> { + let (traits, impls) = gen::traverse(defs, node); + let full_macro = full::get_macro(); + file::write( + VISIT_SRC, + quote! { + #![allow(unused_variables)] + + use crate::*; + #[cfg(any(feature = "full", feature = "derive"))] + use crate::punctuated::Punctuated; + use proc_macro2::Span; + #[cfg(any(feature = "full", feature = "derive"))] + use crate::gen::helper::visit::*; + + #full_macro + + #[cfg(any(feature = "full", feature = "derive"))] + macro_rules! skip { + ($($tt:tt)*) => {}; + } + + /// Syntax tree traversal to walk a shared borrow of a syntax tree. + /// + /// See the [module documentation] for details. + /// + /// [module documentation]: self + /// + /// *This trait is available if Syn is built with the `"visit"` feature.* + pub trait Visit<'ast> { + #traits + } + + #impls + }, + )?; + Ok(()) +} diff --git a/syn/codegen/src/visit_mut.rs b/syn/codegen/src/visit_mut.rs new file mode 100644 index 0000000..71e56b3 --- /dev/null +++ b/syn/codegen/src/visit_mut.rs @@ -0,0 +1,262 @@ +use crate::operand::{Borrowed, Operand, Owned}; +use crate::{file, full, gen}; +use anyhow::Result; +use proc_macro2::{Ident, Span, TokenStream}; +use quote::quote; +use syn::Index; +use syn_codegen::{Data, Definitions, Features, Node, Type}; + +const VISIT_MUT_SRC: &str = "../src/gen/visit_mut.rs"; + +fn simple_visit(item: &str, name: &Operand) -> TokenStream { + let ident = gen::under_name(item); + let method = Ident::new(&format!("visit_{}_mut", ident), Span::call_site()); + let name = name.ref_mut_tokens(); + quote! { + v.#method(#name) + } +} + +fn noop_visit(name: &Operand) -> TokenStream { + let name = name.tokens(); + quote! { + skip!(#name) + } +} + +fn visit( + ty: &Type, + features: &Features, + defs: &Definitions, + name: &Operand, +) -> Option { + match ty { + Type::Box(t) => { + let name = name.owned_tokens(); + visit(t, features, defs, &Owned(quote!(*#name))) + } + Type::Vec(t) => { + let operand = Borrowed(quote!(it)); + let val = visit(t, features, defs, &operand)?; + let name = name.ref_mut_tokens(); + Some(quote! { + for it in #name { + #val + } + }) + } + Type::Punctuated(p) => { + let operand = Borrowed(quote!(it)); + let val = visit(&p.element, features, defs, &operand)?; + let name = name.ref_mut_tokens(); + Some(quote! { + for el in Punctuated::pairs_mut(#name) { + let (it, p) = el.into_tuple(); + #val; + if let Some(p) = p { + tokens_helper(v, &mut p.spans); + } + } + }) + } + Type::Option(t) => { + let it = Borrowed(quote!(it)); + let val = visit(t, features, defs, &it)?; + let name = name.owned_tokens(); + Some(quote! { + if let Some(it) = &mut #name { + #val + } + }) + } + Type::Tuple(t) => { + let mut code = TokenStream::new(); + for (i, elem) in t.iter().enumerate() { + let name = name.tokens(); + let i = Index::from(i); + let it = Owned(quote!((#name).#i)); + let val = visit(elem, features, defs, &it).unwrap_or_else(|| noop_visit(&it)); + code.extend(val); + code.extend(quote!(;)); + } + Some(code) + } + Type::Token(t) => { + let name = name.tokens(); + let repr = &defs.tokens[t]; + let is_keyword = repr.chars().next().unwrap().is_alphabetic(); + let spans = if is_keyword { + quote!(span) + } else { + quote!(spans) + }; + Some(quote! { + tokens_helper(v, &mut #name.#spans) + }) + } + Type::Group(_) => { + let name = name.tokens(); + Some(quote! { + tokens_helper(v, &mut #name.span) + }) + } + Type::Syn(t) => { + fn requires_full(features: &Features) -> bool { + features.any.contains("full") && features.any.len() == 1 + } + let mut res = simple_visit(t, name); + let target = defs.types.iter().find(|ty| ty.ident == *t).unwrap(); + if requires_full(&target.features) && !requires_full(features) { + res = quote!(full!(#res)); + } + Some(res) + } + Type::Ext(t) if gen::TERMINAL_TYPES.contains(&&t[..]) => Some(simple_visit(t, name)), + Type::Ext(_) | Type::Std(_) => None, + } +} + +fn node(traits: &mut TokenStream, impls: &mut TokenStream, s: &Node, defs: &Definitions) { + let under_name = gen::under_name(&s.ident); + let ty = Ident::new(&s.ident, Span::call_site()); + let visit_mut_fn = Ident::new(&format!("visit_{}_mut", under_name), Span::call_site()); + + let mut visit_mut_impl = TokenStream::new(); + + match &s.data { + Data::Enum(variants) => { + let mut visit_mut_variants = TokenStream::new(); + + for (variant, fields) in variants { + let variant_ident = Ident::new(variant, Span::call_site()); + + if fields.is_empty() { + visit_mut_variants.extend(quote! { + #ty::#variant_ident => {} + }); + } else { + let mut bind_visit_mut_fields = TokenStream::new(); + let mut visit_mut_fields = TokenStream::new(); + + for (idx, ty) in fields.iter().enumerate() { + let name = format!("_binding_{}", idx); + let binding = Ident::new(&name, Span::call_site()); + + bind_visit_mut_fields.extend(quote! { + #binding, + }); + + let borrowed_binding = Borrowed(quote!(#binding)); + + visit_mut_fields.extend( + visit(ty, &s.features, defs, &borrowed_binding) + .unwrap_or_else(|| noop_visit(&borrowed_binding)), + ); + + visit_mut_fields.extend(quote!(;)); + } + + visit_mut_variants.extend(quote! { + #ty::#variant_ident(#bind_visit_mut_fields) => { + #visit_mut_fields + } + }); + } + } + + let nonexhaustive = if s.exhaustive { + None + } else { + Some(quote!(_ => unreachable!())) + }; + + visit_mut_impl.extend(quote! { + match node { + #visit_mut_variants + #nonexhaustive + } + }); + } + Data::Struct(fields) => { + for (field, ty) in fields { + if let Type::Syn(ty) = ty { + if ty == "Reserved" { + continue; + } + } + + let id = Ident::new(&field, Span::call_site()); + let ref_toks = Owned(quote!(node.#id)); + let visit_mut_field = visit(&ty, &s.features, defs, &ref_toks) + .unwrap_or_else(|| noop_visit(&ref_toks)); + visit_mut_impl.extend(quote! { + #visit_mut_field; + }); + } + } + Data::Private => { + if ty == "Ident" { + visit_mut_impl.extend(quote! { + let mut span = node.span(); + v.visit_span_mut(&mut span); + node.set_span(span); + }); + } + } + } + + traits.extend(quote! { + fn #visit_mut_fn(&mut self, i: &mut #ty) { + #visit_mut_fn(self, i) + } + }); + + impls.extend(quote! { + pub fn #visit_mut_fn(v: &mut V, node: &mut #ty) + where + V: VisitMut + ?Sized, + { + #visit_mut_impl + } + }); +} + +pub fn generate(defs: &Definitions) -> Result<()> { + let (traits, impls) = gen::traverse(defs, node); + let full_macro = full::get_macro(); + file::write( + VISIT_MUT_SRC, + quote! { + #![allow(unused_variables)] + + use crate::*; + #[cfg(any(feature = "full", feature = "derive"))] + use crate::punctuated::Punctuated; + use proc_macro2::Span; + #[cfg(any(feature = "full", feature = "derive"))] + use crate::gen::helper::visit_mut::*; + + #full_macro + + #[cfg(any(feature = "full", feature = "derive"))] + macro_rules! skip { + ($($tt:tt)*) => {}; + } + + /// Syntax tree traversal to mutate an exclusive borrow of a syntax tree in + /// place. + /// + /// See the [module documentation] for details. + /// + /// [module documentation]: self + /// + /// *This trait is available if Syn is built with the `"visit-mut"` feature.* + pub trait VisitMut { + #traits + } + + #impls + }, + )?; + Ok(()) +} diff --git a/syn/dev/Cargo.toml b/syn/dev/Cargo.toml new file mode 100644 index 0000000..79486c1 --- /dev/null +++ b/syn/dev/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "syn-dev" +version = "0.0.0" +authors = ["David Tolnay "] +edition = "2018" +publish = false + +[lib] +path = "parse.rs" +proc-macro = true + +[[bin]] +path = "main.rs" +name = "syn-dev" + +[dependencies] +quote = "1.0" + +[dependencies.syn] +path = ".." +default-features = false +features = ["parsing", "full", "extra-traits", "proc-macro"] diff --git a/syn/dev/README.md b/syn/dev/README.md new file mode 100644 index 0000000..91b9846 --- /dev/null +++ b/syn/dev/README.md @@ -0,0 +1,6 @@ +A little project skeleton for troubleshooting Syn's parsers during development, +especially when adding support for new Rust syntax. + +Place a sample of the syntax you are working on into main.rs and then run `cargo +check` to try parsing it, revealing the resulting syntax tree or else showing +the position and error message if the input fails to parse. diff --git a/syn/dev/main.rs b/syn/dev/main.rs new file mode 100644 index 0000000..eb67546 --- /dev/null +++ b/syn/dev/main.rs @@ -0,0 +1,4 @@ +syn_dev::r#mod! { + // Write Rust code here and run `cargo check` to have Syn parse it. + +} diff --git a/syn/dev/parse.rs b/syn/dev/parse.rs new file mode 100644 index 0000000..2a92550 --- /dev/null +++ b/syn/dev/parse.rs @@ -0,0 +1,18 @@ +extern crate proc_macro; + +use proc_macro::TokenStream; +use quote::quote; +use syn::File; + +#[proc_macro] +pub fn r#mod(input: TokenStream) -> TokenStream { + let compile_error = syn::parse::(input) + .map(|file| println!("{:#?}", file)) + .map_err(|err| err.to_compile_error()) + .err(); + + TokenStream::from(quote! { + #compile_error + fn main() {} + }) +} diff --git a/syn/examples/README.md b/syn/examples/README.md new file mode 100644 index 0000000..fdd69d6 --- /dev/null +++ b/syn/examples/README.md @@ -0,0 +1,19 @@ +### [`dump-syntax`](dump-syntax) + +Little utility to parse a Rust source file into a `syn::File` and print out a +debug representation of the syntax tree. + +### [`heapsize`](heapsize) + +An example implementation of a derive macro that generates trait impls. + +### [`lazy-static`](lazy-static) + +An example of parsing a custom syntax within a `functionlike!(...)` procedural +macro. Demonstrates how to trigger custom warnings and error messages on +individual tokens of the input. + +### [`trace-var`](trace-var) + +An attribute procedural macro that uses a syntax tree traversal to transform +certain syntax tree nodes in a function body. diff --git a/syn/examples/dump-syntax/Cargo.toml b/syn/examples/dump-syntax/Cargo.toml new file mode 100644 index 0000000..0bc9f62 --- /dev/null +++ b/syn/examples/dump-syntax/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "dump-syntax" +version = "0.0.0" +authors = ["David Tolnay "] +edition = "2018" +publish = false + +[dependencies] +colored = "1.7" +proc-macro2 = { version = "1.0", features = ["span-locations"] } + +[dependencies.syn] +path = "../.." +default-features = false +features = ["parsing", "full", "extra-traits"] + +[workspace] diff --git a/syn/examples/dump-syntax/README.md b/syn/examples/dump-syntax/README.md new file mode 100644 index 0000000..37c84d8 --- /dev/null +++ b/syn/examples/dump-syntax/README.md @@ -0,0 +1,28 @@ +Parse a Rust source file into a `syn::File` and print out a debug representation +of the syntax tree. + +Use the following command from this directory to test this program by running it +on its own source code: + +``` +cargo run -- src/main.rs +``` + +The output will begin with: + +``` +File { + shebang: None, + attrs: [ + Attribute { + pound_token: Pound, + style: Inner( + Bang + ), + bracket_token: Bracket, + path: Path { + leading_colon: None, + segments: [ + ... +} +``` diff --git a/syn/examples/dump-syntax/src/main.rs b/syn/examples/dump-syntax/src/main.rs new file mode 100644 index 0000000..240b7a2 --- /dev/null +++ b/syn/examples/dump-syntax/src/main.rs @@ -0,0 +1,149 @@ +//! Parse a Rust source file into a `syn::File` and print out a debug +//! representation of the syntax tree. +//! +//! Use the following command from this directory to test this program by +//! running it on its own source code: +//! +//! cargo run -- src/main.rs +//! +//! The output will begin with: +//! +//! File { +//! shebang: None, +//! attrs: [ +//! Attribute { +//! pound_token: Pound, +//! style: Inner( +//! ... +//! } + +use std::borrow::Cow; +use std::env; +use std::ffi::OsStr; +use std::fmt::{self, Display}; +use std::fs; +use std::io::{self, Write}; +use std::path::{Path, PathBuf}; +use std::process; + +use colored::Colorize; + +enum Error { + IncorrectUsage, + ReadFile(io::Error), + ParseFile { + error: syn::Error, + filepath: PathBuf, + source_code: String, + }, +} + +impl Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use self::Error::*; + + match self { + IncorrectUsage => write!(f, "Usage: dump-syntax path/to/filename.rs"), + ReadFile(error) => write!(f, "Unable to read file: {}", error), + ParseFile { + error, + filepath, + source_code, + } => render_location(f, error, filepath, source_code), + } + } +} + +fn main() { + if let Err(error) = try_main() { + let _ = writeln!(io::stderr(), "{}", error); + process::exit(1); + } +} + +fn try_main() -> Result<(), Error> { + let mut args = env::args_os(); + let _ = args.next(); // executable name + + let filepath = match (args.next(), args.next()) { + (Some(arg), None) => PathBuf::from(arg), + _ => return Err(Error::IncorrectUsage), + }; + + let code = fs::read_to_string(&filepath).map_err(Error::ReadFile)?; + let syntax = syn::parse_file(&code).map_err({ + |error| Error::ParseFile { + error, + filepath, + source_code: code, + } + })?; + println!("{:#?}", syntax); + + Ok(()) +} + +// Render a rustc-style error message, including colors. +// +// error: Syn unable to parse file +// --> main.rs:40:17 +// | +// 40 | fn fmt(&self formatter: &mut fmt::Formatter) -> fmt::Result { +// | ^^^^^^^^^ expected `,` +// +fn render_location( + formatter: &mut fmt::Formatter, + err: &syn::Error, + filepath: &Path, + code: &str, +) -> fmt::Result { + let start = err.span().start(); + let mut end = err.span().end(); + + if start.line == end.line && start.column == end.column { + return render_fallback(formatter, err); + } + + let code_line = match code.lines().nth(start.line - 1) { + Some(line) => line, + None => return render_fallback(formatter, err), + }; + + if end.line > start.line { + end.line = start.line; + end.column = code_line.len(); + } + + let filename = filepath + .file_name() + .map(OsStr::to_string_lossy) + .unwrap_or(Cow::Borrowed("main.rs")); + + write!( + formatter, + "\n\ + {error}{header}\n\ + {indent}{arrow} {filename}:{linenum}:{colnum}\n\ + {indent} {pipe}\n\ + {label} {pipe} {code}\n\ + {indent} {pipe} {offset}{underline} {message}\n\ + ", + error = "error".red().bold(), + header = ": Syn unable to parse file".bold(), + indent = " ".repeat(start.line.to_string().len()), + arrow = "-->".blue().bold(), + filename = filename, + linenum = start.line, + colnum = start.column, + pipe = "|".blue().bold(), + label = start.line.to_string().blue().bold(), + code = code_line.trim_end(), + offset = " ".repeat(start.column), + underline = "^".repeat(end.column - start.column).red().bold(), + message = err.to_string().red(), + ) +} + +fn render_fallback(formatter: &mut fmt::Formatter, err: &syn::Error) -> fmt::Result { + write!(formatter, "Unable to parse file: {}", err) +} diff --git a/syn/examples/heapsize/Cargo.toml b/syn/examples/heapsize/Cargo.toml new file mode 100644 index 0000000..9b19214 --- /dev/null +++ b/syn/examples/heapsize/Cargo.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["example", "heapsize", "heapsize_derive"] diff --git a/syn/examples/heapsize/README.md b/syn/examples/heapsize/README.md new file mode 100644 index 0000000..e789559 --- /dev/null +++ b/syn/examples/heapsize/README.md @@ -0,0 +1,72 @@ +A derive macro that generates trait impls. + +- [`heapsize/src/lib.rs`](heapsize/src/lib.rs) +- [`heapsize_derive/src/lib.rs`](heapsize_derive/src/lib.rs) +- [`example/src/main.rs`](example/src/main.rs) + +We are deriving the `HeapSize` trait which computes an estimate of the amount of +heap memory owned by a value. + +```rust +pub trait HeapSize { + /// Total number of bytes of heap memory owned by `self`. + fn heap_size_of_children(&self) -> usize; +} +``` + +The derive macro allows users to write `#[derive(HeapSize)]` on data structures +in their program. + +```rust +#[derive(HeapSize)] +struct Demo<'a, T: ?Sized> { + a: Box, + b: u8, + c: &'a str, + d: String, +} +``` + +The trait impl generated by the derive macro here would look like: + +```rust +impl<'a, T: ?Sized + heapsize::HeapSize> heapsize::HeapSize for Demo<'a, T> { + fn heap_size_of_children(&self) -> usize { + 0 + heapsize::HeapSize::heap_size_of_children(&self.a) + + heapsize::HeapSize::heap_size_of_children(&self.b) + + heapsize::HeapSize::heap_size_of_children(&self.c) + + heapsize::HeapSize::heap_size_of_children(&self.d) + } +} +``` + +The implementation of `heapsize_derive` demonstrates some attention to "spans" +of error messages. For each subexpression in the generated code we apply the +span of the input fragment under which we would want to trigger a compiler error +if the subexpression fails to compile. In this example, each recursive call to +`heap_size_of_children` is associated with the span of the corresponding struct +field. Thus we get errors in the right place if any of the field types do not +implement the `HeapSize` trait. + +``` +error[E0277]: the trait bound `std::thread::Thread: HeapSize` is not satisfied + --> src/main.rs:7:5 + | +7 | bad: std::thread::Thread, + | ^^^ the trait `HeapSize` is not implemented for `std::thread::Thread` +``` + +Some unstable APIs in the `proc-macro2` crate let us improve this further by +joining together the span of the field name and the field type. There is no +difference in our code — everything is as shown in this directory — +but building the example crate with `cargo build` shows errors like the one +above and building with `RUSTFLAGS='--cfg procmacro2_semver_exempt' cargo build` +is able to show errors like the following. + +``` +error[E0277]: the trait bound `std::thread::Thread: HeapSize` is not satisfied + --> src/main.rs:7:5 + | +7 | bad: std::thread::Thread, + | ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `HeapSize` is not implemented for `std::thread::Thread` +``` diff --git a/syn/examples/heapsize/example/Cargo.toml b/syn/examples/heapsize/example/Cargo.toml new file mode 100644 index 0000000..85c7699 --- /dev/null +++ b/syn/examples/heapsize/example/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "heapsize_example" +version = "0.0.0" +authors = ["David Tolnay "] +edition = "2018" +publish = false + +[dependencies] +heapsize = { path = "../heapsize" } diff --git a/syn/examples/heapsize/example/src/main.rs b/syn/examples/heapsize/example/src/main.rs new file mode 100644 index 0000000..9332b11 --- /dev/null +++ b/syn/examples/heapsize/example/src/main.rs @@ -0,0 +1,28 @@ +use heapsize::HeapSize; + +#[derive(HeapSize)] +struct Demo<'a, T: ?Sized> { + a: Box, + b: u8, + c: &'a str, + d: String, +} + +fn main() { + let demo = Demo { + a: b"bytestring".to_vec().into_boxed_slice(), + b: 255, + c: "&'static str", + d: "String".to_owned(), + }; + + // 10 + 0 + 0 + 6 = 16 + println!( + "heap size = {} + {} + {} + {} = {}", + demo.a.heap_size_of_children(), + demo.b.heap_size_of_children(), + demo.c.heap_size_of_children(), + demo.d.heap_size_of_children(), + demo.heap_size_of_children() + ); +} diff --git a/syn/examples/heapsize/heapsize/Cargo.toml b/syn/examples/heapsize/heapsize/Cargo.toml new file mode 100644 index 0000000..27bb954 --- /dev/null +++ b/syn/examples/heapsize/heapsize/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "heapsize" +version = "0.0.0" +authors = ["David Tolnay "] +edition = "2018" +publish = false + +[dependencies] +heapsize_derive = { path = "../heapsize_derive" } diff --git a/syn/examples/heapsize/heapsize/src/lib.rs b/syn/examples/heapsize/heapsize/src/lib.rs new file mode 100644 index 0000000..30bb6d6 --- /dev/null +++ b/syn/examples/heapsize/heapsize/src/lib.rs @@ -0,0 +1,64 @@ +use std::mem; + +pub use heapsize_derive::*; + +pub trait HeapSize { + /// Total number of bytes of heap memory owned by `self`. + /// + /// Does not include the size of `self` itself, which may or may not be on + /// the heap. Includes only children of `self`, meaning things pointed to by + /// `self`. + fn heap_size_of_children(&self) -> usize; +} + +// +// In a real version of this library there would be lots more impls here, but +// here are some interesting ones. +// + +impl HeapSize for u8 { + /// A `u8` does not own any heap memory. + fn heap_size_of_children(&self) -> usize { + 0 + } +} + +impl HeapSize for String { + /// A `String` owns enough heap memory to hold its reserved capacity. + fn heap_size_of_children(&self) -> usize { + self.capacity() + } +} + +impl HeapSize for Box +where + T: ?Sized + HeapSize, +{ + /// A `Box` owns however much heap memory was allocated to hold the value of + /// type `T` that we placed on the heap, plus transitively however much `T` + /// itself owns. + fn heap_size_of_children(&self) -> usize { + mem::size_of_val(&**self) + (**self).heap_size_of_children() + } +} + +impl HeapSize for [T] +where + T: HeapSize, +{ + /// Sum of heap memory owned by each element of a dynamically sized slice of + /// `T`. + fn heap_size_of_children(&self) -> usize { + self.iter().map(HeapSize::heap_size_of_children).sum() + } +} + +impl<'a, T> HeapSize for &'a T +where + T: ?Sized, +{ + /// A shared reference does not own heap memory. + fn heap_size_of_children(&self) -> usize { + 0 + } +} diff --git a/syn/examples/heapsize/heapsize_derive/Cargo.toml b/syn/examples/heapsize/heapsize_derive/Cargo.toml new file mode 100644 index 0000000..f4357b9 --- /dev/null +++ b/syn/examples/heapsize/heapsize_derive/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "heapsize_derive" +version = "0.0.0" +authors = ["David Tolnay "] +edition = "2018" +publish = false + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0" +quote = "1.0" +syn = { path = "../../.." } diff --git a/syn/examples/heapsize/heapsize_derive/src/lib.rs b/syn/examples/heapsize/heapsize_derive/src/lib.rs new file mode 100644 index 0000000..9176b29 --- /dev/null +++ b/syn/examples/heapsize/heapsize_derive/src/lib.rs @@ -0,0 +1,96 @@ +extern crate proc_macro; + +use proc_macro2::TokenStream; +use quote::{quote, quote_spanned}; +use syn::spanned::Spanned; +use syn::{parse_macro_input, parse_quote, Data, DeriveInput, Fields, GenericParam, Generics, Index}; + +#[proc_macro_derive(HeapSize)] +pub fn derive_heap_size(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + // Parse the input tokens into a syntax tree. + let input = parse_macro_input!(input as DeriveInput); + + // Used in the quasi-quotation below as `#name`. + let name = input.ident; + + // Add a bound `T: HeapSize` to every type parameter T. + let generics = add_trait_bounds(input.generics); + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + // Generate an expression to sum up the heap size of each field. + let sum = heap_size_sum(&input.data); + + let expanded = quote! { + // The generated impl. + impl #impl_generics heapsize::HeapSize for #name #ty_generics #where_clause { + fn heap_size_of_children(&self) -> usize { + #sum + } + } + }; + + // Hand the output tokens back to the compiler. + proc_macro::TokenStream::from(expanded) +} + +// Add a bound `T: HeapSize` to every type parameter T. +fn add_trait_bounds(mut generics: Generics) -> Generics { + for param in &mut generics.params { + if let GenericParam::Type(ref mut type_param) = *param { + type_param.bounds.push(parse_quote!(heapsize::HeapSize)); + } + } + generics +} + +// Generate an expression to sum up the heap size of each field. +fn heap_size_sum(data: &Data) -> TokenStream { + match *data { + Data::Struct(ref data) => { + match data.fields { + Fields::Named(ref fields) => { + // Expands to an expression like + // + // 0 + self.x.heap_size() + self.y.heap_size() + self.z.heap_size() + // + // but using fully qualified function call syntax. + // + // We take some care to use the span of each `syn::Field` as + // the span of the corresponding `heap_size_of_children` + // call. This way if one of the field types does not + // implement `HeapSize` then the compiler's error message + // underlines which field it is. An example is shown in the + // readme of the parent directory. + let recurse = fields.named.iter().map(|f| { + let name = &f.ident; + quote_spanned! {f.span()=> + heapsize::HeapSize::heap_size_of_children(&self.#name) + } + }); + quote! { + 0 #(+ #recurse)* + } + } + Fields::Unnamed(ref fields) => { + // Expands to an expression like + // + // 0 + self.0.heap_size() + self.1.heap_size() + self.2.heap_size() + let recurse = fields.unnamed.iter().enumerate().map(|(i, f)| { + let index = Index::from(i); + quote_spanned! {f.span()=> + heapsize::HeapSize::heap_size_of_children(&self.#index) + } + }); + quote! { + 0 #(+ #recurse)* + } + } + Fields::Unit => { + // Unit structs cannot own more than 0 bytes of heap memory. + quote!(0) + } + } + } + Data::Enum(_) | Data::Union(_) => unimplemented!(), + } +} diff --git a/syn/examples/lazy-static/Cargo.toml b/syn/examples/lazy-static/Cargo.toml new file mode 100644 index 0000000..586e547 --- /dev/null +++ b/syn/examples/lazy-static/Cargo.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["example", "lazy-static"] diff --git a/syn/examples/lazy-static/README.md b/syn/examples/lazy-static/README.md new file mode 100644 index 0000000..bc64585 --- /dev/null +++ b/syn/examples/lazy-static/README.md @@ -0,0 +1,42 @@ +An example of parsing a custom syntax within a `functionlike!(...)` procedural +macro. Demonstrates how to trigger custom warnings and error messages on +individual tokens of the input. + +- [`lazy-static/src/lib.rs`](lazy-static/src/lib.rs) +- [`example/src/main.rs`](example/src/main.rs) + +The library implements a `lazy_static!` macro similar to the one from the real +[`lazy_static`](https://docs.rs/lazy_static/1.0.0/lazy_static/) crate on +crates.io. + +```rust +lazy_static! { + static ref USERNAME: Regex = Regex::new("^[a-z0-9_-]{3,16}$").unwrap(); +} +``` + +Compile and run the example by doing `cargo run` in the directory of the +`example` crate. + +The implementation shows how to trigger custom warnings and error messages on +the macro input. For example if you try adding an uncreatively named `FOO` lazy +static, the macro will scold you with the following warning. + +``` +warning: come on, pick a more creative name + --> src/main.rs:10:16 + | +10 | static ref FOO: String = "lazy_static".to_owned(); + | ^^^ +``` + +And if you try to lazily initialize `() = ()`, the macro will outright refuse to +compile it for you. + +``` +error: I can't think of a legitimate use for lazily initializing the value `()` + --> src/main.rs:10:27 + | +10 | static ref UNIT: () = (); + | ^^ +``` diff --git a/syn/examples/lazy-static/example/Cargo.toml b/syn/examples/lazy-static/example/Cargo.toml new file mode 100644 index 0000000..716b08c --- /dev/null +++ b/syn/examples/lazy-static/example/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "example" +version = "0.0.0" +authors = ["David Tolnay "] +edition = "2018" +publish = false + +[dependencies] +lazy_static = { path = "../lazy-static" } +regex = "0.2" diff --git a/syn/examples/lazy-static/example/src/main.rs b/syn/examples/lazy-static/example/src/main.rs new file mode 100644 index 0000000..c4f64af --- /dev/null +++ b/syn/examples/lazy-static/example/src/main.rs @@ -0,0 +1,20 @@ +use lazy_static::lazy_static; +use regex::Regex; + +lazy_static! { + static ref USERNAME: Regex = { + println!("Compiling username regex..."); + Regex::new("^[a-z0-9_-]{3,16}$").unwrap() + }; +} + +fn main() { + println!("Let's validate some usernames."); + validate("fergie"); + validate("will.i.am"); +} + +fn validate(name: &str) { + // The USERNAME regex is compiled lazily the first time its value is accessed. + println!("is_match({:?}): {}", name, USERNAME.is_match(name)); +} diff --git a/syn/examples/lazy-static/lazy-static/Cargo.toml b/syn/examples/lazy-static/lazy-static/Cargo.toml new file mode 100644 index 0000000..bf65787 --- /dev/null +++ b/syn/examples/lazy-static/lazy-static/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "lazy_static" +version = "0.0.0" +authors = ["David Tolnay "] +edition = "2018" +publish = false + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = { version = "1.0", features = ["nightly"] } +quote = "1.0" +syn = { path = "../../../", features = ["full"] } diff --git a/syn/examples/lazy-static/lazy-static/src/lib.rs b/syn/examples/lazy-static/lazy-static/src/lib.rs new file mode 100644 index 0000000..254ca72 --- /dev/null +++ b/syn/examples/lazy-static/lazy-static/src/lib.rs @@ -0,0 +1,143 @@ +#![recursion_limit = "128"] +#![feature(proc_macro_diagnostic)] + +extern crate proc_macro; +use self::proc_macro::TokenStream; + +use quote::{quote, quote_spanned}; +use syn::parse::{Parse, ParseStream, Result}; +use syn::spanned::Spanned; +use syn::{parse_macro_input, Expr, Ident, Token, Type, Visibility}; + +/// Parses the following syntax, which aligns with the input of the real +/// `lazy_static` crate. +/// +/// lazy_static! { +/// $VISIBILITY static ref $NAME: $TYPE = $EXPR; +/// } +/// +/// For example: +/// +/// lazy_static! { +/// static ref USERNAME: Regex = Regex::new("^[a-z0-9_-]{3,16}$").unwrap(); +/// } +struct LazyStatic { + visibility: Visibility, + name: Ident, + ty: Type, + init: Expr, +} + +impl Parse for LazyStatic { + fn parse(input: ParseStream) -> Result { + let visibility: Visibility = input.parse()?; + input.parse::()?; + input.parse::()?; + let name: Ident = input.parse()?; + input.parse::()?; + let ty: Type = input.parse()?; + input.parse::()?; + let init: Expr = input.parse()?; + input.parse::()?; + Ok(LazyStatic { + visibility, + name, + ty, + init, + }) + } +} + +#[proc_macro] +pub fn lazy_static(input: TokenStream) -> TokenStream { + let LazyStatic { + visibility, + name, + ty, + init, + } = parse_macro_input!(input as LazyStatic); + + // The warning looks like this. + // + // warning: come on, pick a more creative name + // --> src/main.rs:10:16 + // | + // 10 | static ref FOO: String = "lazy_static".to_owned(); + // | ^^^ + if name == "FOO" { + name.span() + .unwrap() + .warning("come on, pick a more creative name") + .emit(); + } + + // The error looks like this. + // + // error: I can't think of a legitimate use for lazily initializing the value `()` + // --> src/main.rs:10:27 + // | + // 10 | static ref UNIT: () = (); + // | ^^ + if let Expr::Tuple(ref init) = init { + if init.elems.is_empty() { + init.span() + .unwrap() + .error("I can't think of a legitimate use for lazily initializing the value `()`") + .emit(); + return TokenStream::new(); + } + } + + // Assert that the static type implements Sync. If not, user sees an error + // message like the following. We span this assertion with the field type's + // line/column so that the error message appears in the correct place. + // + // error[E0277]: the trait bound `*const (): std::marker::Sync` is not satisfied + // --> src/main.rs:10:21 + // | + // 10 | static ref PTR: *const () = &(); + // | ^^^^^^^^^ `*const ()` cannot be shared between threads safely + let assert_sync = quote_spanned! {ty.span()=> + struct _AssertSync where #ty: std::marker::Sync; + }; + + // Check for Sized. Not vital to check here, but the error message is less + // confusing this way than if they get a Sized error in one of our + // implementation details where it assumes Sized. + // + // error[E0277]: the trait bound `str: std::marker::Sized` is not satisfied + // --> src/main.rs:10:19 + // | + // 10 | static ref A: str = ""; + // | ^^^ `str` does not have a constant size known at compile-time + let assert_sized = quote_spanned! {ty.span()=> + struct _AssertSized where #ty: std::marker::Sized; + }; + + let init_ptr = quote_spanned! {init.span()=> + Box::into_raw(Box::new(#init)) + }; + + let expanded = quote! { + #visibility struct #name; + + impl std::ops::Deref for #name { + type Target = #ty; + + fn deref(&self) -> &#ty { + #assert_sync + #assert_sized + + static ONCE: std::sync::Once = std::sync::Once::new(); + static mut VALUE: *mut #ty = 0 as *mut #ty; + + unsafe { + ONCE.call_once(|| VALUE = #init_ptr); + &*VALUE + } + } + } + }; + + TokenStream::from(expanded) +} diff --git a/syn/examples/trace-var/Cargo.toml b/syn/examples/trace-var/Cargo.toml new file mode 100644 index 0000000..b54454d --- /dev/null +++ b/syn/examples/trace-var/Cargo.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["example", "trace-var"] diff --git a/syn/examples/trace-var/README.md b/syn/examples/trace-var/README.md new file mode 100644 index 0000000..b93fae2 --- /dev/null +++ b/syn/examples/trace-var/README.md @@ -0,0 +1,61 @@ +An example of an attribute procedural macro. The `#[trace_var(...)]` attribute +prints the value of the given variables each time they are reassigned. + +- [`trace-var/src/lib.rs`](trace-var/src/lib.rs) +- [`example/src/main.rs`](example/src/main.rs) + +Consider the following factorial implementation. + +```rust +#[trace_var(p, n)] +fn factorial(mut n: u64) -> u64 { + let mut p = 1; + while n > 1 { + p *= n; + n -= 1; + } + p +} +``` + +Invoking this with `factorial(8)` prints all the values of `p` and `n` during +the execution of the function. + +``` +p = 1 +p = 8 +n = 7 +p = 56 +n = 6 +p = 336 +n = 5 +p = 1680 +n = 4 +p = 6720 +n = 3 +p = 20160 +n = 2 +p = 40320 +n = 1 +``` + +The procedural macro uses a syntax tree [`Fold`] to rewrite every `let` +statement and assignment expression in the following way: + +[`Fold`]: https://docs.rs/syn/1.0/syn/fold/trait.Fold.html + +```rust +// Before +let VAR = INIT; + +// After +let VAR = { let VAR = INIT; println!("VAR = {:?}", VAR); VAR }; +``` + +```rust +// Before +VAR = INIT + +// After +{ VAR = INIT; println!("VAR = {:?}", VAR); } +``` diff --git a/syn/examples/trace-var/example/Cargo.toml b/syn/examples/trace-var/example/Cargo.toml new file mode 100644 index 0000000..d2ad650 --- /dev/null +++ b/syn/examples/trace-var/example/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "example" +version = "0.0.0" +authors = ["David Tolnay "] +edition = "2018" +publish = false + +[dependencies] +trace-var = { path = "../trace-var" } diff --git a/syn/examples/trace-var/example/src/main.rs b/syn/examples/trace-var/example/src/main.rs new file mode 100644 index 0000000..da2c10b --- /dev/null +++ b/syn/examples/trace-var/example/src/main.rs @@ -0,0 +1,15 @@ +use trace_var::trace_var; + +fn main() { + println!("{}", factorial(8)); +} + +#[trace_var(p, n)] +fn factorial(mut n: u64) -> u64 { + let mut p = 1; + while n > 1 { + p *= n; + n -= 1; + } + p +} diff --git a/syn/examples/trace-var/trace-var/Cargo.toml b/syn/examples/trace-var/trace-var/Cargo.toml new file mode 100644 index 0000000..72f56e9 --- /dev/null +++ b/syn/examples/trace-var/trace-var/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "trace-var" +version = "0.0.0" +authors = ["David Tolnay "] +edition = "2018" +publish = false + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = { version = "1.0", features = ["nightly"] } +quote = "1.0" +syn = { path = "../../../", features = ["full", "fold"] } diff --git a/syn/examples/trace-var/trace-var/src/lib.rs b/syn/examples/trace-var/trace-var/src/lib.rs new file mode 100644 index 0000000..0ecfb47 --- /dev/null +++ b/syn/examples/trace-var/trace-var/src/lib.rs @@ -0,0 +1,180 @@ +extern crate proc_macro; +use self::proc_macro::TokenStream; + +use quote::{quote, ToTokens}; +use std::collections::HashSet as Set; +use syn::fold::{self, Fold}; +use syn::parse::{Parse, ParseStream, Result}; +use syn::punctuated::Punctuated; +use syn::{parse_macro_input, parse_quote, Expr, Ident, ItemFn, Local, Pat, Stmt, Token}; + +/// Parses a list of variable names separated by commas. +/// +/// a, b, c +/// +/// This is how the compiler passes in arguments to our attribute -- it is +/// everything inside the delimiters after the attribute name. +/// +/// #[trace_var(a, b, c)] +/// ^^^^^^^ +struct Args { + vars: Set, +} + +impl Parse for Args { + fn parse(input: ParseStream) -> Result { + let vars = Punctuated::::parse_terminated(input)?; + Ok(Args { + vars: vars.into_iter().collect(), + }) + } +} + +impl Args { + /// Determines whether the given `Expr` is a path referring to one of the + /// variables we intend to print. Expressions are used as the left-hand side + /// of the assignment operator. + fn should_print_expr(&self, e: &Expr) -> bool { + match *e { + Expr::Path(ref e) => { + if e.path.leading_colon.is_some() { + false + } else if e.path.segments.len() != 1 { + false + } else { + let first = e.path.segments.first().unwrap(); + self.vars.contains(&first.ident) && first.arguments.is_empty() + } + } + _ => false, + } + } + + /// Determines whether the given `Pat` is an identifier equal to one of the + /// variables we intend to print. Patterns are used as the left-hand side of + /// a `let` binding. + fn should_print_pat(&self, p: &Pat) -> bool { + match p { + Pat::Ident(ref p) => self.vars.contains(&p.ident), + _ => false, + } + } + + /// Produces an expression that assigns the right-hand side to the left-hand + /// side and then prints the value. + /// + /// // Before + /// VAR = INIT + /// + /// // After + /// { VAR = INIT; println!("VAR = {:?}", VAR); } + fn assign_and_print(&mut self, left: Expr, op: &dyn ToTokens, right: Expr) -> Expr { + let right = fold::fold_expr(self, right); + parse_quote!({ + #left #op #right; + println!(concat!(stringify!(#left), " = {:?}"), #left); + }) + } + + /// Produces a let-binding that assigns the right-hand side to the left-hand + /// side and then prints the value. + /// + /// // Before + /// let VAR = INIT; + /// + /// // After + /// let VAR = { let VAR = INIT; println!("VAR = {:?}", VAR); VAR }; + fn let_and_print(&mut self, local: Local) -> Stmt { + let Local { pat, init, .. } = local; + let init = self.fold_expr(*init.unwrap().1); + let ident = match pat { + Pat::Ident(ref p) => &p.ident, + _ => unreachable!(), + }; + parse_quote! { + let #pat = { + #[allow(unused_mut)] + let #pat = #init; + println!(concat!(stringify!(#ident), " = {:?}"), #ident); + #ident + }; + } + } +} + +/// The `Fold` trait is a way to traverse an owned syntax tree and replace some +/// of its nodes. +/// +/// Syn provides two other syntax tree traversal traits: `Visit` which walks a +/// shared borrow of a syntax tree, and `VisitMut` which walks an exclusive +/// borrow of a syntax tree and can mutate it in place. +/// +/// All three traits have a method corresponding to each type of node in Syn's +/// syntax tree. All of these methods have default no-op implementations that +/// simply recurse on any child nodes. We can override only those methods for +/// which we want non-default behavior. In this case the traversal needs to +/// transform `Expr` and `Stmt` nodes. +impl Fold for Args { + fn fold_expr(&mut self, e: Expr) -> Expr { + match e { + Expr::Assign(e) => { + if self.should_print_expr(&e.left) { + self.assign_and_print(*e.left, &e.eq_token, *e.right) + } else { + Expr::Assign(fold::fold_expr_assign(self, e)) + } + } + Expr::AssignOp(e) => { + if self.should_print_expr(&e.left) { + self.assign_and_print(*e.left, &e.op, *e.right) + } else { + Expr::AssignOp(fold::fold_expr_assign_op(self, e)) + } + } + _ => fold::fold_expr(self, e), + } + } + + fn fold_stmt(&mut self, s: Stmt) -> Stmt { + match s { + Stmt::Local(s) => { + if s.init.is_some() && self.should_print_pat(&s.pat) { + self.let_and_print(s) + } else { + Stmt::Local(fold::fold_local(self, s)) + } + } + _ => fold::fold_stmt(self, s), + } + } +} + +/// Attribute to print the value of the given variables each time they are +/// reassigned. +/// +/// # Example +/// +/// ``` +/// #[trace_var(p, n)] +/// fn factorial(mut n: u64) -> u64 { +/// let mut p = 1; +/// while n > 1 { +/// p *= n; +/// n -= 1; +/// } +/// p +/// } +/// ``` +#[proc_macro_attribute] +pub fn trace_var(args: TokenStream, input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as ItemFn); + + // Parse the list of variables the user wanted to print. + let mut args = parse_macro_input!(args as Args); + + // Use a syntax tree traversal to transform the function body. + let output = args.fold_item_fn(input); + + // Hand the resulting function body back to the compiler. + TokenStream::from(quote!(#output)) +} diff --git a/syn/json/Cargo.toml b/syn/json/Cargo.toml new file mode 100644 index 0000000..77104dc --- /dev/null +++ b/syn/json/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "syn-codegen" +version = "0.1.0" +authors = ["David Tolnay "] +edition = "2018" +license = "MIT OR Apache-2.0" +description = "Syntax tree describing Syn's syntax tree" +repository = "https://github.com/dtolnay/syn" +documentation = "https://docs.rs/syn-codegen" +categories = ["development-tools::procedural-macro-helpers"] + +[dependencies] +indexmap = { version = "1.0", features = ["serde-1"] } +semver = { version = "0.9", features = ["serde"] } +serde = { version = "1.0.88", features = ["derive"] } + +[dev-dependencies] +serde_json = "1.0" diff --git a/syn/json/src/lib.rs b/syn/json/src/lib.rs new file mode 100644 index 0000000..e3546d9 --- /dev/null +++ b/syn/json/src/lib.rs @@ -0,0 +1,214 @@ +//! # Data structures that describe Syn's syntax tree. +//! +//! The Syn syntax tree is made up of more than 200 types. Occasionally it can +//! come up that you need to implement some behavior across them all. +//! +//! - For example [the Rust integration for AST Explorer][astexplorer] wants to +//! turn a syntax tree from Syn into a JavaScript value understood by the +//! platform's existing cross-language syntax tree visualization code. +//! +//! [astexplorer]: https://astexplorer.net/#/gist/388150a52f74d45a355d2b5e865ded96/0c6d563f28d900472f699c21a1845ad20ae9927f +//! +//! - As another example from within Syn itself, the traits and implementations +//! of the [`visit`], [`visit_mut`], and [`fold`] modules can be generated +//! programmatically from a description of the syntax tree. +//! +//! [`visit`]: https://docs.rs/syn/1.0/syn/visit/index.html +//! [`visit_mut`]: https://docs.rs/syn/1.0/syn/visit_mut/index.html +//! [`fold`]: https://docs.rs/syn/1.0/syn/fold/index.html +//! +//! To make this type of code as easy as possible to implement in any language, +//! every Syn release comes with a machine-readable description of that version +//! of the syntax tree as a JSON file [syn.json]. This `syn-codegen` crate +//! provides the canonical data structures for parsing and making use of the +//! representation in syn.json from Rust code. +//! +//! [syn.json]: https://raw.githubusercontent.com/dtolnay/syn/master/syn.json +//! +//! ## Example +//! +//! ``` +//! use syn_codegen::Definitions; +//! +//! # const IGNORE: &str = stringify! { +//! const SYN: &str = include_str!("syn.json"); +//! # }; +//! # const SYN: &str = include_str!("../../syn.json"); +//! +//! fn main() { +//! let defs: Definitions = serde_json::from_str(SYN).unwrap(); +//! +//! for node in &defs.types { +//! println!("syn::{}", node.ident); +//! } +//! } +//! ``` + +use indexmap::IndexMap; +use semver::Version; +use serde::{Deserialize, Deserializer, Serialize}; + +use std::collections::{BTreeMap, BTreeSet}; + +/// Top-level content of the syntax tree description. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct Definitions { + /// The Syn version whose syntax tree is described by this data. + pub version: Version, + + /// Syntax tree types defined by Syn. + pub types: Vec, + + /// Token types defined by Syn (keywords as well as punctuation). + /// + /// The keys in the map are the Rust type name for the token. The values in + /// the map are the printed token representation. + /// + /// These tokens are accessible in the Syn public API as `syn::token::#name` + /// or alternatively `syn::Token![#repr]`. + pub tokens: BTreeMap, +} + +/// Syntax tree type defined by Syn. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct Node { + /// Name of the type. + /// + /// This type is accessible in the Syn public API as `syn::#name`. + pub ident: String, + + /// Features behind which this type is cfg gated. + pub features: Features, + + /// Content of the data structure. + #[serde( + flatten, + skip_serializing_if = "is_private", + deserialize_with = "private_if_absent" + )] + pub data: Data, + + #[serde(skip_serializing_if = "is_true", default = "bool_true")] + pub exhaustive: bool, +} + +/// Content of a syntax tree data structure. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub enum Data { + /// This is an opaque type with no publicy accessible structure. + Private, + + /// This type is a braced struct with named fields. + #[serde(rename = "fields")] + Struct(Fields), + + /// This type is an enum. + #[serde(rename = "variants")] + Enum(Variants), +} + +/// Fields of a braced struct syntax tree node with named fields. +/// +/// The keys in the map are the field names. +pub type Fields = IndexMap; + +/// Variants of an enum syntax tree node. +/// +/// The keys in the map are the variant names. +/// +/// Variants are unit variants if they hold no data and tuple variants +/// otherwise. The Syn syntax tree does not make use of braced variants. +pub type Variants = IndexMap>; + +/// Type of a struct field or tuple variant field in the syntax tree. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum Type { + /// Syntax tree type defined by Syn. + /// + /// This name will match the ident of some `Node`. + Syn(String), + + /// Type defined by the Rust language or standard library. + /// + /// All such types used by Syn are accessible in the Rust prelude and can be + /// used without a qualifying path in most Rust code. + Std(String), + + /// Type defined by proc-macro2. + /// + /// The type is accessible in the proc-macro2 public API as + /// `proc_macro2::#name`. + #[serde(rename = "proc_macro2")] + Ext(String), + + /// Keyword or punctuation token type defined by Syn. + /// + /// This name will match one of the keys in the `tokens` map. + Token(String), + + /// Grouping token defined by Syn. + /// + /// The type is accessible in the Syn public API as `syn::token::#name`. + Group(String), + + /// Punctuated list. + /// + /// This refers to `syn::punctuated::Punctuated` with the specified + /// element type and punctuation. + Punctuated(Punctuated), + + /// `std::option::Option` + Option(Box), + + /// `std::boxed::Box` + Box(Box), + + /// `std::vec::Vec` + Vec(Box), + + /// Rust tuple with two or more fields. + Tuple(Vec), +} + +/// Type of a punctuated list. +/// +/// This refers to `syn::punctuated::Punctuated<#element, #punct>`. +/// +/// The punct string will match one of the keys in the `tokens` map. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct Punctuated { + pub element: Box, + pub punct: String, +} + +/// Features behind which a syntax tree type is cfg gated. +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] +pub struct Features { + /// Type is accessible if at least one of these features is enabled against + /// the Syn dependency. + pub any: BTreeSet, +} + +fn is_private(data: &Data) -> bool { + match data { + Data::Private => true, + Data::Struct(_) | Data::Enum(_) => false, + } +} + +fn private_if_absent<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let option = Option::deserialize(deserializer)?; + Ok(option.unwrap_or(Data::Private)) +} + +fn is_true(b: &bool) -> bool { + *b +} + +fn bool_true() -> bool { + true +} diff --git a/syn/src/attr.rs b/syn/src/attr.rs new file mode 100644 index 0000000..a8e16ea --- /dev/null +++ b/syn/src/attr.rs @@ -0,0 +1,682 @@ +use super::*; +use crate::punctuated::Punctuated; + +use std::iter; + +use proc_macro2::TokenStream; + +#[cfg(feature = "parsing")] +use crate::parse::{Parse, ParseBuffer, ParseStream, Parser, Result}; +#[cfg(feature = "parsing")] +use crate::punctuated::Pair; +#[cfg(feature = "extra-traits")] +use crate::tt::TokenStreamHelper; +#[cfg(feature = "extra-traits")] +use std::hash::{Hash, Hasher}; + +ast_struct! { + /// An attribute like `#[repr(transparent)]`. + /// + /// *This type is available if Syn is built with the `"derive"` or `"full"` + /// feature.* + /// + ///
+ /// + /// # Syntax + /// + /// Rust has six types of attributes. + /// + /// - Outer attributes like `#[repr(transparent)]`. These appear outside or + /// in front of the item they describe. + /// - Inner attributes like `#![feature(proc_macro)]`. These appear inside + /// of the item they describe, usually a module. + /// - Outer doc comments like `/// # Example`. + /// - Inner doc comments like `//! Please file an issue`. + /// - Outer block comments `/** # Example */`. + /// - Inner block comments `/*! Please file an issue */`. + /// + /// The `style` field of type `AttrStyle` distinguishes whether an attribute + /// is outer or inner. Doc comments and block comments are promoted to + /// attributes, as this is how they are processed by the compiler and by + /// `macro_rules!` macros. + /// + /// The `path` field gives the possibly colon-delimited path against which + /// the attribute is resolved. It is equal to `"doc"` for desugared doc + /// comments. The `tokens` field contains the rest of the attribute body as + /// tokens. + /// + /// ```text + /// #[derive(Copy)] #[crate::precondition x < 5] + /// ^^^^^^~~~~~~ ^^^^^^^^^^^^^^^^^^^ ~~~~~ + /// path tokens path tokens + /// ``` + /// + ///
+ /// + /// # Parsing from tokens to Attribute + /// + /// This type does not implement the [`Parse`] trait and thus cannot be + /// parsed directly by [`ParseStream::parse`]. Instead use + /// [`ParseStream::call`] with one of the two parser functions + /// [`Attribute::parse_outer`] or [`Attribute::parse_inner`] depending on + /// which you intend to parse. + /// + /// [`Parse`]: parse::Parse + /// [`ParseStream::parse`]: parse::ParseBuffer::parse + /// [`ParseStream::call`]: parse::ParseBuffer::call + /// + /// ``` + /// use syn::{Attribute, Ident, Result, Token}; + /// use syn::parse::{Parse, ParseStream}; + /// + /// // Parses a unit struct with attributes. + /// // + /// // #[path = "s.tmpl"] + /// // struct S; + /// struct UnitStruct { + /// attrs: Vec, + /// struct_token: Token![struct], + /// name: Ident, + /// semi_token: Token![;], + /// } + /// + /// impl Parse for UnitStruct { + /// fn parse(input: ParseStream) -> Result { + /// Ok(UnitStruct { + /// attrs: input.call(Attribute::parse_outer)?, + /// struct_token: input.parse()?, + /// name: input.parse()?, + /// semi_token: input.parse()?, + /// }) + /// } + /// } + /// ``` + /// + ///


+ /// + /// # Parsing from Attribute to structured arguments + /// + /// The grammar of attributes in Rust is very flexible, which makes the + /// syntax tree not that useful on its own. In particular, arguments of the + /// attribute are held in an arbitrary `tokens: TokenStream`. Macros are + /// expected to check the `path` of the attribute, decide whether they + /// recognize it, and then parse the remaining tokens according to whatever + /// grammar they wish to require for that kind of attribute. + /// + /// If the attribute you are parsing is expected to conform to the + /// conventional structured form of attribute, use [`parse_meta()`] to + /// obtain that structured representation. If the attribute follows some + /// other grammar of its own, use [`parse_args()`] to parse that into the + /// expected data structure. + /// + /// [`parse_meta()`]: Attribute::parse_meta + /// [`parse_args()`]: Attribute::parse_args + /// + ///


+ /// + /// # Doc comments + /// + /// The compiler transforms doc comments, such as `/// comment` and `/*! + /// comment */`, into attributes before macros are expanded. Each comment is + /// expanded into an attribute of the form `#[doc = r"comment"]`. + /// + /// As an example, the following `mod` items are expanded identically: + /// + /// ``` + /// # use syn::{ItemMod, parse_quote}; + /// let doc: ItemMod = parse_quote! { + /// /// Single line doc comments + /// /// We write so many! + /// /** + /// * Multi-line comments... + /// * May span many lines + /// */ + /// mod example { + /// //! Of course, they can be inner too + /// /*! And fit in a single line */ + /// } + /// }; + /// let attr: ItemMod = parse_quote! { + /// #[doc = r" Single line doc comments"] + /// #[doc = r" We write so many!"] + /// #[doc = r" Multi-line comments... + /// May span many lines"] + /// mod example { + /// #![doc = r" Of course, they can be inner too"] + /// #![doc = r" And fit in a single line "] + /// } + /// }; + /// assert_eq!(doc, attr); + /// ``` + pub struct Attribute #manual_extra_traits { + pub pound_token: Token![#], + pub style: AttrStyle, + pub bracket_token: token::Bracket, + pub path: Path, + pub tokens: TokenStream, + } +} + +#[cfg(feature = "extra-traits")] +impl Eq for Attribute {} + +#[cfg(feature = "extra-traits")] +impl PartialEq for Attribute { + fn eq(&self, other: &Self) -> bool { + self.style == other.style + && self.pound_token == other.pound_token + && self.bracket_token == other.bracket_token + && self.path == other.path + && TokenStreamHelper(&self.tokens) == TokenStreamHelper(&other.tokens) + } +} + +#[cfg(feature = "extra-traits")] +impl Hash for Attribute { + fn hash(&self, state: &mut H) + where + H: Hasher, + { + self.style.hash(state); + self.pound_token.hash(state); + self.bracket_token.hash(state); + self.path.hash(state); + TokenStreamHelper(&self.tokens).hash(state); + } +} + +impl Attribute { + /// Parses the content of the attribute, consisting of the path and tokens, + /// as a [`Meta`] if possible. + /// + /// *This function is available if Syn is built with the `"parsing"` + /// feature.* + #[cfg(feature = "parsing")] + pub fn parse_meta(&self) -> Result { + fn clone_ident_segment(segment: &PathSegment) -> PathSegment { + PathSegment { + ident: segment.ident.clone(), + arguments: PathArguments::None, + } + } + + let path = Path { + leading_colon: self + .path + .leading_colon + .as_ref() + .map(|colon| Token![::](colon.spans)), + segments: self + .path + .segments + .pairs() + .map(|pair| match pair { + Pair::Punctuated(seg, punct) => { + Pair::Punctuated(clone_ident_segment(seg), Token![::](punct.spans)) + } + Pair::End(seg) => Pair::End(clone_ident_segment(seg)), + }) + .collect(), + }; + + let parser = |input: ParseStream| parsing::parse_meta_after_path(path, input); + parse::Parser::parse2(parser, self.tokens.clone()) + } + + /// Parse the arguments to the attribute as a syntax tree. + /// + /// This is similar to `syn::parse2::(attr.tokens)` except that: + /// + /// - the surrounding delimiters are *not* included in the input to the + /// parser; and + /// - the error message has a more useful span when `tokens` is empty. + /// + /// ```text + /// #[my_attr(value < 5)] + /// ^^^^^^^^^ what gets parsed + /// ``` + /// + /// *This function is available if Syn is built with the `"parsing"` + /// feature.* + #[cfg(feature = "parsing")] + pub fn parse_args(&self) -> Result { + self.parse_args_with(T::parse) + } + + /// Parse the arguments to the attribute using the given parser. + /// + /// *This function is available if Syn is built with the `"parsing"` + /// feature.* + #[cfg(feature = "parsing")] + pub fn parse_args_with(&self, parser: F) -> Result { + let parser = |input: ParseStream| { + let args = enter_args(self, input)?; + parse::parse_stream(parser, &args) + }; + parser.parse2(self.tokens.clone()) + } + + /// Parses zero or more outer attributes from the stream. + /// + /// *This function is available if Syn is built with the `"parsing"` + /// feature.* + #[cfg(feature = "parsing")] + pub fn parse_outer(input: ParseStream) -> Result> { + let mut attrs = Vec::new(); + while input.peek(Token![#]) { + attrs.push(input.call(parsing::single_parse_outer)?); + } + Ok(attrs) + } + + /// Parses zero or more inner attributes from the stream. + /// + /// *This function is available if Syn is built with the `"parsing"` + /// feature.* + #[cfg(feature = "parsing")] + pub fn parse_inner(input: ParseStream) -> Result> { + let mut attrs = Vec::new(); + while input.peek(Token![#]) && input.peek2(Token![!]) { + attrs.push(input.call(parsing::single_parse_inner)?); + } + Ok(attrs) + } +} + +#[cfg(feature = "parsing")] +fn error_expected_args(attr: &Attribute) -> Error { + let style = match attr.style { + AttrStyle::Outer => "#", + AttrStyle::Inner(_) => "#!", + }; + + let mut path = String::new(); + for segment in &attr.path.segments { + if !path.is_empty() || attr.path.leading_colon.is_some() { + path += "::"; + } + path += &segment.ident.to_string(); + } + + let msg = format!("expected attribute arguments: {}[{}(...)]", style, path); + + #[cfg(feature = "printing")] + return Error::new_spanned(attr, msg); + + #[cfg(not(feature = "printing"))] + return Error::new(attr.bracket_token.span, msg); +} + +#[cfg(feature = "parsing")] +fn enter_args<'a>(attr: &Attribute, input: ParseStream<'a>) -> Result> { + if input.is_empty() { + return Err(error_expected_args(attr)); + }; + + let content; + if input.peek(token::Paren) { + parenthesized!(content in input); + } else if input.peek(token::Bracket) { + bracketed!(content in input); + } else if input.peek(token::Brace) { + braced!(content in input); + } else { + return Err(input.error("unexpected token in attribute arguments")); + } + + if input.is_empty() { + Ok(content) + } else { + Err(input.error("unexpected token in attribute arguments")) + } +} + +ast_enum! { + /// Distinguishes between attributes that decorate an item and attributes + /// that are contained within an item. + /// + /// *This type is available if Syn is built with the `"derive"` or `"full"` + /// feature.* + /// + /// # Outer attributes + /// + /// - `#[repr(transparent)]` + /// - `/// # Example` + /// - `/** Please file an issue */` + /// + /// # Inner attributes + /// + /// - `#![feature(proc_macro)]` + /// - `//! # Example` + /// - `/*! Please file an issue */` + #[cfg_attr(feature = "clone-impls", derive(Copy))] + pub enum AttrStyle { + Outer, + Inner(Token![!]), + } +} + +ast_enum_of_structs! { + /// Content of a compile-time structured attribute. + /// + /// *This type is available if Syn is built with the `"derive"` or `"full"` + /// feature.* + /// + /// ## Path + /// + /// A meta path is like the `test` in `#[test]`. + /// + /// ## List + /// + /// A meta list is like the `derive(Copy)` in `#[derive(Copy)]`. + /// + /// ## NameValue + /// + /// A name-value meta is like the `path = "..."` in `#[path = + /// "sys/windows.rs"]`. + /// + /// # Syntax tree enum + /// + /// This type is a [syntax tree enum]. + /// + /// [syntax tree enum]: enum.Expr.html#syntax-tree-enums + // + // TODO: change syntax-tree-enum link to an intra rustdoc link, currently + // blocked on https://github.com/rust-lang/rust/issues/62833 + pub enum Meta { + Path(Path), + + /// A structured list within an attribute, like `derive(Copy, Clone)`. + List(MetaList), + + /// A name-value pair within an attribute, like `feature = "nightly"`. + NameValue(MetaNameValue), + } +} + +ast_struct! { + /// A structured list within an attribute, like `derive(Copy, Clone)`. + /// + /// *This type is available if Syn is built with the `"derive"` or + /// `"full"` feature.* + pub struct MetaList { + pub path: Path, + pub paren_token: token::Paren, + pub nested: Punctuated, + } +} + +ast_struct! { + /// A name-value pair within an attribute, like `feature = "nightly"`. + /// + /// *This type is available if Syn is built with the `"derive"` or + /// `"full"` feature.* + pub struct MetaNameValue { + pub path: Path, + pub eq_token: Token![=], + pub lit: Lit, + } +} + +impl Meta { + /// Returns the identifier that begins this structured meta item. + /// + /// For example this would return the `test` in `#[test]`, the `derive` in + /// `#[derive(Copy)]`, and the `path` in `#[path = "sys/windows.rs"]`. + pub fn path(&self) -> &Path { + match self { + Meta::Path(path) => path, + Meta::List(meta) => &meta.path, + Meta::NameValue(meta) => &meta.path, + } + } +} + +ast_enum_of_structs! { + /// Element of a compile-time attribute list. + /// + /// *This type is available if Syn is built with the `"derive"` or `"full"` + /// feature.* + pub enum NestedMeta { + /// A structured meta item, like the `Copy` in `#[derive(Copy)]` which + /// would be a nested `Meta::Path`. + Meta(Meta), + + /// A Rust literal, like the `"new_name"` in `#[rename("new_name")]`. + Lit(Lit), + } +} + +/// Conventional argument type associated with an invocation of an attribute +/// macro. +/// +/// For example if we are developing an attribute macro that is intended to be +/// invoked on function items as follows: +/// +/// ``` +/// # const IGNORE: &str = stringify! { +/// #[my_attribute(path = "/v1/refresh")] +/// # }; +/// pub fn refresh() { +/// /* ... */ +/// } +/// ``` +/// +/// The implementation of this macro would want to parse its attribute arguments +/// as type `AttributeArgs`. +/// +/// ``` +/// extern crate proc_macro; +/// +/// use proc_macro::TokenStream; +/// use syn::{parse_macro_input, AttributeArgs, ItemFn}; +/// +/// # const IGNORE: &str = stringify! { +/// #[proc_macro_attribute] +/// # }; +/// pub fn my_attribute(args: TokenStream, input: TokenStream) -> TokenStream { +/// let args = parse_macro_input!(args as AttributeArgs); +/// let input = parse_macro_input!(input as ItemFn); +/// +/// /* ... */ +/// # "".parse().unwrap() +/// } +/// ``` +pub type AttributeArgs = Vec; + +pub trait FilterAttrs<'a> { + type Ret: Iterator; + + fn outer(self) -> Self::Ret; + fn inner(self) -> Self::Ret; +} + +impl<'a, T> FilterAttrs<'a> for T +where + T: IntoIterator, +{ + type Ret = iter::Filter bool>; + + fn outer(self) -> Self::Ret { + fn is_outer(attr: &&Attribute) -> bool { + match attr.style { + AttrStyle::Outer => true, + _ => false, + } + } + self.into_iter().filter(is_outer) + } + + fn inner(self) -> Self::Ret { + fn is_inner(attr: &&Attribute) -> bool { + match attr.style { + AttrStyle::Inner(_) => true, + _ => false, + } + } + self.into_iter().filter(is_inner) + } +} + +#[cfg(feature = "parsing")] +pub mod parsing { + use super::*; + + use crate::ext::IdentExt; + use crate::parse::{Parse, ParseStream, Result}; + #[cfg(feature = "full")] + use crate::private; + + pub fn single_parse_inner(input: ParseStream) -> Result { + let content; + Ok(Attribute { + pound_token: input.parse()?, + style: AttrStyle::Inner(input.parse()?), + bracket_token: bracketed!(content in input), + path: content.call(Path::parse_mod_style)?, + tokens: content.parse()?, + }) + } + + pub fn single_parse_outer(input: ParseStream) -> Result { + let content; + Ok(Attribute { + pound_token: input.parse()?, + style: AttrStyle::Outer, + bracket_token: bracketed!(content in input), + path: content.call(Path::parse_mod_style)?, + tokens: content.parse()?, + }) + } + + #[cfg(feature = "full")] + impl private { + pub fn attrs(outer: Vec, inner: Vec) -> Vec { + let mut attrs = outer; + attrs.extend(inner); + attrs + } + } + + // Like Path::parse_mod_style but accepts keywords in the path. + fn parse_meta_path(input: ParseStream) -> Result { + Ok(Path { + leading_colon: input.parse()?, + segments: { + let mut segments = Punctuated::new(); + while input.peek(Ident::peek_any) { + let ident = Ident::parse_any(input)?; + segments.push_value(PathSegment::from(ident)); + if !input.peek(Token![::]) { + break; + } + let punct = input.parse()?; + segments.push_punct(punct); + } + if segments.is_empty() { + return Err(input.error("expected path")); + } else if segments.trailing_punct() { + return Err(input.error("expected path segment")); + } + segments + }, + }) + } + + impl Parse for Meta { + fn parse(input: ParseStream) -> Result { + let path = input.call(parse_meta_path)?; + parse_meta_after_path(path, input) + } + } + + impl Parse for MetaList { + fn parse(input: ParseStream) -> Result { + let path = input.call(parse_meta_path)?; + parse_meta_list_after_path(path, input) + } + } + + impl Parse for MetaNameValue { + fn parse(input: ParseStream) -> Result { + let path = input.call(parse_meta_path)?; + parse_meta_name_value_after_path(path, input) + } + } + + impl Parse for NestedMeta { + fn parse(input: ParseStream) -> Result { + if input.peek(Lit) && !(input.peek(LitBool) && input.peek2(Token![=])) { + input.parse().map(NestedMeta::Lit) + } else if input.peek(Ident::peek_any) { + input.parse().map(NestedMeta::Meta) + } else { + Err(input.error("expected identifier or literal")) + } + } + } + + pub fn parse_meta_after_path(path: Path, input: ParseStream) -> Result { + if input.peek(token::Paren) { + parse_meta_list_after_path(path, input).map(Meta::List) + } else if input.peek(Token![=]) { + parse_meta_name_value_after_path(path, input).map(Meta::NameValue) + } else { + Ok(Meta::Path(path)) + } + } + + fn parse_meta_list_after_path(path: Path, input: ParseStream) -> Result { + let content; + Ok(MetaList { + path, + paren_token: parenthesized!(content in input), + nested: content.parse_terminated(NestedMeta::parse)?, + }) + } + + fn parse_meta_name_value_after_path(path: Path, input: ParseStream) -> Result { + Ok(MetaNameValue { + path, + eq_token: input.parse()?, + lit: input.parse()?, + }) + } +} + +#[cfg(feature = "printing")] +mod printing { + use super::*; + use proc_macro2::TokenStream; + use quote::ToTokens; + + impl ToTokens for Attribute { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.pound_token.to_tokens(tokens); + if let AttrStyle::Inner(b) = &self.style { + b.to_tokens(tokens); + } + self.bracket_token.surround(tokens, |tokens| { + self.path.to_tokens(tokens); + self.tokens.to_tokens(tokens); + }); + } + } + + impl ToTokens for MetaList { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.path.to_tokens(tokens); + self.paren_token.surround(tokens, |tokens| { + self.nested.to_tokens(tokens); + }) + } + } + + impl ToTokens for MetaNameValue { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.path.to_tokens(tokens); + self.eq_token.to_tokens(tokens); + self.lit.to_tokens(tokens); + } + } +} diff --git a/syn/src/await.rs b/syn/src/await.rs new file mode 100644 index 0000000..a8e24fd --- /dev/null +++ b/syn/src/await.rs @@ -0,0 +1,2 @@ +// See include!("await.rs") in token.rs. +export_token_macro![(await)]; diff --git a/syn/src/bigint.rs b/syn/src/bigint.rs new file mode 100644 index 0000000..5397d6b --- /dev/null +++ b/syn/src/bigint.rs @@ -0,0 +1,66 @@ +use std::ops::{AddAssign, MulAssign}; + +// For implementing base10_digits() accessor on LitInt. +pub struct BigInt { + digits: Vec, +} + +impl BigInt { + pub fn new() -> Self { + BigInt { digits: Vec::new() } + } + + pub fn to_string(&self) -> String { + let mut repr = String::with_capacity(self.digits.len()); + + let mut has_nonzero = false; + for digit in self.digits.iter().rev() { + has_nonzero |= *digit != 0; + if has_nonzero { + repr.push((*digit + b'0') as char); + } + } + + if repr.is_empty() { + repr.push('0'); + } + + repr + } + + fn reserve_two_digits(&mut self) { + let len = self.digits.len(); + let desired = + len + !self.digits.ends_with(&[0, 0]) as usize + !self.digits.ends_with(&[0]) as usize; + self.digits.resize(desired, 0); + } +} + +impl AddAssign for BigInt { + // Assumes increment <16. + fn add_assign(&mut self, mut increment: u8) { + self.reserve_two_digits(); + + let mut i = 0; + while increment > 0 { + let sum = self.digits[i] + increment; + self.digits[i] = sum % 10; + increment = sum / 10; + i += 1; + } + } +} + +impl MulAssign for BigInt { + // Assumes base <=16. + fn mul_assign(&mut self, base: u8) { + self.reserve_two_digits(); + + let mut carry = 0; + for digit in &mut self.digits { + let prod = *digit * base + carry; + *digit = prod % 10; + carry = prod / 10; + } + } +} diff --git a/syn/src/buffer.rs b/syn/src/buffer.rs new file mode 100644 index 0000000..5c2dd8a --- /dev/null +++ b/syn/src/buffer.rs @@ -0,0 +1,382 @@ +//! A stably addressed token buffer supporting efficient traversal based on a +//! cheaply copyable cursor. +//! +//! *This module is available if Syn is built with the `"parsing"` feature.* + +// This module is heavily commented as it contains most of the unsafe code in +// Syn, and caution should be used when editing it. The public-facing interface +// is 100% safe but the implementation is fragile internally. + +#[cfg(all( + not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "wasi"))), + feature = "proc-macro" +))] +use crate::proc_macro as pm; +use proc_macro2::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream, TokenTree}; + +use std::marker::PhantomData; +use std::ptr; + +use crate::Lifetime; + +/// Internal type which is used instead of `TokenTree` to represent a token tree +/// within a `TokenBuffer`. +enum Entry { + // Mimicking types from proc-macro. + Group(Group, TokenBuffer), + Ident(Ident), + Punct(Punct), + Literal(Literal), + // End entries contain a raw pointer to the entry from the containing + // token tree, or null if this is the outermost level. + End(*const Entry), +} + +/// A buffer that can be efficiently traversed multiple times, unlike +/// `TokenStream` which requires a deep copy in order to traverse more than +/// once. +/// +/// *This type is available if Syn is built with the `"parsing"` feature.* +pub struct TokenBuffer { + // NOTE: Do not derive clone on this - there are raw pointers inside which + // will be messed up. Moving the `TokenBuffer` itself is safe as the actual + // backing slices won't be moved. + data: Box<[Entry]>, +} + +impl TokenBuffer { + // NOTE: DO NOT MUTATE THE `Vec` RETURNED FROM THIS FUNCTION ONCE IT + // RETURNS, THE ADDRESS OF ITS BACKING MEMORY MUST REMAIN STABLE. + fn inner_new(stream: TokenStream, up: *const Entry) -> TokenBuffer { + // Build up the entries list, recording the locations of any Groups + // in the list to be processed later. + let mut entries = Vec::new(); + let mut seqs = Vec::new(); + for tt in stream { + match tt { + TokenTree::Ident(sym) => { + entries.push(Entry::Ident(sym)); + } + TokenTree::Punct(op) => { + entries.push(Entry::Punct(op)); + } + TokenTree::Literal(l) => { + entries.push(Entry::Literal(l)); + } + TokenTree::Group(g) => { + // Record the index of the interesting entry, and store an + // `End(null)` there temporarially. + seqs.push((entries.len(), g)); + entries.push(Entry::End(ptr::null())); + } + } + } + // Add an `End` entry to the end with a reference to the enclosing token + // stream which was passed in. + entries.push(Entry::End(up)); + + // NOTE: This is done to ensure that we don't accidentally modify the + // length of the backing buffer. The backing buffer must remain at a + // constant address after this point, as we are going to store a raw + // pointer into it. + let mut entries = entries.into_boxed_slice(); + for (idx, group) in seqs { + // We know that this index refers to one of the temporary + // `End(null)` entries, and we know that the last entry is + // `End(up)`, so the next index is also valid. + let seq_up = &entries[idx + 1] as *const Entry; + + // The end entry stored at the end of this Entry::Group should + // point to the Entry which follows the Group in the list. + let inner = Self::inner_new(group.stream(), seq_up); + entries[idx] = Entry::Group(group, inner); + } + + TokenBuffer { data: entries } + } + + /// Creates a `TokenBuffer` containing all the tokens from the input + /// `TokenStream`. + /// + /// *This method is available if Syn is built with both the `"parsing"` and + /// `"proc-macro"` features.* + #[cfg(all( + not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "wasi"))), + feature = "proc-macro" + ))] + pub fn new(stream: pm::TokenStream) -> TokenBuffer { + Self::new2(stream.into()) + } + + /// Creates a `TokenBuffer` containing all the tokens from the input + /// `TokenStream`. + pub fn new2(stream: TokenStream) -> TokenBuffer { + Self::inner_new(stream, ptr::null()) + } + + /// Creates a cursor referencing the first token in the buffer and able to + /// traverse until the end of the buffer. + pub fn begin(&self) -> Cursor { + unsafe { Cursor::create(&self.data[0], &self.data[self.data.len() - 1]) } + } +} + +/// A cheaply copyable cursor into a `TokenBuffer`. +/// +/// This cursor holds a shared reference into the immutable data which is used +/// internally to represent a `TokenStream`, and can be efficiently manipulated +/// and copied around. +/// +/// An empty `Cursor` can be created directly, or one may create a `TokenBuffer` +/// object and get a cursor to its first token with `begin()`. +/// +/// Two cursors are equal if they have the same location in the same input +/// stream, and have the same scope. +/// +/// *This type is available if Syn is built with the `"parsing"` feature.* +#[derive(Copy, Clone, Eq, PartialEq)] +pub struct Cursor<'a> { + // The current entry which the `Cursor` is pointing at. + ptr: *const Entry, + // This is the only `Entry::End(..)` object which this cursor is allowed to + // point at. All other `End` objects are skipped over in `Cursor::create`. + scope: *const Entry, + // Cursor is covariant in 'a. This field ensures that our pointers are still + // valid. + marker: PhantomData<&'a Entry>, +} + +impl<'a> Cursor<'a> { + /// Creates a cursor referencing a static empty TokenStream. + pub fn empty() -> Self { + // It's safe in this situation for us to put an `Entry` object in global + // storage, despite it not actually being safe to send across threads + // (`Ident` is a reference into a thread-local table). This is because + // this entry never includes a `Ident` object. + // + // This wrapper struct allows us to break the rules and put a `Sync` + // object in global storage. + struct UnsafeSyncEntry(Entry); + unsafe impl Sync for UnsafeSyncEntry {} + static EMPTY_ENTRY: UnsafeSyncEntry = UnsafeSyncEntry(Entry::End(0 as *const Entry)); + + Cursor { + ptr: &EMPTY_ENTRY.0, + scope: &EMPTY_ENTRY.0, + marker: PhantomData, + } + } + + /// This create method intelligently exits non-explicitly-entered + /// `None`-delimited scopes when the cursor reaches the end of them, + /// allowing for them to be treated transparently. + unsafe fn create(mut ptr: *const Entry, scope: *const Entry) -> Self { + // NOTE: If we're looking at a `End(..)`, we want to advance the cursor + // past it, unless `ptr == scope`, which means that we're at the edge of + // our cursor's scope. We should only have `ptr != scope` at the exit + // from None-delimited groups entered with `ignore_none`. + while let Entry::End(exit) = *ptr { + if ptr == scope { + break; + } + ptr = exit; + } + + Cursor { + ptr, + scope, + marker: PhantomData, + } + } + + /// Get the current entry. + fn entry(self) -> &'a Entry { + unsafe { &*self.ptr } + } + + /// Bump the cursor to point at the next token after the current one. This + /// is undefined behavior if the cursor is currently looking at an + /// `Entry::End`. + unsafe fn bump(self) -> Cursor<'a> { + Cursor::create(self.ptr.offset(1), self.scope) + } + + /// If the cursor is looking at a `None`-delimited group, move it to look at + /// the first token inside instead. If the group is empty, this will move + /// the cursor past the `None`-delimited group. + /// + /// WARNING: This mutates its argument. + fn ignore_none(&mut self) { + if let Entry::Group(group, buf) = self.entry() { + if group.delimiter() == Delimiter::None { + // NOTE: We call `Cursor::create` here to make sure that + // situations where we should immediately exit the span after + // entering it are handled correctly. + unsafe { + *self = Cursor::create(&buf.data[0], self.scope); + } + } + } + } + + /// Checks whether the cursor is currently pointing at the end of its valid + /// scope. + pub fn eof(self) -> bool { + // We're at eof if we're at the end of our scope. + self.ptr == self.scope + } + + /// If the cursor is pointing at a `Group` with the given delimiter, returns + /// a cursor into that group and one pointing to the next `TokenTree`. + pub fn group(mut self, delim: Delimiter) -> Option<(Cursor<'a>, Span, Cursor<'a>)> { + // If we're not trying to enter a none-delimited group, we want to + // ignore them. We have to make sure to _not_ ignore them when we want + // to enter them, of course. For obvious reasons. + if delim != Delimiter::None { + self.ignore_none(); + } + + if let Entry::Group(group, buf) = self.entry() { + if group.delimiter() == delim { + return Some((buf.begin(), group.span(), unsafe { self.bump() })); + } + } + + None + } + + /// If the cursor is pointing at a `Ident`, returns it along with a cursor + /// pointing at the next `TokenTree`. + pub fn ident(mut self) -> Option<(Ident, Cursor<'a>)> { + self.ignore_none(); + match self.entry() { + Entry::Ident(ident) => Some((ident.clone(), unsafe { self.bump() })), + _ => None, + } + } + + /// If the cursor is pointing at an `Punct`, returns it along with a cursor + /// pointing at the next `TokenTree`. + pub fn punct(mut self) -> Option<(Punct, Cursor<'a>)> { + self.ignore_none(); + match self.entry() { + Entry::Punct(op) if op.as_char() != '\'' => Some((op.clone(), unsafe { self.bump() })), + _ => None, + } + } + + /// If the cursor is pointing at a `Literal`, return it along with a cursor + /// pointing at the next `TokenTree`. + pub fn literal(mut self) -> Option<(Literal, Cursor<'a>)> { + self.ignore_none(); + match self.entry() { + Entry::Literal(lit) => Some((lit.clone(), unsafe { self.bump() })), + _ => None, + } + } + + /// If the cursor is pointing at a `Lifetime`, returns it along with a + /// cursor pointing at the next `TokenTree`. + pub fn lifetime(mut self) -> Option<(Lifetime, Cursor<'a>)> { + self.ignore_none(); + match self.entry() { + Entry::Punct(op) if op.as_char() == '\'' && op.spacing() == Spacing::Joint => { + let next = unsafe { self.bump() }; + match next.ident() { + Some((ident, rest)) => { + let lifetime = Lifetime { + apostrophe: op.span(), + ident, + }; + Some((lifetime, rest)) + } + None => None, + } + } + _ => None, + } + } + + /// Copies all remaining tokens visible from this cursor into a + /// `TokenStream`. + pub fn token_stream(self) -> TokenStream { + let mut tts = Vec::new(); + let mut cursor = self; + while let Some((tt, rest)) = cursor.token_tree() { + tts.push(tt); + cursor = rest; + } + tts.into_iter().collect() + } + + /// If the cursor is pointing at a `TokenTree`, returns it along with a + /// cursor pointing at the next `TokenTree`. + /// + /// Returns `None` if the cursor has reached the end of its stream. + /// + /// This method does not treat `None`-delimited groups as transparent, and + /// will return a `Group(None, ..)` if the cursor is looking at one. + pub fn token_tree(self) -> Option<(TokenTree, Cursor<'a>)> { + let tree = match self.entry() { + Entry::Group(group, _) => group.clone().into(), + Entry::Literal(lit) => lit.clone().into(), + Entry::Ident(ident) => ident.clone().into(), + Entry::Punct(op) => op.clone().into(), + Entry::End(..) => { + return None; + } + }; + + Some((tree, unsafe { self.bump() })) + } + + /// Returns the `Span` of the current token, or `Span::call_site()` if this + /// cursor points to eof. + pub fn span(self) -> Span { + match self.entry() { + Entry::Group(group, _) => group.span(), + Entry::Literal(l) => l.span(), + Entry::Ident(t) => t.span(), + Entry::Punct(o) => o.span(), + Entry::End(..) => Span::call_site(), + } + } + + /// Skip over the next token without cloning it. Returns `None` if this + /// cursor points to eof. + /// + /// This method treats `'lifetimes` as a single token. + pub(crate) fn skip(self) -> Option> { + match self.entry() { + Entry::End(..) => None, + + // Treat lifetimes as a single tt for the purposes of 'skip'. + Entry::Punct(op) if op.as_char() == '\'' && op.spacing() == Spacing::Joint => { + let next = unsafe { self.bump() }; + match next.entry() { + Entry::Ident(_) => Some(unsafe { next.bump() }), + _ => Some(next), + } + } + _ => Some(unsafe { self.bump() }), + } + } +} + +pub(crate) fn same_scope(a: Cursor, b: Cursor) -> bool { + a.scope == b.scope +} + +pub(crate) fn open_span_of_group(cursor: Cursor) -> Span { + match cursor.entry() { + Entry::Group(group, _) => group.span_open(), + _ => cursor.span(), + } +} + +pub(crate) fn close_span_of_group(cursor: Cursor) -> Span { + match cursor.entry() { + Entry::Group(group, _) => group.span_close(), + _ => cursor.span(), + } +} diff --git a/syn/src/custom_keyword.rs b/syn/src/custom_keyword.rs new file mode 100644 index 0000000..200e847 --- /dev/null +++ b/syn/src/custom_keyword.rs @@ -0,0 +1,252 @@ +/// Define a type that supports parsing and printing a given identifier as if it +/// were a keyword. +/// +/// # Usage +/// +/// As a convention, it is recommended that this macro be invoked within a +/// module called `kw` or `keyword` and that the resulting parser be invoked +/// with a `kw::` or `keyword::` prefix. +/// +/// ``` +/// mod kw { +/// syn::custom_keyword!(whatever); +/// } +/// ``` +/// +/// The generated syntax tree node supports the following operations just like +/// any built-in keyword token. +/// +/// - [Peeking] — `input.peek(kw::whatever)` +/// +/// - [Parsing] — `input.parse::()?` +/// +/// - [Printing] — `quote!( ... #whatever_token ... )` +/// +/// - Construction from a [`Span`] — `let whatever_token = kw::whatever(sp)` +/// +/// - Field access to its span — `let sp = whatever_token.span` +/// +/// [Peeking]: parse::ParseBuffer::peek +/// [Parsing]: parse::ParseBuffer::parse +/// [Printing]: quote::ToTokens +/// [`Span`]: proc_macro2::Span +/// +/// # Example +/// +/// This example parses input that looks like `bool = true` or `str = "value"`. +/// The key must be either the identifier `bool` or the identifier `str`. If +/// `bool`, the value may be either `true` or `false`. If `str`, the value may +/// be any string literal. +/// +/// The symbols `bool` and `str` are not reserved keywords in Rust so these are +/// not considered keywords in the `syn::token` module. Like any other +/// identifier that is not a keyword, these can be declared as custom keywords +/// by crates that need to use them as such. +/// +/// ``` +/// use syn::{LitBool, LitStr, Result, Token}; +/// use syn::parse::{Parse, ParseStream}; +/// +/// mod kw { +/// syn::custom_keyword!(bool); +/// syn::custom_keyword!(str); +/// } +/// +/// enum Argument { +/// Bool { +/// bool_token: kw::bool, +/// eq_token: Token![=], +/// value: LitBool, +/// }, +/// Str { +/// str_token: kw::str, +/// eq_token: Token![=], +/// value: LitStr, +/// }, +/// } +/// +/// impl Parse for Argument { +/// fn parse(input: ParseStream) -> Result { +/// let lookahead = input.lookahead1(); +/// if lookahead.peek(kw::bool) { +/// Ok(Argument::Bool { +/// bool_token: input.parse::()?, +/// eq_token: input.parse()?, +/// value: input.parse()?, +/// }) +/// } else if lookahead.peek(kw::str) { +/// Ok(Argument::Str { +/// str_token: input.parse::()?, +/// eq_token: input.parse()?, +/// value: input.parse()?, +/// }) +/// } else { +/// Err(lookahead.error()) +/// } +/// } +/// } +/// ``` +#[macro_export(local_inner_macros)] +macro_rules! custom_keyword { + ($ident:ident) => { + #[allow(non_camel_case_types)] + pub struct $ident { + pub span: $crate::export::Span, + } + + #[doc(hidden)] + #[allow(non_snake_case)] + pub fn $ident<__S: $crate::export::IntoSpans<[$crate::export::Span; 1]>>( + span: __S, + ) -> $ident { + $ident { + span: $crate::export::IntoSpans::into_spans(span)[0], + } + } + + impl $crate::export::Default for $ident { + fn default() -> Self { + $ident { + span: $crate::export::Span::call_site(), + } + } + } + + impl_parse_for_custom_keyword!($ident); + impl_to_tokens_for_custom_keyword!($ident); + impl_clone_for_custom_keyword!($ident); + impl_extra_traits_for_custom_keyword!($ident); + }; +} + +// Not public API. +#[cfg(feature = "parsing")] +#[doc(hidden)] +#[macro_export] +macro_rules! impl_parse_for_custom_keyword { + ($ident:ident) => { + // For peek. + impl $crate::token::CustomToken for $ident { + fn peek(cursor: $crate::buffer::Cursor) -> $crate::export::bool { + if let Some((ident, _rest)) = cursor.ident() { + ident == stringify!($ident) + } else { + false + } + } + + fn display() -> &'static $crate::export::str { + concat!("`", stringify!($ident), "`") + } + } + + impl $crate::parse::Parse for $ident { + fn parse(input: $crate::parse::ParseStream) -> $crate::parse::Result<$ident> { + input.step(|cursor| { + if let $crate::export::Some((ident, rest)) = cursor.ident() { + if ident == stringify!($ident) { + return $crate::export::Ok(($ident { span: ident.span() }, rest)); + } + } + $crate::export::Err(cursor.error(concat!( + "expected `", + stringify!($ident), + "`" + ))) + }) + } + } + }; +} + +// Not public API. +#[cfg(not(feature = "parsing"))] +#[doc(hidden)] +#[macro_export] +macro_rules! impl_parse_for_custom_keyword { + ($ident:ident) => {}; +} + +// Not public API. +#[cfg(feature = "printing")] +#[doc(hidden)] +#[macro_export] +macro_rules! impl_to_tokens_for_custom_keyword { + ($ident:ident) => { + impl $crate::export::ToTokens for $ident { + fn to_tokens(&self, tokens: &mut $crate::export::TokenStream2) { + let ident = $crate::Ident::new(stringify!($ident), self.span); + $crate::export::TokenStreamExt::append(tokens, ident); + } + } + }; +} + +// Not public API. +#[cfg(not(feature = "printing"))] +#[doc(hidden)] +#[macro_export] +macro_rules! impl_to_tokens_for_custom_keyword { + ($ident:ident) => {}; +} + +// Not public API. +#[cfg(feature = "clone-impls")] +#[doc(hidden)] +#[macro_export] +macro_rules! impl_clone_for_custom_keyword { + ($ident:ident) => { + impl $crate::export::Copy for $ident {} + + impl $crate::export::Clone for $ident { + fn clone(&self) -> Self { + *self + } + } + }; +} + +// Not public API. +#[cfg(not(feature = "clone-impls"))] +#[doc(hidden)] +#[macro_export] +macro_rules! impl_clone_for_custom_keyword { + ($ident:ident) => {}; +} + +// Not public API. +#[cfg(feature = "extra-traits")] +#[doc(hidden)] +#[macro_export] +macro_rules! impl_extra_traits_for_custom_keyword { + ($ident:ident) => { + impl $crate::export::Debug for $ident { + fn fmt(&self, f: &mut $crate::export::Formatter) -> $crate::export::fmt::Result { + $crate::export::Formatter::write_str( + f, + concat!("Keyword [", stringify!($ident), "]"), + ) + } + } + + impl $crate::export::Eq for $ident {} + + impl $crate::export::PartialEq for $ident { + fn eq(&self, _other: &Self) -> $crate::export::bool { + true + } + } + + impl $crate::export::Hash for $ident { + fn hash<__H: $crate::export::Hasher>(&self, _state: &mut __H) {} + } + }; +} + +// Not public API. +#[cfg(not(feature = "extra-traits"))] +#[doc(hidden)] +#[macro_export] +macro_rules! impl_extra_traits_for_custom_keyword { + ($ident:ident) => {}; +} diff --git a/syn/src/custom_punctuation.rs b/syn/src/custom_punctuation.rs new file mode 100644 index 0000000..29fa448 --- /dev/null +++ b/syn/src/custom_punctuation.rs @@ -0,0 +1,309 @@ +/// Define a type that supports parsing and printing a multi-character symbol +/// as if it were a punctuation token. +/// +/// # Usage +/// +/// ``` +/// syn::custom_punctuation!(LeftRightArrow, <=>); +/// ``` +/// +/// The generated syntax tree node supports the following operations just like +/// any built-in punctuation token. +/// +/// - [Peeking] — `input.peek(LeftRightArrow)` +/// +/// - [Parsing] — `input.parse::()?` +/// +/// - [Printing] — `quote!( ... #lrarrow ... )` +/// +/// - Construction from a [`Span`] — `let lrarrow = LeftRightArrow(sp)` +/// +/// - Construction from multiple [`Span`] — `let lrarrow = LeftRightArrow([sp, sp, sp])` +/// +/// - Field access to its spans — `let spans = lrarrow.spans` +/// +/// [Peeking]: parse::ParseBuffer::peek +/// [Parsing]: parse::ParseBuffer::parse +/// [Printing]: quote::ToTokens +/// [`Span`]: proc_macro2::Span +/// +/// # Example +/// +/// ``` +/// use proc_macro2::{TokenStream, TokenTree}; +/// use syn::parse::{Parse, ParseStream, Peek, Result}; +/// use syn::punctuated::Punctuated; +/// use syn::Expr; +/// +/// syn::custom_punctuation!(PathSeparator, ); +/// +/// // expr expr expr ... +/// struct PathSegments { +/// segments: Punctuated, +/// } +/// +/// impl Parse for PathSegments { +/// fn parse(input: ParseStream) -> Result { +/// let mut segments = Punctuated::new(); +/// +/// let first = parse_until(input, PathSeparator)?; +/// segments.push_value(syn::parse2(first)?); +/// +/// while input.peek(PathSeparator) { +/// segments.push_punct(input.parse()?); +/// +/// let next = parse_until(input, PathSeparator)?; +/// segments.push_value(syn::parse2(next)?); +/// } +/// +/// Ok(PathSegments { segments }) +/// } +/// } +/// +/// fn parse_until(input: ParseStream, end: E) -> Result { +/// let mut tokens = TokenStream::new(); +/// while !input.is_empty() && !input.peek(end) { +/// let next: TokenTree = input.parse()?; +/// tokens.extend(Some(next)); +/// } +/// Ok(tokens) +/// } +/// +/// fn main() { +/// let input = r#" a::b c::d::e "#; +/// let _: PathSegments = syn::parse_str(input).unwrap(); +/// } +/// ``` +#[macro_export(local_inner_macros)] +macro_rules! custom_punctuation { + ($ident:ident, $($tt:tt)+) => { + pub struct $ident { + pub spans: custom_punctuation_repr!($($tt)+), + } + + #[doc(hidden)] + #[allow(non_snake_case)] + pub fn $ident<__S: $crate::export::IntoSpans>( + spans: __S, + ) -> $ident { + let _validate_len = 0 $(+ custom_punctuation_len!(strict, $tt))*; + $ident { + spans: $crate::export::IntoSpans::into_spans(spans) + } + } + + impl $crate::export::Default for $ident { + fn default() -> Self { + $ident($crate::export::Span::call_site()) + } + } + + impl_parse_for_custom_punctuation!($ident, $($tt)+); + impl_to_tokens_for_custom_punctuation!($ident, $($tt)+); + impl_clone_for_custom_punctuation!($ident, $($tt)+); + impl_extra_traits_for_custom_punctuation!($ident, $($tt)+); + }; +} + +// Not public API. +#[cfg(feature = "parsing")] +#[doc(hidden)] +#[macro_export(local_inner_macros)] +macro_rules! impl_parse_for_custom_punctuation { + ($ident:ident, $($tt:tt)+) => { + impl $crate::token::CustomToken for $ident { + fn peek(cursor: $crate::buffer::Cursor) -> bool { + $crate::token::parsing::peek_punct(cursor, stringify_punct!($($tt)+)) + } + + fn display() -> &'static $crate::export::str { + custom_punctuation_concat!("`", stringify_punct!($($tt)+), "`") + } + } + + impl $crate::parse::Parse for $ident { + fn parse(input: $crate::parse::ParseStream) -> $crate::parse::Result<$ident> { + let spans: custom_punctuation_repr!($($tt)+) = + $crate::token::parsing::punct(input, stringify_punct!($($tt)+))?; + Ok($ident(spans)) + } + } + }; +} + +// Not public API. +#[cfg(not(feature = "parsing"))] +#[doc(hidden)] +#[macro_export] +macro_rules! impl_parse_for_custom_punctuation { + ($ident:ident, $($tt:tt)+) => {}; +} + +// Not public API. +#[cfg(feature = "printing")] +#[doc(hidden)] +#[macro_export(local_inner_macros)] +macro_rules! impl_to_tokens_for_custom_punctuation { + ($ident:ident, $($tt:tt)+) => { + impl $crate::export::ToTokens for $ident { + fn to_tokens(&self, tokens: &mut $crate::export::TokenStream2) { + $crate::token::printing::punct(stringify_punct!($($tt)+), &self.spans, tokens) + } + } + }; +} + +// Not public API. +#[cfg(not(feature = "printing"))] +#[doc(hidden)] +#[macro_export] +macro_rules! impl_to_tokens_for_custom_punctuation { + ($ident:ident, $($tt:tt)+) => {}; +} + +// Not public API. +#[cfg(feature = "clone-impls")] +#[doc(hidden)] +#[macro_export] +macro_rules! impl_clone_for_custom_punctuation { + ($ident:ident, $($tt:tt)+) => { + impl $crate::export::Copy for $ident {} + + impl $crate::export::Clone for $ident { + fn clone(&self) -> Self { + *self + } + } + }; +} + +// Not public API. +#[cfg(not(feature = "clone-impls"))] +#[doc(hidden)] +#[macro_export] +macro_rules! impl_clone_for_custom_punctuation { + ($ident:ident, $($tt:tt)+) => {}; +} + +// Not public API. +#[cfg(feature = "extra-traits")] +#[doc(hidden)] +#[macro_export] +macro_rules! impl_extra_traits_for_custom_punctuation { + ($ident:ident, $($tt:tt)+) => { + impl $crate::export::Debug for $ident { + fn fmt(&self, f: &mut $crate::export::Formatter) -> $crate::export::fmt::Result { + $crate::export::Formatter::write_str(f, stringify!($ident)) + } + } + + impl $crate::export::Eq for $ident {} + + impl $crate::export::PartialEq for $ident { + fn eq(&self, _other: &Self) -> $crate::export::bool { + true + } + } + + impl $crate::export::Hash for $ident { + fn hash<__H: $crate::export::Hasher>(&self, _state: &mut __H) {} + } + }; +} + +// Not public API. +#[cfg(not(feature = "extra-traits"))] +#[doc(hidden)] +#[macro_export] +macro_rules! impl_extra_traits_for_custom_punctuation { + ($ident:ident, $($tt:tt)+) => {}; +} + +// Not public API. +#[doc(hidden)] +#[macro_export(local_inner_macros)] +macro_rules! custom_punctuation_repr { + ($($tt:tt)+) => { + [$crate::export::Span; 0 $(+ custom_punctuation_len!(lenient, $tt))+] + }; +} + +// Not public API. +#[doc(hidden)] +#[macro_export(local_inner_macros)] +#[rustfmt::skip] +macro_rules! custom_punctuation_len { + ($mode:ident, +) => { 1 }; + ($mode:ident, +=) => { 2 }; + ($mode:ident, &) => { 1 }; + ($mode:ident, &&) => { 2 }; + ($mode:ident, &=) => { 2 }; + ($mode:ident, @) => { 1 }; + ($mode:ident, !) => { 1 }; + ($mode:ident, ^) => { 1 }; + ($mode:ident, ^=) => { 2 }; + ($mode:ident, :) => { 1 }; + ($mode:ident, ::) => { 2 }; + ($mode:ident, ,) => { 1 }; + ($mode:ident, /) => { 1 }; + ($mode:ident, /=) => { 2 }; + ($mode:ident, .) => { 1 }; + ($mode:ident, ..) => { 2 }; + ($mode:ident, ...) => { 3 }; + ($mode:ident, ..=) => { 3 }; + ($mode:ident, =) => { 1 }; + ($mode:ident, ==) => { 2 }; + ($mode:ident, >=) => { 2 }; + ($mode:ident, >) => { 1 }; + ($mode:ident, <=) => { 2 }; + ($mode:ident, <) => { 1 }; + ($mode:ident, *=) => { 2 }; + ($mode:ident, !=) => { 2 }; + ($mode:ident, |) => { 1 }; + ($mode:ident, |=) => { 2 }; + ($mode:ident, ||) => { 2 }; + ($mode:ident, #) => { 1 }; + ($mode:ident, ?) => { 1 }; + ($mode:ident, ->) => { 2 }; + ($mode:ident, <-) => { 2 }; + ($mode:ident, %) => { 1 }; + ($mode:ident, %=) => { 2 }; + ($mode:ident, =>) => { 2 }; + ($mode:ident, ;) => { 1 }; + ($mode:ident, <<) => { 2 }; + ($mode:ident, <<=) => { 3 }; + ($mode:ident, >>) => { 2 }; + ($mode:ident, >>=) => { 3 }; + ($mode:ident, *) => { 1 }; + ($mode:ident, -) => { 1 }; + ($mode:ident, -=) => { 2 }; + ($mode:ident, ~) => { 1 }; + (lenient, $tt:tt) => { 0 }; + (strict, $tt:tt) => {{ custom_punctuation_unexpected!($tt); 0 }}; +} + +// Not public API. +#[doc(hidden)] +#[macro_export] +macro_rules! custom_punctuation_unexpected { + () => {}; +} + +// Not public API. +#[doc(hidden)] +#[macro_export] +macro_rules! stringify_punct { + ($($tt:tt)+) => { + concat!($(stringify!($tt)),+) + }; +} + +// Not public API. +// Without this, local_inner_macros breaks when looking for concat! +#[doc(hidden)] +#[macro_export] +macro_rules! custom_punctuation_concat { + ($($tt:tt)*) => { + concat!($($tt)*) + }; +} diff --git a/syn/src/data.rs b/syn/src/data.rs new file mode 100644 index 0000000..184a79e --- /dev/null +++ b/syn/src/data.rs @@ -0,0 +1,456 @@ +use super::*; +use crate::punctuated::Punctuated; + +ast_struct! { + /// An enum variant. + /// + /// *This type is available if Syn is built with the `"derive"` or `"full"` + /// feature.* + pub struct Variant { + /// Attributes tagged on the variant. + pub attrs: Vec, + + /// Name of the variant. + pub ident: Ident, + + /// Content stored in the variant. + pub fields: Fields, + + /// Explicit discriminant: `Variant = 1` + pub discriminant: Option<(Token![=], Expr)>, + } +} + +ast_enum_of_structs! { + /// Data stored within an enum variant or struct. + /// + /// *This type is available if Syn is built with the `"derive"` or `"full"` + /// feature.* + /// + /// # Syntax tree enum + /// + /// This type is a [syntax tree enum]. + /// + /// [syntax tree enum]: enum.Expr.html#syntax-tree-enums + // + // TODO: change syntax-tree-enum link to an intra rustdoc link, currently + // blocked on https://github.com/rust-lang/rust/issues/62833 + pub enum Fields { + /// Named fields of a struct or struct variant such as `Point { x: f64, + /// y: f64 }`. + Named(FieldsNamed), + + /// Unnamed fields of a tuple struct or tuple variant such as `Some(T)`. + Unnamed(FieldsUnnamed), + + /// Unit struct or unit variant such as `None`. + Unit, + } +} + +ast_struct! { + /// Named fields of a struct or struct variant such as `Point { x: f64, + /// y: f64 }`. + /// + /// *This type is available if Syn is built with the `"derive"` or + /// `"full"` feature.* + pub struct FieldsNamed { + pub brace_token: token::Brace, + pub named: Punctuated, + } +} + +ast_struct! { + /// Unnamed fields of a tuple struct or tuple variant such as `Some(T)`. + /// + /// *This type is available if Syn is built with the `"derive"` or + /// `"full"` feature.* + pub struct FieldsUnnamed { + pub paren_token: token::Paren, + pub unnamed: Punctuated, + } +} + +impl Fields { + /// Get an iterator over the borrowed [`Field`] items in this object. This + /// iterator can be used to iterate over a named or unnamed struct or + /// variant's fields uniformly. + pub fn iter(&self) -> punctuated::Iter { + match self { + Fields::Unit => crate::punctuated::empty_punctuated_iter(), + Fields::Named(f) => f.named.iter(), + Fields::Unnamed(f) => f.unnamed.iter(), + } + } + + /// Get an iterator over the mutably borrowed [`Field`] items in this + /// object. This iterator can be used to iterate over a named or unnamed + /// struct or variant's fields uniformly. + pub fn iter_mut(&mut self) -> punctuated::IterMut { + match self { + Fields::Unit => crate::punctuated::empty_punctuated_iter_mut(), + Fields::Named(f) => f.named.iter_mut(), + Fields::Unnamed(f) => f.unnamed.iter_mut(), + } + } + + /// Returns the number of fields. + pub fn len(&self) -> usize { + match self { + Fields::Unit => 0, + Fields::Named(f) => f.named.len(), + Fields::Unnamed(f) => f.unnamed.len(), + } + } + + /// Returns `true` if there are zero fields. + pub fn is_empty(&self) -> bool { + match self { + Fields::Unit => true, + Fields::Named(f) => f.named.is_empty(), + Fields::Unnamed(f) => f.unnamed.is_empty(), + } + } +} + +impl IntoIterator for Fields { + type Item = Field; + type IntoIter = punctuated::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + match self { + Fields::Unit => Punctuated::::new().into_iter(), + Fields::Named(f) => f.named.into_iter(), + Fields::Unnamed(f) => f.unnamed.into_iter(), + } + } +} + +impl<'a> IntoIterator for &'a Fields { + type Item = &'a Field; + type IntoIter = punctuated::Iter<'a, Field>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl<'a> IntoIterator for &'a mut Fields { + type Item = &'a mut Field; + type IntoIter = punctuated::IterMut<'a, Field>; + + fn into_iter(self) -> Self::IntoIter { + self.iter_mut() + } +} + +ast_struct! { + /// A field of a struct or enum variant. + /// + /// *This type is available if Syn is built with the `"derive"` or `"full"` + /// feature.* + pub struct Field { + /// Attributes tagged on the field. + pub attrs: Vec, + + /// Visibility of the field. + pub vis: Visibility, + + /// Name of the field, if any. + /// + /// Fields of tuple structs have no names. + pub ident: Option, + + pub colon_token: Option, + + /// Type of the field. + pub ty: Type, + } +} + +ast_enum_of_structs! { + /// The visibility level of an item: inherited or `pub` or + /// `pub(restricted)`. + /// + /// *This type is available if Syn is built with the `"derive"` or `"full"` + /// feature.* + /// + /// # Syntax tree enum + /// + /// This type is a [syntax tree enum]. + /// + /// [syntax tree enum]: enum.Expr.html#syntax-tree-enums + // + // TODO: change syntax-tree-enum link to an intra rustdoc link, currently + // blocked on https://github.com/rust-lang/rust/issues/62833 + pub enum Visibility { + /// A public visibility level: `pub`. + Public(VisPublic), + + /// A crate-level visibility: `crate`. + Crate(VisCrate), + + /// A visibility level restricted to some path: `pub(self)` or + /// `pub(super)` or `pub(crate)` or `pub(in some::module)`. + Restricted(VisRestricted), + + /// An inherited visibility, which usually means private. + Inherited, + } +} + +ast_struct! { + /// A public visibility level: `pub`. + /// + /// *This type is available if Syn is built with the `"derive"` or + /// `"full"` feature.* + pub struct VisPublic { + pub pub_token: Token![pub], + } +} + +ast_struct! { + /// A crate-level visibility: `crate`. + /// + /// *This type is available if Syn is built with the `"derive"` or + /// `"full"` feature.* + pub struct VisCrate { + pub crate_token: Token![crate], + } +} + +ast_struct! { + /// A visibility level restricted to some path: `pub(self)` or + /// `pub(super)` or `pub(crate)` or `pub(in some::module)`. + /// + /// *This type is available if Syn is built with the `"derive"` or + /// `"full"` feature.* + pub struct VisRestricted { + pub pub_token: Token![pub], + pub paren_token: token::Paren, + pub in_token: Option, + pub path: Box, + } +} + +#[cfg(feature = "parsing")] +pub mod parsing { + use super::*; + + use crate::ext::IdentExt; + use crate::parse::discouraged::Speculative; + use crate::parse::{Parse, ParseStream, Result}; + + impl Parse for Variant { + fn parse(input: ParseStream) -> Result { + Ok(Variant { + attrs: input.call(Attribute::parse_outer)?, + ident: input.parse()?, + fields: { + if input.peek(token::Brace) { + Fields::Named(input.parse()?) + } else if input.peek(token::Paren) { + Fields::Unnamed(input.parse()?) + } else { + Fields::Unit + } + }, + discriminant: { + if input.peek(Token![=]) { + let eq_token: Token![=] = input.parse()?; + let discriminant: Expr = input.parse()?; + Some((eq_token, discriminant)) + } else { + None + } + }, + }) + } + } + + impl Parse for FieldsNamed { + fn parse(input: ParseStream) -> Result { + let content; + Ok(FieldsNamed { + brace_token: braced!(content in input), + named: content.parse_terminated(Field::parse_named)?, + }) + } + } + + impl Parse for FieldsUnnamed { + fn parse(input: ParseStream) -> Result { + let content; + Ok(FieldsUnnamed { + paren_token: parenthesized!(content in input), + unnamed: content.parse_terminated(Field::parse_unnamed)?, + }) + } + } + + impl Field { + /// Parses a named (braced struct) field. + pub fn parse_named(input: ParseStream) -> Result { + Ok(Field { + attrs: input.call(Attribute::parse_outer)?, + vis: input.parse()?, + ident: Some(input.parse()?), + colon_token: Some(input.parse()?), + ty: input.parse()?, + }) + } + + /// Parses an unnamed (tuple struct) field. + pub fn parse_unnamed(input: ParseStream) -> Result { + Ok(Field { + attrs: input.call(Attribute::parse_outer)?, + vis: input.parse()?, + ident: None, + colon_token: None, + ty: input.parse()?, + }) + } + } + + impl Parse for Visibility { + fn parse(input: ParseStream) -> Result { + if input.peek(Token![pub]) { + Self::parse_pub(input) + } else if input.peek(Token![crate]) { + Self::parse_crate(input) + } else { + Ok(Visibility::Inherited) + } + } + } + + impl Visibility { + fn parse_pub(input: ParseStream) -> Result { + let pub_token = input.parse::()?; + + if input.peek(token::Paren) { + let ahead = input.fork(); + + let content; + let paren_token = parenthesized!(content in ahead); + if content.peek(Token![crate]) + || content.peek(Token![self]) + || content.peek(Token![super]) + { + let path = content.call(Ident::parse_any)?; + + // Ensure there are no additional tokens within `content`. + // Without explicitly checking, we may misinterpret a tuple + // field as a restricted visibility, causing a parse error. + // e.g. `pub (crate::A, crate::B)` (Issue #720). + if content.is_empty() { + input.advance_to(&ahead); + return Ok(Visibility::Restricted(VisRestricted { + pub_token, + paren_token, + in_token: None, + path: Box::new(Path::from(path)), + })); + } + } else if content.peek(Token![in]) { + let in_token: Token![in] = content.parse()?; + let path = content.call(Path::parse_mod_style)?; + + input.advance_to(&ahead); + return Ok(Visibility::Restricted(VisRestricted { + pub_token, + paren_token, + in_token: Some(in_token), + path: Box::new(path), + })); + } + } + + Ok(Visibility::Public(VisPublic { pub_token })) + } + + fn parse_crate(input: ParseStream) -> Result { + if input.peek2(Token![::]) { + Ok(Visibility::Inherited) + } else { + Ok(Visibility::Crate(VisCrate { + crate_token: input.parse()?, + })) + } + } + } +} + +#[cfg(feature = "printing")] +mod printing { + use super::*; + + use proc_macro2::TokenStream; + use quote::{ToTokens, TokenStreamExt}; + + use crate::print::TokensOrDefault; + + impl ToTokens for Variant { + fn to_tokens(&self, tokens: &mut TokenStream) { + tokens.append_all(&self.attrs); + self.ident.to_tokens(tokens); + self.fields.to_tokens(tokens); + if let Some((eq_token, disc)) = &self.discriminant { + eq_token.to_tokens(tokens); + disc.to_tokens(tokens); + } + } + } + + impl ToTokens for FieldsNamed { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.brace_token.surround(tokens, |tokens| { + self.named.to_tokens(tokens); + }); + } + } + + impl ToTokens for FieldsUnnamed { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.paren_token.surround(tokens, |tokens| { + self.unnamed.to_tokens(tokens); + }); + } + } + + impl ToTokens for Field { + fn to_tokens(&self, tokens: &mut TokenStream) { + tokens.append_all(&self.attrs); + self.vis.to_tokens(tokens); + if let Some(ident) = &self.ident { + ident.to_tokens(tokens); + TokensOrDefault(&self.colon_token).to_tokens(tokens); + } + self.ty.to_tokens(tokens); + } + } + + impl ToTokens for VisPublic { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.pub_token.to_tokens(tokens) + } + } + + impl ToTokens for VisCrate { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.crate_token.to_tokens(tokens); + } + } + + impl ToTokens for VisRestricted { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.pub_token.to_tokens(tokens); + self.paren_token.surround(tokens, |tokens| { + // TODO: If we have a path which is not "self" or "super" or + // "crate", automatically add the "in" token. + self.in_token.to_tokens(tokens); + self.path.to_tokens(tokens); + }); + } + } +} diff --git a/syn/src/derive.rs b/syn/src/derive.rs new file mode 100644 index 0000000..8cb9cf7 --- /dev/null +++ b/syn/src/derive.rs @@ -0,0 +1,273 @@ +use super::*; +use crate::punctuated::Punctuated; + +ast_struct! { + /// Data structure sent to a `proc_macro_derive` macro. + /// + /// *This type is available if Syn is built with the `"derive"` feature.* + pub struct DeriveInput { + /// Attributes tagged on the whole struct or enum. + pub attrs: Vec, + + /// Visibility of the struct or enum. + pub vis: Visibility, + + /// Name of the struct or enum. + pub ident: Ident, + + /// Generics required to complete the definition. + pub generics: Generics, + + /// Data within the struct or enum. + pub data: Data, + } +} + +ast_enum_of_structs! { + /// The storage of a struct, enum or union data structure. + /// + /// *This type is available if Syn is built with the `"derive"` feature.* + /// + /// # Syntax tree enum + /// + /// This type is a [syntax tree enum]. + /// + /// [syntax tree enum]: enum.Expr.html#syntax-tree-enums + // + // TODO: change syntax-tree-enum link to an intra rustdoc link, currently + // blocked on https://github.com/rust-lang/rust/issues/62833 + pub enum Data { + /// A struct input to a `proc_macro_derive` macro. + Struct(DataStruct), + + /// An enum input to a `proc_macro_derive` macro. + Enum(DataEnum), + + /// An untagged union input to a `proc_macro_derive` macro. + Union(DataUnion), + } + + do_not_generate_to_tokens +} + +ast_struct! { + /// A struct input to a `proc_macro_derive` macro. + /// + /// *This type is available if Syn is built with the `"derive"` + /// feature.* + pub struct DataStruct { + pub struct_token: Token![struct], + pub fields: Fields, + pub semi_token: Option, + } +} + +ast_struct! { + /// An enum input to a `proc_macro_derive` macro. + /// + /// *This type is available if Syn is built with the `"derive"` + /// feature.* + pub struct DataEnum { + pub enum_token: Token![enum], + pub brace_token: token::Brace, + pub variants: Punctuated, + } +} + +ast_struct! { + /// An untagged union input to a `proc_macro_derive` macro. + /// + /// *This type is available if Syn is built with the `"derive"` + /// feature.* + pub struct DataUnion { + pub union_token: Token![union], + pub fields: FieldsNamed, + } +} + +#[cfg(feature = "parsing")] +pub mod parsing { + use super::*; + + use crate::parse::{Parse, ParseStream, Result}; + + impl Parse for DeriveInput { + fn parse(input: ParseStream) -> Result { + let attrs = input.call(Attribute::parse_outer)?; + let vis = input.parse::()?; + + let lookahead = input.lookahead1(); + if lookahead.peek(Token![struct]) { + let struct_token = input.parse::()?; + let ident = input.parse::()?; + let generics = input.parse::()?; + let (where_clause, fields, semi) = data_struct(input)?; + Ok(DeriveInput { + attrs, + vis, + ident, + generics: Generics { + where_clause, + ..generics + }, + data: Data::Struct(DataStruct { + struct_token, + fields, + semi_token: semi, + }), + }) + } else if lookahead.peek(Token![enum]) { + let enum_token = input.parse::()?; + let ident = input.parse::()?; + let generics = input.parse::()?; + let (where_clause, brace, variants) = data_enum(input)?; + Ok(DeriveInput { + attrs, + vis, + ident, + generics: Generics { + where_clause, + ..generics + }, + data: Data::Enum(DataEnum { + enum_token, + brace_token: brace, + variants, + }), + }) + } else if lookahead.peek(Token![union]) { + let union_token = input.parse::()?; + let ident = input.parse::()?; + let generics = input.parse::()?; + let (where_clause, fields) = data_union(input)?; + Ok(DeriveInput { + attrs, + vis, + ident, + generics: Generics { + where_clause, + ..generics + }, + data: Data::Union(DataUnion { + union_token, + fields, + }), + }) + } else { + Err(lookahead.error()) + } + } + } + + pub fn data_struct( + input: ParseStream, + ) -> Result<(Option, Fields, Option)> { + let mut lookahead = input.lookahead1(); + let mut where_clause = None; + if lookahead.peek(Token![where]) { + where_clause = Some(input.parse()?); + lookahead = input.lookahead1(); + } + + if where_clause.is_none() && lookahead.peek(token::Paren) { + let fields = input.parse()?; + + lookahead = input.lookahead1(); + if lookahead.peek(Token![where]) { + where_clause = Some(input.parse()?); + lookahead = input.lookahead1(); + } + + if lookahead.peek(Token![;]) { + let semi = input.parse()?; + Ok((where_clause, Fields::Unnamed(fields), Some(semi))) + } else { + Err(lookahead.error()) + } + } else if lookahead.peek(token::Brace) { + let fields = input.parse()?; + Ok((where_clause, Fields::Named(fields), None)) + } else if lookahead.peek(Token![;]) { + let semi = input.parse()?; + Ok((where_clause, Fields::Unit, Some(semi))) + } else { + Err(lookahead.error()) + } + } + + pub fn data_enum( + input: ParseStream, + ) -> Result<( + Option, + token::Brace, + Punctuated, + )> { + let where_clause = input.parse()?; + + let content; + let brace = braced!(content in input); + let variants = content.parse_terminated(Variant::parse)?; + + Ok((where_clause, brace, variants)) + } + + pub fn data_union(input: ParseStream) -> Result<(Option, FieldsNamed)> { + let where_clause = input.parse()?; + let fields = input.parse()?; + Ok((where_clause, fields)) + } +} + +#[cfg(feature = "printing")] +mod printing { + use super::*; + + use proc_macro2::TokenStream; + use quote::ToTokens; + + use crate::attr::FilterAttrs; + use crate::print::TokensOrDefault; + + impl ToTokens for DeriveInput { + fn to_tokens(&self, tokens: &mut TokenStream) { + for attr in self.attrs.outer() { + attr.to_tokens(tokens); + } + self.vis.to_tokens(tokens); + match &self.data { + Data::Struct(d) => d.struct_token.to_tokens(tokens), + Data::Enum(d) => d.enum_token.to_tokens(tokens), + Data::Union(d) => d.union_token.to_tokens(tokens), + } + self.ident.to_tokens(tokens); + self.generics.to_tokens(tokens); + match &self.data { + Data::Struct(data) => match &data.fields { + Fields::Named(fields) => { + self.generics.where_clause.to_tokens(tokens); + fields.to_tokens(tokens); + } + Fields::Unnamed(fields) => { + fields.to_tokens(tokens); + self.generics.where_clause.to_tokens(tokens); + TokensOrDefault(&data.semi_token).to_tokens(tokens); + } + Fields::Unit => { + self.generics.where_clause.to_tokens(tokens); + TokensOrDefault(&data.semi_token).to_tokens(tokens); + } + }, + Data::Enum(data) => { + self.generics.where_clause.to_tokens(tokens); + data.brace_token.surround(tokens, |tokens| { + data.variants.to_tokens(tokens); + }); + } + Data::Union(data) => { + self.generics.where_clause.to_tokens(tokens); + data.fields.to_tokens(tokens); + } + } + } + } +} diff --git a/syn/src/discouraged.rs b/syn/src/discouraged.rs new file mode 100644 index 0000000..29d1006 --- /dev/null +++ b/syn/src/discouraged.rs @@ -0,0 +1,195 @@ +//! Extensions to the parsing API with niche applicability. + +use super::*; + +/// Extensions to the `ParseStream` API to support speculative parsing. +pub trait Speculative { + /// Advance this parse stream to the position of a forked parse stream. + /// + /// This is the opposite operation to [`ParseStream::fork`]. You can fork a + /// parse stream, perform some speculative parsing, then join the original + /// stream to the fork to "commit" the parsing from the fork to the main + /// stream. + /// + /// If you can avoid doing this, you should, as it limits the ability to + /// generate useful errors. That said, it is often the only way to parse + /// syntax of the form `A* B*` for arbitrary syntax `A` and `B`. The problem + /// is that when the fork fails to parse an `A`, it's impossible to tell + /// whether that was because of a syntax error and the user meant to provide + /// an `A`, or that the `A`s are finished and its time to start parsing + /// `B`s. Use with care. + /// + /// Also note that if `A` is a subset of `B`, `A* B*` can be parsed by + /// parsing `B*` and removing the leading members of `A` from the + /// repetition, bypassing the need to involve the downsides associated with + /// speculative parsing. + /// + /// [`ParseStream::fork`]: ParseBuffer::fork + /// + /// # Example + /// + /// There has been chatter about the possibility of making the colons in the + /// turbofish syntax like `path::to::` no longer required by accepting + /// `path::to` in expression position. Specifically, according to [RFC + /// 2544], [`PathSegment`] parsing should always try to consume a following + /// `<` token as the start of generic arguments, and reset to the `<` if + /// that fails (e.g. the token is acting as a less-than operator). + /// + /// This is the exact kind of parsing behavior which requires the "fork, + /// try, commit" behavior that [`ParseStream::fork`] discourages. With + /// `advance_to`, we can avoid having to parse the speculatively parsed + /// content a second time. + /// + /// This change in behavior can be implemented in syn by replacing just the + /// `Parse` implementation for `PathSegment`: + /// + /// ``` + /// # use syn::ext::IdentExt; + /// use syn::parse::discouraged::Speculative; + /// # use syn::parse::{Parse, ParseStream}; + /// # use syn::{Ident, PathArguments, Result, Token}; + /// + /// pub struct PathSegment { + /// pub ident: Ident, + /// pub arguments: PathArguments, + /// } + /// # + /// # impl From for PathSegment + /// # where + /// # T: Into, + /// # { + /// # fn from(ident: T) -> Self { + /// # PathSegment { + /// # ident: ident.into(), + /// # arguments: PathArguments::None, + /// # } + /// # } + /// # } + /// + /// impl Parse for PathSegment { + /// fn parse(input: ParseStream) -> Result { + /// if input.peek(Token![super]) + /// || input.peek(Token![self]) + /// || input.peek(Token![Self]) + /// || input.peek(Token![crate]) + /// || input.peek(Token![extern]) + /// { + /// let ident = input.call(Ident::parse_any)?; + /// return Ok(PathSegment::from(ident)); + /// } + /// + /// let ident = input.parse()?; + /// if input.peek(Token![::]) && input.peek3(Token![<]) { + /// return Ok(PathSegment { + /// ident, + /// arguments: PathArguments::AngleBracketed(input.parse()?), + /// }); + /// } + /// if input.peek(Token![<]) && !input.peek(Token![<=]) { + /// let fork = input.fork(); + /// if let Ok(arguments) = fork.parse() { + /// input.advance_to(&fork); + /// return Ok(PathSegment { + /// ident, + /// arguments: PathArguments::AngleBracketed(arguments), + /// }); + /// } + /// } + /// Ok(PathSegment::from(ident)) + /// } + /// } + /// + /// # syn::parse_str::("a").unwrap(); + /// ``` + /// + /// # Drawbacks + /// + /// The main drawback of this style of speculative parsing is in error + /// presentation. Even if the lookahead is the "correct" parse, the error + /// that is shown is that of the "fallback" parse. To use the same example + /// as the turbofish above, take the following unfinished "turbofish": + /// + /// ```text + /// let _ = f<&'a fn(), for<'a> serde::>(); + /// ``` + /// + /// If this is parsed as generic arguments, we can provide the error message + /// + /// ```text + /// error: expected identifier + /// --> src.rs:L:C + /// | + /// L | let _ = f<&'a fn(), for<'a> serde::>(); + /// | ^ + /// ``` + /// + /// but if parsed using the above speculative parsing, it falls back to + /// assuming that the `<` is a less-than when it fails to parse the generic + /// arguments, and tries to interpret the `&'a` as the start of a labelled + /// loop, resulting in the much less helpful error + /// + /// ```text + /// error: expected `:` + /// --> src.rs:L:C + /// | + /// L | let _ = f<&'a fn(), for<'a> serde::>(); + /// | ^^ + /// ``` + /// + /// This can be mitigated with various heuristics (two examples: show both + /// forks' parse errors, or show the one that consumed more tokens), but + /// when you can control the grammar, sticking to something that can be + /// parsed LL(3) and without the LL(*) speculative parsing this makes + /// possible, displaying reasonable errors becomes much more simple. + /// + /// [RFC 2544]: https://github.com/rust-lang/rfcs/pull/2544 + /// [`PathSegment`]: crate::PathSegment + /// + /// # Performance + /// + /// This method performs a cheap fixed amount of work that does not depend + /// on how far apart the two streams are positioned. + /// + /// # Panics + /// + /// The forked stream in the argument of `advance_to` must have been + /// obtained by forking `self`. Attempting to advance to any other stream + /// will cause a panic. + fn advance_to(&self, fork: &Self); +} + +impl<'a> Speculative for ParseBuffer<'a> { + fn advance_to(&self, fork: &Self) { + if !crate::buffer::same_scope(self.cursor(), fork.cursor()) { + panic!("Fork was not derived from the advancing parse stream"); + } + + let (self_unexp, self_sp) = inner_unexpected(self); + let (fork_unexp, fork_sp) = inner_unexpected(fork); + if !Rc::ptr_eq(&self_unexp, &fork_unexp) { + match (fork_sp, self_sp) { + // Unexpected set on the fork, but not on `self`, copy it over. + (Some(span), None) => { + self_unexp.set(Unexpected::Some(span)); + } + // Unexpected unset. Use chain to propagate errors from fork. + (None, None) => { + fork_unexp.set(Unexpected::Chain(self_unexp)); + + // Ensure toplevel 'unexpected' tokens from the fork don't + // bubble up the chain by replacing the root `unexpected` + // pointer, only 'unexpected' tokens from existing group + // parsers should bubble. + fork.unexpected + .set(Some(Rc::new(Cell::new(Unexpected::None)))); + } + // Unexpected has been set on `self`. No changes needed. + (_, Some(_)) => {} + } + } + + // See comment on `cell` in the struct definition. + self.cell + .set(unsafe { mem::transmute::>(fork.cursor()) }) + } +} diff --git a/syn/src/error.rs b/syn/src/error.rs new file mode 100644 index 0000000..146d652 --- /dev/null +++ b/syn/src/error.rs @@ -0,0 +1,357 @@ +use std; +use std::fmt::{self, Debug, Display}; +use std::iter::FromIterator; +use std::slice; +use std::vec; + +use proc_macro2::{ + Delimiter, Group, Ident, LexError, Literal, Punct, Spacing, Span, TokenStream, TokenTree, +}; +#[cfg(feature = "printing")] +use quote::ToTokens; + +#[cfg(feature = "parsing")] +use crate::buffer::Cursor; +use crate::thread::ThreadBound; + +/// The result of a Syn parser. +pub type Result = std::result::Result; + +/// Error returned when a Syn parser cannot parse the input tokens. +/// +/// # Error reporting in proc macros +/// +/// The correct way to report errors back to the compiler from a procedural +/// macro is by emitting an appropriately spanned invocation of +/// [`compile_error!`] in the generated code. This produces a better diagnostic +/// message than simply panicking the macro. +/// +/// [`compile_error!`]: https://doc.rust-lang.org/std/macro.compile_error.html +/// +/// When parsing macro input, the [`parse_macro_input!`] macro handles the +/// conversion to `compile_error!` automatically. +/// +/// ``` +/// extern crate proc_macro; +/// +/// use proc_macro::TokenStream; +/// use syn::{parse_macro_input, AttributeArgs, ItemFn}; +/// +/// # const IGNORE: &str = stringify! { +/// #[proc_macro_attribute] +/// # }; +/// pub fn my_attr(args: TokenStream, input: TokenStream) -> TokenStream { +/// let args = parse_macro_input!(args as AttributeArgs); +/// let input = parse_macro_input!(input as ItemFn); +/// +/// /* ... */ +/// # TokenStream::new() +/// } +/// ``` +/// +/// For errors that arise later than the initial parsing stage, the +/// [`.to_compile_error()`] method can be used to perform an explicit conversion +/// to `compile_error!`. +/// +/// [`.to_compile_error()`]: Error::to_compile_error +/// +/// ``` +/// # extern crate proc_macro; +/// # +/// # use proc_macro::TokenStream; +/// # use syn::{parse_macro_input, DeriveInput}; +/// # +/// # const IGNORE: &str = stringify! { +/// #[proc_macro_derive(MyDerive)] +/// # }; +/// pub fn my_derive(input: TokenStream) -> TokenStream { +/// let input = parse_macro_input!(input as DeriveInput); +/// +/// // fn(DeriveInput) -> syn::Result +/// expand::my_derive(input) +/// .unwrap_or_else(|err| err.to_compile_error()) +/// .into() +/// } +/// # +/// # mod expand { +/// # use proc_macro2::TokenStream; +/// # use syn::{DeriveInput, Result}; +/// # +/// # pub fn my_derive(input: DeriveInput) -> Result { +/// # unimplemented!() +/// # } +/// # } +/// ``` +#[derive(Clone)] +pub struct Error { + messages: Vec, +} + +struct ErrorMessage { + // Span is implemented as an index into a thread-local interner to keep the + // size small. It is not safe to access from a different thread. We want + // errors to be Send and Sync to play nicely with the Failure crate, so pin + // the span we're given to its original thread and assume it is + // Span::call_site if accessed from any other thread. + start_span: ThreadBound, + end_span: ThreadBound, + message: String, +} + +#[cfg(test)] +struct _Test +where + Error: Send + Sync; + +impl Error { + /// Usually the [`ParseStream::error`] method will be used instead, which + /// automatically uses the correct span from the current position of the + /// parse stream. + /// + /// Use `Error::new` when the error needs to be triggered on some span other + /// than where the parse stream is currently positioned. + /// + /// [`ParseStream::error`]: crate::parse::ParseBuffer::error + /// + /// # Example + /// + /// ``` + /// use syn::{Error, Ident, LitStr, Result, Token}; + /// use syn::parse::ParseStream; + /// + /// // Parses input that looks like `name = "string"` where the key must be + /// // the identifier `name` and the value may be any string literal. + /// // Returns the string literal. + /// fn parse_name(input: ParseStream) -> Result { + /// let name_token: Ident = input.parse()?; + /// if name_token != "name" { + /// // Trigger an error not on the current position of the stream, + /// // but on the position of the unexpected identifier. + /// return Err(Error::new(name_token.span(), "expected `name`")); + /// } + /// input.parse::()?; + /// let s: LitStr = input.parse()?; + /// Ok(s) + /// } + /// ``` + pub fn new(span: Span, message: T) -> Self { + Error { + messages: vec![ErrorMessage { + start_span: ThreadBound::new(span), + end_span: ThreadBound::new(span), + message: message.to_string(), + }], + } + } + + /// Creates an error with the specified message spanning the given syntax + /// tree node. + /// + /// Unlike the `Error::new` constructor, this constructor takes an argument + /// `tokens` which is a syntax tree node. This allows the resulting `Error` + /// to attempt to span all tokens inside of `tokens`. While you would + /// typically be able to use the `Spanned` trait with the above `Error::new` + /// constructor, implementation limitations today mean that + /// `Error::new_spanned` may provide a higher-quality error message on + /// stable Rust. + /// + /// When in doubt it's recommended to stick to `Error::new` (or + /// `ParseStream::error`)! + #[cfg(feature = "printing")] + pub fn new_spanned(tokens: T, message: U) -> Self { + let mut iter = tokens.into_token_stream().into_iter(); + let start = iter.next().map_or_else(Span::call_site, |t| t.span()); + let end = iter.last().map_or(start, |t| t.span()); + Error { + messages: vec![ErrorMessage { + start_span: ThreadBound::new(start), + end_span: ThreadBound::new(end), + message: message.to_string(), + }], + } + } + + /// The source location of the error. + /// + /// Spans are not thread-safe so this function returns `Span::call_site()` + /// if called from a different thread than the one on which the `Error` was + /// originally created. + pub fn span(&self) -> Span { + let start = match self.messages[0].start_span.get() { + Some(span) => *span, + None => return Span::call_site(), + }; + let end = match self.messages[0].end_span.get() { + Some(span) => *span, + None => return Span::call_site(), + }; + start.join(end).unwrap_or(start) + } + + /// Render the error as an invocation of [`compile_error!`]. + /// + /// The [`parse_macro_input!`] macro provides a convenient way to invoke + /// this method correctly in a procedural macro. + /// + /// [`compile_error!`]: https://doc.rust-lang.org/std/macro.compile_error.html + pub fn to_compile_error(&self) -> TokenStream { + self.messages + .iter() + .map(ErrorMessage::to_compile_error) + .collect() + } + + /// Add another error message to self such that when `to_compile_error()` is + /// called, both errors will be emitted together. + pub fn combine(&mut self, another: Error) { + self.messages.extend(another.messages) + } +} + +impl ErrorMessage { + fn to_compile_error(&self) -> TokenStream { + let start = self + .start_span + .get() + .cloned() + .unwrap_or_else(Span::call_site); + let end = self.end_span.get().cloned().unwrap_or_else(Span::call_site); + + // compile_error!($message) + TokenStream::from_iter(vec![ + TokenTree::Ident(Ident::new("compile_error", start)), + TokenTree::Punct({ + let mut punct = Punct::new('!', Spacing::Alone); + punct.set_span(start); + punct + }), + TokenTree::Group({ + let mut group = Group::new(Delimiter::Brace, { + TokenStream::from_iter(vec![TokenTree::Literal({ + let mut string = Literal::string(&self.message); + string.set_span(end); + string + })]) + }); + group.set_span(end); + group + }), + ]) + } +} + +#[cfg(feature = "parsing")] +pub fn new_at(scope: Span, cursor: Cursor, message: T) -> Error { + if cursor.eof() { + Error::new(scope, format!("unexpected end of input, {}", message)) + } else { + let span = crate::buffer::open_span_of_group(cursor); + Error::new(span, message) + } +} + +impl Debug for Error { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + if self.messages.len() == 1 { + formatter + .debug_tuple("Error") + .field(&self.messages[0]) + .finish() + } else { + formatter + .debug_tuple("Error") + .field(&self.messages) + .finish() + } + } +} + +impl Debug for ErrorMessage { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + Debug::fmt(&self.message, formatter) + } +} + +impl Display for Error { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str(&self.messages[0].message) + } +} + +impl Clone for ErrorMessage { + fn clone(&self) -> Self { + let start = self + .start_span + .get() + .cloned() + .unwrap_or_else(Span::call_site); + let end = self.end_span.get().cloned().unwrap_or_else(Span::call_site); + ErrorMessage { + start_span: ThreadBound::new(start), + end_span: ThreadBound::new(end), + message: self.message.clone(), + } + } +} + +impl std::error::Error for Error { + fn description(&self) -> &str { + "parse error" + } +} + +impl From for Error { + fn from(err: LexError) -> Self { + Error::new(Span::call_site(), format!("{:?}", err)) + } +} + +impl IntoIterator for Error { + type Item = Error; + type IntoIter = IntoIter; + + fn into_iter(self) -> Self::IntoIter { + IntoIter { + messages: self.messages.into_iter(), + } + } +} + +pub struct IntoIter { + messages: vec::IntoIter, +} + +impl Iterator for IntoIter { + type Item = Error; + + fn next(&mut self) -> Option { + Some(Error { + messages: vec![self.messages.next()?], + }) + } +} + +impl<'a> IntoIterator for &'a Error { + type Item = Error; + type IntoIter = Iter<'a>; + + fn into_iter(self) -> Self::IntoIter { + Iter { + messages: self.messages.iter(), + } + } +} + +pub struct Iter<'a> { + messages: slice::Iter<'a, ErrorMessage>, +} + +impl<'a> Iterator for Iter<'a> { + type Item = Error; + + fn next(&mut self) -> Option { + Some(Error { + messages: vec![self.messages.next()?.clone()], + }) + } +} diff --git a/syn/src/export.rs b/syn/src/export.rs new file mode 100644 index 0000000..37dc467 --- /dev/null +++ b/syn/src/export.rs @@ -0,0 +1,35 @@ +pub use std::clone::Clone; +pub use std::cmp::{Eq, PartialEq}; +pub use std::convert::From; +pub use std::default::Default; +pub use std::fmt::{self, Debug, Formatter}; +pub use std::hash::{Hash, Hasher}; +pub use std::marker::Copy; +pub use std::option::Option::{None, Some}; +pub use std::result::Result::{Err, Ok}; + +#[cfg(feature = "printing")] +pub extern crate quote; + +pub use proc_macro2::{Span, TokenStream as TokenStream2}; + +pub use crate::span::IntoSpans; + +#[cfg(all( + not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "wasi"))), + feature = "proc-macro" +))] +pub use proc_macro::TokenStream; + +#[cfg(feature = "printing")] +pub use quote::{ToTokens, TokenStreamExt}; + +#[allow(non_camel_case_types)] +pub type bool = help::Bool; +#[allow(non_camel_case_types)] +pub type str = help::Str; + +mod help { + pub type Bool = bool; + pub type Str = str; +} diff --git a/syn/src/expr.rs b/syn/src/expr.rs new file mode 100644 index 0000000..603dc32 --- /dev/null +++ b/syn/src/expr.rs @@ -0,0 +1,3236 @@ +use super::*; +use crate::punctuated::Punctuated; +#[cfg(feature = "extra-traits")] +use crate::tt::TokenStreamHelper; +use proc_macro2::{Span, TokenStream}; +#[cfg(feature = "printing")] +use quote::IdentFragment; +#[cfg(feature = "printing")] +use std::fmt::{self, Display}; +use std::hash::{Hash, Hasher}; +#[cfg(all(feature = "parsing", feature = "full"))] +use std::mem; + +ast_enum_of_structs! { + /// A Rust expression. + /// + /// *This type is available if Syn is built with the `"derive"` or `"full"` + /// feature.* + /// + /// # Syntax tree enums + /// + /// This type is a syntax tree enum. In Syn this and other syntax tree enums + /// are designed to be traversed using the following rebinding idiom. + /// + /// ``` + /// # use syn::Expr; + /// # + /// # fn example(expr: Expr) { + /// # const IGNORE: &str = stringify! { + /// let expr: Expr = /* ... */; + /// # }; + /// match expr { + /// Expr::MethodCall(expr) => { + /// /* ... */ + /// } + /// Expr::Cast(expr) => { + /// /* ... */ + /// } + /// Expr::If(expr) => { + /// /* ... */ + /// } + /// + /// /* ... */ + /// # _ => {} + /// # } + /// # } + /// ``` + /// + /// We begin with a variable `expr` of type `Expr` that has no fields + /// (because it is an enum), and by matching on it and rebinding a variable + /// with the same name `expr` we effectively imbue our variable with all of + /// the data fields provided by the variant that it turned out to be. So for + /// example above if we ended up in the `MethodCall` case then we get to use + /// `expr.receiver`, `expr.args` etc; if we ended up in the `If` case we get + /// to use `expr.cond`, `expr.then_branch`, `expr.else_branch`. + /// + /// This approach avoids repeating the variant names twice on every line. + /// + /// ``` + /// # use syn::{Expr, ExprMethodCall}; + /// # + /// # fn example(expr: Expr) { + /// // Repetitive; recommend not doing this. + /// match expr { + /// Expr::MethodCall(ExprMethodCall { method, args, .. }) => { + /// # } + /// # _ => {} + /// # } + /// # } + /// ``` + /// + /// In general, the name to which a syntax tree enum variant is bound should + /// be a suitable name for the complete syntax tree enum type. + /// + /// ``` + /// # use syn::{Expr, ExprField}; + /// # + /// # fn example(discriminant: ExprField) { + /// // Binding is called `base` which is the name I would use if I were + /// // assigning `*discriminant.base` without an `if let`. + /// if let Expr::Tuple(base) = *discriminant.base { + /// # } + /// # } + /// ``` + /// + /// A sign that you may not be choosing the right variable names is if you + /// see names getting repeated in your code, like accessing + /// `receiver.receiver` or `pat.pat` or `cond.cond`. + pub enum Expr #manual_extra_traits { + /// A slice literal expression: `[a, b, c, d]`. + Array(ExprArray), + + /// An assignment expression: `a = compute()`. + Assign(ExprAssign), + + /// A compound assignment expression: `counter += 1`. + AssignOp(ExprAssignOp), + + /// An async block: `async { ... }`. + Async(ExprAsync), + + /// An await expression: `fut.await`. + Await(ExprAwait), + + /// A binary operation: `a + b`, `a * b`. + Binary(ExprBinary), + + /// A blocked scope: `{ ... }`. + Block(ExprBlock), + + /// A box expression: `box f`. + Box(ExprBox), + + /// A `break`, with an optional label to break and an optional + /// expression. + Break(ExprBreak), + + /// A function call expression: `invoke(a, b)`. + Call(ExprCall), + + /// A cast expression: `foo as f64`. + Cast(ExprCast), + + /// A closure expression: `|a, b| a + b`. + Closure(ExprClosure), + + /// A `continue`, with an optional label. + Continue(ExprContinue), + + /// Access of a named struct field (`obj.k`) or unnamed tuple struct + /// field (`obj.0`). + Field(ExprField), + + /// A for loop: `for pat in expr { ... }`. + ForLoop(ExprForLoop), + + /// An expression contained within invisible delimiters. + /// + /// This variant is important for faithfully representing the precedence + /// of expressions and is related to `None`-delimited spans in a + /// `TokenStream`. + Group(ExprGroup), + + /// An `if` expression with an optional `else` block: `if expr { ... } + /// else { ... }`. + /// + /// The `else` branch expression may only be an `If` or `Block` + /// expression, not any of the other types of expression. + If(ExprIf), + + /// A square bracketed indexing expression: `vector[2]`. + Index(ExprIndex), + + /// A `let` guard: `let Some(x) = opt`. + Let(ExprLet), + + /// A literal in place of an expression: `1`, `"foo"`. + Lit(ExprLit), + + /// Conditionless loop: `loop { ... }`. + Loop(ExprLoop), + + /// A macro invocation expression: `format!("{}", q)`. + Macro(ExprMacro), + + /// A `match` expression: `match n { Some(n) => {}, None => {} }`. + Match(ExprMatch), + + /// A method call expression: `x.foo::(a, b)`. + MethodCall(ExprMethodCall), + + /// A parenthesized expression: `(a + b)`. + Paren(ExprParen), + + /// A path like `std::mem::replace` possibly containing generic + /// parameters and a qualified self-type. + /// + /// A plain identifier like `x` is a path of length 1. + Path(ExprPath), + + /// A range expression: `1..2`, `1..`, `..2`, `1..=2`, `..=2`. + Range(ExprRange), + + /// A referencing operation: `&a` or `&mut a`. + Reference(ExprReference), + + /// An array literal constructed from one repeated element: `[0u8; N]`. + Repeat(ExprRepeat), + + /// A `return`, with an optional value to be returned. + Return(ExprReturn), + + /// A struct literal expression: `Point { x: 1, y: 1 }`. + /// + /// The `rest` provides the value of the remaining fields as in `S { a: + /// 1, b: 1, ..rest }`. + Struct(ExprStruct), + + /// A try-expression: `expr?`. + Try(ExprTry), + + /// A try block: `try { ... }`. + TryBlock(ExprTryBlock), + + /// A tuple expression: `(a, b, c, d)`. + Tuple(ExprTuple), + + /// A type ascription expression: `foo: f64`. + Type(ExprType), + + /// A unary operation: `!x`, `*x`. + Unary(ExprUnary), + + /// An unsafe block: `unsafe { ... }`. + Unsafe(ExprUnsafe), + + /// Tokens in expression position not interpreted by Syn. + Verbatim(TokenStream), + + /// A while loop: `while expr { ... }`. + While(ExprWhile), + + /// A yield expression: `yield expr`. + Yield(ExprYield), + + #[doc(hidden)] + __Nonexhaustive, + } +} + +ast_struct! { + /// A slice literal expression: `[a, b, c, d]`. + /// + /// *This type is available if Syn is built with the `"full"` feature.* + pub struct ExprArray #full { + pub attrs: Vec, + pub bracket_token: token::Bracket, + pub elems: Punctuated, + } +} + +ast_struct! { + /// An assignment expression: `a = compute()`. + /// + /// *This type is available if Syn is built with the `"full"` feature.* + pub struct ExprAssign #full { + pub attrs: Vec, + pub left: Box, + pub eq_token: Token![=], + pub right: Box, + } +} + +ast_struct! { + /// A compound assignment expression: `counter += 1`. + /// + /// *This type is available if Syn is built with the `"full"` feature.* + pub struct ExprAssignOp #full { + pub attrs: Vec, + pub left: Box, + pub op: BinOp, + pub right: Box, + } +} + +ast_struct! { + /// An async block: `async { ... }`. + /// + /// *This type is available if Syn is built with the `"full"` feature.* + pub struct ExprAsync #full { + pub attrs: Vec, + pub async_token: Token![async], + pub capture: Option, + pub block: Block, + } +} + +ast_struct! { + /// An await expression: `fut.await`. + /// + /// *This type is available if Syn is built with the `"full"` feature.* + pub struct ExprAwait #full { + pub attrs: Vec, + pub base: Box, + pub dot_token: Token![.], + pub await_token: token::Await, + } +} + +ast_struct! { + /// A binary operation: `a + b`, `a * b`. + /// + /// *This type is available if Syn is built with the `"derive"` or + /// `"full"` feature.* + pub struct ExprBinary { + pub attrs: Vec, + pub left: Box, + pub op: BinOp, + pub right: Box, + } +} + +ast_struct! { + /// A blocked scope: `{ ... }`. + /// + /// *This type is available if Syn is built with the `"full"` feature.* + pub struct ExprBlock #full { + pub attrs: Vec, + pub label: Option