congen 0.2.0

congen helps you build configuration systems that support partial updates from structured changes and CLI input
Documentation
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()) });