dynconfig 0.1.2

Dynamically change fields of a struct based on a path.
Documentation
use core::fmt;
use core::iter::FusedIterator;

/// An iterator over the components of the path of a field.
///
/// [`Path`] is an iterator over the components of a path in the format:
///
/// `<component> $(.<component>)*`
///
/// See: [`Dynconfig`].
///
/// [`Dynconfig`]: crate::Dynconfig
#[derive(Clone)]
pub struct Path<'a> {
    full: &'a str,
    idx: usize,
}

impl<'a> Path<'a> {
    /// Create a new path from `full`.
    pub fn new(full: &'a str) -> Self {
        Self { full, idx: 0 }
    }

    /// Get the full path.
    ///
    /// This method returns the `full` argument passed in to [`Path::new`].
    pub fn full(&self) -> &'a str {
        self.full
    }
}

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

impl<'a> From<&'a str> for Path<'a> {
    fn from(path: &'a str) -> Self {
        Self::new(path)
    }
}

impl<'a> Iterator for Path<'a> {
    type Item = &'a str;

    fn next(&mut self) -> Option<Self::Item> {
        if self.idx >= self.full.len() {
            return None;
        }

        match self.full[self.idx..].find('.') {
            Some(next_sep_idx) => {
                let x = &self.full[self.idx..][..next_sep_idx];
                self.idx = self.idx.strict_add(next_sep_idx).strict_add('.'.len_utf8());
                Some(x)
            }
            None => {
                let x = &self.full[self.idx..];
                self.idx = self.full.len();
                Some(x)
            }
        }
    }

    fn size_hint(&self) -> (usize, Option<usize>) {
        (0, Some(self.full.len().strict_sub(self.idx)))
    }
}

impl ExactSizeIterator for Path<'_> {
    fn len(&self) -> usize {
        if self.idx >= self.full.len() {
            return 0;
        }

        self.full[self.idx..]
            .chars()
            .filter(|c| *c == '.')
            .count()
            .strict_add(1)
    }
}

impl Path<'_> {
    /// Check whether the [`Path`] is empty.
    ///
    /// A path is empty if and only if the iterator does not return any more
    /// items.
    pub fn is_empty(&self) -> bool {
        self.idx >= self.full.len()
    }
}

impl FusedIterator for Path<'_> {}

#[cfg(test)]
mod tests {
    use core::iter::zip;

    use super::*;

    fn x(x: &str, expected: &[&str]) {
        let path = Path::new(x);
        assert_eq!(path.len(), expected.len());

        for (a, b) in zip(path, expected) {
            assert_eq!(a, *b);
        }
    }

    #[test]
    fn test_simple() {
        x("foo.bar", &["foo", "bar"]);
        x("foo.bar.foobar.barfoo", &["foo", "bar", "foobar", "barfoo"]);
        x("foo", &["foo"]);
    }

    #[test]
    fn test_empty() {
        x("", &[]);
        x(".", &["", ""]);
        x("..", &["", "", ""]);
        x("...", &["", "", "", ""]);
    }

    #[test]
    fn test_end_in_sep() {
        x("foo.", &["foo", ""]);
        x("foo.bar.", &["foo", "bar", ""]);
    }

    #[test]
    fn test_start_with_sep() {
        x(".foo", &["", "foo"]);
        x(".foo.bar", &["", "foo", "bar"]);
    }

    #[test]
    fn test_double_sep() {
        x("foo..bar.foobar", &["foo", "", "bar", "foobar"]);
        x("barfoo.foo..bar", &["barfoo", "foo", "", "bar"]);
    }

    #[test]
    fn test_triple_sep() {
        x("foo...bar.foobar", &["foo", "", "", "bar", "foobar"]);
        x("barfoo.foo...bar", &["barfoo", "foo", "", "", "bar"]);
    }
}