Skip to main content

kasl/libs/messages/
macros.rs

1//! Print macros that route to the console or to `tracing`.
2//!
3//! Every macro checks [`is_debug_mode`] once per call: with `KASL_DEBUG` or
4//! `RUST_LOG` set, output goes through `tracing` (to stderr, beside the debug
5//! log); otherwise [`crate::libs::output::emit`] prints it with the mark and on
6//! the stream this run's style gives its kind. Each macro takes an optional
7//! `true` second argument to pad the message with blank lines.
8//!
9//! ```rust
10//! use kasl::{msg_info, msg_error, msg_success, msg_warning};
11//! use kasl::libs::messages::types::Message;
12//!
13//! msg_info!(Message::TaskCreated);
14//! msg_success!(Message::DailyReportSent("2025-01-15".to_string()));
15//! msg_error!(Message::ConfigSaveError);
16//!
17//! let count = 5;
18//! msg_info!(format!("Processing {} items", count));
19//! ```
20
21use std::sync::OnceLock;
22
23/// Cached result of the environment check; env vars are read once per run.
24static DEBUG_MODE: OnceLock<bool> = OnceLock::new();
25
26/// True when `KASL_DEBUG` or `RUST_LOG` is set.
27///
28/// ```rust
29/// use kasl::libs::messages::macros::is_debug_mode;
30///
31/// if is_debug_mode() {
32///     println!("Running in debug mode with enhanced logging");
33/// } else {
34///     println!("Running in normal mode with simple output");
35/// }
36/// ```
37#[doc(hidden)]
38pub fn is_debug_mode() -> bool {
39    *DEBUG_MODE.get_or_init(|| std::env::var("KASL_DEBUG").is_ok() || std::env::var("RUST_LOG").is_ok())
40}
41
42/// Prints the message with no prefix.
43///
44/// ```rust
45/// use kasl::msg_print;
46/// use kasl::libs::messages::types::Message;
47///
48/// msg_print!(Message::ConfigSaved);
49/// ```
50///
51/// ```rust
52/// use kasl::msg_print;
53/// use kasl::libs::messages::types::Message;
54///
55/// msg_print!(Message::ReportHeader("2025-01-15".to_string()), true);
56/// ```
57#[macro_export]
58macro_rules! msg_print {
59    ($msg:expr) => {
60        if $crate::libs::messages::macros::is_debug_mode() {
61            tracing::info!("{}{}", $crate::libs::output::style().prefix($crate::libs::output::Kind::Print), $msg);
62        } else {
63            $crate::libs::output::emit($crate::libs::output::Kind::Print, &$msg, false);
64        }
65    };
66    ($msg:expr, true) => {
67        if $crate::libs::messages::macros::is_debug_mode() {
68            tracing::info!("\n{}{}\n", $crate::libs::output::style().prefix($crate::libs::output::Kind::Print), $msg);
69        } else {
70            $crate::libs::output::emit($crate::libs::output::Kind::Print, &$msg, true);
71        }
72    };
73}
74
75/// Prints the message with the ✅ prefix.
76///
77/// ```rust
78/// use kasl::msg_success;
79/// use kasl::libs::messages::types::Message;
80///
81/// msg_success!(Message::TaskCreated);
82/// ```
83///
84/// ```rust
85/// use kasl::msg_success;
86/// use kasl::libs::messages::types::Message;
87///
88/// msg_success!(Message::ExportCompleted("data.csv".to_string()), true);
89/// ```
90#[macro_export]
91macro_rules! msg_success {
92    ($msg:expr) => {
93        if $crate::libs::messages::macros::is_debug_mode() {
94            tracing::info!("{}{}", $crate::libs::output::style().prefix($crate::libs::output::Kind::Success), $msg);
95        } else {
96            $crate::libs::output::emit($crate::libs::output::Kind::Success, &$msg, false);
97        }
98    };
99    ($msg:expr, true) => {
100        if $crate::libs::messages::macros::is_debug_mode() {
101            tracing::info!("\n{}{}\n", $crate::libs::output::style().prefix($crate::libs::output::Kind::Success), $msg);
102        } else {
103            $crate::libs::output::emit($crate::libs::output::Kind::Success, &$msg, true);
104        }
105    };
106}
107
108/// Prints the message with the ❌ prefix - to stderr outside debug mode, so
109/// errors stay separable from data in pipes.
110///
111/// ```rust
112/// use kasl::msg_error;
113/// use kasl::libs::messages::types::Message;
114///
115/// msg_error!(Message::TaskNotFound);
116/// ```
117///
118/// ```rust
119/// use kasl::msg_error;
120/// use kasl::libs::messages::types::Message;
121///
122/// msg_error!(Message::ConfigParseError, true);
123/// ```
124#[macro_export]
125macro_rules! msg_error {
126    ($msg:expr) => {
127        if $crate::libs::messages::macros::is_debug_mode() {
128            tracing::error!("{}{}", $crate::libs::output::style().prefix($crate::libs::output::Kind::Error), $msg);
129        } else {
130            $crate::libs::output::emit($crate::libs::output::Kind::Error, &$msg, false);
131        }
132    };
133    ($msg:expr, true) => {
134        if $crate::libs::messages::macros::is_debug_mode() {
135            tracing::error!("\n{}{}\n", $crate::libs::output::style().prefix($crate::libs::output::Kind::Error), $msg);
136        } else {
137            $crate::libs::output::emit($crate::libs::output::Kind::Error, &$msg, true);
138        }
139    };
140}
141
142/// Prints the message with the ⚠️ prefix.
143///
144/// ```rust
145/// use kasl::msg_warning;
146/// use kasl::libs::messages::types::Message;
147///
148/// msg_warning!(Message::AutostartCheckingAlternative);
149/// ```
150///
151/// ```rust
152/// use kasl::msg_warning;
153/// use kasl::libs::messages::types::Message;
154///
155/// msg_warning!(Message::WatcherSignalHandlingNotSupported, true);
156/// ```
157#[macro_export]
158macro_rules! msg_warning {
159    ($msg:expr) => {
160        if $crate::libs::messages::macros::is_debug_mode() {
161            tracing::warn!("{}{}", $crate::libs::output::style().prefix($crate::libs::output::Kind::Warning), $msg);
162        } else {
163            $crate::libs::output::emit($crate::libs::output::Kind::Warning, &$msg, false);
164        }
165    };
166    ($msg:expr, true) => {
167        if $crate::libs::messages::macros::is_debug_mode() {
168            tracing::warn!("\n{}{}\n", $crate::libs::output::style().prefix($crate::libs::output::Kind::Warning), $msg);
169        } else {
170            $crate::libs::output::emit($crate::libs::output::Kind::Warning, &$msg, true);
171        }
172    };
173}
174
175/// Prints the message with the ℹ️ prefix.
176///
177/// ```rust
178/// use kasl::msg_info;
179/// use kasl::libs::messages::types::Message;
180///
181/// msg_info!(Message::WatcherStarted(1234));
182/// ```
183///
184/// ```rust
185/// use kasl::msg_info;
186/// use kasl::libs::messages::types::Message;
187///
188/// msg_info!(Message::WorkingHoursForMonth("2025-01".to_string()), true);
189/// ```
190#[macro_export]
191macro_rules! msg_info {
192    ($msg:expr) => {
193        if $crate::libs::messages::macros::is_debug_mode() {
194            tracing::info!("{}{}", $crate::libs::output::style().prefix($crate::libs::output::Kind::Info), $msg);
195        } else {
196            $crate::libs::output::emit($crate::libs::output::Kind::Info, &$msg, false);
197        }
198    };
199    ($msg:expr, true) => {
200        if $crate::libs::messages::macros::is_debug_mode() {
201            tracing::info!("\n{}{}\n", $crate::libs::output::style().prefix($crate::libs::output::Kind::Info), $msg);
202        } else {
203            $crate::libs::output::emit($crate::libs::output::Kind::Info, &$msg, true);
204        }
205    };
206}
207
208/// Logs the message with the 🔍 prefix in debug mode; silent otherwise.
209///
210/// ```rust
211/// use kasl::msg_debug;
212///
213/// let task_id = 42;
214/// msg_debug!(format!("Processing task with ID: {}", task_id));
215/// ```
216///
217/// ```rust
218/// use kasl::msg_debug;
219///
220/// let old_state = "Active";
221/// let new_state = "InPause";
222/// msg_debug!(format!("State transition: {:?} -> {:?}", old_state, new_state));
223/// ```
224#[macro_export]
225macro_rules! msg_debug {
226    ($msg:expr) => {
227        if $crate::libs::messages::macros::is_debug_mode() {
228            tracing::debug!("🔍 {}", $msg);
229        }
230    };
231}
232
233/// Builds an `anyhow::Error` from the message.
234///
235/// Unmarked: the mark belongs to where the error is printed, which knows
236/// whether it is `❌` for a person or `error:` for `--plain`
237/// ([`crate::libs::output::report_error`]).
238///
239/// ```rust
240/// use anyhow::Result;
241/// use kasl::{msg_error_anyhow, libs::messages::Message};
242///
243/// # fn config_is_invalid() -> bool { false }
244/// fn validate_config() -> Result<()> {
245///     if config_is_invalid() {
246///         return Err(msg_error_anyhow!(Message::ConfigParseError));
247///     }
248///     Ok(())
249/// }
250/// ```
251///
252/// ```rust
253/// use anyhow::{Result, Context};
254/// use kasl::{msg_error_anyhow, libs::messages::Message};
255///
256/// # fn some_operation() -> Result<()> { Ok(()) }
257/// fn complex_operation() -> Result<()> {
258///     some_operation()
259///         .context(msg_error_anyhow!(Message::TaskUpdateFailed))
260/// }
261/// ```
262#[macro_export]
263macro_rules! msg_error_anyhow {
264    ($msg:expr) => {
265        anyhow::anyhow!("{}", $msg)
266    };
267}
268
269/// `return Err(...)` with the message, unmarked like [`msg_error_anyhow!`].
270///
271/// ```rust
272/// use anyhow::Result;
273/// use kasl::{msg_bail_anyhow, libs::messages::Message};
274///
275/// fn process_task(task_id: Option<i32>) -> Result<()> {
276///     let id = match task_id {
277///         Some(id) => id,
278///         None => msg_bail_anyhow!(Message::InvalidInput),
279///     };
280///     let _ = id;
281///     Ok(())
282/// }
283/// ```
284///
285/// ```rust
286/// use anyhow::Result;
287/// use kasl::{msg_bail_anyhow, libs::messages::Message};
288///
289/// # fn user_has_permission() -> bool { true }
290/// fn secure_operation() -> Result<()> {
291///     if !user_has_permission() {
292///         msg_bail_anyhow!(Message::PermissionDenied);
293///     }
294///     Ok(())
295/// }
296/// ```
297///
298/// ```rust
299/// use anyhow::Result;
300/// use kasl::{msg_bail_anyhow, libs::messages::Message};
301///
302/// # fn resource_exists(_path: &str) -> bool { true }
303/// fn access_resource(path: &str) -> Result<()> {
304///     if !resource_exists(path) {
305///         msg_bail_anyhow!(Message::FileNotFound);
306///     }
307///     Ok(())
308/// }
309/// ```
310#[macro_export]
311macro_rules! msg_bail_anyhow {
312    ($msg:expr) => {
313        anyhow::bail!("{}", $msg)
314    };
315}