Skip to content

Commit

Permalink
fix: adds support for building ArgGroups from standalone YAML
Browse files Browse the repository at this point in the history
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
  • Loading branch information
kbknapp committed Feb 4, 2016
1 parent 355a5fd commit fcbc7e1
Show file tree
Hide file tree
Showing 2 changed files with 151 additions and 123 deletions.
143 changes: 75 additions & 68 deletions src/app/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
}
131 changes: 76 additions & 55 deletions src/args/group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ use yaml_rust::Yaml;
/// .required(true))
/// # ;
/// ```
#[derive(Default)]
pub struct ArgGroup<'a> {
#[doc(hidden)]
pub name: &'a str,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -325,13 +280,13 @@ 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,
self.required,
Expand All @@ -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() {
Expand Down

0 comments on commit fcbc7e1

Please sign in to comment.