Skip to content

Commit 0f8764c

Browse files
committed
Auto merge of #709 - Nemo157:derive_order_pr, r=kbknapp
Derive display order after propagation Don't attempt to change the display order of flags/options until any app settings have been propagated down from a parent `App` in case `DeriveDisplayOrder` and/or `UnifiedHelpMessage` are propagated. Adds tests that try to stress the combinations of `DeriveDisplayOrder` and `UnifiedHelpMessage` along with propagating them to subcommands and explicitly setting a display order.
2 parents 0199446 + 9cb6fac commit 0f8764c

File tree

5 files changed

+277
-17
lines changed

5 files changed

+277
-17
lines changed

src/app/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1318,6 +1318,7 @@ impl<'a, 'b> App<'a, 'b> {
13181318
// before parsing incase we run into a subcommand
13191319
self.p.propogate_globals();
13201320
self.p.propogate_settings();
1321+
self.p.derive_display_order();
13211322

13221323
let mut matcher = ArgMatcher::new();
13231324

src/app/parser.rs

+20-17
Original file line numberDiff line numberDiff line change
@@ -179,23 +179,11 @@ impl<'a, 'b> Parser<'a, 'b>
179179
self.positionals.insert(i, pb);
180180
} else if a.is_set(ArgSettings::TakesValue) {
181181
let mut ob = OptBuilder::from_arg(a, &mut self.required);
182-
if self.settings.is_set(AppSettings::DeriveDisplayOrder) && a.disp_ord == 999 {
183-
ob.disp_ord = if self.settings.is_set(AppSettings::UnifiedHelpMessage) {
184-
self.flags.len() + self.opts.len()
185-
} else {
186-
self.opts.len()
187-
};
188-
}
182+
ob.unified_ord = self.flags.len() + self.opts.len();
189183
self.opts.push(ob);
190184
} else {
191185
let mut fb = FlagBuilder::from(a);
192-
if self.settings.is_set(AppSettings::DeriveDisplayOrder) && a.disp_ord == 999 {
193-
fb.disp_ord = if self.settings.is_set(AppSettings::UnifiedHelpMessage) {
194-
self.flags.len() + self.opts.len()
195-
} else {
196-
self.flags.len()
197-
};
198-
}
186+
fb.unified_ord = self.flags.len() + self.opts.len();
199187
self.flags.push(fb);
200188
}
201189
if a.is_set(ArgSettings::Global) {
@@ -242,9 +230,6 @@ impl<'a, 'b> Parser<'a, 'b>
242230
sdebugln!("No");
243231
}
244232

245-
if self.settings.is_set(AppSettings::DeriveDisplayOrder) {
246-
subcmd.p.meta.disp_ord = self.subcommands.len();
247-
}
248233
self.subcommands.push(subcmd);
249234
}
250235

@@ -275,6 +260,24 @@ impl<'a, 'b> Parser<'a, 'b>
275260
}
276261
}
277262

263+
pub fn derive_display_order(&mut self) {
264+
if self.settings.is_set(AppSettings::DeriveDisplayOrder) {
265+
let unified = self.settings.is_set(AppSettings::UnifiedHelpMessage);
266+
for (i, o) in self.opts.iter_mut().enumerate().filter(|&(_, ref o)| o.disp_ord == 999) {
267+
o.disp_ord = if unified { o.unified_ord } else { i };
268+
}
269+
for (i, f) in self.flags.iter_mut().enumerate().filter(|&(_, ref f)| f.disp_ord == 999) {
270+
f.disp_ord = if unified { f.unified_ord } else { i };
271+
}
272+
for (i, sc) in &mut self.subcommands.iter_mut().enumerate().filter(|&(_, ref sc)| sc.p.meta.disp_ord == 999) {
273+
sc.p.meta.disp_ord = i;
274+
}
275+
}
276+
for sc in &mut self.subcommands {
277+
sc.p.derive_display_order();
278+
}
279+
}
280+
278281
pub fn required(&self) -> Iter<&str> {
279282
self.required.iter()
280283
}

src/args/arg_builder/flag.rs

+4
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ pub struct FlagBuilder<'n, 'e> {
2525
pub overrides: Option<Vec<&'e str>>,
2626
pub settings: ArgFlags,
2727
pub disp_ord: usize,
28+
pub unified_ord: usize,
2829
}
2930

3031
impl<'n, 'e> Default for FlagBuilder<'n, 'e> {
@@ -40,6 +41,7 @@ impl<'n, 'e> Default for FlagBuilder<'n, 'e> {
4041
overrides: None,
4142
settings: ArgFlags::new(),
4243
disp_ord: 999,
44+
unified_ord: 999,
4345
}
4446
}
4547
}
@@ -77,6 +79,7 @@ impl<'a, 'b, 'z> From<&'z Arg<'a, 'b>> for FlagBuilder<'a, 'b> {
7779
requires: a.requires.clone(),
7880
settings: a.settings,
7981
disp_ord: a.disp_ord,
82+
..Default::default()
8083
}
8184
}
8285
}
@@ -106,6 +109,7 @@ impl<'n, 'e> Clone for FlagBuilder<'n, 'e> {
106109
requires: self.requires.clone(),
107110
settings: self.settings,
108111
disp_ord: self.disp_ord,
112+
unified_ord: self.unified_ord,
109113
}
110114
}
111115
}

src/args/arg_builder/option.rs

+3
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ pub struct OptBuilder<'n, 'e> {
3131
pub val_delim: Option<char>,
3232
pub default_val: Option<&'n str>,
3333
pub disp_ord: usize,
34+
pub unified_ord: usize,
3435
pub r_unless: Option<Vec<&'e str>>,
3536
}
3637

@@ -55,6 +56,7 @@ impl<'n, 'e> Default for OptBuilder<'n, 'e> {
5556
val_delim: Some(','),
5657
default_val: None,
5758
disp_ord: 999,
59+
unified_ord: 999,
5860
r_unless: None,
5961
}
6062
}
@@ -177,6 +179,7 @@ impl<'n, 'e> Clone for OptBuilder<'n, 'e> {
177179
requires: self.requires.clone(),
178180
settings: self.settings,
179181
disp_ord: self.disp_ord,
182+
unified_ord: self.unified_ord,
180183
num_vals: self.num_vals,
181184
min_vals: self.min_vals,
182185
max_vals: self.max_vals,

tests/derive_order.rs

+249
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
extern crate clap;
2+
3+
use std::str;
4+
5+
use clap::{App, Arg, SubCommand, AppSettings};
6+
7+
fn condense_whitespace(s: &str) -> String {
8+
s.split_whitespace().collect::<Vec<_>>().join(" ")
9+
}
10+
11+
fn normalize(s: &str) -> Vec<String> {
12+
s.lines().map(|l| l.trim()).filter(|l| !l.is_empty()).map(condense_whitespace).collect()
13+
}
14+
15+
fn get_help(app: App, args: &[&'static str]) -> Vec<String> {
16+
let res = app.get_matches_from_safe(args.iter().chain(["--help"].iter()));
17+
normalize(&*res.unwrap_err().message)
18+
}
19+
20+
#[test]
21+
fn no_derive_order() {
22+
let app = App::new("test")
23+
.args(&[
24+
Arg::with_name("flag_b").long("flag_b").help("first flag"),
25+
Arg::with_name("option_b").long("option_b").takes_value(true).help("first option"),
26+
Arg::with_name("flag_a").long("flag_a").help("second flag"),
27+
Arg::with_name("option_a").long("option_a").takes_value(true).help("second option"),
28+
]);
29+
30+
assert_eq!(get_help(app, &["test"]), normalize("
31+
test
32+
33+
USAGE:
34+
test [FLAGS] [OPTIONS]
35+
36+
FLAGS:
37+
--flag_a second flag
38+
--flag_b first flag
39+
-h, --help Prints help information
40+
-V, --version Prints version information
41+
42+
OPTIONS:
43+
--option_a <option_a> second option
44+
--option_b <option_b> first option
45+
"));
46+
}
47+
48+
#[test]
49+
fn derive_order() {
50+
let app = App::new("test")
51+
.setting(AppSettings::DeriveDisplayOrder)
52+
.args(&[
53+
Arg::with_name("flag_b").long("flag_b").help("first flag"),
54+
Arg::with_name("option_b").long("option_b").takes_value(true).help("first option"),
55+
Arg::with_name("flag_a").long("flag_a").help("second flag"),
56+
Arg::with_name("option_a").long("option_a").takes_value(true).help("second option"),
57+
]);
58+
59+
assert_eq!(get_help(app, &["test"]), normalize("
60+
test
61+
62+
USAGE:
63+
test [FLAGS] [OPTIONS]
64+
65+
FLAGS:
66+
--flag_b first flag
67+
--flag_a second flag
68+
-h, --help Prints help information
69+
-V, --version Prints version information
70+
71+
OPTIONS:
72+
--option_b <option_b> first option
73+
--option_a <option_a> second option
74+
"));
75+
}
76+
77+
#[test]
78+
fn unified_help() {
79+
let app = App::new("test")
80+
.setting(AppSettings::UnifiedHelpMessage)
81+
.args(&[
82+
Arg::with_name("flag_b").long("flag_b").help("first flag"),
83+
Arg::with_name("option_b").long("option_b").takes_value(true).help("first option"),
84+
Arg::with_name("flag_a").long("flag_a").help("second flag"),
85+
Arg::with_name("option_a").long("option_a").takes_value(true).help("second option"),
86+
]);
87+
88+
assert_eq!(get_help(app, &["test"]), normalize("
89+
test
90+
91+
USAGE:
92+
test [OPTIONS]
93+
94+
OPTIONS:
95+
--flag_a second flag
96+
--flag_b first flag
97+
-h, --help Prints help information
98+
--option_a <option_a> second option
99+
--option_b <option_b> first option
100+
-V, --version Prints version information
101+
"));
102+
}
103+
104+
#[test]
105+
fn unified_help_and_derive_order() {
106+
let app = App::new("test")
107+
.setting(AppSettings::DeriveDisplayOrder)
108+
.setting(AppSettings::UnifiedHelpMessage)
109+
.args(&[
110+
Arg::with_name("flag_b").long("flag_b").help("first flag"),
111+
Arg::with_name("option_b").long("option_b").takes_value(true).help("first option"),
112+
Arg::with_name("flag_a").long("flag_a").help("second flag"),
113+
Arg::with_name("option_a").long("option_a").takes_value(true).help("second option"),
114+
]);
115+
116+
assert_eq!(get_help(app, &["test"]), normalize("
117+
test
118+
119+
USAGE:
120+
test [OPTIONS]
121+
122+
OPTIONS:
123+
--flag_b first flag
124+
--option_b <option_b> first option
125+
--flag_a second flag
126+
--option_a <option_a> second option
127+
-h, --help Prints help information
128+
-V, --version Prints version information
129+
"));
130+
}
131+
132+
#[test]
133+
fn derive_order_subcommand_propagate() {
134+
let app = App::new("test")
135+
.global_setting(AppSettings::DeriveDisplayOrder)
136+
.subcommand(SubCommand::with_name("sub")
137+
.args(&[
138+
Arg::with_name("flag_b").long("flag_b").help("first flag"),
139+
Arg::with_name("option_b").long("option_b").takes_value(true).help("first option"),
140+
Arg::with_name("flag_a").long("flag_a").help("second flag"),
141+
Arg::with_name("option_a").long("option_a").takes_value(true).help("second option"),
142+
]));
143+
144+
assert_eq!(get_help(app, &["test", "sub"]), normalize("
145+
test-sub
146+
147+
USAGE:
148+
test sub [FLAGS] [OPTIONS]
149+
150+
FLAGS:
151+
--flag_b first flag
152+
--flag_a second flag
153+
-h, --help Prints help information
154+
-V, --version Prints version information
155+
156+
OPTIONS:
157+
--option_b <option_b> first option
158+
--option_a <option_a> second option
159+
"));
160+
}
161+
162+
#[test]
163+
fn unified_help_subcommand_propagate() {
164+
let app = App::new("test")
165+
.global_setting(AppSettings::UnifiedHelpMessage)
166+
.subcommand(SubCommand::with_name("sub")
167+
.args(&[
168+
Arg::with_name("flag_b").long("flag_b").help("first flag"),
169+
Arg::with_name("option_b").long("option_b").takes_value(true).help("first option"),
170+
Arg::with_name("flag_a").long("flag_a").help("second flag"),
171+
Arg::with_name("option_a").long("option_a").takes_value(true).help("second option"),
172+
]));
173+
174+
assert_eq!(get_help(app, &["test", "sub"]), normalize("
175+
test-sub
176+
177+
USAGE:
178+
test sub [OPTIONS]
179+
180+
OPTIONS:
181+
--flag_a second flag
182+
--flag_b first flag
183+
-h, --help Prints help information
184+
--option_a <option_a> second option
185+
--option_b <option_b> first option
186+
-V, --version Prints version information
187+
188+
"));
189+
}
190+
191+
#[test]
192+
fn unified_help_and_derive_order_subcommand_propagate() {
193+
let app = App::new("test")
194+
.global_setting(AppSettings::DeriveDisplayOrder)
195+
.global_setting(AppSettings::UnifiedHelpMessage)
196+
.subcommand(SubCommand::with_name("sub")
197+
.args(&[
198+
Arg::with_name("flag_b").long("flag_b").help("first flag"),
199+
Arg::with_name("option_b").long("option_b").takes_value(true).help("first option"),
200+
Arg::with_name("flag_a").long("flag_a").help("second flag"),
201+
Arg::with_name("option_a").long("option_a").takes_value(true).help("second option"),
202+
]));
203+
204+
assert_eq!(get_help(app, &["test", "sub"]), normalize("
205+
test-sub
206+
207+
USAGE:
208+
test sub [OPTIONS]
209+
210+
OPTIONS:
211+
--flag_b first flag
212+
--option_b <option_b> first option
213+
--flag_a second flag
214+
--option_a <option_a> second option
215+
-h, --help Prints help information
216+
-V, --version Prints version information
217+
218+
"));
219+
}
220+
221+
#[test]
222+
fn unified_help_and_derive_order_subcommand_propagate_with_explicit_display_order() {
223+
let app = App::new("test")
224+
.global_setting(AppSettings::DeriveDisplayOrder)
225+
.global_setting(AppSettings::UnifiedHelpMessage)
226+
.subcommand(SubCommand::with_name("sub")
227+
.args(&[
228+
Arg::with_name("flag_b").long("flag_b").help("first flag"),
229+
Arg::with_name("option_b").long("option_b").takes_value(true).help("first option"),
230+
Arg::with_name("flag_a").long("flag_a").help("second flag").display_order(0),
231+
Arg::with_name("option_a").long("option_a").takes_value(true).help("second option"),
232+
]));
233+
234+
assert_eq!(get_help(app, &["test", "sub"]), normalize("
235+
test-sub
236+
237+
USAGE:
238+
test sub [OPTIONS]
239+
240+
OPTIONS:
241+
--flag_a second flag
242+
--flag_b first flag
243+
--option_b <option_b> first option
244+
--option_a <option_a> second option
245+
-h, --help Prints help information
246+
-V, --version Prints version information
247+
248+
"));
249+
}

0 commit comments

Comments
 (0)