use crate::{
AnsiColor,
Msg,
};
use fyi_ansi::ansi;
use std::{
borrow::Cow,
fmt,
};
macro_rules! msg_kind {
(@count $odd:tt) => ( 1 );
(@count $odd:tt $( $a:tt $b:tt )+) => ( (msg_kind!(@count $($a)+) * 2) + 1 );
(@count $( $a:tt $b:tt )+) => ( msg_kind!(@count $($a)+) * 2 );
(@build $( $k:ident $v:expr, )+) => (
#[expect(missing_docs, reason = "Redudant.")]
#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
pub enum MsgKind {
$( $k, )+
}
impl MsgKind {
pub const ALL: [Self; msg_kind!(@count $($k)+)] = [
$( Self::$k, )+
];
#[inline]
#[must_use]
pub(crate) const fn as_str_prefix(self) -> &'static str {
match self {
$( Self::$k => $v, )+
}
}
}
);
(@msg $( $k:ident $fn:ident $v:expr, )+) => (
impl Msg {
$(
#[must_use]
#[doc = concat!("# New ", stringify!($k), ".")]
#[doc = concat!("Create a new [`Msg`] with a built-in [`MsgKind::", stringify!($k), "`] prefix _and_ trailing line break.")]
#[doc = concat!(" Msg::", stringify!($fn), "(\"Hello World\"),")]
#[doc = concat!(" Msg::new(MsgKind::", stringify!($k), ", \"Hello World\").with_newline(true),")]
pub fn $fn<S: AsRef<str>>(msg: S) -> Self {
let msg = msg.as_ref();
let m_end = $v.len() + msg.len();
let mut inner = String::with_capacity(m_end + 1);
inner.push_str($v);
inner.push_str(msg);
inner.push('\n');
Self {
inner,
toc: super::toc!($v.len(), m_end, true),
}
}
)+
}
);
(@prefix $kind:ident $color:tt) => (
concat!(ansi!((bold, $color) stringify!($kind), ":"), " ")
);
($( $kind:ident $fn:ident $bytes:literal $color:tt $color_ident:ident, )+) => (
#[cfg(feature = "bin_kinds")]
msg_kind!{
@build
None "",
Confirm msg_kind!(@prefix Confirm dark_orange),
$( $kind msg_kind!(@prefix $kind $color), )+
Blank "",
Custom "",
}
#[cfg(not(feature = "bin_kinds"))]
msg_kind!{
@build
None "",
Confirm msg_kind!(@prefix Confirm dark_orange),
$( $kind msg_kind!(@prefix $kind $color), )+
}
msg_kind!{
@msg
$( $kind $fn msg_kind!(@prefix $kind $color), )+
}
#[cfg(feature = "bin_kinds")]
impl From<&[u8]> for MsgKind {
fn from(src: &[u8]) -> Self {
match src.trim_ascii() {
b"blank" => Self::Blank,
b"confirm" | b"prompt" => Self::Confirm,
b"print" => Self::Custom,
$( $bytes => Self::$kind, )+
_ => Self::None,
}
}
}
impl MsgKind {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Confirm => "Confirm",
$( Self::$kind => stringify!($kind), )+
_ => "",
}
}
#[cfg(feature = "bin_kinds")]
#[doc(hidden)]
#[must_use]
pub const fn command(self) -> &'static str {
match self {
Self::Blank => "blank",
Self::Confirm => "confirm",
Self::Custom => "print",
Self::None => "",
$( Self::$kind => stringify!($fn), )+
}
}
#[must_use]
pub const fn prefix_color(self) -> Option<AnsiColor> {
match self {
#[cfg(feature = "bin_kinds")] Self::None | Self::Blank | Self::Custom => None,
#[cfg(not(feature = "bin_kinds"))] Self::None => None,
Self::Confirm => Some(AnsiColor::DarkOrange),
$( Self::$kind => Some(AnsiColor::$color_ident), )+
}
}
}
);
}
msg_kind! {
Aborted aborted b"aborted" light_red LightRed,
Crunched crunched b"crunched" light_green LightGreen,
Debug debug b"debug" light_cyan LightCyan,
Done done b"done" light_green LightGreen,
Error error b"error" light_red LightRed,
Found found b"found" light_green LightGreen,
Info info b"info" light_magenta LightMagenta,
Notice notice b"notice" light_magenta LightMagenta,
Review review b"review" light_cyan LightCyan,
Skipped skipped b"skipped" light_yellow LightYellow,
Success success b"success" light_green LightGreen,
Task task b"task" 199 Misc199,
Warning warning b"warning" light_yellow LightYellow,
}
impl Default for MsgKind {
#[inline]
fn default() -> Self { Self::None }
}
impl fmt::Display for MsgKind {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
<str as fmt::Display>::fmt(self.as_str(), f)
}
}
impl MsgKind {
#[cfg(feature = "bin_kinds")]
#[must_use]
pub const fn is_empty(self) -> bool {
matches!(self, Self::None | Self::Blank | Self::Custom)
}
#[cfg(not(feature = "bin_kinds"))]
#[must_use]
pub const fn is_empty(self) -> bool { matches!(self, Self::None) }
#[inline]
#[must_use]
pub fn into_msg<S>(self, msg: S) -> Msg
where S: AsRef<str> { Msg::new(self, msg) }
}
pub trait IntoMsgPrefix {
fn prefix_len(&self) -> usize;
fn prefix_push(&self, dst: &mut String);
#[inline]
fn prefix_str(&self) -> Cow<'_, str> {
let mut out = String::with_capacity(self.prefix_len());
self.prefix_push(&mut out);
Cow::Owned(out)
}
}
impl IntoMsgPrefix for MsgKind {
#[inline]
fn prefix_len(&self) -> usize { self.as_str_prefix().len() }
#[inline]
fn prefix_str(&self) -> Cow<'_, str> { Cow::Borrowed(self.as_str_prefix()) }
#[inline]
fn prefix_push(&self, dst: &mut String) { dst.push_str(self.as_str_prefix()); }
}
macro_rules! into_prefix {
($($ty:ty),+) => ($(
impl IntoMsgPrefix for $ty {
#[inline]
fn prefix_len(&self) -> usize {
let len = self.len();
if len == 0 { 0 }
else { len + 2 } }
#[inline]
fn prefix_push(&self, dst: &mut String) {
if ! self.is_empty() {
dst.push_str(self);
dst.push_str(": ");
}
}
}
impl IntoMsgPrefix for ($ty, AnsiColor) {
#[inline]
fn prefix_len(&self) -> usize {
let len = self.0.len();
if len == 0 { 0 }
else {
self.1.as_str_bold().len() + self.0.len() +
AnsiColor::RESET_PREFIX.len()
}
}
#[inline]
fn prefix_push(&self, dst: &mut String) {
if ! self.0.is_empty() {
dst.push_str(self.1.as_str_bold());
dst.push_str(&self.0);
dst.push_str(AnsiColor::RESET_PREFIX);
}
}
}
impl IntoMsgPrefix for ($ty, u8) {
#[inline]
fn prefix_len(&self) -> usize {
let len = self.0.len();
if len == 0 { 0 }
else {
let color = AnsiColor::from_u8(self.1);
color.as_str_bold().len() + self.0.len() +
AnsiColor::RESET_PREFIX.len()
}
}
#[inline]
fn prefix_push(&self, dst: &mut String) {
if ! self.0.is_empty() {
dst.push_str(AnsiColor::from_u8(self.1).as_str_bold());
dst.push_str(&self.0);
dst.push_str(AnsiColor::RESET_PREFIX);
}
}
}
)+);
}
into_prefix!(&str, &String, String, &Cow<'_, str>, Cow<'_, str>);
#[cfg(test)]
mod test {
use super::*;
#[test]
fn t_as_str_prefix() {
for kind in MsgKind::ALL {
let Some(color) = kind.prefix_color() else { continue; };
let manual = format!("{}{kind}:{} ", color.as_str_bold(), AnsiColor::RESET);
assert_eq!(manual, kind.as_str_prefix());
}
}
}