conciliator 0.3.10

[WIP] Library for interactive CLI programs
Documentation
use std::fmt::Display;
use std::ops::{
	Deref,
	DerefMut
};

use crate::Buffer;
use crate::style::{
	Color,
	Paint
};

/// [`Display`](std::fmt::Display) with colors
///
/// Write `self` into the buffer with colors.
/// Please use the named colors provided by the color [`Palette`](crate::style::Palette).
/// While it is also possible to use all terminal colors directly, this should only be done as an exception.
/// This way, it is possible to customize the appearance by switching color [`Palette`](crate::style::Palette)s.
///
/// Implementations should not write newlines.
/// For multi-line segments, implement the [`Print`](crate::Print) trait instead.
pub trait Inline {
	/// Write `self` into the buffer with colors
	fn inline(&self, buffer: &mut Buffer);
}

/// Types that are [`Inline`] *or* [`Display`]
///
/// This is a helper trait that is blanket-implemented for **both** [`Inline`] and [`Display`] types.
/// [`Display`] types are written to the [`Buffer`] without colors.
/// Useful for functions/structs where the marker type `M` can be inferred.
///
/// This trait is sealed and cannot be implemented.
///
/// # Implementation
///
/// Because Rust does not have any specialization or way to express `where T: NOT Trait`, it is not possible to blanket-implement the (exact) same trait twice in such a way that a conflict *could* exist.
/// It would be impossible to blanket-implement a `trait Pushable { … }` for **both** [`Inline`] and [`Display`] types: a type could exist that is itself [`Inline`] as well as [`Display`] and this type would have two different implementations of `Pushable`.
///
/// The workaround used to resolve this is described in detail below.
///
/// This trait has a generic type parameter `M` that exists solely as a "marker" to disambiguate implementations.
/// Then a different empty marker type is specified for each of the implementations:
/// - `impl<T: Display> Pushable<marker::AsDisplay> for T`
/// - `impl<T: Inline> Pushable<marker::AsInline> for T`
///
/// This sidesteps the conflict: `Pushable<AsDisplay>` and `Pushable<AsInline>` are essentially just two different traits - there is no issue with having both implemented for the same type.
/// But then again, two different traits is what we started with: [`Inline`] and [`Display`].
/// So, why bother?
///
/// The trick to using this trait is to be generic over a `T: Pushable<M>` for *any* `M` and getting type inference to figure out the correct `M`.
/// So, for a function to accept any type that is either [`Inline`] or [`Display`], it would look like this:
/// ```
/// # trait Pushable<M> {}
/// fn push_any<M, T: Pushable<M>>(thing: T) {}
/// ```
/// Somewhat surprisingly, this works very well!  
/// The marker type is (correctly) inferred for all types that are **either** [`Inline`] or [`Display`].
/// Theoretically, it is even possible to pick which implementations to use when both apply by disambiguating the function call with type annotations like so: `push_any::<marker::AsInline, _>(…)` (note the `_` to have the rest inferred).
/// But because this is inconvenient in practice, the marker types are (currently) not exposed.
///
/// # Caveats
///
/// Arguably, this workaround is only really useful in cases where type inference can be relied on (certainly, those are the only cases where it is convenient).
/// Unfortunately, type inference can only be relied on as much as the types can be relied on to not implement *the other* trait.
/// In practice, this means that one should think about whether a type will need to implement both [`Inline`] and [`Display`] beforehand (and consider whether using a [newtype] instead is better).
/// Otherwise, it is possible to wind up having to fix all the places where type inference was relied on but no longer can be.
///
/// Crucially, **for a type that is [`Inline`] or [`Display`], implementing the other trait as well could be a breaking change**.
/// This is surprising because generally implementing a trait is not considered breaking, but in this case it could lead to code failing to compile with `type annotations needed`.  
/// Because of Rust's [orphan rules], this problem is merely theoretical when it comes to third party crates.
/// For any type, such a surprise breaking change could only be introduced by [`conciliator`](crate) implementing [`Inline`] for a third-party type that is already [`Display`] (I won't do this) or a third-party crate deciding to depend on [`conciliator`](crate) and doing so for it's own type (very unlikely).  
/// Note though that this only applies because crates are unable to implement `Pushable` directly.
///
/// [newtype]: https://doc.rust-lang.org/book/ch19-03-advanced-traits.html#using-the-newtype-pattern-to-implement-external-traits-on-external-types
/// [orphan rules]: https://rust-lang.github.io/chalk/book/clauses/coherence.html#coherence
pub trait Pushable<M>: private::Sealed<M> {
	/// Either write to a buffer without colors using [`Display`], or [`Inline`] into it
	fn push_into(&self, buffer: &mut Buffer);
}

/// Private module to seal [`Pushable`]
mod private {
	/// Empty but inaccessible trait to seal [`Pushable`]
	///
	/// For [`Pushable`] to be blanket-implemented, [`Sealed`] will need to be as well, but if it were implemented for *any* `T` then it wouldn't do much sealing.
	/// Because of this, it needs to be generic over `M` as well and only be implemented for the same `T` & `M` combinations that [`Pushable`] is.
	///
	/// [`Pushable`]: super::Pushable
	pub trait Sealed<M> {}
}

/// Marker types to disambiguate [`Pushable`] implementations
///
/// See [`Pushable`] for a detailed explanation.
mod marker {
	/// Empty marker type for the `T: `[`Display`](super::Display) [`Pushable`](super::Pushable) blanket impl
	pub struct AsDisplay;
	/// Empty marker type for the `T: `[`Inline`](super::Inline) [`Pushable`](super::Pushable) blanket impl
	pub struct AsInline;
}

/// Helper type that calls the wrapped closure when [`Inline`]d
///
/// [`Inline`] is implemented only for [`Fn`]`(&mut `[`Buffer`]`)` closures.
/// [`FnMut`] closures don't work because [`Inline::inline`] has an immutable `&self`.
///
/// This is the type returned by [`inline!`](crate::inline).
pub struct LambdaFmt<F: Fn(&mut Buffer)>(pub F);

/// Concatenate [`Pushable`] types to become [`Inline`] as one
///
/// This macro takes any number of unambiguously [`Pushable`] values ([Expressions] to be precise) and returns a value that, when [`Inline`]d, pushes all the provided values into the buffer.
/// Simply put, it concatenates all of its arguments into a single value that implements [`Inline`].
///
/// For example:
/// ```
/// let con = conciliator::init();
/// use conciliator::{Conciliator, inline};
/// con.status(inline!("Hello", ' ', "World", '!'));
/// ```
/// Prints:
/// ```text
/// [ > ] Hello World!
/// ```
///  Using it like this, it is only very slightly different to using [`format_args!`], but crucially, it is very easy to adjust this example to color the word `World` with [`Color::Beta`](crate::style::Color::Beta) and to make both words **bold**:
/// ```
/// let con = conciliator::init();
/// use conciliator::{Conciliator, inline, WrapBold as Bold};
/// con.status(inline!(Bold::Plain("Hello"), ' ', Bold::Beta("World"), '!'));
/// ```
///
/// In this case, the [`inline!`](crate::inline) macro is just slightly more concise than appending to the [`status`](crate::Conciliator::status) [`Line`](crate::core::Line) before printing it ([by dropping it](crate::core)), but in other cases, like creating a [`List`](crate::List) with a header, using this macro is significantly more ergonomic than the alternatives.
///
/// E.g.
/// ```
/// let con = conciliator::init();
/// use conciliator::{List, inline, WrapBold::Plain as Bold};
/// List::headless(0..3)
///     .with_count(inline!(Bold("bold"), " numbers"))
///     .with_wrap(Bold)
///     .print_to(&con);
/// ```
/// Prints the list with a header like this:
///
/// `[ > ] 3 `**`bold`**` numbers:`
///
/// [Expressions]: https://doc.rust-lang.org/reference/expressions.html
#[macro_export]
macro_rules! inline {
	($($item:expr),*) => {
		$crate::LambdaFmt(|buf: &mut $crate::Buffer| {
			$(buf.push($item);)*
		})
	};
}

/// Wraps any [`Display`] type to be [`Inline`]d with the chosen [`Color`]
pub enum Wrap<T: Display> {
	/// Wrap without setting a color
	Plain(T),
	/// Wrap with [`Color::Alpha`]
	Alpha(T),
	/// Wrap with [`Color::Beta`]
	Beta(T),
	/// Wrap with [`Color::Gamma`]
	Gamma(T),
	/// Wrap with [`Color::Delta`]
	Delta(T),
	/// Wrap with [`Color::Zeta`]
	Zeta(T),
	/// Wrap with [`Color::Iota`]
	Iota(T),
	/// Wrap with [`Color::Omega`]
	Omega(T)
}
/// Wraps any [`Display`] type to be [`Inline`]d with the chosen [`Color`] but in **bold**
pub enum WrapBold<T: Display> {
	/// Wrap without setting a color, but **bold**
	Plain(T),
	/// Wrap with [`Color::Alpha`], **bold**
	Alpha(T),
	/// Wrap with [`Color::Beta`], **bold**
	Beta(T),
	/// Wrap with [`Color::Gamma`], **bold**
	Gamma(T),
	/// Wrap with [`Color::Delta`], **bold**
	Delta(T),
	/// Wrap with [`Color::Zeta`], **bold**
	Zeta(T),
	/// Wrap with [`Color::Iota`], **bold**
	Iota(T),
	/// Wrap with [`Color::Omega`], **bold**
	Omega(T)
}

/// Wraps a [`str`] to be [`Inline`]d as a colored **`[ tag ]`**
///
/// Simply calls [`Paint::tag`] when [`Inline`]d, appending the [`str`] in square brackets, with a trailing space.
/// The color of the brackets is determined by [`Palette::tag`](crate::style::Palette) and the entire segment is in **bold**.
/// ```
/// let con = conciliator::init();
/// use conciliator::{Conciliator, Tag, style::Color};
/// con.line(Tag(Color::Alpha, "×")).push("A new tag!");
/// ```
///
/// ```text
/// [ × ] A new tag!
/// ```
/// (Obviously the formatting & colors aren't reproduced here.)
pub struct Tag<'s>(pub Color, pub &'s str);

/*
 *	INLINE
 */

/// Blanket impl for convenience
impl<T: Inline + ?Sized> Inline for &T {
	fn inline(&self, buffer: &mut Buffer) {
		(*self).inline(buffer)
	}
}

/*
 *	PUSHABLE
 */

impl<T: Inline> Pushable<marker::AsInline> for T {
	fn push_into(&self, buffer: &mut Buffer) {
		self.inline(buffer)
	}
}
impl<T: Display> Pushable<marker::AsDisplay> for T {
	fn push_into(&self, buffer: &mut Buffer) {
		buffer.push_plain(self);
	}
}
impl<T: Inline> private::Sealed<marker::AsInline> for T {}
impl<T: Display> private::Sealed<marker::AsDisplay> for T {}

/*
 *	LAMBDA FMT
 */

impl<F: Fn(&mut Buffer)> Inline for LambdaFmt<F> {
	fn inline(&self, buffer: &mut Buffer) {
		(self.0)(buffer)
	}
}

/*
 *	WRAP & WRAP BOLD
 */

impl<T: Display> Inline for Wrap<T> {
	fn inline(&self, buffer: &mut Buffer) {
		use Color::*;
		match self {
			Self::Plain(t) => buffer.push_plain(t),
			Self::Alpha(t) => buffer.push_with_color(Alpha, t),
			Self::Beta(t) => buffer.push_with_color(Beta, t),
			Self::Gamma(t) => buffer.push_with_color(Gamma, t),
			Self::Delta(t) => buffer.push_with_color(Delta, t),
			Self::Zeta(t) => buffer.push_with_color(Zeta, t),
			Self::Iota(t) => buffer.push_with_color(Iota, t),
			Self::Omega(t) => buffer.push_with_color(Omega, t)
		};
	}
}

impl<T: Display> Inline for WrapBold<T> {
	fn inline(&self, buffer: &mut Buffer) {
		use Color::*;
		match self {
			Self::Plain(t) => buffer.push_bold(t),
			Self::Alpha(t) => buffer.push_with_color_bold(Alpha, t),
			Self::Beta(t) => buffer.push_with_color_bold(Beta, t),
			Self::Gamma(t) => buffer.push_with_color_bold(Gamma, t),
			Self::Delta(t) => buffer.push_with_color_bold(Delta, t),
			Self::Zeta(t) => buffer.push_with_color_bold(Zeta, t),
			Self::Iota(t) => buffer.push_with_color_bold(Iota, t),
			Self::Omega(t) => buffer.push_with_color_bold(Omega, t)
		};
	}
}

macro_rules! unwrap {
	($self:ident) => {
		match $self {
			Self::Plain(t) |
			Self::Alpha(t) |
			Self::Beta(t) |
			Self::Gamma(t) |
			Self::Delta(t) |
			Self::Zeta(t) |
			Self::Iota(t) |
			Self::Omega(t) => t
		}
	};
}

impl<T: Display> Wrap<T> {
	/// Un-`Wrap` the contained value
	pub fn inner(self) -> T {unwrap!(self)}
}
impl<T: Display> Deref for Wrap<T> {
	type Target = T;
	fn deref(&self) -> &Self::Target {unwrap!(self)}
}
impl<T: Display> DerefMut for Wrap<T> {
	fn deref_mut(&mut self) -> &mut Self::Target {unwrap!(self)}
}


impl<T: Display> WrapBold<T> {
	/// Un-`WrapBold` the contained value
	pub fn inner(self) -> T {unwrap!(self)}
}
impl<T: Display> Deref for WrapBold<T> {
	type Target = T;
	fn deref(&self) -> &Self::Target {unwrap!(self)}
}
impl<T: Display> DerefMut for WrapBold<T> {
	fn deref_mut(&mut self) -> &mut Self::Target {unwrap!(self)}
}

/*
 *	TAG
 */

impl<'s> Inline for Tag<'s> {
	fn inline(&self, buffer: &mut Buffer) {
		buffer.tag(self.0, self.1);
	}
}

/*
 *	TESTS
 */

#[test]
fn pushable() {
	use crate::Conciliator;
	let con = crate::init();

	con.line(..)
		.push("str")
		.push(' ')
		.push(Wrap::Omega("Wrap"));

	struct Both(&'static str);
	impl Display for Both {
		fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
			f.write_str(self.0)
		}
	}
	impl Inline for Both {
		fn inline(&self, buffer: &mut Buffer) {
			buffer.push(self.0);
		}
	}
	con.line(..).push::<marker::AsDisplay, _>(Both("manual resolution"));
	con.line(..).push::<marker::AsInline, _>(Both("manual resolution"));

	con.line(..).push(format_args!("With {{}} formatting!"));

	let s = String::from("STRING");

	con.line(..).push(&s);
	con.line(..).push(s);

}

#[test]
fn lambda_fmt() {
	use crate::Conciliator;
	let con = crate::init();

	con.status(LambdaFmt(|buf: &mut Buffer| {buf.push("Test123");}));

	let lambda = |buf: &mut Buffer| {
		buf.push("Test");
		buf.push(123);
		buf.push(' ' );
		buf.push(":^)");
	};

	con.status(LambdaFmt(lambda));
	con.line(..).push(LambdaFmt(lambda));

	fn func(buf: &mut Buffer) {
		buf.push("Test");
		buf.push(123);
		buf.push(' ' );
		buf.push(":^)");
	}

	con.status(LambdaFmt(func));
	con.line(..).push(LambdaFmt(func));
}