conciliator 0.3.10

[WIP] Library for interactive CLI programs
Documentation
use crate::{
	Buffer,
	Conciliator,
	Inline
};
use crate::style::{
	Color,
	Paint
};
use crate::data::tree;

use super::Print;

/// Tree structure of things to print, recursively traversed
///
/// Instead of constraining the user to any particular data structure, this object is built around a *traversal function*.
/// This function is used to traverse the tree depth-first, starting from an iterator of "root" nodes.
/// 
/// Requires [`ExactSizeIterator`]s so that it can print the correct symbols (`├─` vs `└─`).
///
/// For example:
/// ```rust
/// # let con = conciliator::init();
/// # use conciliator::{Conciliator, Tree, Inline, Paint, Buffer};
/// struct Animal (&'static str, Vec<Animal>);
/// let tree = [
///     Animal ("Reptiles", vec![
///         Animal ("Frogs", vec![]),
///         Animal ("Lizards", vec![
///             Animal ("Chameleon", vec![]),
///             Animal ("Gecko", vec![]),
///         ])
///     ]),
///     Animal ("Mammals", vec![
///         Animal ("Tiger", vec![]),
///         Animal ("Monkey", vec![])
///     ]),
///     Animal ("Crab", vec![])
/// ];
/// impl Inline for Animal {
///     fn inline(&self, buffer: &mut Buffer) {
///         buffer.push_plain(self.0);
///     }
/// }
/// con.print(Tree::new(
///     "Animals:",
///     tree.iter(),
///     |a| Some(a.1.iter())
/// ));
/// ```
/// Produces something like:
/// ```text
/// [ ┬ ] Animals:
///   ├─Reptiles
///   │ ├─Frogs
///   │ └─Lizards
///   │   ├─Chameleon
///   │   └─Gecko
///   ├─Mammals
///   │ ├─Tiger
///   │ └─Monkey
///   └─Crab
/// ```
/// Unfortunately, unlike the [`List`](super::List), the Tree does not currently support a `formatter` - the items need to be [`Inline`](crate::Inline).
pub struct Tree<'s, I: ExactSizeIterator<Item = T>, T, F> {
	root_nodes: I,
	description: &'s str,
	traverser: F
}

/// For the [`Tree`]: a chunk of the prefix (the line-drawing symbols)
///
/// The prefix stack only contains `Stem` or `None`, though
enum Chunk {
	Stem,
	Knot,
	Tail,
	None
}

/*
 *	TREE
 */

impl<'s, I, T, F> Tree<'s, I, T, F>
	where
		I: ExactSizeIterator<Item = T>,
		F: FnMut(T) -> Option<I>,
		T: Inline
{
	/// Create a new [`Tree`]
	///
	/// The `traverser` function is called with each item (node) in the tree and returns an iterator of its children: `FnMut(T) -> Option<ExactSizeIterator<Item = T>>`.
	///
	/// The `Option<…>` return type was chosen to simplify printing trees with distinct Branch / Leaf types, for which the `traverser` function, if they are merged into an `enum Node`, might otherwise have difficulty creating an empty `ExactSizeIterator` in the leaf case.
	pub fn new(description: &'s str, root_nodes: I, traverser: F) -> Self {
		Self {root_nodes, description, traverser}
	}
	/// Wraps/Maps an iterator to also return true for the last item
	fn with_last(items: I) -> impl Iterator<Item = (T, bool)> {
		let last = items.len().saturating_sub(1);
		items
			.enumerate()
			.map(move |(i, item)| (item, i == last))
	}
}

impl<'s, I, T, F> Print for Tree<'s, I, T, F>
	where
		I: ExactSizeIterator<Item = T>,
		F: FnMut(T) -> Option<I>,
		T: Inline
{
	fn print<C: Conciliator + ?Sized>(mut self, con: &C) {
		let mut iter_stack: Vec<_> = vec![Self::with_last(self.root_nodes)];
		let mut prefix_stack: Vec<Chunk> = vec![Chunk::None];

		con.get_line()
			.tag(Color::Omega, tree::ROOT)
			.push_plain(&self.description);

		// outer loop of iterators
		'outer: while let Some(mut iter) = iter_stack.pop() {
			// inner loop of items
			while let Some((item, is_last)) = iter.next() {
				let mut line = con.get_line();
				prefix_stack.iter().for_each(|c| {line.push(c);});
				line
					.push(if is_last {&Chunk::Tail} else {&Chunk::Knot})
					.push(&item);

				if let Some(children) = (self.traverser)(item) {
					// extend the prefix to indent the children accordingly
					prefix_stack.push(
						if is_last {Chunk::None} else {Chunk::Stem}
					);
					// put the current iterator back on the stack
					iter_stack.push(iter);
					// put this new iterator on the stack
					iter_stack.push(Self::with_last(children));
					// poor mans depth-first traversal
					continue 'outer;
				}
			}
			// this sub-tree is finished
			prefix_stack.pop();
		}
	}

}

/*
 *	CHUNK
 */

impl Inline for Chunk {
	fn inline(&self, buffer: &mut Buffer) {
		use tree::*;
		match self {
			Self::Stem => buffer.push_omega(STEM),
			Self::Knot => buffer.push_omega(KNOT),
			Self::Tail => buffer.push_omega(TAIL),
			Self::None => buffer.push_plain("  ")
		};
	}
}