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