nstree 1.0.0

construct branched 'namespace strings' for nested subcomponents, often for logging
Documentation
use crate::NamespaceComponent;

use super::{NamespaceComponentsHint, NamespacePath};

/// Something that creates a [`ComponentsSizeHintOverrideIter`] with the given [`IntoIterator`] and
/// [`NamespaceComponentsHint`]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct IntoComponentsSizeHintOverrideIter<IntoIter> {
    hint_override: NamespaceComponentsHint,
    into_iter: IntoIter,
}

impl<II> IntoComponentsSizeHintOverrideIter<II> {
    #[inline]
    pub(super) const fn new(into_iter: II, hint: NamespaceComponentsHint) -> Self {
        Self {
            hint_override: hint,
            into_iter,
        }
    }
}

impl<I: IntoIterator> IntoIterator for IntoComponentsSizeHintOverrideIter<I>
where
    ComponentsSizeHintOverrideIter<I::IntoIter>: Iterator<Item = I::Item>,
{
    type Item = I::Item;

    type IntoIter = ComponentsSizeHintOverrideIter<I::IntoIter>;

    #[inline]
    fn into_iter(self) -> Self::IntoIter {
        let Self {
            hint_override,
            into_iter,
        } = self;
        ComponentsSizeHintOverrideIter::new(into_iter.into_iter(), hint_override)
    }
}

/// Iterator that overrides the [`Iterator::size_hint`] using [`NamespaceComponentsHint`].
///
/// Should the iterator turn out to be longer than expected (i.e. we end up with zero components),
/// then size_hint will revert to using the inner iterator's size_hint. If it returns [`None`]
/// early, then we also reset things so that default size_hint is used.
///
/// This is used to make allocation in [`FromIterator`] impls more effective.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ComponentsSizeHintOverrideIter<Iter> {
    hint_override: NamespaceComponentsHint,
    iter: Iter,
}

impl<I> ComponentsSizeHintOverrideIter<I> {
    #[must_use]
    #[inline]
    pub(super) const fn new(iterator: I, hint: NamespaceComponentsHint) -> Self {
        Self {
            hint_override: hint,
            iter: iterator,
        }
    }

    /// If true, then the component hint has been flatlined to zero so we know we either overran
    /// the hint or underran the hint for sure if we're still iterating at this point.
    #[inline]
    const fn hint_was_wrong(&self) -> bool {
        self.hint_override.component_count == 0
    }

    /// "Remove" the given component quantity from the components hint. This will use saturating
    /// subtraction such that if there's "too much" then it just gets flatlined to zero. If there
    /// are zero components remaining, then the total "byte length" is also reset to zero.
    #[inline]
    fn remove_components(&mut self, actual_component_info: NamespaceComponentsHint) {
        self.hint_override.component_count = self
            .hint_override
            .component_count
            .saturating_sub(actual_component_info.component_count);
        if self.hint_override.component_count == 0 {
            self.hint_override.total_component_length = 0;
        } else {
            self.hint_override.total_component_length = self
                .hint_override
                .total_component_length
                .saturating_sub(actual_component_info.total_component_length);
        }
    }

    /// Discard component hint information to extract inner iterator.
    ///
    /// Useful for any consuming [`Iterator`] methods.
    #[inline(always)]
    pub fn into_inner(self) -> I {
        self.iter
    }
}

// For many of these functions - especially the consuming ones - we can simply avoid caring about
// the hint
impl<'s, I: Iterator<Item = NamespaceComponent<'s>>> Iterator
    for ComponentsSizeHintOverrideIter<I>
{
    type Item = NamespaceComponent<'s>;

    fn next(&mut self) -> Option<Self::Item> {
        let output = self.iter.next();
        // If we have been emptied, then remove the entire amount we currently have. If not, then
        // calculate.
        let to_remove = output
            .as_ref()
            .map(NamespacePath::components_hint)
            .unwrap_or_else(|| self.hint_override);
        self.remove_components(to_remove);
        output
    }

    #[inline]
    fn size_hint(&self) -> (usize, Option<usize>) {
        if self.hint_was_wrong() {
            self.iter.size_hint()
        } else {
            (self.hint_override.component_count, None)
        }
    }

    #[inline]
    fn last(self) -> Option<Self::Item>
    where
        Self: Sized,
    {
        self.into_inner().last()
    }

    #[inline]
    fn for_each<F>(self, f: F)
    where
        Self: Sized,
        F: FnMut(Self::Item),
    {
        self.into_inner().for_each(f);
    }

    #[inline]
    fn fold<B, F>(self, init: B, f: F) -> B
    where
        Self: Sized,
        F: FnMut(B, Self::Item) -> B,
    {
        self.into_inner().fold(init, f)
    }

    #[inline]
    fn reduce<F>(self, f: F) -> Option<Self::Item>
    where
        Self: Sized,
        F: FnMut(Self::Item, Self::Item) -> Self::Item,
    {
        self.into_inner().reduce(f)
    }

    #[inline]
    fn max(self) -> Option<Self::Item>
    where
        Self: Sized,
        Self::Item: Ord,
    {
        self.into_inner().max()
    }

    #[inline]
    fn min(self) -> Option<Self::Item>
    where
        Self: Sized,
        Self::Item: Ord,
    {
        self.into_inner().min()
    }

    #[inline]
    fn max_by_key<B: Ord, F>(self, f: F) -> Option<Self::Item>
    where
        Self: Sized,
        F: FnMut(&Self::Item) -> B,
    {
        self.into_inner().max_by_key(f)
    }

    #[inline]
    fn max_by<F>(self, compare: F) -> Option<Self::Item>
    where
        Self: Sized,
        F: FnMut(&Self::Item, &Self::Item) -> core::cmp::Ordering,
    {
        self.into_inner().max_by(compare)
    }

    #[inline]
    fn min_by_key<B: Ord, F>(self, f: F) -> Option<Self::Item>
    where
        Self: Sized,
        F: FnMut(&Self::Item) -> B,
    {
        self.into_inner().min_by_key(f)
    }

    #[inline]
    fn min_by<F>(self, compare: F) -> Option<Self::Item>
    where
        Self: Sized,
        F: FnMut(&Self::Item, &Self::Item) -> core::cmp::Ordering,
    {
        self.into_inner().min_by(compare)
    }

    #[inline]
    fn cmp<It>(self, other: It) -> core::cmp::Ordering
    where
        It: IntoIterator<Item = Self::Item>,
        Self::Item: Ord,
        Self: Sized,
    {
        self.into_inner().cmp(other)
    }

    #[inline]
    fn partial_cmp<It>(self, other: It) -> Option<core::cmp::Ordering>
    where
        It: IntoIterator,
        Self::Item: PartialOrd<It::Item>,
        Self: Sized,
    {
        self.into_inner().partial_cmp(other)
    }

    #[inline]
    fn eq<It>(self, other: It) -> bool
    where
        It: IntoIterator,
        Self::Item: PartialEq<It::Item>,
        Self: Sized,
    {
        self.into_inner().eq(other)
    }

    #[inline]
    fn ne<It>(self, other: It) -> bool
    where
        It: IntoIterator,
        Self::Item: PartialEq<It::Item>,
        Self: Sized,
    {
        self.into_inner().ne(other)
    }

    #[inline]
    fn lt<It>(self, other: It) -> bool
    where
        It: IntoIterator,
        Self::Item: PartialOrd<It::Item>,
        Self: Sized,
    {
        self.into_inner().lt(other)
    }

    #[inline]
    fn le<It>(self, other: It) -> bool
    where
        It: IntoIterator,
        Self::Item: PartialOrd<It::Item>,
        Self: Sized,
    {
        self.into_inner().le(other)
    }

    #[inline]
    fn gt<It>(self, other: It) -> bool
    where
        It: IntoIterator,
        Self::Item: PartialOrd<It::Item>,
        Self: Sized,
    {
        self.into_inner().gt(other)
    }

    #[inline]
    fn ge<It>(self, other: It) -> bool
    where
        It: IntoIterator,
        Self::Item: PartialOrd<It::Item>,
        Self: Sized,
    {
        self.into_inner().ge(other)
    }

    #[inline]
    fn is_sorted(self) -> bool
    where
        Self: Sized,
        Self::Item: PartialOrd,
    {
        self.into_inner().is_sorted()
    }

    #[inline]
    fn is_sorted_by<F>(self, compare: F) -> bool
    where
        Self: Sized,
        F: FnMut(&Self::Item, &Self::Item) -> bool,
    {
        self.into_inner().is_sorted_by(compare)
    }

    #[inline]
    fn is_sorted_by_key<F, K>(self, f: F) -> bool
    where
        Self: Sized,
        F: FnMut(Self::Item) -> K,
        K: PartialOrd,
    {
        self.into_inner().is_sorted_by_key(f)
    }
}

impl<'s, I: DoubleEndedIterator + Iterator<Item = NamespaceComponent<'s>>> DoubleEndedIterator
    for ComponentsSizeHintOverrideIter<I>
{
    fn next_back(&mut self) -> Option<Self::Item> {
        let output = self.iter.next_back();
        // Either remove the actual amount, or if the Option was None, then the hint was wrong if
        // it wasn't zero so force it to be zero by removing the entire hint.
        let removed_amount = output
            .as_ref()
            .map(NamespacePath::components_hint)
            .unwrap_or_else(|| self.hint_override);
        self.remove_components(removed_amount);
        output
    }

    #[inline]
    fn rfold<B, F>(self, init: B, f: F) -> B
    where
        Self: Sized,
        F: FnMut(B, Self::Item) -> B,
    {
        self.into_inner().rfold(init, f)
    }
}

// nstree - nested namespace string-generating abstraction library
// Copyright (C) 2025  Matti <infomorphic-matti at protonmail dot com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
// ------
// SPDX-License-Identifier: GPL-3.0-or-later