miniconf 0.20.1

Serialize/deserialize/access reflection for trees
Documentation
use core::fmt::Write;

#[cfg(feature = "alloc")]
use alloc::vec::Vec;

use serde::{Deserialize, Serialize};

use crate::{DescendError, Internal, IntoKeys, Key, Schema, Track, Transcode};

// index
macro_rules! impl_key_integer {
    ($($t:ty)+) => {$(
        impl Key for $t {
            fn find(&self, internal: &Internal) -> Option<usize> {
                (*self).try_into().ok().filter(|i| *i < internal.len().get())
            }
        }
    )+};
}
impl_key_integer!(usize u8 u16 u32 u64 u128 isize i8 i16 i32 i64 i128);

/// Indices of `usize` to identify a node in a `TreeSchema`
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Default)]
pub struct Indices<T: ?Sized> {
    len: usize,
    data: T,
}

impl<T> Indices<T> {
    /// Create a new `Indices`
    pub fn new(data: T, len: usize) -> Self {
        Self { len, data }
    }

    /// The length of the indices keys
    pub fn len(&self) -> usize {
        self.len
    }

    /// See [`Self::len()`]
    pub fn is_empty(&self) -> bool {
        self.len == 0
    }

    /// Split indices into data and length
    pub fn into_inner(self) -> (T, usize) {
        (self.data, self.len)
    }
}

impl<T> From<T> for Indices<T> {
    fn from(value: T) -> Self {
        Self {
            len: 0,
            data: value,
        }
    }
}

impl<U, T: AsRef<[U]> + ?Sized> AsRef<[U]> for Indices<T> {
    fn as_ref(&self) -> &[U] {
        &self.data.as_ref()[..self.len]
    }
}

impl<'a, U, T: ?Sized> IntoIterator for &'a Indices<T>
where
    &'a T: IntoIterator<Item = U>,
{
    type Item = U;

    type IntoIter = core::iter::Take<<&'a T as IntoIterator>::IntoIter>;

    fn into_iter(self) -> Self::IntoIter {
        (&self.data).into_iter().take(self.len)
    }
}

impl<T: AsMut<[usize]> + ?Sized> Transcode for Indices<T> {
    type Error = <[usize] as Transcode>::Error;

    fn transcode(
        &mut self,
        schema: &Schema,
        keys: impl IntoKeys,
    ) -> Result<(), DescendError<Self::Error>> {
        let mut slic = Track::new(self.data.as_mut());
        let ret = slic.transcode(schema, keys);
        self.len = slic.depth();
        ret
    }
}

macro_rules! impl_transcode_slice {
    ($($t:ty)+) => {$(
        impl Transcode for [$t] {
            type Error = ();

            fn transcode(&mut self, schema: &Schema, keys: impl IntoKeys) -> Result<(), DescendError<Self::Error>> {
                let mut it = self.iter_mut();
                schema.descend(keys.into_keys(), |_meta, idx_schema| {
                    if let Some((index, internal)) = idx_schema {
                        debug_assert!(internal.len().get() <= <$t>::MAX as _);
                        let i = index.try_into().or(Err(()))?;
                        let idx = it.next().ok_or(())?;
                        *idx = i;
                    }
                    Ok(())
                })
            }
        }
    )+};
}
impl_transcode_slice!(usize u8 u16 u32 u64 u128 isize i8 i16 i32 i64 i128);

#[cfg(feature = "alloc")]
impl<T> Transcode for Vec<T>
where
    usize: TryInto<T>,
{
    type Error = <usize as TryInto<T>>::Error;

    fn transcode(
        &mut self,
        schema: &Schema,
        keys: impl IntoKeys,
    ) -> Result<(), DescendError<Self::Error>> {
        schema.descend(keys.into_keys(), |_meta, idx_schema| {
            if let Some((index, _schema)) = idx_schema {
                self.push(index.try_into()?);
            }
            Ok(())
        })
    }
}

#[cfg(feature = "heapless")]
impl<T, const N: usize> Transcode for heapless::Vec<T, N>
where
    usize: TryInto<T>,
{
    type Error = ();

    fn transcode(
        &mut self,
        schema: &Schema,
        keys: impl IntoKeys,
    ) -> Result<(), DescendError<Self::Error>> {
        schema.descend(keys.into_keys(), |_meta, idx_schema| {
            if let Some((index, _schema)) = idx_schema {
                let i = index.try_into().or(Err(()))?;
                self.push(i).or(Err(()))?;
            }
            Ok(())
        })
    }
}

#[cfg(feature = "heapless-09")]
impl<T, const N: usize> Transcode for heapless_09::Vec<T, N>
where
    usize: TryInto<T>,
{
    type Error = ();

    fn transcode(
        &mut self,
        schema: &Schema,
        keys: impl IntoKeys,
    ) -> Result<(), DescendError<Self::Error>> {
        schema.descend(keys.into_keys(), |_meta, idx_schema| {
            if let Some((index, _schema)) = idx_schema {
                let i = index.try_into().or(Err(()))?;
                self.push(i).or(Err(()))?;
            }
            Ok(())
        })
    }
}

////////////////////////////////////////////////////////////////////

// name
impl Key for str {
    fn find(&self, internal: &Internal) -> Option<usize> {
        internal.get_index(self)
    }
}

/// Path with named keys separated by a separator char
///
/// The path will either be empty or start with the separator.
///
/// * `path: T`: A `Write` to write the separators and node names into during `Transcode`.
///   See also [Schema::transcode()] and `Shape.max_length` for upper bounds
///   on path length. Can also be a `AsRef<str>` to implement `IntoKeys` (see [`crate::KeysIter`]).
/// * `const S: char`: The path hierarchy separator to be inserted before each name,
///   e.g. `'/'`.
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[repr(transparent)]
#[serde(transparent)]
pub struct Path<T: ?Sized, const S: char>(pub T);

impl<T: ?Sized, const S: char> Path<T, S> {
    /// The path hierarchy separator
    pub const fn separator(&self) -> char {
        S
    }
}

impl<T, const S: char> Path<T, S> {
    /// Extract just the path
    pub fn into_inner(self) -> T {
        self.0
    }
}

impl<T: AsRef<str> + ?Sized, const S: char> AsRef<str> for Path<T, S> {
    fn as_ref(&self) -> &str {
        self.0.as_ref()
    }
}

impl<T: core::fmt::Display, const S: char> core::fmt::Display for Path<T, S> {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        self.0.fmt(f)
    }
}

/// String split/skip wrapper, smaller/simpler than `.split(S).skip(1)`
#[derive(Copy, Clone, Default, Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
pub struct PathIter<'a> {
    data: Option<&'a str>,
    sep: char,
}

impl<'a> PathIter<'a> {
    /// Create a new `PathIter`
    pub fn new(data: Option<&'a str>, sep: char) -> Self {
        Self { data, sep }
    }

    /// Create a new `PathIter` starting at the root.
    ///
    /// This calls `next()` once to pop everything up to and including the first separator.
    pub fn root(data: &'a str, sep: char) -> Self {
        let mut s = Self::new(Some(data), sep);
        // Skip the first part to disambiguate between
        // the one-Key Keys `[""]` and the zero-Key Keys `[]`.
        // This is relevant in the case of e.g. `Option` and newtypes.
        // See the corresponding unittests (`just_option`).
        // It implies that Paths start with the separator
        // or are empty. Everything before the first separator is ignored.
        // This also means that paths can always be concatenated without having to
        // worry about adding/trimming leading or trailing separators.
        s.next();
        s
    }
}

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

    fn next(&mut self) -> Option<Self::Item> {
        self.data.map(|s| {
            let pos = s
                .chars()
                .map_while(|c| (c != self.sep).then_some(c.len_utf8()))
                .sum();
            let (left, right) = s.split_at(pos);
            self.data = right.get(self.sep.len_utf8()..);
            left
        })
    }
}

impl core::iter::FusedIterator for PathIter<'_> {}

impl<'a, T: AsRef<str> + ?Sized, const S: char> IntoKeys for Path<&'a T, S> {
    type IntoKeys = <PathIter<'a> as IntoKeys>::IntoKeys;

    fn into_keys(self) -> Self::IntoKeys {
        PathIter::root(self.0.as_ref(), S).into_keys()
    }
}

impl<'a, T: AsRef<str> + ?Sized, const S: char> IntoKeys for &'a Path<T, S> {
    type IntoKeys = <Path<&'a str, S> as IntoKeys>::IntoKeys;

    fn into_keys(self) -> Self::IntoKeys {
        PathIter::root(self.0.as_ref(), S).into_keys()
    }
}

impl<T: Write + ?Sized, const S: char> Transcode for Path<T, S> {
    type Error = core::fmt::Error;

    fn transcode(
        &mut self,
        schema: &Schema,
        keys: impl IntoKeys,
    ) -> Result<(), DescendError<Self::Error>> {
        schema.descend(keys.into_keys(), |_meta, idx_schema| {
            if let Some((index, internal)) = idx_schema {
                self.0.write_char(S)?;
                let mut buf = itoa::Buffer::new();
                let name = internal
                    .get_name(index)
                    .unwrap_or_else(|| buf.format(index));
                debug_assert!(!name.contains(S));
                self.0.write_str(name)
            } else {
                Ok(())
            }
        })
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn strsplit() {
        use heapless_09::Vec;
        for p in ["/d/1", "/a/bccc//d/e/", "", "/", "a/b", "a"] {
            let a: Vec<_, 10> = PathIter::root(p, '/').collect();
            let b: Vec<_, 10> = p.split('/').skip(1).collect();
            assert_eq!(a, b);
        }
    }
}