1use crate::{
6 AnsiColor,
7 Msg,
8};
9use fyi_ansi::ansi;
10use std::{
11 borrow::Cow,
12 fmt,
13};
14
15
16
17macro_rules! msg_kind {
19 (@count $odd:tt) => ( 1 );
22 (@count $odd:tt $( $a:tt $b:tt )+) => ( (msg_kind!(@count $($a)+) * 2) + 1 );
23 (@count $( $a:tt $b:tt )+) => ( msg_kind!(@count $($a)+) * 2 );
24
25 (@build $( $k:ident $v:expr, )+) => (
27 #[expect(missing_docs, reason = "Redudant.")]
28 #[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
29 pub enum MsgKind {
59 $( $k, )+
60 }
61
62 impl MsgKind {
63 pub const ALL: [Self; msg_kind!(@count $($k)+)] = [
67 $( Self::$k, )+
68 ];
69
70 #[inline]
71 #[must_use]
72 pub(crate) const fn as_str_prefix(self) -> &'static str {
77 match self {
78 $( Self::$k => $v, )+
79 }
80 }
81 }
82 );
83
84 (@msg $( $k:ident $fn:ident $v:expr, )+) => (
86 impl Msg {
88 $(
89 #[must_use]
90 #[doc = concat!("# New ", stringify!($k), ".")]
91 #[doc = concat!("Create a new [`Msg`] with a built-in [`MsgKind::", stringify!($k), "`] prefix _and_ trailing line break.")]
93 #[doc = concat!(" Msg::", stringify!($fn), "(\"Hello World\"),")]
101 #[doc = concat!(" Msg::new(MsgKind::", stringify!($k), ", \"Hello World\").with_newline(true),")]
102 pub fn $fn<S: AsRef<str>>(msg: S) -> Self {
105 let msg = msg.as_ref();
107 let m_end = $v.len() + msg.len();
108 let mut inner = String::with_capacity(m_end + 1);
109 inner.push_str($v);
110 inner.push_str(msg);
111 inner.push('\n');
112
113 Self {
115 inner,
116 toc: super::toc!($v.len(), m_end, true),
117 }
118 }
119 )+
120 }
121 );
122
123 (@prefix $kind:ident $color:tt) => (
125 concat!(ansi!((bold, $color) stringify!($kind), ":"), " ")
126 );
127
128 ($( $kind:ident $fn:ident $str:literal $color:tt $color_ident:ident, )+) => (
130 #[cfg(feature = "bin_kinds")]
131 msg_kind!{
132 @build
133 None "",
134 Confirm msg_kind!(@prefix Confirm dark_orange),
135 $( $kind msg_kind!(@prefix $kind $color), )+
136 Blank "",
137 Custom "",
138 }
139
140 #[cfg(not(feature = "bin_kinds"))]
141 msg_kind!{
142 @build
143 None "",
144 Confirm msg_kind!(@prefix Confirm dark_orange),
145 $( $kind msg_kind!(@prefix $kind $color), )+
146 }
147
148 msg_kind!{
149 @msg
150 $( $kind $fn msg_kind!(@prefix $kind $color), )+
151 }
152
153 impl MsgKind {
154 #[must_use]
155 pub const fn as_str(self) -> &'static str {
172 match self {
173 Self::Confirm => "Confirm",
174 $( Self::$kind => stringify!($kind), )+
175 _ => "",
176 }
177 }
178
179 #[cfg(feature = "bin_kinds")]
180 #[doc(hidden)]
181 #[must_use]
182 pub const fn command(self) -> &'static str {
189 match self {
190 Self::Blank => "blank",
191 Self::Confirm => "confirm",
192 Self::Custom => "print",
193 Self::None => "",
194 $( Self::$kind => stringify!($fn), )+
195 }
196 }
197
198 #[must_use]
199 pub const fn prefix_color(self) -> Option<AnsiColor> {
216 match self {
217 #[cfg(feature = "bin_kinds")] Self::None | Self::Blank | Self::Custom => None,
218 #[cfg(not(feature = "bin_kinds"))] Self::None => None,
219 Self::Confirm => Some(AnsiColor::DarkOrange),
220 $( Self::$kind => Some(AnsiColor::$color_ident), )+
221 }
222 }
223 }
224
225 #[expect(clippy::string_lit_as_bytes, reason = "We need to test equality.")]
226 const _: () = {
231 $(
232 assert!(
233 stringify!($fn).len() == $str.len(),
234 "BUG: Function/string/bytes are not equal.",
235 );
236 let b1 = stringify!($fn).as_bytes();
237 let b2 = $str.as_bytes();
238 let mut i = 0;
239 while i < b1.len() {
240 assert!(
241 b1[i] == b2[i],
242 "BUG: Function/string/bytes are not equal.",
243 );
244 i += 1;
245 }
246 )+
247 };
248
249 #[cfg(feature = "bin_kinds")]
250 argyle::argue! {
251 pub CliCommandArg,
253
254 pub CliCommandArgIter,
256
257 Blank "blank",
259 Confirm "confirm" "prompt",
260 Custom "print",
261 $( $kind $str, )+
262 Version "-V" "--version" "version",
263 }
264 );
265}
266
267msg_kind! {
268 Aborted aborted "aborted" light_red LightRed,
269 Crunched crunched "crunched" light_green LightGreen,
270 Debug debug "debug" light_cyan LightCyan,
271 Done done "done" light_green LightGreen,
272 Error error "error" light_red LightRed,
273 Found found "found" light_green LightGreen,
274 Info info "info" light_magenta LightMagenta,
275 Notice notice "notice" light_magenta LightMagenta,
276 Review review "review" light_cyan LightCyan,
277 Skipped skipped "skipped" light_yellow LightYellow,
278 Success success "success" light_green LightGreen,
279 Task task "task" 199 Misc199,
280 Warning warning "warning" light_yellow LightYellow,
281}
282
283impl Default for MsgKind {
284 #[inline]
285 fn default() -> Self { Self::None }
286}
287
288impl fmt::Display for MsgKind {
289 #[inline]
290 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
291 <str as fmt::Display>::fmt(self.as_str(), f)
292 }
293}
294
295impl MsgKind {
297 #[cfg(feature = "bin_kinds")]
298 #[must_use]
299 pub const fn is_empty(self) -> bool {
304 matches!(self, Self::None | Self::Blank | Self::Custom)
305 }
306
307 #[cfg(not(feature = "bin_kinds"))]
308 #[must_use]
309 pub const fn is_empty(self) -> bool { matches!(self, Self::None) }
313
314 #[inline]
315 #[must_use]
316 pub fn into_msg<S>(self, msg: S) -> Msg
345 where S: AsRef<str> { Msg::new(self, msg) }
346}
347
348
349
350pub trait IntoMsgPrefix {
364 fn prefix_len(&self) -> usize;
369
370 fn prefix_push(&self, dst: &mut String);
374
375 #[inline]
376 fn prefix_str(&self) -> Cow<'_, str> {
383 let mut out = String::with_capacity(self.prefix_len());
384 self.prefix_push(&mut out);
385 Cow::Owned(out)
386 }
387}
388
389impl IntoMsgPrefix for MsgKind {
390 #[inline]
391 fn prefix_len(&self) -> usize { self.as_str_prefix().len() }
393
394 #[inline]
395 fn prefix_str(&self) -> Cow<'_, str> { Cow::Borrowed(self.as_str_prefix()) }
397
398 #[inline]
399 fn prefix_push(&self, dst: &mut String) { dst.push_str(self.as_str_prefix()); }
401}
402
403macro_rules! into_prefix {
405 ($($ty:ty),+) => ($(
406 impl IntoMsgPrefix for $ty {
407 #[inline]
408 fn prefix_len(&self) -> usize {
410 let len = self.len();
411 if len == 0 { 0 }
412 else { len + 2 } }
414
415 #[inline]
416 fn prefix_push(&self, dst: &mut String) {
418 if ! self.is_empty() {
419 dst.push_str(self);
420 dst.push_str(": ");
421 }
422 }
423 }
424
425 impl IntoMsgPrefix for ($ty, AnsiColor) {
426 #[inline]
427 fn prefix_len(&self) -> usize {
429 let len = self.0.len();
430 if len == 0 { 0 }
431 else {
432 self.1.as_str_bold().len() + self.0.len() +
433 AnsiColor::RESET_PREFIX.len()
434 }
435 }
436
437 #[inline]
438 fn prefix_push(&self, dst: &mut String) {
440 if ! self.0.is_empty() {
441 dst.push_str(self.1.as_str_bold());
442 dst.push_str(&self.0);
443 dst.push_str(AnsiColor::RESET_PREFIX);
444 }
445 }
446 }
447
448 impl IntoMsgPrefix for ($ty, u8) {
449 #[inline]
450 fn prefix_len(&self) -> usize {
452 let len = self.0.len();
453 if len == 0 { 0 }
454 else {
455 let color = AnsiColor::from_u8(self.1);
456 color.as_str_bold().len() + self.0.len() +
457 AnsiColor::RESET_PREFIX.len()
458 }
459 }
460
461 #[inline]
462 fn prefix_push(&self, dst: &mut String) {
464 if ! self.0.is_empty() {
465 dst.push_str(AnsiColor::from_u8(self.1).as_str_bold());
466 dst.push_str(&self.0);
467 dst.push_str(AnsiColor::RESET_PREFIX);
468 }
469 }
470 }
471 )+);
472}
473into_prefix!(&str, &String, String, &Cow<'_, str>, Cow<'_, str>);
474
475
476
477#[cfg(test)]
478mod test {
479 use super::*;
480
481 #[test]
482 fn t_as_str_prefix() {
483 for kind in MsgKind::ALL {
486 let Some(color) = kind.prefix_color() else { continue; };
488 let manual = format!("{}{kind}:{} ", color.as_str_bold(), AnsiColor::RESET);
489
490 assert_eq!(manual, kind.as_str_prefix());
491 }
492 }
493}