falcon_cli/macros.rs
1// Copyright 2025 Aquila Labs of Alberta, Canada <matt@cicero.sh>
2// Licensed under either the Apache License, Version 2.0 OR the MIT License, at your option.
3// You may not use this file except in compliance with one of the Licenses.
4// Apache License text: https://www.apache.org/licenses/LICENSE-2.0
5// MIT License text: https://opensource.org/licenses/MIT
6
7pub use std::io::{self, Write};
8pub use textwrap::Options as Textwrap_Options;
9pub use textwrap::fill as Textwrap_Fill;
10
11/// Log levels for CLI output.
12///
13/// Defines the different types of messages that can be logged, from simple
14/// output to debug and trace messages.
15#[derive(Debug, Clone, Copy)]
16pub enum CliLevel {
17 /// Send output without newline.
18 Send,
19 /// Send output with newline.
20 SendLn,
21 /// Informational message.
22 Info,
23 /// Warning message.
24 Warn,
25 /// Error message.
26 Error,
27 /// Debug message (requires `log` feature).
28 Debug,
29 /// Trace message (requires `log` feature).
30 Trace,
31}
32
33/// Core logging function used by CLI output macros.
34///
35/// Formats the text with provided arguments, applies word wrapping, and outputs
36/// according to the specified log level. When the `log` feature is enabled, also
37/// logs to the configured logging backend.
38///
39/// # Arguments
40///
41/// * `level` - The log level determining output behavior
42/// * `text` - The text to output (may contain `{}` placeholders)
43/// * `args` - Arguments to replace placeholders in the text
44pub fn cli_log(level: CliLevel, text: &str, args: &[String]) {
45 let wrapped = format_wrapped(text, args, None);
46
47 match level {
48 CliLevel::Send => {
49 print!("{}", wrapped);
50 }
51 CliLevel::SendLn => {
52 println!("{}", wrapped);
53 }
54
55 _other => {
56 #[cfg(feature = "log")]
57 match other {
58 CliLevel::Info => log::info!("{}", text),
59 CliLevel::Warn => log::warn!("{}", text),
60 CliLevel::Error => log::error!("{}", text),
61 CliLevel::Debug => log::debug!("{}", text),
62 CliLevel::Trace => log::trace!("{}", text),
63 _ => {}
64 }
65 println!("{}", wrapped);
66 }
67 }
68
69 io::stdout().flush().unwrap();
70}
71
72/// Formats text with argument substitution and word wrapping.
73///
74/// Replaces `{}` placeholders in the text with arguments, optionally adds a prefix,
75/// and applies word wrapping at 75 characters.
76///
77/// # Arguments
78///
79/// * `text` - The text to format (may contain `{}` placeholders)
80/// * `args` - Arguments to replace placeholders
81/// * `prefix` - Optional prefix to prepend to the text
82fn format_wrapped(text: &str, args: &[String], prefix: Option<&str>) -> String {
83 // Replace placeholders
84 let mut text = text.to_string();
85 for arg in args {
86 text = text.replacen("{}", arg, 1);
87 }
88
89 if let Some(prefix) = prefix {
90 text = format!("{}{}", prefix, text);
91 }
92
93 // Word wrap
94 Textwrap_Fill(&text, Textwrap_Options::new(75))
95}
96
97/// Outputs text without a newline.
98///
99/// Similar to `print!`, but with word wrapping. Supports format-style arguments.
100///
101/// # Example
102///
103/// ```
104/// use falcon_cli::cli_send;
105///
106/// cli_send!("Loading");
107/// cli_send!("..."); // Continues on same line
108/// ```
109#[macro_export]
110macro_rules! cli_send {
111 ($text:expr) => { $crate::cli_log($crate::CliLevel::Send, $text, &[]) };
112 ($text:expr, $( $arg:expr ),*) => {{
113 let mut args = vec![];
114 $( args.push($arg.to_string()); )*
115 $crate::cli_log($crate::CliLevel::Send, $text, &args)
116 }};
117}
118
119/// Outputs text with a newline.
120///
121/// Similar to `println!`, but with word wrapping. Supports format-style arguments.
122///
123/// # Example
124///
125/// ```
126/// use falcon_cli::cli_sendln;
127///
128/// cli_sendln!("Hello, world!");
129/// cli_sendln!("User: {}", "Alice");
130/// ```
131#[macro_export]
132macro_rules! cli_sendln {
133 ($text:expr) => { $crate::cli_log($crate::CliLevel::SendLn, $text, &[]) };
134 ($text:expr, $( $arg:expr ),*) => {{
135 let mut args = vec![];
136 $( args.push($arg.to_string()); )*
137 $crate::cli_log($crate::CliLevel::SendLn, $text, &args)
138 }};
139}
140
141/// Outputs an informational message.
142///
143/// Displays text and optionally logs to the configured logger when the `log` feature is enabled.
144///
145/// # Example
146///
147/// ```
148/// use falcon_cli::cli_info;
149///
150/// cli_info!("Application started successfully");
151/// cli_info!("Loaded {} configuration files", 5);
152/// ```
153#[macro_export]
154macro_rules! cli_info {
155 ($text:expr) => { $crate::cli_log($crate::CliLevel::Info, $text, &[]) };
156 ($text:expr, $( $arg:expr ),*) => {{
157 let mut args = vec![];
158 $( args.push($arg.to_string()); )*
159 $crate::cli_log($crate::CliLevel::Info, $text, &args)
160 }};
161}
162
163/// Outputs a warning message.
164///
165/// Displays text and optionally logs to the configured logger when the `log` feature is enabled.
166///
167/// # Example
168///
169/// ```
170/// use falcon_cli::cli_warn;
171///
172/// cli_warn!("Configuration file not found, using defaults");
173/// cli_warn!("Deprecated feature: {}", "old_api");
174/// ```
175#[macro_export]
176macro_rules! cli_warn {
177 ($text:expr) => { $crate::cli_log($crate::CliLevel::Warn, $text, &[]) };
178 ($text:expr, $( $arg:expr ),*) => {{
179 let mut args = vec![];
180 $( args.push($arg.to_string()); )*
181 $crate::cli_log($crate::CliLevel::Warn, $text, &args)
182 }};
183}
184
185/// Outputs an error message.
186///
187/// Displays text and optionally logs to the configured logger when the `log` feature is enabled.
188///
189/// # Example
190///
191/// ```
192/// use falcon_cli::cli_error;
193///
194/// cli_error!("Failed to connect to database");
195/// cli_error!("Invalid input: {}", input_value);
196/// ```
197#[macro_export]
198macro_rules! cli_error {
199 ($text:expr) => { $crate::cli_log($crate::CliLevel::Error, $text, &[]) };
200 ($text:expr, $( $arg:expr ),*) => {{
201 let mut args = vec![];
202 $( args.push($arg.to_string()); )*
203 $crate::cli_log($crate::CliLevel::Error, $text, &args)
204 }};
205}
206
207/// Outputs a debug message.
208///
209/// Displays text and logs to the configured logger when the `log` feature is enabled.
210///
211/// # Example
212///
213/// ```
214/// use falcon_cli::cli_debug;
215///
216/// cli_debug!("Processing step 1 of 3");
217/// cli_debug!("Variable value: {}", debug_value);
218/// ```
219#[macro_export]
220macro_rules! cli_debug {
221 ($text:expr) => { $crate::cli_log($crate::CliLevel::Debug, $text, &[]) };
222 ($text:expr, $( $arg:expr ),*) => {{
223 let mut args = vec![];
224 $( args.push($arg.to_string()); )*
225 $crate::cli_log($crate::CliLevel::Debug, $text, &args)
226 }};
227}
228
229/// Outputs a trace message.
230///
231/// Displays text and logs to the configured logger when the `log` feature is enabled.
232/// Used for very detailed diagnostic information.
233///
234/// # Example
235///
236/// ```
237/// use falcon_cli::cli_trace;
238///
239/// cli_trace!("Entering function parse_config");
240/// cli_trace!("Loop iteration: {}", i);
241/// ```
242#[macro_export]
243macro_rules! cli_trace {
244 ($text:expr) => { $crate::cli_log($crate::CliLevel::Trace, $text, &[]) };
245 ($text:expr, $( $arg:expr ),*) => {{
246 let mut args = vec![];
247 $( args.push($arg.to_string()); )*
248 $crate::cli_log($crate::CliLevel::Trace, $text, &args)
249 }};
250}