fyi_msg 0.10.6

Simple ANSI-formatted, prefixed messages for console printing.
Documentation
/*!
# FYI Msg
*/

pub(super) mod buffer;
pub(super) mod kind;

use crate::{
	MsgKind,
	MsgBuffer,
};

#[cfg(feature = "progress")] use crate::BeforeAfter;

use dactyl::NiceU8;
use std::{
	borrow::Borrow,
	fmt,
	hash,
	io,
	ops::Deref,
};

#[cfg(feature = "fitted")] use std::borrow::Cow;



#[cfg(feature = "timestamps")]      const MSGBUFFER: usize = crate::BUFFER6;
#[cfg(not(feature = "timestamps"))] const MSGBUFFER: usize = crate::BUFFER5;



#[cfg(feature = "timestamps")]
/// # Helper: `ToC` Setup.
macro_rules! new_toc {
	($p_end:expr, $m_end:expr) => (
		[
			0, 0,           // Indentation.
			0, 0,           // Timestamp.
			0, $p_end,      // Prefix.
			$p_end, $m_end, // Message.
			$m_end, $m_end, // Suffix.
			$m_end, $m_end, // Newline.
		]
	);
	($p_end:expr, $m_end:expr, true) => (
		[
			0, 0,               // Indentation.
			0, 0,               // Timestamp.
			0, $p_end,          // Prefix.
			$p_end, $m_end,     // Message.
			$m_end, $m_end,     // Suffix.
			$m_end, $m_end + 1, // Newline.
		]
	);
}

#[cfg(not(feature = "timestamps"))]
/// # Helper: `ToC` Setup.
macro_rules! new_toc {
	($p_end:expr, $m_end:expr) => (
		[
			0, 0,           // Indentation.
			0, $p_end,      // Prefix.
			$p_end, $m_end, // Message.
			$m_end, $m_end, // Suffix.
			$m_end, $m_end, // Newline.
		]
	);
	($p_end:expr, $m_end:expr, true) => (
		[
			0, 0,               // Indentation.
			0, $p_end,          // Prefix.
			$p_end, $m_end,     // Message.
			$m_end, $m_end,     // Suffix.
			$m_end, $m_end + 1, // Newline.
		]
	);
}

/// # Helper: Impl Built-in `MsgKind` Methods.
macro_rules! impl_builtins {
	($name:literal, $fn:ident, $kind:expr, $p_len:literal) => (
		impl_builtins!(
			concat!("# New ", $name),
			concat!("Msg::", stringify!($fn), r#"("Hello World").print(); // "#, $name, ": Hello World"),
			$fn,
			$kind,
			$p_len
		);
	);

	($name:expr, $ex:expr, $fn:ident, $kind:expr, $p_len:literal) => (
		#[doc = $name]
		///
		/// This is a convenience method to create a thusly prefixed message
		/// with a trailing line break.
		///
		/// This is equivalent to combining [`Msg::new`] with [`Msg::with_newline`],
		/// but optimized for this specific prefix.
		///
		/// ## Examples
		///
		/// ```no_run
		/// use fyi_msg::Msg;
		#[doc = $ex]
		/// ```
		pub fn $fn<S>(msg: S) -> Self
		where S: AsRef<str> {
			let msg = msg.as_ref().as_bytes();
			let len = msg.len();
			let m_end = len as u32 + $p_len;

			let mut v: Vec<u8> = Vec::with_capacity($p_len + 1 + len);
			v.extend_from_slice($kind.as_bytes());
			v.extend_from_slice(msg);
			v.push(b'\n');

			Self(MsgBuffer::from_raw_parts(v, new_toc!($p_len, m_end, true)))
		}
	);
}



// Buffer Indexes.

/// Buffer Index: Indentation.
const PART_INDENT: usize = 0;

/// Buffer Index: Timestamp.
#[cfg(feature = "timestamps")] const PART_TIMESTAMP: usize = 1;

/// Buffer Index: Prefix.
#[cfg(feature = "timestamps")] const PART_PREFIX: usize = 2;
#[cfg(not(feature = "timestamps"))] const PART_PREFIX: usize = 1;

/// Buffer Index: Message body.
#[cfg(feature = "timestamps")] const PART_MSG: usize = 3;
#[cfg(not(feature = "timestamps"))] const PART_MSG: usize = 2;

/// Buffer Index: Suffix.
#[cfg(feature = "timestamps")] const PART_SUFFIX: usize = 4;
#[cfg(not(feature = "timestamps"))] const PART_SUFFIX: usize = 3;

/// Buffer Index: Newline.
#[cfg(feature = "timestamps")] const PART_NEWLINE: usize = 5;
#[cfg(not(feature = "timestamps"))] const PART_NEWLINE: usize = 4;



// Configuration Flags.
//
// These flags are an alternative way to configure indentation and
// timestamping.

/// Enable Indentation (equivalent to 4 spaces).
pub const FLAG_INDENT: u8 =    0b0001;

#[cfg(feature = "timestamps")]
#[cfg_attr(feature = "docsrs", doc(cfg(feature = "timestamps")))]
/// Enable Timestamp.
pub const FLAG_TIMESTAMP: u8 = 0b0010;

/// Enable Trailing Line.
pub const FLAG_NEWLINE: u8 =   0b0100;



#[derive(Debug, Default, Clone)]
/// # Message.
///
/// The `Msg` struct provides a partitioned, contiguous byte source to hold
/// arbitrary messages of the "Error: Oh no!" variety. They can be modified
/// efficiently in place (per-part) and printed to `STDOUT` with [`Msg::print`]
/// or `STDERR` with [`Msg::eprint`].
///
/// Take a look at `examples/msg`, which covers just about all the standard use
/// cases.
///
/// There are two crate feature gates that augment this struct (at the expense
/// of additional dependencies):
///
/// * `fitted` adds the [`Msg::fitted`] method, which returns a byte slice that should fit within a given display width, shrinking the message part of the message as necessary to make room (leaving prefixes and suffixes in tact).
/// * `timestamps` adds [`Msg::with_timestamp`] and [`Msg::set_timestamp`] methods for adding a local datetime value before the prefix.
///
/// Everything else comes stock!
///
/// ## Examples
///
/// ```no_run
/// use fyi_msg::{Msg, MsgKind};
/// Msg::new(MsgKind::Success, "You did it!")
///     .with_newline(true)
///     .print();
/// ```
///
/// There are a bunch of built-in prefix types, each of which (except
/// [`MsgKind::Confirm`]) has a corresponding "quick" method on this struct,
/// like [`Msg::error`], [`Msg::success`], etc. See [`MsgKind`] for the full
/// list. These are equivalent to chaining [`Msg::new`] and [`Msg::with_newline`]
/// for the given type.
///
/// Confirmations have a convenience macro instead, [`confirm`](crate::confirm),
/// that handles all the setup and prompting, returning a simple `bool`
/// indicating the yes/noness of the user response.
///
/// Again, the `examples/msg` demo covers just about all the possibilities.
///
/// ## Conversion
///
/// While [`Msg`] objects are perfectly usable as-is, they can be easily
/// converted to other formats. They dereference to a byte slice and implement
/// `AsRef<[u8]>` and `Borrow<[u8]>`. They also implement `AsRef<str>` and
/// `Borrow<str>` for stringy situations. And if you want to consume the struct
/// into an owned type, there's also [`Msg::into_vec`] and [`Msg::into_string`].
pub struct Msg(MsgBuffer<MSGBUFFER>);

impl AsRef<[u8]> for Msg {
	#[inline]
	fn as_ref(&self) -> &[u8] { self.as_bytes() }
}

impl AsRef<str> for Msg {
	#[inline]
	fn as_ref(&self) -> &str { self.as_str() }
}

impl Borrow<[u8]> for Msg {
	#[inline]
	fn borrow(&self) -> &[u8] { self }
}

impl Borrow<str> for Msg {
	#[inline]
	fn borrow(&self) -> &str { self.as_str() }
}

impl Deref for Msg {
	type Target = [u8];
	#[inline]
	fn deref(&self) -> &Self::Target { &self.0 }
}

impl fmt::Display for Msg {
	#[inline]
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		f.write_str(self.as_str())
	}
}

impl From<&str> for Msg {
	#[inline]
	fn from(src: &str) -> Self { Self::plain(src) }
}

impl From<String> for Msg {
	#[inline]
	fn from(src: String) -> Self { Self::plain(src) }
}

impl Eq for Msg {}

impl hash::Hash for Msg {
	#[inline]
	fn hash<H: hash::Hasher>(&self, state: &mut H) { self.0.hash(state); }
}

impl PartialEq for Msg {
	#[inline]
	fn eq(&self, other: &Self) -> bool { self.0 == other.0 }
}

impl PartialEq<str> for Msg {
	#[inline]
	fn eq(&self, other: &str) -> bool { self.as_str() == other }
}

impl PartialEq<String> for Msg {
	#[inline]
	fn eq(&self, other: &String) -> bool { self.as_str() == other }
}

impl PartialEq<[u8]> for Msg {
	#[inline]
	fn eq(&self, other: &[u8]) -> bool { self.as_bytes() == other }
}

impl PartialEq<Vec<u8>> for Msg {
	#[inline]
	fn eq(&self, other: &Vec<u8>) -> bool { self.0 == *other }
}

/// ## Instantiation.
impl Msg {
	#[allow(clippy::cast_possible_truncation)] // MsgBuffer checks fit.
	/// # New Message.
	///
	/// This creates a new message with a built-in prefix (which can be
	/// [`MsgKind::None`](crate::MsgKind::None), though in that case, [`Msg::plain`]
	/// is better).
	///
	/// If your prefix choice is built-in and known at compile time and you
	/// want the message to have a trailing line break, consider using the
	/// corresponding dedicated method instead ([`Msg::error`], [`Msg::success`],
	/// etc.), as it will be slightly faster.
	///
	/// ## Examples
	///
	/// ```no_run
	/// use fyi_msg::{Msg, MsgKind};
	/// let msg = Msg::new(MsgKind::Info, "This is a message.");
	/// ```
	pub fn new<S>(kind: MsgKind, msg: S) -> Self
	where S: AsRef<str> {
		let msg = msg.as_ref().as_bytes();
		let p_end = kind.len_32();
		let m_end = p_end + msg.len() as u32;

		Self(MsgBuffer::from_raw_parts(
			[kind.as_bytes(), msg].concat(),
			new_toc!(p_end, m_end)
		))
	}

	#[allow(clippy::cast_possible_truncation)] // MsgBuffer checks fit.
	/// # Custom Prefix.
	///
	/// This creates a new message with a user-defined prefix and color. See
	/// [here](https://misc.flogisoft.com/bash/tip_colors_and_formatting) for a BASH color code primer.
	///
	/// The prefix you provide will automatically have a `": "` added to the
	/// end, so you should pass "Prefix" rather than "Prefix:".
	///
	/// If you don't like the colonics, use [`Msg::custom_preformatted`]
	/// instead.
	///
	/// ## Examples
	///
	/// ```no_run
	/// use fyi_msg::{Msg, MsgKind};
	/// let msg = Msg::custom("Prefix", 199, "This message has a pink prefix.");
	/// ```
	pub fn custom<S>(prefix: S, color: u8, msg: S) -> Self
	where S: AsRef<str> {
		let prefix = prefix.as_ref().as_bytes();
		if prefix.is_empty() {
			return Self::plain(msg);
		}

		// Start a vector with the prefix bits.
		let msg = msg.as_ref().as_bytes();
		let v = [
			b"\x1b[1;38;5;",
			&*NiceU8::from(color),
			b"m",
			prefix,
			b":\x1b[0m ",
			msg,
		].concat();

		let m_end = v.len() as u32;
		let p_end = m_end - msg.len() as u32;

		Self(MsgBuffer::from_raw_parts(v, new_toc!(p_end, m_end)))
	}

	#[allow(clippy::cast_possible_truncation)] // MsgBuffer checks fit.
	/// # Custom Prefix (Pre-formatted)
	///
	/// Same as [`Msg::custom`], except no validation or formatting is applied
	/// to the provided prefix. This can be useful in cases where the prefix
	/// requires special spacing, delimiters, or formatting that don't match
	/// the default format.
	///
	/// It is worth noting that when using this method, you must provide the
	/// punctuation and space that will separate the prefix and message parts,
	/// otherwise you'll end up with "prefixmessage" glued together.
	///
	/// ## Examples
	///
	/// ```no_run
	/// use fyi_msg::{Msg, MsgKind};
	/// let msg = Msg::custom_preformatted(
	///     "e.g. ",
	///     "This message has an unformatted prefix."
	/// );
	/// ```
	pub fn custom_preformatted<S>(prefix: S, msg: S) -> Self
	where S: AsRef<str> {
		let prefix = prefix.as_ref().as_bytes();
		let msg = msg.as_ref().as_bytes();

		let p_end = prefix.len() as u32;
		let m_end = p_end + msg.len() as u32;

		Self(MsgBuffer::from_raw_parts(
			[prefix, msg].concat(),
			new_toc!(p_end, m_end)
		))
	}

	#[allow(clippy::cast_possible_truncation)] // MsgBuffer checks fit.
	/// # New Message Without Any Prefix.
	///
	/// This is a streamlined equivalent of calling [`Msg::new`] with a
	/// [`MsgKind::None`].
	///
	/// ## Examples
	///
	/// ```no_run
	/// use fyi_msg::Msg;
	/// let msg = Msg::plain("This message has no prefix.");
	/// ```
	pub fn plain<S>(msg: S) -> Self
	where S: AsRef<str> {
		let msg = msg.as_ref().as_bytes();
		let len = msg.len() as u32;

		Self(MsgBuffer::from_raw_parts(
			msg.to_vec(),
			new_toc!(0, len)
		))
	}
}

/// # Built-ins.
///
/// This contains convenience methods for creating a new message with a
/// built-in prefix and trailing line break. All of the stock kinds are covered
/// except for [`MsgKind::Confirm`], which does not have trailing line breaks
/// in its prompt form, and is kind of weird to use without a prompt.
///
/// Speaking of, there is a dedicated [`confirm`](crate::confirm) macro, that
/// renders the message with the right prefix, pops the prompt, and returns the
/// `bool`.
impl Msg {
	impl_builtins!("Crunched", crunched, MsgKind::Crunched, 21);
	impl_builtins!("Debug", debug, MsgKind::Debug, 18);
	impl_builtins!("Done", done, MsgKind::Done, 17);
	impl_builtins!("Info", info, MsgKind::Info, 17);
	impl_builtins!("Error", error, MsgKind::Error, 18);
	impl_builtins!("Notice", notice, MsgKind::Notice, 19);
	impl_builtins!("Review", review, MsgKind::Review, 19);
	impl_builtins!("Success", success, MsgKind::Success, 20);
	impl_builtins!("Task", task, MsgKind::Task, 23);
	impl_builtins!("Warning", warning, MsgKind::Warning, 20);
}

/// ## Builders.
impl Msg {
	#[must_use]
	/// # With Flags.
	///
	/// This can be used to quickly set indentation, timestamps, and/or a
	/// trailing line break, but only affirmatively; it will not unset any
	/// missing value.
	///
	/// There are 2-3 flags total (depending on whether or not the `timestamps`
	/// feature has been enabled):
	///
	/// * [`FLAG_INDENT`] indents the message one level (four spaces);
	/// * [`FLAG_NEWLINE`] adds a trailing line break to the end;
	/// * [`FLAG_TIMESTAMP`] adds a `[YYYY-MM-DD HH:MM:SS]`-style timestamp between the indentation and prefix;
	///
	/// ## Examples
	///
	/// ```no_run
	/// use fyi_msg::{Msg, FLAG_INDENT, FLAG_NEWLINE};
	/// let msg = Msg::plain("Indented message with trailing line.")
	///     .with_flags(FLAG_INDENT | FLAG_NEWLINE);
	/// ```
	pub fn with_flags(mut self, flags: u8) -> Self {
		if 0 != flags & FLAG_INDENT {
			self.set_indent(1);
		}

		#[cfg(feature = "timestamps")]
		if 0 != flags & FLAG_TIMESTAMP {
			self.set_timestamp(true);
		}

		if 0 != flags & FLAG_NEWLINE {
			self.set_newline(true);
		}
		self
	}

	#[must_use]
	/// # With Indent.
	///
	/// Indent the message using four spaces per `indent`. To remove
	/// indentation, pass `0`. Large values are capped at a maximum of `4`
	/// levels of indentation.
	///
	/// ## Examples
	///
	/// ```no_run
	/// use fyi_msg::Msg;
	/// let msg = Msg::plain("Indented message.")
	///     .with_indent(1);
	/// ```
	pub fn with_indent(mut self, indent: u8) -> Self {
		self.set_indent(indent);
		self
	}

	#[cfg(feature = "timestamps")]
	#[cfg_attr(feature = "docsrs", doc(cfg(feature = "timestamps")))]
	#[must_use]
	/// # With Timestamp.
	///
	/// Disable, enable, and/or recalculate the timestamp prefix for the
	/// message.
	///
	/// **This requires the `timestamps` crate feature.**
	///
	/// ## Examples
	///
	/// ```no_run
	/// use fyi_msg::Msg;
	/// let msg = Msg::plain("Timestamped message.")
	///     .with_timestamp(true);
	/// ```
	pub fn with_timestamp(mut self, timestamp: bool) -> Self {
		self.set_timestamp(timestamp);
		self
	}

	#[must_use]
	/// # With Linebreak.
	///
	/// Add a trailing linebreak to the end of the message. This is either one
	/// or none; calling it multiple times won't add more lines.
	///
	/// Without a linebreak, [`Msg::print`] is analogous to `print!()`,
	/// whereas with a linebreak, it is more like `println!()`. (The newline
	/// isn't limited to print contexts, but that's mainly what it is for.)
	///
	/// ## Examples
	///
	/// ```no_run
	/// use fyi_msg::Msg;
	/// let msg = Msg::plain("This has a trailing newline.")
	///     .with_newline(true);
	/// ```
	pub fn with_newline(mut self, newline: bool) -> Self {
		self.set_newline(newline);
		self
	}

	#[must_use]
	/// # With Prefix.
	///
	/// Set or reset the message prefix.
	///
	/// ## Examples
	///
	/// ```no_run
	/// use fyi_msg::{Msg, MsgKind};
	/// assert_eq!(
	///     Msg::plain("Hello world.").with_prefix(MsgKind::Success),
	///     Msg::new(MsgKind::Success, "Hello world.")
	/// );
	/// ```
	pub fn with_prefix(mut self, kind: MsgKind) -> Self {
		self.set_prefix(kind);
		self
	}

	#[must_use]
	/// # With Custom Prefix.
	///
	/// Set or reset the message with a user-defined prefix and color.
	///
	/// ## Examples
	///
	/// ```no_run
	/// use fyi_msg::Msg;
	/// assert_eq!(
	///     Msg::plain("Hello world.").with_custom_prefix("Prefix", 4),
	///     Msg::custom("Prefix", 4, "Hello world.")
	/// );
	/// ```
	pub fn with_custom_prefix<S>(mut self, prefix: S, color: u8) -> Self
	where S: AsRef<str> {
		self.set_custom_prefix(prefix, color);
		self
	}

	#[must_use]
	/// # With Message.
	///
	/// Set or reset the message portion of the message.
	///
	/// ## Examples
	///
	/// ```no_run
	/// use fyi_msg::Msg;
	///
	/// // A contrived example…
	/// let mut msg = Msg::plain("Should I say this?")
	///     .with_msg("No, this!");
	/// ```
	pub fn with_msg<S>(mut self, msg: S) -> Self
	where S: AsRef<str> {
		self.set_msg(msg);
		self
	}

	#[must_use]
	/// # With Suffix.
	///
	/// Set or reset the message suffix.
	///
	/// Unlike prefixes, there are no built-in suffixes, and as such, no
	/// assumptions or automatic formatting is applied. The value you set must
	/// include any spacing, delimiters, and formatting needed to have it look
	/// right. Generally you'll want to at least have a leading space,
	/// otherwise you'll get "messagesuffix" all glued together.
	///
	/// ## Examples
	///
	/// ```no_run
	/// use fyi_msg::Msg;
	///
	/// // A contrived example…
	/// let mut msg = Msg::plain("5,000 matching files were found.")
	///     .with_suffix(" (75%)");
	/// ```
	pub fn with_suffix<S>(mut self, suffix: S) -> Self
	where S: AsRef<str> {
		self.set_suffix(suffix);
		self
	}
}

/// ## Setters.
impl Msg {
	/// # Set Indentation.
	///
	/// This is the setter companion to the [`Msg::with_indent`] builder
	/// method. Refer to that documentation for more information.
	pub fn set_indent(&mut self, indent: u8) {
		static SPACES: [u8; 16] = [32_u8; 16];
		self.0.replace(PART_INDENT, &SPACES[0..4.min(usize::from(indent)) << 2]);
	}

	#[cfg(feature = "timestamps")]
	#[cfg_attr(feature = "docsrs", doc(cfg(feature = "timestamps")))]
	#[allow(clippy::cast_possible_truncation)] // Date pieces have known values.
	#[allow(clippy::cast_sign_loss)] // Date pieces have known values.
	/// # Set Timestamp.
	///
	/// This is the setter companion to the [`Msg::with_timestamp`] builder
	/// method. Refer to that documentation for more information.
	///
	/// **This requires the `timestamps` crate feature.**
	pub fn set_timestamp(&mut self, timestamp: bool) {
		use utc2k::FmtUtc2k;

		if timestamp {
			let now = FmtUtc2k::now_local();
			self.0.replace(
				PART_TIMESTAMP,
				&[
					b"\x1b[2m[\x1b[0;34m",
					now.as_bytes(),
					b"\x1b[39;2m]\x1b[0m ",
				].concat()
			);
			return;
		}

		// Clear the timestamp if it exists.
		if 0 != self.0.len(PART_TIMESTAMP) {
			self.0.truncate(PART_TIMESTAMP, 0);
		}
	}

	/// # Set Linebreak.
	///
	/// This is the setter companion to the [`Msg::with_newline`] builder
	/// method. Refer to that documentation for more information.
	pub fn set_newline(&mut self, newline: bool) {
		if newline {
			if 0 == self.0.len(PART_NEWLINE) {
				self.0.extend(PART_NEWLINE, b"\n");
			}
		}
		else if 0 != self.0.len(PART_NEWLINE) {
			self.0.truncate(PART_NEWLINE, 0);
		}
	}

	#[inline]
	/// # Set Prefix.
	///
	/// This is the setter companion to the [`Msg::with_prefix`] builder
	/// method. Refer to that documentation for more information.
	pub fn set_prefix(&mut self, kind: MsgKind) {
		self.0.replace(PART_PREFIX, kind.as_bytes());
	}

	/// # Set Custom Prefix.
	///
	/// This is the setter companion to the [`Msg::with_custom_prefix`] builder
	/// method. Refer to that documentation for more information.
	pub fn set_custom_prefix<S>(&mut self, prefix: S, color: u8)
	where S: AsRef<str> {
		let prefix = prefix.as_ref().as_bytes();

		if prefix.is_empty() { self.0.truncate(PART_PREFIX, 0); }
		else {
			self.0.replace(
				PART_PREFIX,
				&[
					b"\x1b[1;38;5;",
					&*NiceU8::from(color),
					b"m",
					prefix,
					b":\x1b[0m ",
				].concat(),
			);
		}
	}

	#[inline]
	/// # Set Message.
	///
	/// This is the setter companion to the [`Msg::with_msg`] builder method.
	/// Refer to that documentation for more information.
	pub fn set_msg<S>(&mut self, msg: S)
	where S: AsRef<str> {
		self.0.replace(PART_MSG, msg.as_ref().as_bytes());
	}

	#[inline]
	/// # Set Suffix.
	///
	/// This is the setter companion to the [`Msg::with_suffix`] builder
	/// method. Refer to that documentation for more information.
	pub fn set_suffix<S>(&mut self, suffix: S)
	where S: AsRef<str> {
		self.0.replace(PART_SUFFIX, suffix.as_ref().as_bytes());
	}
}

#[cfg(feature = "progress")]
/// ## Bytes Saved Suffix.
///
/// A lot of our own programs crunch data and report the savings as a suffix.
/// This section just adds a quick helper for that.
impl Msg {
	#[cfg_attr(feature = "docsrs", doc(cfg(feature = "progress")))]
	#[must_use]
	/// # Bytes Saved Suffix.
	///
	/// A lot of our own programs using this lib crunch data and report the
	/// savings as a suffix. This method just provides a quick way to generate
	/// that.
	pub fn with_bytes_saved(mut self, state: BeforeAfter) -> Self {
		use dactyl::{NicePercent, NiceU64};

		if let Some(saved) = state.less() {
			self.0.replace(
				PART_SUFFIX,
				&state.less_percent().map_or_else(
					|| [
						&b" \x1b[2m(Saved "[..],
						NiceU64::from(saved).as_bytes(),
						b" bytes.)\x1b[0m",
					].concat(),
					|percent| [
						&b" \x1b[2m(Saved "[..],
						NiceU64::from(saved).as_bytes(),
						b" bytes, ",
						NicePercent::from(percent).as_bytes(),
						b".)\x1b[0m",
					].concat()
				)
			);
		}
		else {
			self.0.replace(PART_SUFFIX, b" \x1b[2m(No savings.)\x1b[0m");
		}

		self
	}
}

/// ## Conversion.
impl Msg {
	#[must_use]
	#[inline]
	/// # As Bytes.
	///
	/// Return the entire message as a byte slice. Alternatively, you could
	/// dereference the struct or use [`Msg::as_ref`] or [`Msg::borrow`].
	pub fn as_bytes(&self) -> &[u8] { &self.0 }

	#[allow(unsafe_code)]
	#[must_use]
	#[inline]
	/// # As Str.
	///
	/// Return the entire message as a string slice. Alternatively, you could
	/// use [`Msg::as_ref`] or [`Msg::borrow`].
	pub fn as_str(&self) -> &str {
		unsafe { std::str::from_utf8_unchecked(&self.0) }
	}

	#[must_use]
	#[inline]
	/// # Into Vec.
	///
	/// Consume the message, returning an owned `Vec<u8>`.
	pub fn into_vec(self) -> Vec<u8> { self.0.into_vec() }

	#[allow(unsafe_code)]
	#[must_use]
	#[inline]
	/// # Into String.
	///
	/// Consume the message, returning an owned string.
	pub fn into_string(self) -> String {
		unsafe { String::from_utf8_unchecked(self.0.into_vec()) }
	}

	#[cfg(feature = "fitted")]
	#[cfg_attr(feature = "docsrs", doc(cfg(feature = "fitted")))]
	#[allow(clippy::cast_possible_truncation)] // MsgBuffer checks fit.
	#[must_use]
	/// # Capped Width Slice.
	///
	/// This will return a byte string that should fit a given console width if
	/// printed. This is subject to the usual disclaimers of "Unicode is
	/// monstrously complicated…", but it does its best, and will be more
	/// accurate than simply chopping to the [`Msg::len`].
	///
	/// Only the user-defined message portion of the `Msg` will be trimmed for
	/// space. Prefixes, suffixes, the trailing newline, etc., are left
	/// unchanged.
	///
	/// If the message cannot be made to fit, an empty byte string is returned.
	///
	/// **This requires the `fitted` crate feature.**
	pub fn fitted(&self, width: usize) -> Cow<[u8]> {
		// Quick length bypass; length will only ever be greater or equal to
		// width, so if that fits, the message fits.
		if self.len() <= width {
			return Cow::Borrowed(self);
		}

		// If the fixed width bits are themselves too big, we can't fit print.
		#[cfg(feature = "timestamps")]
		let fixed_width: usize =
			self.0.len(PART_INDENT) as usize +
			crate::width(self.0.get(PART_PREFIX)) +
			crate::width(self.0.get(PART_SUFFIX)) +
			if 0 == self.0.len(PART_TIMESTAMP) { 0 }
			else { 21 };

		#[cfg(not(feature = "timestamps"))]
		let fixed_width: usize =
			self.0.len(PART_INDENT) as usize +
			crate::width(self.0.get(PART_PREFIX)) +
			crate::width(self.0.get(PART_SUFFIX));

		if fixed_width > width {
			return Cow::Owned(Vec::new());
		}

		// Check the length again; the fixed bits might just have a lot of
		// ANSI.
		let keep = crate::length_width(self.0.get(PART_MSG), width - fixed_width) as u32;
		if keep == 0 { Cow::Owned(Vec::new()) }
		else if keep == self.0.len(PART_MSG) { Cow::Borrowed(self) }
		else {
			// We have to trim the message to fit. Let's do it on a copy.
			let mut tmp = self.clone();
			tmp.0.truncate(PART_MSG, keep);

			// We might need to append an ANSI reset to be safe. This might be
			// unnecessary, but nitpicking is more expensive than redundancy
			// here.
			if tmp.0.get(PART_MSG).contains(&b'\x1b') {
				tmp.0.extend(PART_MSG, b"\x1b[0m");
			}

			Cow::Owned(tmp.into_vec())
		}
	}
}

/// ## Details.
impl Msg {
	#[must_use]
	#[inline]
	/// # Length.
	///
	/// This returns the total length of the entire `Msg`, ANSI markup and all.
	pub const fn len(&self) -> usize {
		// Because the buffers used by `Msg` end on partitioned space, the end
		// of the last part is equal to the total length. Let's use that method
		// since it is constant!
		self.0.end(PART_NEWLINE) as usize
	}

	#[must_use]
	#[inline]
	/// # Is Empty.
	pub const fn is_empty(&self) -> bool { self.len() == 0 }
}

/// ## Printing.
impl Msg {
	/// # Locked Print to `STDOUT`.
	///
	/// This is equivalent to calling either `print!()` or `println()`
	/// depending on whether or not a trailing linebreak has been set.
	///
	/// In fact, [`Msg`] does implement `Display`, so you could do just that,
	/// but this method avoids the allocation penalty.
	///
	/// ## Examples
	///
	/// ```no_run
	/// use fyi_msg::Msg;
	/// Msg::plain("Hello world!").with_newline(true).print();
	/// ```
	pub fn print(&self) {
		use io::Write;

		let writer = io::stdout();
		let mut handle = writer.lock();
		let _res = handle.write_all(&self.0).and_then(|_| handle.flush());
	}

	/// # Locked Print to `STDERR`.
	///
	/// This is equivalent to calling either `eprint!()` or `eprintln()`
	/// depending on whether or not a trailing linebreak has been set.
	///
	/// In fact, [`Msg`] does implement `Display`, so you could do just that,
	/// but this method avoids the allocation penalty.
	///
	/// ## Examples
	///
	/// ```no_run
	/// use fyi_msg::Msg;
	/// Msg::error("Oh no!").with_newline(true).eprint();
	/// ```
	pub fn eprint(&self) {
		use io::Write;

		let writer = io::stderr();
		let mut handle = writer.lock();
		let _res = handle.write_all(&self.0).and_then(|_| handle.flush());
	}

	/// # Print and Die.
	///
	/// This is a convenience method for printing a message to `STDERR` and
	/// terminating the thread with the provided exit code. Generally you'd
	/// want to pass a non-zero value here.
	///
	/// Be careful calling this method in parallel contexts as it will only
	/// stop the current thread, not the entire program execution.
	///
	/// ## Examples
	///
	/// ```no_run
	/// use fyi_msg::Msg;
	/// Msg::error("Oh no!").with_newline(true).die(1);
	/// unreachable!();
	/// ```
	pub fn die(&self, code: i32) -> ! {
		self.eprint();
		std::process::exit(code);
	}

	#[must_use]
	#[inline]
	/// # Prompt.
	///
	/// This produces a simple y/N input prompt, requiring the user type "Y" or
	/// "N" to proceed. Positive values return `true`, negative values return
	/// `false`. The default (if the user just hits <enter>) is "N".
	///
	/// Note: the prompt normalizes the suffix and newline parts for display.
	/// If your message contains these parts, they will be ignored by the
	/// prompt action, but will be retained in the original struct should you
	/// wish to use it in some other manner later in your code.
	///
	/// Every example in the docs shows this in combination with the built-in
	/// [`MsgKind::Confirm`] prefix, but this can be called on any [`Msg`]
	/// object. The main thing worth noting is the suffix portion is
	/// overridden for display, so don't bother putting anything there.
	///
	/// ## Example
	///
	/// ```no_run
	/// use fyi_msg::{confirm, Msg, MsgKind};
	///
	/// // The manual way:
	/// if Msg::new(MsgKind::Confirm, "Do you like chickens?").prompt() {
	///     println!("That's great! They like you too!");
	/// }
	///
	/// // The macro way:
	/// if confirm!("Do you like chickens?") {
	///     println!("That's great! They like you too!");
	/// }
	/// ```
	pub fn prompt(&self) -> bool { self.prompt_with_default(false) }

	#[must_use]
	/// # Prompt (w/ Default).
	///
	/// This is identical to [`Msg::prompt`], except you specify the default
	/// return value — `true` for Yes, `false` for No — that is returned when
	/// the user just hits `<ENTER>`.
	pub fn prompt_with_default(&self, default: bool) -> bool {
		// Clone the message and append a little [y/N] instructional bit to the
		// end. This might not be necessary, but preserves the original message
		// in case it is needed again.
		let q = self.clone()
			.with_suffix(
				if default { " \x1b[2m[\x1b[4mY\x1b[0;2m/n]\x1b[0m " }
				else       { " \x1b[2m[y/\x1b[4mN\x1b[0;2m]\x1b[0m " }
			)
			.with_newline(false);

		// Ask and collect input, looping until a valid response is typed.
		let mut result = String::new();
		loop {
			q.print();

			if let Some(res) = io::stdin().read_line(&mut result)
				.ok()
				.and_then(|_| match result.to_lowercase().trim() {
					"" => Some(default),
					"n" | "no" => Some(false),
					"y" | "yes" => Some(true),
					_ => None,
				})
			{ break res; }

			// Print an error and do it all over again.
			result.truncate(0);
			Self::error("Invalid input; enter \x1b[91mN\x1b[0m or \x1b[92mY\x1b[0m.")
				.print();
		}
	}
}



#[cfg(test)]
mod tests {
	use super::*;
	use brunch as _;
	use rayon as _;

	#[test]
	fn t_msg() {
		let mut msg = Msg::plain("My dear aunt sally.");
		assert_eq!(&*msg, b"My dear aunt sally.");

		msg.set_prefix(MsgKind::Error);
		assert!(msg.starts_with(MsgKind::Error.as_bytes()));
		assert!(msg.ends_with(b"My dear aunt sally."));

		msg.set_indent(1);
		assert!(msg.starts_with(b"    "));
		msg.set_indent(3);
		assert!(msg.starts_with(b"            "));
		msg.set_indent(0);
		assert!(msg.starts_with(MsgKind::Error.as_bytes()));

		msg.set_suffix(" Heyo");
		assert!(msg.ends_with(b" Heyo"), "{:?}", msg.as_str());
		msg.set_suffix("");
		assert!(msg.ends_with(b"My dear aunt sally."));

		msg.set_msg("My dear aunt");
		assert!(msg.ends_with(b"My dear aunt"));
	}

	#[cfg(feature = "fitted")]
	#[test]
	fn t_fitted() {
		let mut msg = Msg::plain("Hello World");

		assert_eq!(msg.fitted(5), &b"Hello"[..]);
		assert_eq!(msg.fitted(20), &b"Hello World"[..]);

		// Try it with a new line.
		msg.set_newline(true);
		assert_eq!(msg.fitted(5), &b"Hello\n"[..]);

		// Give it a prefix.
		msg.set_prefix(MsgKind::Error);
		assert_eq!(msg.fitted(5), Vec::<u8>::new());
		assert_eq!(msg.fitted(12), &b"\x1b[91;1mError:\x1b[0m Hello\n"[..]);

		// Colorize the message.
		msg.set_msg("\x1b[1mHello\x1b[0m World");
		assert_eq!(msg.fitted(12), &b"\x1b[91;1mError:\x1b[0m \x1b[1mHello\x1b[0m\x1b[0m\n"[..]);
		assert_eq!(msg.fitted(11), &b"\x1b[91;1mError:\x1b[0m \x1b[1mHell\x1b[0m\n"[..]);

		// Try it with Unicode!
		msg.set_msg("Björk Guðmundsdóttir");
		assert_eq!(msg.fitted(12), "\x1b[91;1mError:\x1b[0m Björk\n".as_bytes());
	}
}