use crate::{AnyResult, Case, Parse, StdResult, Value, DEFAULT_KEYS_SEPARATOR};
use clap::{Arg, ArgMatches, Command};
use serde_yaml::Value as YamlValue;
use std::{borrow::Cow, env, ffi::OsString};
pub type Result<T> = StdResult<T, Error>;
pub const DEFAULT_MAX_DEPTH: u8 = 16;
#[non_exhaustive]
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("Yaml parser error")]
ParseYaml(#[from] serde_yaml::Error),
#[error("Clap error")]
Clap(#[from] clap::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> {
command: Command<'a>,
args: Option<Vec<OsString>>,
global_key_names: bool,
max_depth: u8,
keys_delimiter: String,
case_sensitive: bool,
exit_on_error: bool,
}
impl<'a> ParserBuilder<'a> {
#[inline]
pub fn new(command: Command<'a>) -> Self {
Self {
command,
args: Default::default(),
global_key_names: true,
max_depth: DEFAULT_MAX_DEPTH,
keys_delimiter: DEFAULT_KEYS_SEPARATOR.to_string(),
case_sensitive: true,
exit_on_error: false,
}
}
#[inline]
pub fn args<I, T>(&mut self, args: I) -> &mut Self
where
I: IntoIterator<Item = T>,
T: Into<OsString>,
{
self.args = Some(args.into_iter().map(|a| a.into()).collect());
self
}
#[inline]
pub fn global_key_names(&mut self, on: bool) -> &mut Self {
self.global_key_names = on;
self
}
#[inline]
pub fn max_depth(&mut self, depth: u8) -> &mut Self {
self.max_depth = depth;
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
}
#[inline]
pub fn exit_on_error(&mut self, on: bool) -> &mut Self {
self.exit_on_error = on;
self
}
pub fn build(&mut self) -> Result<Parser> {
let result = self.get_matches();
let matches = if self.exit_on_error {
result.unwrap_or_else(|e| e.exit())
} else {
result?
};
let value = self.get_app_arguments(
Value::with_case(self.case_sensitive),
&self.command,
&matches,
"",
self.max_depth,
)?;
Ok(Parser { value })
}
fn get_matches(&mut self) -> clap::Result<ArgMatches> {
if let Some(ref args) = self.args {
self.command.try_get_matches_from_mut(args)
} else {
self.command.try_get_matches_from_mut(&mut env::args_os())
}
}
fn get_app_arguments(
&self,
mut value: Value,
command: &Command,
matches: &ArgMatches,
app_name: &str,
depth: u8,
) -> Result<Value> {
let prefix = if self.global_key_names || app_name.is_empty() {
Default::default()
} else {
[app_name, &self.keys_delimiter].concat()
};
for arg in command.get_arguments() {
value = set_value(
value,
matches,
&[&prefix, arg.get_id()].concat(),
&self.keys_delimiter,
arg,
)?;
}
if depth == 0 {
return Ok(value);
}
for a in command.get_subcommands() {
if let Some(m) = matches.subcommand_matches(a.get_name()) {
value = self.get_app_arguments(
value,
a,
m,
&[&prefix, a.get_name()].concat(),
depth - 1,
)?;
}
}
Ok(value)
}
}
fn get_values(matches: &ArgMatches, arg: &Arg) -> Option<Vec<String>> {
let id = arg.get_id();
if arg.is_allow_invalid_utf8_set() {
return matches.values_of_lossy(id);
}
matches
.values_of(id)
.map(|v| v.map(|i| i.to_string()).collect())
}
fn set_value(
mut value: Value,
matches: &ArgMatches,
path: &str,
delim: &str,
arg: &Arg,
) -> Result<Value> {
if let Some(v) = get_values(matches, arg) {
let is_list = arg.is_multiple_values_set() || arg.is_multiple_occurrences_set();
let v: Vec<_> = v
.into_iter()
.map(|i| if i.is_empty() { "''".to_string() } else { i })
.collect();
let v = match v[..] {
[ref a] if !is_list => Cow::Borrowed(a),
[] if !arg.is_takes_value_set() => {
Cow::Owned(matches.occurrences_of(arg.get_id()).to_string())
}
_ => Cow::Owned(["[", &v.join(","), "]"].concat()),
};
value.set_by_key_path_with_delim(path, delim, serde_yaml::from_str::<YamlValue>(&v)?)?;
}
Ok(value)
}