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}