extern crate clap;
#[macro_use]
extern crate pest_derive;
extern crate pest;
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate nereon_derive;
use std::collections::HashMap;
use std::env;
use std::ffi::OsString;
use std::fs::File;
use std::io::Read;
mod nos;
use nos::{Nos, UserOption};
mod noc;
pub use noc::{parse_noc, ConversionError, FromValue, NocError, ParseError, Value};
pub fn configure<T, U: IntoIterator<Item = I>, I: Into<OsString> + Clone>(
nos: &str,
args: U,
) -> Result<T, String>
where
T: FromValue,
{
let nos = parse_noc::<Nos>(nos).expect("Invalid NOS");
let options = nos.option.as_ref().expect("Invalid NOS");
let mut clap_app = clap::App::new(nos.name.to_owned())
.version(nos.version.as_str())
.about(nos.license.as_str());
for a in nos.authors.iter() {
clap_app = clap_app.author(a.as_str());
}
for (n, o) in options.iter() {
let mut arg = clap::Arg::with_name(n.as_str());
for f in o.flags.iter() {
match f.as_ref() {
"required" => arg = arg.required(true),
"multiple" => arg = arg.multiple(true),
"takesvalue" => arg = arg.takes_value(true),
_ => panic!("No such argument flag ({})", f),
}
}
if let Some(ref s) = o.short {
arg = arg.short(s);
}
if let Some(ref l) = o.long {
arg = arg.long(l);
}
if let Some(ref d) = o.default {
arg = arg.default_value(d);
}
if let Some(ref e) = o.env {
arg = arg.env(e);
}
if let Some(ref h) = o.hint {
arg = arg.value_name(h);
}
clap_app = clap_app.arg(arg);
}
let matches = clap_app
.get_matches_from_safe(args)
.map_err(|e| format!("{}", e))?;
fn key_to_strs(option: &UserOption) -> Vec<&str> {
option.key.iter().map(|k| k.as_str()).collect()
}
let mut config = Value::Table(HashMap::new());
if let Some(n) = matches.value_of_os("config") {
let mut buffer = String::new();
config = File::open(&n)
.and_then(|ref mut f| f.read_to_string(&mut buffer))
.map_err(|e| format!("{}", e))
.and_then(|_| parse_noc::<Value>(&buffer).map_err(|e| format!("{:?}", e)))
.and_then(|v| {
Ok({
let keys = key_to_strs(&options.get("config").unwrap());
config.insert(keys, v)
})
})?
};
config = options.iter().fold(config, |mut config, (name, option)| {
if name != "config" {
let value = if matches.occurrences_of(name) == 0 {
option
.env
.as_ref()
.and_then(env::var_os)
.map(Value::from)
.or_else(|| {
config
.lookup(key_to_strs(&option))
.map_or_else(|| option.default.clone().map(Value::from), |_| None)
})
} else if option.flags.iter().any(|ref f| f.as_str() == "multiple") {
matches
.values_of_os(name)
.map(|vs| Value::from(vs.collect::<Vec<_>>()))
.or_else(|| option.default_arg.clone().map(Value::from))
} else {
matches
.value_of_os(name)
.map(Value::from)
.or_else(|| option.default_arg.clone().map(Value::from))
};
if let Some(v) = value {
let keys = key_to_strs(option);
config = config.insert(keys, v);
}
}
config
});
T::from_value(config).map_err(|e| format!("{}", e))
}
#[cfg(test)]
mod tests {
#[test]
fn test_configure() {
use super::{configure, Value};
use std::collections::HashMap;
let nos = r#"
name "Nereon test"
authors ["John Doe <john@doe.me>"]
version "0.0.1"
license Free
option username {
short u
long user
env NEREON_USER
default admin
hint USER
usage "User name"
key [username]
}"#;
let expected = Value::Table(HashMap::new()).insert(vec!["username"], Value::from("root"));
assert_eq!(
configure::<Value, _, _>(nos, &["program", "-u", "root"]),
Ok(expected)
);
}
#[test]
fn test_configure1() {
use super::{configure, parse_noc, Value};
use std::collections::HashMap;
let noc = r#"user admin {uid 1000}"#;
let expected =
Value::Table(HashMap::new()).insert(vec!["user", "admin", "uid"], Value::from("1000"));
assert_eq!(parse_noc::<Value>(noc), Ok(expected));
let nos = r#"
name "Nereon test"
authors ["John Doe <john@doe.me>"]
license Free
version "0.0.1"
option config {
env nereon_config
usage "Config file"
key []
}
option user {
short u
key [user, admin, uid]
usage "User's UID"
hint USER
}
option permissions {
env nereon_permissions
key [user, admin, permissions]
usage "Permissions for user"
}"#;
::std::fs::write("/tmp/nereon_test", "user admin permissions read").unwrap();
::std::env::set_var("nereon_config", "/tmp/nereon_test");
::std::env::set_var("nereon_permissions", "read,write");
let expected = parse_noc::<Value>(
r#"
user admin uid 100
user admin permissions "read,write""#,
).unwrap();
assert_eq!(
configure::<Value, _, _>(nos, &["program", "-u", "100"]),
Ok(expected)
);
::std::fs::remove_file("/tmp/nereon_test").unwrap();
}
}