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}