use crate::{AnyResult, Case, Parse, StdResult, Value, DEFAULT_KEYS_SEPARATOR};
use clap::ArgMatches;
use either::Either;
use serde_yaml::{Mapping, Value as YamlValue};
pub type Result<T> = StdResult<T, Error>;
#[non_exhaustive]
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("Uninitialized field: {0:?}")]
UninitializedField(&'static str),
#[error("Yaml parser error")]
ParseYaml(#[from] serde_yaml::Error),
#[error(transparent)]
Common(#[from] crate::Error),
}
pub struct Parser {
value: Value,
}
impl Case for Parser {
#[inline]
fn is_case_sensitive(&self) -> bool {
self.value.is_case_sensitive()
}
}
impl Parse for Parser {
#[inline]
fn parse(&mut self, _value: &Value) -> AnyResult<Value> {
Ok(self.value.clone())
}
}
pub struct ParserBuilder<'a> {
matches: Option<ArgMatches<'a>>,
arg_names: Vec<String>,
keys_delimiter: String,
case_sensitive: bool,
}
impl Default for ParserBuilder<'_> {
fn default() -> Self {
Self {
matches: Default::default(),
arg_names: Default::default(),
keys_delimiter: DEFAULT_KEYS_SEPARATOR.to_string(),
case_sensitive: true,
}
}
}
impl<'a> ParserBuilder<'a> {
#[inline]
pub fn matches(&mut self, matches: ArgMatches<'a>) -> &mut Self {
self.matches = Some(matches);
self
}
#[inline]
pub fn arg_names<I, T>(&mut self, names: I) -> &mut Self
where
I: IntoIterator<Item = T>,
T: Into<String>,
{
self.arg_names = names.into_iter().map(|n| n.into()).collect();
self
}
pub fn try_arg_names_from_yaml<D>(&mut self, data: D) -> Result<&mut Self>
where
D: AsRef<str>,
{
fn inner(data: &str) -> Result<Vec<String>> {
if let YamlValue::Mapping(ref m) = serde_yaml::from_str(data)? {
let args = get_args_from_mapping(m);
Ok(args.chain(get_args_from_subcommands(m)).cloned().collect())
} else {
Err(Error::Common(crate::Error::NotMap))
}
}
self.arg_names = inner(data.as_ref())?;
Ok(self)
}
#[inline]
pub fn keys_delimiter<S>(&mut self, delim: S) -> &mut Self
where
S: Into<String>,
{
self.keys_delimiter = delim.into();
self
}
#[inline]
pub fn case_sensitive(&mut self, on: bool) -> &mut Self {
self.case_sensitive = on;
self
}
pub fn build(&self) -> Result<Parser> {
let matches = self
.matches
.as_ref()
.ok_or(Error::UninitializedField("matches"))?;
let mut value = Value::with_case(self.case_sensitive);
for arg in self.arg_names.iter() {
if let Some(v) = matches.values_of_lossy(arg) {
match v[..] {
[ref a] => {
value.set_by_key_path_with_delim(
arg,
&self.keys_delimiter,
serde_yaml::from_str::<YamlValue>(a)?,
)?;
}
_ => {
value.set_by_key_path_with_delim(
arg,
&self.keys_delimiter,
serde_yaml::from_str::<YamlValue>(&format!("[{}]", v.join(",")))?,
)?;
}
}
}
}
Ok(Parser { value })
}
}
fn get_args_from_mapping(map: &Mapping) -> impl Iterator<Item = &String> {
let args = YamlValue::String("args".to_string());
if let Some(YamlValue::Sequence(args)) = map.get(&args) {
Either::Left(args.iter().filter_map(|a| {
if let YamlValue::Mapping(m) = a {
m.iter().next().and_then(|(k, _)| match k {
YamlValue::String(s) => Some(s),
_ => None,
})
} else {
None
}
}))
} else {
Either::Right(std::iter::empty())
}
}
fn get_args_from_subcommands(map: &Mapping) -> impl Iterator<Item = &String> {
let subcmds = YamlValue::String("subcommands".to_string());
if let Some(YamlValue::Sequence(cmds)) = map.get(&subcmds) {
Either::Left(
cmds.iter()
.filter_map(|c| {
if let YamlValue::Mapping(m) = c {
m.iter().next().and_then(|(_, v)| match v {
YamlValue::Mapping(m) => Some(get_args_from_mapping(m)),
_ => None,
})
} else {
None
}
})
.flatten(),
)
} else {
Either::Right(std::iter::empty())
}
}