1use clap::{Arg, ArgAction, ArgMatches, Command};
2
3fn get_app() -> Command {
4    Command::new("myprog")
5        .arg(
6            Arg::new("GLOBAL_ARG")
7                .long("global-arg")
8                .help("Specifies something needed by the subcommands")
9                .global(true)
10                .action(ArgAction::Set)
11                .default_value("default_value"),
12        )
13        .arg(
14            Arg::new("GLOBAL_FLAG")
15                .long("global-flag")
16                .help("Specifies something needed by the subcommands")
17                .global(true)
18                .action(ArgAction::Count),
19        )
20        .subcommand(Command::new("outer").subcommand(Command::new("inner")))
21}
22
23fn get_matches(cmd: Command, argv: &'static str) -> ArgMatches {
24    cmd.try_get_matches_from(argv.split(' ').collect::<Vec<_>>())
25        .unwrap()
26}
27
28fn get_outer_matches(m: &ArgMatches) -> &ArgMatches {
29    m.subcommand_matches("outer")
30        .expect("could not access outer subcommand")
31}
32
33fn get_inner_matches(m: &ArgMatches) -> &ArgMatches {
34    get_outer_matches(m)
35        .subcommand_matches("inner")
36        .expect("could not access inner subcommand")
37}
38
39fn top_can_access_arg<T: Into<Option<&'static str>>>(m: &ArgMatches, val: T) -> bool {
40    m.get_one::<String>("GLOBAL_ARG").map(|v| v.as_str()) == val.into()
41}
42
43fn inner_can_access_arg<T: Into<Option<&'static str>>>(m: &ArgMatches, val: T) -> bool {
44    get_inner_matches(m)
45        .get_one::<String>("GLOBAL_ARG")
46        .map(|v| v.as_str())
47        == val.into()
48}
49
50fn outer_can_access_arg<T: Into<Option<&'static str>>>(m: &ArgMatches, val: T) -> bool {
51    get_outer_matches(m)
52        .get_one::<String>("GLOBAL_ARG")
53        .map(|v| v.as_str())
54        == val.into()
55}
56
57fn top_can_access_flag(m: &ArgMatches, present: bool, occurrences: u8) -> bool {
58    (m.contains_id("GLOBAL_FLAG") == present)
59        && (m.get_one::<u8>("GLOBAL_FLAG").copied() == Some(occurrences))
60}
61
62fn inner_can_access_flag(m: &ArgMatches, present: bool, occurrences: u8) -> bool {
63    let m = get_inner_matches(m);
64    (m.contains_id("GLOBAL_FLAG") == present)
65        && (m.get_one::<u8>("GLOBAL_FLAG").copied() == Some(occurrences))
66}
67
68fn outer_can_access_flag(m: &ArgMatches, present: bool, occurrences: u8) -> bool {
69    let m = get_outer_matches(m);
70    (m.contains_id("GLOBAL_FLAG") == present)
71        && (m.get_one::<u8>("GLOBAL_FLAG").copied() == Some(occurrences))
72}
73
74#[test]
75fn global_arg_used_top_level() {
76    let m = get_matches(get_app(), "myprog --global-arg=some_value outer inner");
77
78    assert!(top_can_access_arg(&m, "some_value"));
79    assert!(inner_can_access_arg(&m, "some_value"));
80    assert!(outer_can_access_arg(&m, "some_value"));
81}
82
83#[test]
84fn global_arg_used_outer() {
85    let m = get_matches(get_app(), "myprog outer --global-arg=some_value inner");
86
87    assert!(top_can_access_arg(&m, "some_value"));
88    assert!(inner_can_access_arg(&m, "some_value"));
89    assert!(outer_can_access_arg(&m, "some_value"));
90}
91
92#[test]
93fn global_arg_used_inner() {
94    let m = get_matches(get_app(), "myprog outer inner --global-arg=some_value");
95
96    assert!(top_can_access_arg(&m, "some_value"));
97    assert!(inner_can_access_arg(&m, "some_value"));
98    assert!(outer_can_access_arg(&m, "some_value"));
99}
100
101#[test]
102fn global_arg_default_value() {
103    let m = get_matches(get_app(), "myprog outer inner");
104
105    assert!(top_can_access_arg(&m, "default_value"));
106    assert!(inner_can_access_arg(&m, "default_value"));
107    assert!(outer_can_access_arg(&m, "default_value"));
108}
109
110#[test]
111fn global_flag_used_top_level() {
112    let m = get_matches(get_app(), "myprog --global-flag outer inner");
113
114    assert!(top_can_access_flag(&m, true, 1));
115    assert!(inner_can_access_flag(&m, true, 1));
116    assert!(outer_can_access_flag(&m, true, 1));
117}
118
119#[test]
120fn global_flag_used_outer() {
121    let m = get_matches(get_app(), "myprog outer --global-flag inner");
122
123    assert!(top_can_access_flag(&m, true, 1));
124    assert!(inner_can_access_flag(&m, true, 1));
125    assert!(outer_can_access_flag(&m, true, 1));
126}
127
128#[test]
129fn global_flag_used_inner() {
130    let m = get_matches(get_app(), "myprog outer inner --global-flag");
131
132    assert!(top_can_access_flag(&m, true, 1));
133    assert!(inner_can_access_flag(&m, true, 1));
134    assert!(outer_can_access_flag(&m, true, 1));
135}
136
137#[test]
138fn global_flag_2x_used_top_level() {
139    let m = get_matches(get_app(), "myprog --global-flag --global-flag outer inner");
140
141    assert!(top_can_access_flag(&m, true, 2));
142    assert!(inner_can_access_flag(&m, true, 2));
143    assert!(outer_can_access_flag(&m, true, 2));
144}
145
146#[test]
147fn global_flag_2x_used_inner() {
148    let m = get_matches(get_app(), "myprog outer inner --global-flag --global-flag");
149
150    assert!(top_can_access_flag(&m, true, 2));
151    assert!(inner_can_access_flag(&m, true, 2));
152    assert!(outer_can_access_flag(&m, true, 2));
153}
154