fix: adds support for building ArgGroups from standalone YAML

ArgGroups can now be built from standalone YAML documents. This is
labeled as a fix because it should have been the case prior. A
standalone YAML document for a group would look something like

```
name: test
args:
    - arg1
    - arg2
required: true
conflicts:
    - arg3
requires:
    - arg4
```

This commit also keeps support building groups as part of a larger
entire App YAML document where the name is specified as a key to the
yaml tree
This commit is contained in:
Kevin K 2016-02-04 01:10:37 -05:00
parent 355a5fdabd
commit fcbc7e12f5
2 changed files with 151 additions and 123 deletions

View file

@ -98,74 +98,8 @@ impl<'a, 'b> App<'a, 'b> {
/// // continued logic goes here, such as `app.get_matches()` etc.
/// ```
#[cfg(feature = "yaml")]
pub fn from_yaml<'y>(mut yaml: &'y Yaml) -> App<'y, 'y> {
use args::SubCommand;
// We WANT this to panic on error...so expect() is good.
let mut is_sc = None;
let mut a = if let Some(name) = yaml["name"].as_str() {
App::new(name)
} else {
let yaml_hash = yaml.as_hash().unwrap();
let sc_key = yaml_hash.keys().nth(0).unwrap();
is_sc = Some(yaml_hash.get(sc_key).unwrap());
App::new(sc_key.as_str().unwrap())
};
yaml = if let Some(sc) = is_sc {
sc
} else {
yaml
};
if let Some(v) = yaml["version"].as_str() {
a = a.version(v);
}
if let Some(v) = yaml["author"].as_str() {
a = a.author(v);
}
if let Some(v) = yaml["bin_name"].as_str() {
a = a.bin_name(v);
}
if let Some(v) = yaml["about"].as_str() {
a = a.about(v);
}
if let Some(v) = yaml["after_help"].as_str() {
a = a.after_help(v);
}
if let Some(v) = yaml["usage"].as_str() {
a = a.usage(v);
}
if let Some(v) = yaml["help"].as_str() {
a = a.help(v);
}
if let Some(v) = yaml["help_short"].as_str() {
a = a.help_short(v);
}
if let Some(v) = yaml["version_short"].as_str() {
a = a.version_short(v);
}
if let Some(v) = yaml["settings"].as_vec() {
for ys in v {
if let Some(s) = ys.as_str() {
a = a.setting(s.parse().ok().expect("unknown AppSetting found in YAML file"));
}
}
}
if let Some(v) = yaml["args"].as_vec() {
for arg_yaml in v {
a = a.arg(Arg::from_yaml(&arg_yaml.as_hash().unwrap()));
}
}
if let Some(v) = yaml["subcommands"].as_vec() {
for sc_yaml in v {
a = a.subcommand(SubCommand::from_yaml(&sc_yaml));
}
}
if let Some(v) = yaml["groups"].as_vec() {
for ag_yaml in v {
a = a.group(ArgGroup::from_yaml(&ag_yaml.as_hash().unwrap()));
}
}
a
pub fn from_yaml(yaml: &'a Yaml) -> App<'a, 'a> {
App::from(yaml)
}
/// Sets a string of author(s) that will be displayed to the user when they request the help
@ -810,3 +744,76 @@ impl<'a, 'b> App<'a, 'b> {
e.exit()
}
}
#[cfg(feature = "yaml")]
impl<'a> From<&'a Yaml> for App<'a, 'a> {
fn from(mut yaml: &'a Yaml) -> Self {
use args::SubCommand;
// We WANT this to panic on error...so expect() is good.
let mut is_sc = None;
let mut a = if let Some(name) = yaml["name"].as_str() {
App::new(name)
} else {
let yaml_hash = yaml.as_hash().unwrap();
let sc_key = yaml_hash.keys().nth(0).unwrap();
is_sc = Some(yaml_hash.get(sc_key).unwrap());
App::new(sc_key.as_str().unwrap())
};
yaml = if let Some(sc) = is_sc {
sc
} else {
yaml
};
if let Some(v) = yaml["version"].as_str() {
a = a.version(v);
}
if let Some(v) = yaml["author"].as_str() {
a = a.author(v);
}
if let Some(v) = yaml["bin_name"].as_str() {
a = a.bin_name(v);
}
if let Some(v) = yaml["about"].as_str() {
a = a.about(v);
}
if let Some(v) = yaml["after_help"].as_str() {
a = a.after_help(v);
}
if let Some(v) = yaml["usage"].as_str() {
a = a.usage(v);
}
if let Some(v) = yaml["help"].as_str() {
a = a.help(v);
}
if let Some(v) = yaml["help_short"].as_str() {
a = a.help_short(v);
}
if let Some(v) = yaml["version_short"].as_str() {
a = a.version_short(v);
}
if let Some(v) = yaml["settings"].as_vec() {
for ys in v {
if let Some(s) = ys.as_str() {
a = a.setting(s.parse().ok().expect("unknown AppSetting found in YAML file"));
}
}
}
if let Some(v) = yaml["args"].as_vec() {
for arg_yaml in v {
a = a.arg(Arg::from_yaml(&arg_yaml.as_hash().unwrap()));
}
}
if let Some(v) = yaml["subcommands"].as_vec() {
for sc_yaml in v {
a = a.subcommand(SubCommand::from_yaml(&sc_yaml));
}
}
if let Some(v) = yaml["groups"].as_vec() {
for ag_yaml in v {
a = a.group(ArgGroup::from(ag_yaml.as_hash().unwrap()));
}
}
a
}
}

View file

@ -49,6 +49,7 @@ use yaml_rust::Yaml;
/// .required(true))
/// # ;
/// ```
#[derive(Default)]
pub struct ArgGroup<'a> {
#[doc(hidden)]
pub name: &'a str,
@ -93,54 +94,8 @@ impl<'a> ArgGroup<'a> {
/// let ag = ArgGroup::from_yaml(yml);
/// ```
#[cfg(feature = "yaml")]
pub fn from_yaml<'y>(y: &'y BTreeMap<Yaml, Yaml>) -> ArgGroup<'y> {
// We WANT this to panic on error...so expect() is good.
let name_yml = y.keys().nth(0).unwrap();
let name_str = name_yml.as_str().unwrap();
let mut a = ArgGroup::with_name(name_str);
let group_settings = y.get(name_yml).unwrap().as_hash().unwrap();
for (k, v) in group_settings.iter() {
a = match k.as_str().unwrap() {
"required" => a.required(v.as_bool().unwrap()),
"args" => {
for ys in v.as_vec().unwrap() {
if let Some(s) = ys.as_str() {
a = a.arg(s);
}
}
a
}
"arg" => {
if let Some(ys) = v.as_str() {
a = a.arg(ys);
}
a
}
"requires" => {
for ys in v.as_vec().unwrap() {
if let Some(s) = ys.as_str() {
a = a.requires(s);
}
}
a
}
"conflicts_with" => {
for ys in v.as_vec().unwrap() {
if let Some(s) = ys.as_str() {
a = a.conflicts_with(s);
}
}
a
}
s => panic!("Unknown ArgGroup setting '{}' in YAML file for \
ArgGroup '{}'",
s,
name_str),
}
}
a
pub fn from_yaml(y: &'a Yaml) -> ArgGroup<'a> {
ArgGroup::from(y.as_hash().unwrap())
}
/// Adds an argument to this group by name
@ -325,12 +280,12 @@ impl<'a> ArgGroup<'a> {
impl<'a> Debug for ArgGroup<'a> {
fn fmt(&self, f: &mut Formatter) -> Result {
write!(f,
"{{
name:{:?},
args: {:?},
required: {:?},
requires: {:?},
conflicts: {:?},
"{{\n\
\tname: {:?},\n\
\targs: {:?},\n\
\trequired: {:?},\n\
\trequires: {:?},\n\
\tconflicts: {:?},\n\
}}",
self.name,
self.args,
@ -352,9 +307,75 @@ impl<'a, 'z> From<&'z ArgGroup<'a>> for ArgGroup<'a> {
}
}
#[cfg(feature = "yaml")]
impl<'a> From<&'a BTreeMap<Yaml, Yaml>> for ArgGroup<'a> {
fn from(b: &'a BTreeMap<Yaml, Yaml>) -> Self {
// We WANT this to panic on error...so expect() is good.
let mut a = ArgGroup::default();
let group_settings = if b.len() == 1 {
let name_yml = b.keys().nth(0).expect("failed to get name");
let name_str = name_yml.as_str().expect("failed to convert name to str");
a.name = name_str;
b.get(name_yml).expect("failed to get name_str").as_hash().expect("failed to convert to a hash")
} else {
b
};
for (k, v) in group_settings.iter() {
a = match k.as_str().unwrap() {
"required" => a.required(v.as_bool().unwrap()),
"args" => {
for ys in v.as_vec().unwrap() {
if let Some(s) = ys.as_str() {
a = a.arg(s);
}
}
a
}
"arg" => {
if let Some(ys) = v.as_str() {
a = a.arg(ys);
}
a
}
"requires" => {
for ys in v.as_vec().unwrap() {
if let Some(s) = ys.as_str() {
a = a.requires(s);
}
}
a
}
"conflicts_with" => {
for ys in v.as_vec().unwrap() {
if let Some(s) = ys.as_str() {
a = a.conflicts_with(s);
}
}
a
}
"name" => {
if let Some(ys) = v.as_str() {
a.name = ys;
}
a
}
s => panic!("Unknown ArgGroup setting '{}' in YAML file for \
ArgGroup '{}'",
s,
a.name),
}
}
a
}
}
#[cfg(test)]
mod test {
use super::ArgGroup;
#[cfg(feature = "yaml")]
use yaml_rust::YamlLoader;
#[test]
fn groups() {