dynconfig 0.1.2

Dynamically change fields of a struct based on a path.
Documentation
//! [`Pair`].

use core::fmt;

use crate::path::Path;
use crate::{Dynconfig, Result};

/// A [`Path`] and `value`.
///
/// Can be parsed from any string in the format: `<path> = <value>`.
///
/// # Example
/// ```rust
/// # use dynconfig::{Dynconfig, Pair};
/// #[derive(Default, Dynconfig)]
/// struct Foo {
///     x: u32,
///     y: usize,
///     z: f32,
/// }
///
/// let mut foo = Foo::default();
///
/// let p1 = Pair::try_from("x=32").unwrap();
/// let p2 = Pair::try_from("foo.y   =43").unwrap();
/// let p3 = Pair::try_from("z =      23").unwrap();
/// # let _e1 = Pair::try_from("x 32").unwrap_err();
/// # let _e2 = Pair::try_from(" 32").unwrap_err();
/// # let _e3 = Pair::try_from("z").unwrap_err();
///
/// assert_eq!(p1.0.full(), "x");
/// assert_eq!(p1.1, "32");
///
/// assert_eq!(p2.0.full(), "foo.y");
/// assert_eq!(p2.1, "43");
///
/// assert_eq!(p3.0.full(), "z");
/// assert_eq!(p3.1, "23");
///
/// dynconfig::set(&mut foo, p1).unwrap();
/// dynconfig::set(&mut foo, p2).unwrap_err();
/// dynconfig::set(&mut foo, p3).unwrap();
///
/// assert_eq!(foo.x, 32);
/// assert_eq!(foo.y, 0);
/// assert_eq!(foo.z, 23.0);
/// ```
#[derive(Clone)]
pub struct Pair<'a>(pub Path<'a>, pub &'a str);

impl<'a> From<(Path<'a>, &'a str)> for Pair<'a> {
    fn from(x: (Path<'a>, &'a str)) -> Self {
        Self(x.0, x.1)
    }
}

impl<'a> From<Pair<'a>> for (Path<'a>, &'a str) {
    fn from(pair: Pair<'a>) -> Self {
        (pair.0, pair.1)
    }
}

impl<'a> Pair<'a> {
    /// Set `self` on `target`.
    ///
    /// Equivalent to: [`target.set()`](Dynconfig::set).
    ///
    /// See: [`Pair`].
    pub fn set_on<D>(&self, target: &mut D) -> Result<()>
    where
        D: Dynconfig,
    {
        let mut path = self.0.clone();
        target.set(&mut path, self.1)
    }
}

impl fmt::Debug for Pair<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "\"{}={}\"", self.0.full(), self.1)
    }
}

impl fmt::Display for Pair<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if f.alternate() {
            write!(f, "{} = {}", self.0.full(), self.1)
        } else {
            write!(f, "{}={}", self.0.full(), self.1)
        }
    }
}

/// An error which can be returned when parsing a [`Pair`].
#[derive(Debug, Clone)]
pub struct ParsePairError();

impl fmt::Display for ParsePairError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "missing '='")
    }
}

impl core::error::Error for ParsePairError {}

impl<'a> TryFrom<&'a str> for Pair<'a> {
    type Error = ParsePairError;

    fn try_from(value: &'a str) -> Result<Self, Self::Error> {
        let Some((path, value)) = value.split_once('=') else {
            return Err(ParsePairError());
        };

        let path = path.trim();
        let path = Path::new(path);

        let value = value.trim();

        Ok(Self(path, value))
    }
}

/// Shorthand for parsing `x` into a [`Pair`].
#[inline]
pub fn parse<'a>(x: &'a str) -> Result<Pair<'a>, ParsePairError> {
    x.try_into()
}