Skip to content

Commit f43b7c6

Browse files
committed
imp(YAML Errors): vastly improves error messages when using YAML
When errors are made while developing, the panic error messages have been improved instead of relying on the default panic message which is extremely unhelpful. Closes #574
1 parent 5520bb0 commit f43b7c6

File tree

7 files changed

+153
-122
lines changed

7 files changed

+153
-122
lines changed

examples/17_yaml.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: yml_app
2-
version: 1.0
2+
version: "1.0"
33
about: an example using a .yml file to build a CLI
44
author: Kevin K. <[email protected]>
55

@@ -69,7 +69,7 @@ subcommands:
6969
# Rust code later
7070
- subcmd:
7171
about: demos subcommands from yaml
72-
version: 0.1
72+
version: "0.1"
7373
author: Kevin K. <[email protected]>
7474
# Subcommand args are exactly like App args
7575
args:

src/app/mod.rs

+67-51
Original file line numberDiff line numberDiff line change
@@ -1345,82 +1345,98 @@ impl<'a> From<&'a Yaml> for App<'a, 'a> {
13451345
} else {
13461346
yaml
13471347
};
1348-
if let Some(v) = yaml["version"].as_str() {
1349-
a = a.version(v);
1350-
}
1351-
if let Some(v) = yaml["author"].as_str() {
1352-
a = a.author(v);
1353-
}
1354-
if let Some(v) = yaml["bin_name"].as_str() {
1355-
a = a.bin_name(v);
1356-
}
1357-
if let Some(v) = yaml["about"].as_str() {
1358-
a = a.about(v);
1359-
}
1360-
if let Some(v) = yaml["before_help"].as_str() {
1361-
a = a.before_help(v);
1362-
}
1363-
if let Some(v) = yaml["template"].as_str() {
1364-
a = a.template(v);
1365-
}
1366-
if let Some(v) = yaml["after_help"].as_str() {
1367-
a = a.after_help(v);
1348+
1349+
macro_rules! yaml_str {
1350+
($a:ident, $y:ident, $i:ident) => {
1351+
if let Some(v) = $y[stringify!($i)].as_str() {
1352+
$a = $a.$i(v);
1353+
} else if $y[stringify!($i)] != Yaml::BadValue {
1354+
panic!("Failed to convert YAML value {:?} to a string", $y[stringify!($i)]);
1355+
}
1356+
};
13681357
}
1358+
1359+
yaml_str!(a, yaml, version);
1360+
yaml_str!(a, yaml, bin_name);
1361+
yaml_str!(a, yaml, about);
1362+
yaml_str!(a, yaml, before_help);
1363+
yaml_str!(a, yaml, after_help);
1364+
yaml_str!(a, yaml, template);
1365+
yaml_str!(a, yaml, usage);
1366+
yaml_str!(a, yaml, help);
1367+
yaml_str!(a, yaml, help_short);
1368+
yaml_str!(a, yaml, version_short);
1369+
yaml_str!(a, yaml, alias);
1370+
yaml_str!(a, yaml, visible_alias);
1371+
13691372
if let Some(v) = yaml["display_order"].as_i64() {
13701373
a = a.display_order(v as usize);
1371-
}
1372-
if let Some(v) = yaml["usage"].as_str() {
1373-
a = a.usage(v);
1374-
}
1375-
if let Some(v) = yaml["help"].as_str() {
1376-
a = a.help(v);
1377-
}
1378-
if let Some(v) = yaml["help_short"].as_str() {
1379-
a = a.help_short(v);
1380-
}
1381-
if let Some(v) = yaml["version_short"].as_str() {
1382-
a = a.version_short(v);
1374+
} else if yaml["display_order"] != Yaml::BadValue {
1375+
panic!("Failed to convert YAML value {:?} to a u64", yaml["display_order"]);
13831376
}
13841377
if let Some(v) = yaml["setting"].as_str() {
1385-
a = a.setting(v.parse().ok().expect("unknown AppSetting found in YAML file"));
1378+
a = a.setting(v.parse().expect("unknown AppSetting found in YAML file"));
1379+
} else if yaml["setting"] != Yaml::BadValue {
1380+
panic!("Failed to convert YAML value {:?} to an AppSetting", yaml["setting"]);
13861381
}
13871382
if let Some(v) = yaml["settings"].as_vec() {
13881383
for ys in v {
13891384
if let Some(s) = ys.as_str() {
1390-
a = a.setting(s.parse().ok().expect("unknown AppSetting found in YAML file"));
1385+
a = a.setting(s.parse().expect("unknown AppSetting found in YAML file"));
13911386
}
13921387
}
1388+
} else {
1389+
if let Some(v) = yaml["settings"].as_str() {
1390+
a = a.setting(v.parse().expect("unknown AppSetting found in YAML file"));
1391+
} else if yaml["settings"] != Yaml::BadValue {
1392+
panic!("Failed to convert YAML value {:?} to a string", yaml["settings"]);
1393+
}
13931394
}
13941395
if let Some(v) = yaml["global_setting"].as_str() {
13951396
a = a.setting(v.parse().ok().expect("unknown AppSetting found in YAML file"));
1397+
} else if yaml["global_setting"] != Yaml::BadValue {
1398+
panic!("Failed to convert YAML value {:?} to an AppSetting", yaml["setting"]);
13961399
}
13971400
if let Some(v) = yaml["global_settings"].as_vec() {
13981401
for ys in v {
13991402
if let Some(s) = ys.as_str() {
14001403
a = a.global_setting(s.parse().ok().expect("unknown AppSetting found in YAML file"));
14011404
}
14021405
}
1403-
}
1404-
if let Some(v) = yaml["alias"].as_str() {
1405-
a = a.alias(v);
1406-
}
1407-
if let Some(v) = yaml["aliases"].as_vec() {
1408-
for ys in v {
1409-
if let Some(s) = ys.as_str() {
1410-
a = a.alias(s);
1411-
}
1406+
} else {
1407+
if let Some(v) = yaml["global_settings"].as_str() {
1408+
a = a.global_setting(v.parse().expect("unknown AppSetting found in YAML file"));
1409+
} else if yaml["global_settings"] != Yaml::BadValue {
1410+
panic!("Failed to convert YAML value {:?} to a string", yaml["global_settings"]);
14121411
}
14131412
}
1414-
if let Some(v) = yaml["visible_alias"].as_str() {
1415-
a = a.visible_alias(v);
1416-
}
1417-
if let Some(v) = yaml["visible_aliases"].as_vec() {
1418-
for ys in v {
1419-
if let Some(s) = ys.as_str() {
1420-
a = a.visible_alias(s);
1413+
1414+
macro_rules! vec_or_str {
1415+
($a:ident, $y:ident, $as_vec:ident, $as_single:ident) => {{
1416+
let maybe_vec = $y[stringify!($as_vec)].as_vec();
1417+
if let Some(vec) = maybe_vec {
1418+
for ys in vec {
1419+
if let Some(s) = ys.as_str() {
1420+
$a = $a.$as_single(s);
1421+
} else {
1422+
panic!("Failed to convert YAML value {:?} to a string", ys);
1423+
}
1424+
}
1425+
} else {
1426+
if let Some(s) = $y[stringify!($as_vec)].as_str() {
1427+
$a = $a.$as_single(s);
1428+
} else if $y[stringify!($as_vec)] != Yaml::BadValue {
1429+
panic!("Failed to convert YAML value {:?} to either a vec or string", $y[stringify!($as_vec)]);
1430+
}
1431+
}
1432+
$a
14211433
}
1422-
}
1434+
};
14231435
}
1436+
1437+
a = vec_or_str!(a, yaml, aliases, alias);
1438+
a = vec_or_str!(a, yaml, visible_aliases, visible_alias);
1439+
14241440
if let Some(v) = yaml["args"].as_vec() {
14251441
for arg_yaml in v {
14261442
a = a.arg(Arg::from_yaml(&arg_yaml.as_hash().unwrap()));

src/args/arg.rs

+29-48
Original file line numberDiff line numberDiff line change
@@ -145,71 +145,52 @@ impl<'a, 'b> Arg<'a, 'b> {
145145
let mut a = Arg::with_name(name_str);
146146
let arg_settings = y.get(name_yml).unwrap().as_hash().unwrap();
147147

148-
macro_rules! vec_or_str {
149-
($v:ident, $a:ident, $c:ident) => {{
150-
let maybe_vec = $v.as_vec();
151-
if let Some(vec) = maybe_vec {
152-
for ys in vec {
153-
if let Some(s) = ys.as_str() {
154-
$a = $a.$c(s);
155-
}
156-
}
157-
} else {
158-
if let Some(s) = $v.as_str() {
159-
$a = $a.$c(s);
160-
}
161-
}
162-
$a
163-
}
164-
};
165-
}
166-
167148
for (k, v) in arg_settings.iter() {
168149
a = match k.as_str().unwrap() {
169-
"short" => a.short(v.as_str().unwrap()),
170-
"long" => a.long(v.as_str().unwrap()),
171-
"help" => a.help(v.as_str().unwrap()),
172-
"required" => a.required(v.as_bool().unwrap()),
173-
"takes_value" => a.takes_value(v.as_bool().unwrap()),
174-
"index" => a.index(v.as_i64().unwrap() as u64),
175-
"global" => a.global(v.as_bool().unwrap()),
176-
"multiple" => a.multiple(v.as_bool().unwrap()),
177-
"hidden" => a.hidden(v.as_bool().unwrap()),
178-
"next_line_help" => a.next_line_help(v.as_bool().unwrap()),
179-
"empty_values" => a.empty_values(v.as_bool().unwrap()),
180-
"group" => a.group(v.as_str().unwrap()),
181-
"number_of_values" => a.number_of_values(v.as_i64().unwrap() as u64),
182-
"max_values" => a.max_values(v.as_i64().unwrap() as u64),
183-
"min_values" => a.min_values(v.as_i64().unwrap() as u64),
184-
"value_name" => a.value_name(v.as_str().unwrap()),
185-
"use_delimiter" => a.use_delimiter(v.as_bool().unwrap()),
186-
"value_delimiter" => a.value_delimiter(v.as_str().unwrap()),
187-
"required_unless" => a.required_unless(v.as_str().unwrap()),
188-
"display_order" => a.display_order(v.as_i64().unwrap() as usize),
189-
"default_value" => a.default_value(v.as_str().unwrap()),
150+
"short" => yaml_to_str!(a, v, short),
151+
"long" => yaml_to_str!(a, v, long),
152+
"help" => yaml_to_str!(a, v, help),
153+
"required" => yaml_to_bool!(a, v, required),
154+
"takes_value" => yaml_to_bool!(a, v, takes_value),
155+
"index" => yaml_to_u64!(a, v, index),
156+
"global" => yaml_to_bool!(a, v, global),
157+
"multiple" => yaml_to_bool!(a, v, multiple),
158+
"hidden" => yaml_to_bool!(a, v, hidden),
159+
"next_line_help" => yaml_to_bool!(a, v, next_line_help),
160+
"empty_values" => yaml_to_bool!(a, v, empty_values),
161+
"group" => yaml_to_str!(a, v, group),
162+
"number_of_values" => yaml_to_u64!(a, v, number_of_values),
163+
"max_values" => yaml_to_u64!(a, v, max_values),
164+
"min_values" => yaml_to_u64!(a, v, min_values),
165+
"value_name" => yaml_to_str!(a, v, value_name),
166+
"use_delimiter" => yaml_to_bool!(a, v, use_delimiter),
167+
"value_delimiter" => yaml_to_str!(a, v, value_delimiter),
168+
"required_unless" => yaml_to_str!(a, v, required_unless),
169+
"display_order" => yaml_to_usize!(a, v, display_order),
170+
"default_value" => yaml_to_str!(a, v, default_value),
190171
"value_names" => {
191-
vec_or_str!(v, a, value_name)
172+
yaml_vec_or_str!(v, a, value_name)
192173
}
193174
"groups" => {
194-
vec_or_str!(v, a, group)
175+
yaml_vec_or_str!(v, a, group)
195176
}
196177
"requires" => {
197-
vec_or_str!(v, a, requires)
178+
yaml_vec_or_str!(v, a, requires)
198179
}
199180
"conflicts_with" => {
200-
vec_or_str!(v, a, conflicts_with)
181+
yaml_vec_or_str!(v, a, conflicts_with)
201182
}
202183
"overrides_with" => {
203-
vec_or_str!(v, a, overrides_with)
184+
yaml_vec_or_str!(v, a, overrides_with)
204185
}
205186
"possible_values" => {
206-
vec_or_str!(v, a, possible_value)
187+
yaml_vec_or_str!(v, a, possible_value)
207188
}
208189
"required_unless_one" => {
209-
vec_or_str!(v, a, required_unless)
190+
yaml_vec_or_str!(v, a, required_unless)
210191
}
211192
"required_unless_all" => {
212-
a = vec_or_str!(v, a, required_unless);
193+
a = yaml_vec_or_str!(v, a, required_unless);
213194
a.setb(ArgSettings::RequiredUnlessAll);
214195
a
215196
}

src/args/group.rs

+4-19
Original file line numberDiff line numberDiff line change
@@ -458,7 +458,7 @@ impl<'a> From<&'a BTreeMap<Yaml, Yaml>> for ArgGroup<'a> {
458458
let mut a = ArgGroup::default();
459459
let group_settings = if b.len() == 1 {
460460
let name_yml = b.keys().nth(0).expect("failed to get name");
461-
let name_str = name_yml.as_str().expect("failed to convert name to str");
461+
let name_str = name_yml.as_str().expect("failed to convert arg YAML name to str");
462462
a.name = name_str;
463463
b.get(name_yml)
464464
.expect("failed to get name_str")
@@ -472,12 +472,7 @@ impl<'a> From<&'a BTreeMap<Yaml, Yaml>> for ArgGroup<'a> {
472472
a = match k.as_str().unwrap() {
473473
"required" => a.required(v.as_bool().unwrap()),
474474
"args" => {
475-
for ys in v.as_vec().unwrap() {
476-
if let Some(s) = ys.as_str() {
477-
a = a.arg(s);
478-
}
479-
}
480-
a
475+
yaml_vec_or_str!(v, a, arg)
481476
}
482477
"arg" => {
483478
if let Some(ys) = v.as_str() {
@@ -486,20 +481,10 @@ impl<'a> From<&'a BTreeMap<Yaml, Yaml>> for ArgGroup<'a> {
486481
a
487482
}
488483
"requires" => {
489-
for ys in v.as_vec().unwrap() {
490-
if let Some(s) = ys.as_str() {
491-
a = a.requires(s);
492-
}
493-
}
494-
a
484+
yaml_vec_or_str!(v, a, requires)
495485
}
496486
"conflicts_with" => {
497-
for ys in v.as_vec().unwrap() {
498-
if let Some(s) = ys.as_str() {
499-
a = a.conflicts_with(s);
500-
}
501-
}
502-
a
487+
yaml_vec_or_str!(v, a, conflicts_with)
503488
}
504489
"name" => {
505490
if let Some(ys) = v.as_str() {

src/args/macros.rs

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
2+
macro_rules! yaml_vec_or_str {
3+
($v:ident, $a:ident, $c:ident) => {{
4+
let maybe_vec = $v.as_vec();
5+
if let Some(vec) = maybe_vec {
6+
for ys in vec {
7+
if let Some(s) = ys.as_str() {
8+
$a = $a.$c(s);
9+
} else {
10+
panic!("Failed to convert YAML value {:?} to a string", ys);
11+
}
12+
}
13+
} else {
14+
if let Some(s) = $v.as_str() {
15+
$a = $a.$c(s);
16+
} else {
17+
panic!("Failed to convert YAML value {:?} to either a vec or string", $v);
18+
}
19+
}
20+
$a
21+
}
22+
};
23+
}
24+
25+
macro_rules! yaml_to_str {
26+
($a:ident, $v:ident, $c:ident) => {{
27+
$a.$c($v.as_str().unwrap_or_else(|| panic!("failed to convert YAML {:?} value to a string", $v)))
28+
}};
29+
}
30+
31+
macro_rules! yaml_to_bool {
32+
($a:ident, $v:ident, $c:ident) => {{
33+
$a.$c($v.as_bool().unwrap_or_else(|| panic!("failed to convert YAML {:?} value to a string", $v)))
34+
}};
35+
}
36+
37+
macro_rules! yaml_to_u64 {
38+
($a:ident, $v:ident, $c:ident) => {{
39+
$a.$c($v.as_i64().unwrap_or_else(|| panic!("failed to convert YAML {:?} value to a string", $v)) as u64)
40+
}};
41+
}
42+
43+
macro_rules! yaml_to_usize {
44+
($a:ident, $v:ident, $c:ident) => {{
45+
$a.$c($v.as_i64().unwrap_or_else(|| panic!("failed to convert YAML {:?} value to a string", $v)) as usize)
46+
}};
47+
}

src/args/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ pub use self::group::ArgGroup;
88
pub use self::any_arg::{AnyArg, DispOrder};
99
pub use self::settings::ArgSettings;
1010

11+
#[macro_use]
12+
mod macros;
1113
mod arg;
1214
pub mod any_arg;
1315
mod arg_matches;

tests/app.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: claptests
2-
version: 1.0
2+
version: "1.0"
33
about: tests clap library
44
author: Kevin K. <[email protected]>
55
settings:
@@ -81,7 +81,7 @@ arg_groups:
8181
subcommands:
8282
- subcmd:
8383
about: tests subcommands
84-
version: 0.1
84+
version: "0.1"
8585
author: Kevin K. <[email protected]>
8686
args:
8787
- scoption:

0 commit comments

Comments
 (0)