clap icon indicating copy to clipboard operation
clap copied to clipboard

global opts that take a value cannot be mixed with subcommand

Open ehuss opened this issue 4 years ago • 5 comments

Rust Version

rustc 1.38.0 (625451e37 2019-09-23)

Affected Version of clap

2.33.0 and 3.0.0-beta.1 (b8819130de876c4abbd451c34df89161a98d23ef)

Expected Behavior Summary

When a global option that takes a value is used both before and after a subcommand, I would expect that values_of would return all the values.

Actual Behavior Summary

It only returns the values from the subcommand. Flags before the subcommand are ignored.

Note: For global flags that do not take a value, occurrences_of correctly returns the count of all occurrences both before and after.

Sample Code or Link to Sample Code

use clap::{App, Arg};

fn main() {
    let matches = App::new("myapp")
        .arg(
            Arg::with_name("global-opt")
                .short('A')
                .multiple(true)
                .number_of_values(1)
                .global(true),
        )
        .subcommand(App::new("test"))
        .get_matches_from(vec!["myapp", "-Aone", "test", "-Atwo"]);

    let gs = matches.values_of("global-opt").unwrap().collect::<Vec<_>>();
    // I would expect this to be ["one", "two"]
    assert_eq!(gs, vec!["two"]);
}

Debug output

Debug Output

DEBUG:clap:App::_do_parse;
DEBUG:clap:App::_build;
DEBUG:clap:App::_derive_display_order:myapp
DEBUG:clap:App::_derive_display_order:test
DEBUG:clap:App::_create_help_and_version;
DEBUG:clap:App::_create_help_and_version: Building --help
DEBUG:clap:App::_create_help_and_version: Building --version
DEBUG:clap:App::_create_help_and_version: Building help
DEBUG:clap:App::_arg_debug_asserts:global-opt
DEBUG:clap:App::_arg_debug_asserts:help
DEBUG:clap:App::_arg_debug_asserts:version
DEBUG:clap:App::_app_debug_asserts;
DEBUG:clap:Parser::get_matches_with;
DEBUG:clap:Parser::_build;
DEBUG:clap:Parser::_verify_positionals;
DEBUG:clap:Parser::get_matches_with: Begin parsing '"-Aone"' ([45, 65, 111, 110, 101])
DEBUG:clap:Parser::is_new_arg:"-Aone":NotFound
DEBUG:clap:Parser::is_new_arg: arg_allows_tac=false
DEBUG:clap:Parser::is_new_arg: - found
DEBUG:clap:Parser::is_new_arg: starts_new_arg=true
DEBUG:clap:Parser::possible_subcommand: arg="-Aone"
DEBUG:clap:Parser::get_matches_with: possible_sc=false, sc=None
DEBUG:clap:Parser::parse_short_arg: full_arg="-Aone"
DEBUG:clap:Parser::parse_short_arg:iter:A
DEBUG:clap:Parser::parse_short_arg:iter:A: Found valid opt or flag
DEBUG:clap:Parser::parse_short_arg:iter:A: p[0]=[], p[1]=[111, 110, 101]
DEBUG:clap:Parser::parse_short_arg:iter:A: val=[111, 110, 101] (bytes), val="one" (ascii)
DEBUG:clap:Parser::parse_opt; opt=global-opt, val=Some("one")
DEBUG:clap:Parser::parse_opt; opt.settings=ArgFlags(MULTIPLE_OCC | TAKES_VAL | DELIM_NOT_SET | MULTIPLE_VALS)
DEBUG:clap:Parser::parse_opt; Checking for val...Found - "one", len: 3
DEBUG:clap:Parser::parse_opt: "one" contains '='...false
DEBUG:clap:Parser::add_val_to_arg; arg=global-opt, val="one"
DEBUG:clap:Parser::add_val_to_arg; trailing_vals=false, DontDelimTrailingVals=false
DEBUG:clap:Parser::add_single_val_to_arg;
DEBUG:clap:Parser::add_single_val_to_arg: adding val..."one"
DEBUG:clap:Parser::groups_for_arg: name=4551335407501243259
DEBUG:clap:ArgMatcher::needs_more_vals: o=global-opt
DEBUG:clap:ArgMatcher::needs_more_vals: num_vals...1
DEBUG:clap:ArgMatcher::inc_occurrence_of: arg=4551335407501243259
DEBUG:clap:Parser::groups_for_arg: name=4551335407501243259
DEBUG:clap:ArgMatcher::needs_more_vals: o=global-opt
DEBUG:clap:ArgMatcher::needs_more_vals: num_vals...1
DEBUG:clap:Parser::parse_opt: More arg vals not required...
DEBUG:clap:Parser:get_matches_with: After parse_short_arg ValuesDone
DEBUG:clap:Parser::get_matches_with: Begin parsing '"test"' ([116, 101, 115, 116])
DEBUG:clap:Parser::is_new_arg:"test":ValuesDone
DEBUG:clap:Parser::possible_subcommand: arg="test"
DEBUG:clap:Parser::get_matches_with: possible_sc=true, sc=Some("test")
DEBUG:clap:Parser::parse_subcommand;
DEBUG:clap:Usage::get_required_usage_from: incls=[], matcher=false, incl_last=true
DEBUG:clap:Usage::get_required_usage_from: ret_val=[]
DEBUG:clap:App::_propagate:myapp
DEBUG:clap:App::_build;
DEBUG:clap:App::_derive_display_order:test
DEBUG:clap:App::_create_help_and_version;
DEBUG:clap:App::_create_help_and_version: Building --help
DEBUG:clap:App::_create_help_and_version: Building --version
DEBUG:clap:App::_arg_debug_asserts:global-opt
DEBUG:clap:App::_arg_debug_asserts:help
DEBUG:clap:App::_arg_debug_asserts:version
DEBUG:clap:App::_app_debug_asserts;
DEBUG:clap:Parser::parse_subcommand: About to parse sc=test
DEBUG:clap:Parser::get_matches_with;
DEBUG:clap:Parser::_build;
DEBUG:clap:Parser::_verify_positionals;
DEBUG:clap:Parser::get_matches_with: Begin parsing '"-Atwo"' ([45, 65, 116, 119, 111])
DEBUG:clap:Parser::is_new_arg:"-Atwo":NotFound
DEBUG:clap:Parser::is_new_arg: arg_allows_tac=false
DEBUG:clap:Parser::is_new_arg: - found
DEBUG:clap:Parser::is_new_arg: starts_new_arg=true
DEBUG:clap:Parser::possible_subcommand: arg="-Atwo"
DEBUG:clap:Parser::get_matches_with: possible_sc=false, sc=None
DEBUG:clap:Parser::parse_short_arg: full_arg="-Atwo"
DEBUG:clap:Parser::parse_short_arg:iter:A
DEBUG:clap:Parser::parse_short_arg:iter:A: Found valid opt or flag
DEBUG:clap:Parser::parse_short_arg:iter:A: p[0]=[], p[1]=[116, 119, 111]
DEBUG:clap:Parser::parse_short_arg:iter:A: val=[116, 119, 111] (bytes), val="two" (ascii)
DEBUG:clap:Parser::parse_opt; opt=global-opt, val=Some("two")
DEBUG:clap:Parser::parse_opt; opt.settings=ArgFlags(MULTIPLE_OCC | TAKES_VAL | DELIM_NOT_SET | MULTIPLE_VALS)
DEBUG:clap:Parser::parse_opt; Checking for val...Found - "two", len: 3
DEBUG:clap:Parser::parse_opt: "two" contains '='...false
DEBUG:clap:Parser::add_val_to_arg; arg=global-opt, val="two"
DEBUG:clap:Parser::add_val_to_arg; trailing_vals=false, DontDelimTrailingVals=false
DEBUG:clap:Parser::add_single_val_to_arg;
DEBUG:clap:Parser::add_single_val_to_arg: adding val..."two"
DEBUG:clap:Parser::groups_for_arg: name=4551335407501243259
DEBUG:clap:ArgMatcher::needs_more_vals: o=global-opt
DEBUG:clap:ArgMatcher::needs_more_vals: num_vals...1
DEBUG:clap:ArgMatcher::inc_occurrence_of: arg=4551335407501243259
DEBUG:clap:Parser::groups_for_arg: name=4551335407501243259
DEBUG:clap:ArgMatcher::needs_more_vals: o=global-opt
DEBUG:clap:ArgMatcher::needs_more_vals: num_vals...1
DEBUG:clap:Parser::parse_opt: More arg vals not required...
DEBUG:clap:Parser:get_matches_with: After parse_short_arg ValuesDone
DEBUG:clap:Parser::remove_overrides;
DEBUG:clap:Parser::remove_overrides:iter:4551335407501243259;
DEBUG:clap:Validator::validate;
DEBUG:clap:Parser::add_defaults;
DEBUG:clap:Parser::add_defaults:iter:global-opt: doesn't have conditional defaults
DEBUG:clap:Parser::add_defaults:iter:global-opt: doesn't have default vals
DEBUG:clap:Validator::validate_conflicts;
DEBUG:clap:Validator::gather_conflicts;
DEBUG:clap:Validator::gather_conflicts:iter:4551335407501243259;
DEBUG:clap:Parser::groups_for_arg: name=4551335407501243259
DEBUG:clap:Validator::validate_required: required=ChildGraph([]);
DEBUG:clap:Validator::gather_requirements;
DEBUG:clap:Validator::gather_requirements:iter:4551335407501243259;
DEBUG:clap:Validator::validate_required_unless;
DEBUG:clap:Validator::validate_matched_args;
DEBUG:clap:Validator::validate_matched_args:iter:4551335407501243259: vals=[
    "two",
]
DEBUG:clap:Validator::validate_arg_num_vals;
DEBUG:clap:Validator::validate_arg_num_vals: num_vals set...1
DEBUG:clap:Validator::validate_arg_values: arg="global-opt"
DEBUG:clap:Validator::validate_arg_requires:global-opt;
DEBUG:clap:Validator::validate_arg_num_occurs: global-opt=1;
DEBUG:clap:Parser::remove_overrides;
DEBUG:clap:Parser::remove_overrides:iter:4551335407501243259;
DEBUG:clap:Validator::validate;
DEBUG:clap:Parser::add_defaults;
DEBUG:clap:Parser::add_defaults:iter:global-opt: doesn't have conditional defaults
DEBUG:clap:Parser::add_defaults:iter:global-opt: doesn't have default vals
DEBUG:clap:Validator::validate_conflicts;
DEBUG:clap:Validator::gather_conflicts;
DEBUG:clap:Validator::gather_conflicts:iter:4551335407501243259;
DEBUG:clap:Parser::groups_for_arg: name=4551335407501243259
DEBUG:clap:Validator::validate_required: required=ChildGraph([]);
DEBUG:clap:Validator::gather_requirements;
DEBUG:clap:Validator::gather_requirements:iter:4551335407501243259;
DEBUG:clap:Validator::validate_required_unless;
DEBUG:clap:Validator::validate_matched_args;
DEBUG:clap:Validator::validate_matched_args:iter:4551335407501243259: vals=[
    "one",
]
DEBUG:clap:Validator::validate_arg_num_vals;
DEBUG:clap:Validator::validate_arg_num_vals: num_vals set...1
DEBUG:clap:Validator::validate_arg_values: arg="global-opt"
DEBUG:clap:Validator::validate_arg_requires:global-opt;
DEBUG:clap:Validator::validate_arg_num_occurs: global-opt=1;
DEBUG:clap:ArgMatcher::get_global_values: global_arg_vec=[4551335407501243259]

ehuss avatar Oct 08 '19 18:10 ehuss

not sure if this is the same issue or a different issue but I'm seeing something similar where a global arg isn't propogating to the ArgMatches.subcommand() matches properly

use clap::{App, Arg};

fn main() {
    let matches = App::new("myapp")
        .subcommand(
            App::new("test")
                .arg(
                    Arg::with_name("global-opt")
                        .number_of_values(1)
                        .short('A')
                        .global(true),
                )
                .subcommand(App::new("testt")),
        )
        .get_matches_from(vec!["myapp", "test", "testt", "-Aone"]);

    let gs = matches.value_of("global-opt");
    assert_eq!(gs, Some("one"));
}

The assert_eq here fails.

yaahc avatar Feb 04 '20 23:02 yaahc

It is the same issue. Thanks for the reports guys. Unfortunately we won't get to fix this until 3.0 is out.

pksunkara avatar Feb 05 '20 07:02 pksunkara

Hey, is this change still in the v3 roadmap?

kamilogorek avatar Apr 21 '21 13:04 kamilogorek

If you look at the milestone. It's not scheduled for v3 release. But afterwards.

pksunkara avatar Apr 21 '21 14:04 pksunkara

FWIW bpaf supports this: command is just a regular parser that can be composed.

This example is slightly confusing because if was made according to strict requirements,: https://github.com/pacak/bpaf/blob/master/examples/confusing.rs -

This is more or less what I was using in production for some time until I noticed that I don't really use logger everywhere. https://github.com/pacak/hackerman/blob/834e8d72415f89a952d0215176de22077428a92d/src/opts.rs

pacak avatar Sep 04 '22 07:09 pacak

#3938 is a related issue for how to deal with global args appearing before and after a subcommand

epage avatar Nov 08 '22 05:11 epage