Skip to content

Commit 26e54f6

Browse files
cli: Fix clap3 failures/warnings/regressions
* The API changes are a mixed bag but look good overall * Had to swap the failure exit codes (as clap now `exit(1)` instead of 2) Keeping the old codes is possible, but doesn't seem worth the added complexity See clap-rs/clap#3426 * Stumbled upon one shell completion regression, so pining an old clap_complete version for now See clap-rs/clap#3697 * Subcommand completion no longer erroneously include `-V` and `--version` * Help and diagnostic outputs seem a bit nicer
1 parent 3d55f61 commit 26e54f6

File tree

5 files changed

+107
-108
lines changed

5 files changed

+107
-108
lines changed

Cargo.lock

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+3-2
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@ exclude = ["/benches/*.log"]
1919
ansi_term = "0.12.1"
2020
anyhow = "1.0.32"
2121
atty = "0.2.14"
22-
clap = {version = "3.1.15", features = ["cargo"]}
23-
clap_complete = "3.1.3"
22+
clap = { version = "3.1.15", default_features = false, features = ["cargo", "color", "suggestions"] }
23+
# Waiting on https://github.com/clap-rs/clap/issues/3697 to relax version
24+
clap_complete = "=3.1.0"
2425
crossbeam-channel = "0.5.0"
2526
env_logger = {version = "0.9.0", default_features = false, features = ["termcolor", "atty"]}
2627
log = "0.4.11"

src/cli.rs

+74-75
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,73 @@
1-
use clap::{crate_version, App, AppSettings, Arg, SubCommand};
1+
use clap::{crate_version, AppSettings, Arg, Command};
22

33
/// Generate cli argument parser without the `complete` subcommand.
4-
pub fn build_cli_nocomplete() -> App<'static> {
4+
pub fn build_cli_nocomplete() -> Command<'static> {
55
let arg_limit =
6-
Arg::with_name("limit").long("limit")
7-
.takes_value(true)
8-
.default_value("10")
9-
.help("Use the last N merge times to predict next merge time.");
6+
Arg::new("limit").long("limit")
7+
.takes_value(true)
8+
.default_value("10")
9+
.help("Use the last N merge times to predict next merge time.");
1010
let arg_pkg =
11-
Arg::with_name("package").takes_value(true).help("Show only packages matching <package>.");
12-
let arg_exact =
13-
Arg::with_name("exact").short('e')
14-
.long("exact")
15-
.help("Match package with a string instead of a regex.")
16-
.long_help(
17-
"Match package with a string instead of a regex. \
11+
Arg::new("package").takes_value(true).help("Show only packages matching <package>.");
12+
let arg_exact = Arg::new("exact").short('e')
13+
.long("exact")
14+
.help("Match package with a string instead of a regex.")
15+
.long_help(
16+
"Match package with a string instead of a regex. \
1817
Regex is case-insensitive and matches on category/name (see \
1918
https://docs.rs/regex/*/regex/#syntax). String is case-sentitive and \
2019
matches on whole name, or whole category/name if it contains a /.",
20+
);
21+
let arg_show_l =
22+
Arg::new("show").short('s')
23+
.long("show")
24+
.value_name("h,m,u,s,a")
25+
.validator(|s| find_invalid("hmusa", &s))
26+
.default_value("m")
27+
.help("Show (h)eaders, (m)erges, (u)nmerges, (s)yncs, and/or (a)ll.")
28+
.long_help(
29+
"Show individual (m)erges, (u)nmerges, portage tree (s)yncs, \
30+
or (a)ll of these (any letters combination).",
2131
);
22-
let arg_show_l = Arg::with_name("show")
23-
.short('s')
24-
.long("show")
25-
.value_name("h,m,u,s,a")
26-
.validator(|s| find_invalid("hmusa", &s))
27-
.default_value("m")
28-
.help("Show (h)eaders, (m)erges, (u)nmerges, (s)yncs, and/or (a)ll.")
29-
.long_help("Show individual (m)erges, (u)nmerges, portage tree (s)yncs, \
30-
or (a)ll of these (any letters combination).");
31-
let arg_show_s = Arg::with_name("show")
32-
.short('s')
33-
.long("show")
34-
.value_name("h,p,t,s,a")
35-
.validator(|s| find_invalid("hptsa", &s))
36-
.default_value("p")
37-
.help("Show (h)eaders, (p)ackages, (t)otals, (s)yncs, and/or (a)ll.")
38-
.long_help("Show per-(p)ackage merges/unmerges, (t)otal merges/unmerges, \
39-
portage tree (s)yncs, or (a)ll of these (any letters combination).");
40-
let arg_group =
41-
Arg::with_name("group").short('g')
42-
.long("groupby")
43-
.value_name("y,m,w,d")
44-
.possible_values(&["y", "m", "w", "d"])
45-
.hide_possible_values(true)
46-
.help("Group by (y)ear, (m)onth, (w)eek, or (d)ay.")
47-
.long_help(
48-
"Group by (y)ear, (m)onth, (w)eek, or (d)ay.\n\
32+
let arg_show_s =
33+
Arg::new("show").short('s')
34+
.long("show")
35+
.value_name("h,p,t,s,a")
36+
.validator(|s| find_invalid("hptsa", &s))
37+
.default_value("p")
38+
.help("Show (h)eaders, (p)ackages, (t)otals, (s)yncs, and/or (a)ll.")
39+
.long_help(
40+
"Show per-(p)ackage merges/unmerges, (t)otal merges/unmerges, \
41+
portage tree (s)yncs, or (a)ll of these (any letters combination).",
42+
);
43+
let arg_group = Arg::new("group").short('g')
44+
.long("groupby")
45+
.value_name("y,m,w,d")
46+
.possible_values(&["y", "m", "w", "d"])
47+
.hide_possible_values(true)
48+
.help("Group by (y)ear, (m)onth, (w)eek, or (d)ay.")
49+
.long_help(
50+
"Group by (y)ear, (m)onth, (w)eek, or (d)ay.\n\
4951
The grouping key is displayed in the first column. \
5052
Weeks start on monday and are formated as 'year-weeknumber'.",
51-
);
52-
App::new("emlop")
53+
);
54+
Command::new("emlop")
5355
.version(crate_version!())
54-
.global_setting(AppSettings::ColoredHelp)
5556
.global_setting(AppSettings::DeriveDisplayOrder)
56-
.global_setting(AppSettings::UnifiedHelpMessage)
57-
.setting(AppSettings::DisableHelpSubcommand)
58-
.setting(AppSettings::InferSubcommands)
59-
.setting(AppSettings::SubcommandRequiredElseHelp)
60-
// FIXME clap3 what's the equivalent ? .setting(AppSettings::VersionlessSubcommands)
57+
.disable_help_subcommand(true)
58+
.infer_subcommands(true)
59+
.arg_required_else_help(true)
6160
.about("A fast, accurate, ergonomic EMerge LOg Parser.\n\
6261
https://github.com/vincentdephily/emlop")
6362
.after_help("Subcommands can be abbreviated down to a single letter.\n\
6463
Subcommands have their own -h / --help.\n\
65-
Exit code is 0 if sucessful, 1 in case of errors (bad argument...), \
66-
2 if search found nothing.")
67-
.help_message("Show short (-h) or detailed (--help) help.")
68-
.arg(Arg::with_name("utc")
64+
Exit code is 0 if sucessful, 1 if search found nothing, 2 in case of argument errors.")
65+
.mut_arg("help", |a| a.help("Show short (-h) or detailed (--help) help."))
66+
.arg(Arg::new("utc")
6967
.long("utc")
7068
.global(true)
7169
.help("Parse/display dates in UTC instead of local time"))
72-
.arg(Arg::with_name("from")
70+
.arg(Arg::new("from")
7371
.value_name("date")
7472
.short('f')
7573
.long("from")
@@ -79,7 +77,7 @@ pub fn build_cli_nocomplete() -> App<'static> {
7977
.long_help("Only parse log entries after <date>.\n\
8078
Accepts formats like '2018-03-04', '2018-03-04 12:34:56', \
8179
'2018-03-04T12:34', '1 year, 2 months', '10d', and unix timestamps."))
82-
.arg(Arg::with_name("to")
80+
.arg(Arg::new("to")
8381
.value_name("date")
8482
.short('t')
8583
.long("to")
@@ -89,7 +87,7 @@ pub fn build_cli_nocomplete() -> App<'static> {
8987
.long_help("Only parse log entries before <date>.\n\
9088
Accepts formats like '2018-03-04', '2018-03-04 12:34:56', \
9189
'2018-03-04T12:34', '1 year, 2 months', '10d', and unix timestamps."))
92-
.arg(Arg::with_name("duration")
90+
.arg(Arg::new("duration")
9391
.value_name("format")
9492
.long("duration")
9593
.global(true)
@@ -103,7 +101,7 @@ pub fn build_cli_nocomplete() -> App<'static> {
103101
s: 630
104102
human: 10 minutes, 30 seconds
105103
"))
106-
.arg(Arg::with_name("date")
104+
.arg(Arg::new("date")
107105
.value_name("format")
108106
.long("date")
109107
.global(true)
@@ -120,24 +118,24 @@ pub fn build_cli_nocomplete() -> App<'static> {
120118
compact: 20220131085946
121119
unix: 1643619586
122120
"))
123-
.arg(Arg::with_name("logfile")
121+
.arg(Arg::new("logfile")
124122
.value_name("file")
125123
.long("logfile")
126124
.short('F')
127125
.global(true)
128126
.takes_value(true)
129127
.default_value("/var/log/emerge.log")
130128
.help("Location of emerge log file."))
131-
.arg(Arg::with_name("verbose")
129+
.arg(Arg::new("verbose")
132130
.short('v')
133131
.global(true)
134-
.multiple(true)
132+
.multiple_occurrences(true)
135133
.help("Increase verbosity (can be given multiple times).")
136134
.long_help("Increase verbosity (defaults to errors only)
137135
-v: show warnings
138136
-vv: show info
139137
-vvv: show debug"))
140-
.arg(Arg::with_name("color")
138+
.arg(Arg::new("color")
141139
.long("color").alias("colour")
142140
.global(true)
143141
.takes_value(true)
@@ -146,23 +144,21 @@ pub fn build_cli_nocomplete() -> App<'static> {
146144
.default_value("auto")
147145
.value_name("when")
148146
.help("Enable color (auto/always/never/y/n)."))
149-
.subcommand(SubCommand::with_name("log")
147+
.subcommand(Command::new("log")
150148
.about("Show log of sucessful merges, unmerges and syncs.")
151149
.long_about("Show log of sucessful merges, unmerges and syncs.\n\
152150
* (Un)merges: date, duration, package name-version.\n\
153151
* Syncs: date, duration.")
154-
.help_message("Show short (-h) or detailed (--help) help.")
155152
.arg(&arg_show_l)
156153
.arg(&arg_exact)
157154
.arg(&arg_pkg))
158-
.subcommand(SubCommand::with_name("predict")
155+
.subcommand(Command::new("predict")
159156
.about("Predict merge time for current or pretended merges.")
160157
.long_about("Predict merge time for current or pretended merges.\n\
161158
* If input is a terminal, predict time for the current merge (if any).\n\
162159
* If input is a pipe (for example by running `emerge -rOp|emlop p`), predict time for those merges.")
163-
.help_message("Show short (-h) or detailed (--help) help.")
164160
.arg(&arg_limit))
165-
.subcommand(SubCommand::with_name("stats")
161+
.subcommand(Command::new("stats")
166162
.about("Show statistics about sucessful merges, unmerges and syncs.")
167163
.long_about("Show statistics about sucessful (un)merges (overall or per \
168164
package) and syncs.\n\
@@ -171,7 +167,6 @@ pub fn build_cli_nocomplete() -> App<'static> {
171167
* Total: merge count, total merge time, average merge time, \
172168
unmerge count, total unmerge time, average unmerge time.\n\
173169
* Sync: sync count, total sync time, predicted sync time.")
174-
.help_message("Show short (-h) or detailed (--help) help.")
175170
.arg(&arg_show_s)
176171
.arg(&arg_group)
177172
.arg(&arg_exact)
@@ -180,19 +175,23 @@ pub fn build_cli_nocomplete() -> App<'static> {
180175
}
181176

182177
/// Generate cli argument parser.
183-
pub fn build_cli() -> App<'static> {
178+
pub fn build_cli() -> Command<'static> {
184179
let c = build_cli_nocomplete();
185-
c.subcommand(SubCommand::with_name("complete")
186-
.about("Generate shell completion script.")
187-
.long_about("Write shell completion script to stdout.\n\n\
180+
c.subcommand(
181+
Command::new("complete").about("Generate shell completion script.")
182+
.long_about(
183+
"Write shell completion script to stdout.\n\n\
188184
You should redirect the output to a file that will be sourced by your shell.\n\
189185
For example: `emlop complete bash > ~/.bash_completion.d/emlop`.\n\
190186
To apply the changes, either restart you shell or `source` the generated file.
191-
")
192-
.arg(Arg::with_name("shell")
193-
.help("Target shell")
194-
.required(true)
195-
.possible_values(&["bash","zsh","fish"])))
187+
",
188+
)
189+
.arg(
190+
Arg::new("shell").help("Target shell")
191+
.required(true)
192+
.possible_values(&["bash", "zsh", "fish"]),
193+
),
194+
)
196195
}
197196

198197
/// Clap validation helper that checks that all chars are valid.

src/commands.rs

+22-22
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use std::{collections::{BTreeMap, HashMap},
77
///
88
/// We store the start times in a hashmap to compute/print the duration when we reach a stop event.
99
pub fn cmd_list(args: &ArgMatches, subargs: &ArgMatches, st: &Styles) -> Result<bool, Error> {
10-
let show = value_t!(subargs, "show", Show).unwrap();
10+
let show = subargs.value_of_t("show").unwrap();
1111
let hist = new_hist(args.value_of("logfile").unwrap().into(),
1212
value_opt(args, "from", parse_date, st.date_offset),
1313
value_opt(args, "to", parse_date, st.date_offset),
@@ -101,7 +101,7 @@ impl Times {
101101
/// First loop is like cmd_list but we store the merge time for each ebuild instead of printing it.
102102
/// Then we compute the stats per ebuild, and print that.
103103
pub fn cmd_stats(args: &ArgMatches, subargs: &ArgMatches, st: &Styles) -> Result<bool, Error> {
104-
let show = value_t!(subargs, "show", Show).unwrap();
104+
let show = subargs.value_of_t("show").unwrap();
105105
let timespan_opt = value_opt(subargs, "group", parse_timespan, ());
106106
let hist = new_hist(args.value_of("logfile").unwrap().into(),
107107
value_opt(args, "from", parse_date, st.date_offset),
@@ -552,7 +552,7 @@ mod tests {
552552
fn predict_tty() {
553553
emlop().args(&["p", "-F", "test/emerge.10000.log"])
554554
.assert()
555-
.code(2)
555+
.code(1)
556556
.stdout("No pretended merge found\n");
557557
}
558558

@@ -562,7 +562,7 @@ mod tests {
562562
fn predict_emerge_p() {
563563
let _cache_cargo_build = emlop();
564564
let t = vec![// Check garbage input
565-
("blah blah\n", format!("No pretended merge found\n"), 2),
565+
("blah blah\n", format!("No pretended merge found\n"), 1),
566566
// Check all-unknowns
567567
("[ebuild R ~] dev-lang/unknown-1.42\n",
568568
format!("dev-lang/unknown-1.42 ? \n\
@@ -624,7 +624,7 @@ mod tests {
624624
0),
625625
(&["-F","test/emerge.10000.log","s","--from","2018-02-03T23:11:47","--to","2018-02-04","notfound","-sa"],
626626
"",
627-
2),
627+
1),
628628
];
629629
for (a, o, e) in t {
630630
emlop().args(a).assert().code(e).stdout(o);
@@ -835,34 +835,34 @@ mod tests {
835835
#[test]
836836
fn exit_status() {
837837
// 0: no problem
838-
// 1: user or program error
839-
// 2: command ran properly but didn't find anything
838+
// 1: command ran properly but didn't find anything
839+
// 2: user or program error
840840
let t: Vec<(&[&str], i32)> =
841841
vec![// Help, version, badarg (clap)
842842
(&["-h"], 0),
843843
(&["-V"], 0),
844844
(&["l", "-h"], 0),
845-
(&[], 1),
846-
(&["s", "--foo"], 1),
847-
(&["badcmd"], 1),
845+
(&[], 2),
846+
(&["s", "--foo"], 2),
847+
(&["badcmd"], 2),
848848
// Bad arguments (emlop)
849-
(&["l", "--logfile", "notfound"], 1),
850-
(&["s", "--logfile", "notfound"], 1),
851-
(&["p", "--logfile", "notfound"], 1),
852-
(&["l", "bad regex [a-z"], 1),
853-
(&["s", "bad regex [a-z"], 1),
854-
(&["p", "bad regex [a-z"], 1),
849+
(&["l", "--logfile", "notfound"], 2),
850+
(&["s", "--logfile", "notfound"], 2),
851+
(&["p", "--logfile", "notfound"], 2),
852+
(&["l", "bad regex [a-z"], 2),
853+
(&["s", "bad regex [a-z"], 2),
854+
(&["p", "bad regex [a-z"], 2),
855855
// Normal behaviour
856-
(&["-F", "test/emerge.10000.log", "p"], 2),
856+
(&["-F", "test/emerge.10000.log", "p"], 1),
857857
(&["-F", "test/emerge.10000.log", "l"], 0),
858-
(&["-F", "test/emerge.10000.log", "l", "-s"], 0),
858+
(&["-F", "test/emerge.10000.log", "l", "-sm"], 0),
859859
(&["-F", "test/emerge.10000.log", "l", "-e", "icu"], 0),
860-
(&["-F", "test/emerge.10000.log", "l", "-e", "unknown"], 2),
861-
(&["-F", "test/emerge.10000.log", "l", "--from", "2018-09-28"], 2),
862-
(&["-F", "test/emerge.10000.log", "l", "-s", "--from", "2018-09-28"], 2),
860+
(&["-F", "test/emerge.10000.log", "l", "-e", "unknown"], 1),
861+
(&["-F", "test/emerge.10000.log", "l", "--from", "2018-09-28"], 1),
862+
(&["-F", "test/emerge.10000.log", "l", "-sm", "--from", "2018-09-28"], 1),
863863
(&["-F", "test/emerge.10000.log", "s"], 0),
864864
(&["-F", "test/emerge.10000.log", "s", "-e", "icu"], 0),
865-
(&["-F", "test/emerge.10000.log", "s", "-e", "unknown"], 2),];
865+
(&["-F", "test/emerge.10000.log", "s", "-e", "unknown"], 1),];
866866
for (a, e) in t {
867867
emlop().args(a).assert().code(e);
868868
}

0 commit comments

Comments
 (0)