use std::{any::Any, ffi::OsStr, str::FromStr};
#[cfg(feature = "clap")]
use clap::builder::IntoResettable;
#[cfg(feature = "clap")]
use crate::internal::FieldClapDescription;
use crate::{
Configuration,
internal::{
ChangeVerb, CongenChange, CongenDefault, CongenInternal, Description, FieldDescription,
NotSupported, ParseError, VerbError,
},
};
impl Configuration for bool {}
impl CongenInternal for bool {
type CongenChange = Option<bool>;
fn apply_change_with_inner_default(
&mut self,
change: Self::CongenChange,
_inner_default: Option<fn() -> Box<dyn Any>>,
) {
if let Some(new) = change {
*self = new
}
}
fn description(field_name: &'static str) -> Description {
FieldDescription {
field_name,
type_name: Self::type_name(),
is_flag: true,
allow_unset: true,
has_default: false,
#[cfg(feature = "clap")]
clap_data: Some(FieldClapDescription {
value_parser: Some(clap::value_parser!(bool).into_resettable()),
..Default::default()
}),
}
.into()
}
fn default() -> Result<Self, crate::internal::NotSupported> {
Ok(false)
}
fn type_name() -> std::borrow::Cow<'static, str> {
std::any::type_name::<Self>().into()
}
}
impl CongenDefault for bool {}
impl CongenChange for Option<bool> {
type Configuration = bool;
fn empty() -> Self {
None
}
fn parse(input: &OsStr) -> Result<Result<Self, ParseError>, NotSupported> {
let Some(input) = input.to_str() else {
return Ok(Err(ParseError(
"expected utf-8 value \"true\" or \"false\"".to_string(),
)));
};
match bool::from_str(input) {
Ok(value) => Ok(Ok(Some(value))),
Err(e) => Ok(Err(ParseError(e.to_string()))),
}
}
fn apply_change(&mut self, change: Self) {
if let Some(new_change) = change {
*self = Some(new_change)
}
}
fn from_path_and_verb<'a, P>(mut path: P, verb: ChangeVerb) -> Result<Self, VerbError>
where
P: Iterator<Item = &'a str>,
{
assert!(
path.next().is_none(),
"OptionChange<Option<T>> implies this is a field"
);
match verb {
ChangeVerb::Set(unparesd) => Ok(Self::parse(&unparesd)??),
ChangeVerb::SetAny(value) => Ok(Some(
*value.downcast().map_err(|_| VerbError::DowncastFailed)?,
)),
ChangeVerb::SetFlag => Ok(Some(true)),
ChangeVerb::Unset => Ok(Some(false)),
ChangeVerb::UseDefault => Ok(Some(CongenInternal::default()?)),
ChangeVerb::List(_) => Err(VerbError::UnsupportedVerb(verb)),
}
}
fn unwrap_field(self) -> Result<Self::Configuration, Self> {
Ok(self.unwrap())
}
}
macro_rules! impl_number {
(unsigned $int:ty) => {
impl_number!($int, false);
};
(signed $int:ty ) => {
impl_number!($int, true);
};
($int:ty, $signed:expr) => {
impl Configuration for $int {}
impl CongenInternal for $int {
type CongenChange = Option<$int>;
fn apply_change_with_inner_default(
&mut self,
change: Self::CongenChange,
_inner_default: Option<fn() -> Box<dyn Any>>,
) {
if let Some(value) = change {
*self = value;
}
}
fn description(field_name: &'static str) -> Description {
FieldDescription {
field_name,
type_name: Self::type_name(),
is_flag: false,
allow_unset: false,
has_default: false,
#[cfg(feature = "clap")]
clap_data: Some(FieldClapDescription {
value_parser: Some(clap::value_parser!($int).into_resettable()),
allow_negative_numbers: $signed,
..Default::default()
}),
}
.into()
}
}
impl CongenChange for Option<$int> {
type Configuration = $int;
fn empty() -> Self {
None
}
fn parse(input: &OsStr) -> Result<Result<Self, ParseError>, NotSupported> {
let Some(input) = input.to_str() else {
return Ok(Err(ParseError("expected utf-8 integer value".to_string())));
};
match <$int>::from_str(input) {
Ok(value) => Ok(Ok(Some(value))),
Err(e) => Ok(Err(ParseError(e.to_string()))),
}
}
fn apply_change(&mut self, change: Self) {
if let Some(new_change) = change {
*self = Some(new_change)
}
}
fn from_path_and_verb<'a, P>(mut path: P, verb: ChangeVerb) -> Result<Self, VerbError>
where
P: Iterator<Item = &'a str>,
{
assert!(
path.next().is_none(),
"OptionChange<Option<T>> implies this is a field"
);
match verb {
ChangeVerb::Set(unparesd) => Ok(Self::parse(&unparesd)??),
ChangeVerb::SetAny(value) => Ok(Some(
*value.downcast().map_err(|_| VerbError::DowncastFailed)?,
)),
ChangeVerb::UseDefault
| ChangeVerb::SetFlag
| ChangeVerb::Unset
| ChangeVerb::List(_) => Err(VerbError::UnsupportedVerb(verb)),
}
}
fn unwrap_field(self) -> Result<Self::Configuration, Self> {
Ok(self.unwrap())
}
}
};
}
impl_number!(unsigned u8);
impl_number!(unsigned u16);
impl_number!(unsigned u32);
impl_number!(unsigned u64);
impl_number!(unsigned u128);
impl_number!(unsigned usize);
impl_number!(signed i8);
impl_number!(signed i16);
impl_number!(signed i32);
impl_number!(signed i64);
impl_number!(signed i128);
impl_number!(signed isize);
impl_number!(signed f32);
impl_number!(signed f64);
macro_rules! impl_string {
($str:ty, $from:expr) => {
impl Configuration for $str {}
impl CongenInternal for $str {
type CongenChange = Option<$str>;
fn apply_change_with_inner_default(
&mut self,
change: Self::CongenChange,
_inner_default: Option<fn() -> Box<dyn Any>>,
) {
if let Some(value) = change {
*self = value;
}
}
fn description(field_name: &'static str) -> Description {
FieldDescription {
field_name,
type_name: Self::type_name(),
is_flag: false,
allow_unset: false,
has_default: false,
#[cfg(feature = "clap")]
clap_data: None,
}
.into()
}
}
impl CongenChange for Option<$str> {
type Configuration = $str;
fn empty() -> Self {
None
}
fn parse(input: &OsStr) -> Result<Result<Self, ParseError>, NotSupported> {
let from = $from;
let parsed: Result<$str, ParseError> = from(input);
match parsed {
Ok(parsed) => Ok(Ok(Some(parsed))),
Err(err) => Ok(Err(err)),
}
}
fn apply_change(&mut self, change: Self) {
if let Some(new_change) = change {
*self = Some(new_change)
}
}
fn from_path_and_verb<'a, P>(mut path: P, verb: ChangeVerb) -> Result<Self, VerbError>
where
P: Iterator<Item = &'a str>,
{
assert!(
path.next().is_none(),
"OptionChange<Option<T>> implies this is a field"
);
match verb {
ChangeVerb::Set(unparesd) => Ok(Self::parse(&unparesd)??),
ChangeVerb::SetAny(value) => Ok(Some(
*value.downcast().map_err(|_| VerbError::DowncastFailed)?,
)),
ChangeVerb::UseDefault
| ChangeVerb::SetFlag
| ChangeVerb::Unset
| ChangeVerb::List(_) => Err(VerbError::UnsupportedVerb(verb)),
}
}
fn unwrap_field(self) -> Result<Self::Configuration, Self> {
Ok(self.unwrap())
}
}
};
}
impl_string!(String, |input: &OsStr| {
input
.to_str()
.ok_or_else(|| ParseError("expected utf-8 string".to_string()))
.map(|str| str.to_owned())
});
impl_string!(std::ffi::CString, |input: &OsStr| {
std::ffi::CString::new(input.as_encoded_bytes())
.map_err(|_| ParseError("Null byte in str".to_string()))
});
impl_string!(std::ffi::OsString, |input: &OsStr| { Ok(input.to_owned()) });