sublime_cli_tools/cli/
args.rs

1//! Shared argument types for CLI commands.
2//!
3//! This module defines common argument types used across multiple commands
4//! and provides validation and conversion utilities.
5//!
6//! # What
7//!
8//! Provides:
9//! - `LogLevel` enum for controlling logging verbosity
10//! - `OutputFormatArg` wrapper for output format parsing
11//! - Shared validation logic for common argument patterns
12//! - Conversion utilities between CLI args and internal types
13//!
14//! # How
15//!
16//! Uses Clap's `ValueEnum` trait for automatic parsing and validation.
17//! Types implement Display and FromStr for flexible usage.
18//!
19//! # Why
20//!
21//! Centralizes argument type definitions to ensure consistency across
22//! all commands and provide a single source of truth for valid values.
23//!
24//! # Examples
25//!
26//! ```rust
27//! use sublime_cli_tools::cli::LogLevel;
28//! use std::str::FromStr;
29//!
30//! let level = LogLevel::from_str("debug").unwrap();
31//! assert_eq!(level, LogLevel::Debug);
32//! ```
33
34use clap::ValueEnum;
35use std::fmt;
36use std::str::FromStr;
37
38use crate::output::OutputFormat;
39
40/// Logging level for controlling verbosity.
41///
42/// Controls what logs are written to stderr. This is completely independent
43/// of the output format (stdout).
44///
45/// # Examples
46///
47/// ```rust
48/// use sublime_cli_tools::cli::LogLevel;
49///
50/// let level = LogLevel::Info;
51/// let tracing_level = level.to_tracing_level();
52/// assert_eq!(tracing_level, tracing::Level::INFO);
53/// ```
54#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
55#[clap(rename_all = "lowercase")]
56pub enum LogLevel {
57    /// No logs at all.
58    ///
59    /// Complete silence on stderr. Useful for automation where only
60    /// stdout output is desired.
61    Silent,
62
63    /// Only critical errors.
64    ///
65    /// Shows only errors that prevent command execution.
66    Error,
67
68    /// Errors and warnings.
69    ///
70    /// Shows errors and important warnings.
71    Warn,
72
73    /// General progress information.
74    ///
75    /// Shows high-level progress and completion messages.
76    /// This is the default level.
77    Info,
78
79    /// Detailed operation logs.
80    ///
81    /// Shows internal operations and decision points.
82    /// Useful for troubleshooting.
83    Debug,
84
85    /// Very verbose debugging.
86    ///
87    /// Shows all internal operations including low-level details.
88    /// Use for deep debugging only.
89    Trace,
90}
91
92impl LogLevel {
93    /// Converts to a tracing Level.
94    ///
95    /// Note: Silent is mapped to ERROR but should be filtered out
96    /// during logger initialization.
97    ///
98    /// # Examples
99    ///
100    /// ```rust
101    /// use sublime_cli_tools::cli::LogLevel;
102    ///
103    /// assert_eq!(LogLevel::Info.to_tracing_level(), tracing::Level::INFO);
104    /// assert_eq!(LogLevel::Debug.to_tracing_level(), tracing::Level::DEBUG);
105    /// assert_eq!(LogLevel::Trace.to_tracing_level(), tracing::Level::TRACE);
106    /// ```
107    #[must_use]
108    pub const fn to_tracing_level(&self) -> tracing::Level {
109        match self {
110            Self::Silent | Self::Error => tracing::Level::ERROR,
111            Self::Warn => tracing::Level::WARN,
112            Self::Info => tracing::Level::INFO,
113            Self::Debug => tracing::Level::DEBUG,
114            Self::Trace => tracing::Level::TRACE,
115        }
116    }
117
118    /// Returns true if this is silent mode.
119    ///
120    /// # Examples
121    ///
122    /// ```rust
123    /// use sublime_cli_tools::cli::LogLevel;
124    ///
125    /// assert!(LogLevel::Silent.is_silent());
126    /// assert!(!LogLevel::Info.is_silent());
127    /// ```
128    #[must_use]
129    pub const fn is_silent(&self) -> bool {
130        matches!(self, Self::Silent)
131    }
132
133    /// Returns true if this level includes error logs.
134    ///
135    /// # Examples
136    ///
137    /// ```rust
138    /// use sublime_cli_tools::cli::LogLevel;
139    ///
140    /// assert!(LogLevel::Error.includes_errors());
141    /// assert!(LogLevel::Info.includes_errors());
142    /// assert!(!LogLevel::Silent.includes_errors());
143    /// ```
144    #[must_use]
145    pub const fn includes_errors(&self) -> bool {
146        !matches!(self, Self::Silent)
147    }
148
149    /// Returns true if this level includes warning logs.
150    ///
151    /// # Examples
152    ///
153    /// ```rust
154    /// use sublime_cli_tools::cli::LogLevel;
155    ///
156    /// assert!(LogLevel::Warn.includes_warnings());
157    /// assert!(LogLevel::Info.includes_warnings());
158    /// assert!(!LogLevel::Error.includes_warnings());
159    /// ```
160    #[must_use]
161    pub const fn includes_warnings(&self) -> bool {
162        matches!(self, Self::Warn | Self::Info | Self::Debug | Self::Trace)
163    }
164
165    /// Returns true if this level includes info logs.
166    ///
167    /// # Examples
168    ///
169    /// ```rust
170    /// use sublime_cli_tools::cli::LogLevel;
171    ///
172    /// assert!(LogLevel::Info.includes_info());
173    /// assert!(LogLevel::Debug.includes_info());
174    /// assert!(!LogLevel::Warn.includes_info());
175    /// ```
176    #[must_use]
177    pub const fn includes_info(&self) -> bool {
178        matches!(self, Self::Info | Self::Debug | Self::Trace)
179    }
180
181    /// Returns true if this level includes debug logs.
182    ///
183    /// # Examples
184    ///
185    /// ```rust
186    /// use sublime_cli_tools::cli::LogLevel;
187    ///
188    /// assert!(LogLevel::Debug.includes_debug());
189    /// assert!(LogLevel::Trace.includes_debug());
190    /// assert!(!LogLevel::Info.includes_debug());
191    /// ```
192    #[must_use]
193    pub const fn includes_debug(&self) -> bool {
194        matches!(self, Self::Debug | Self::Trace)
195    }
196
197    /// Returns true if this level includes trace logs.
198    ///
199    /// # Examples
200    ///
201    /// ```rust
202    /// use sublime_cli_tools::cli::LogLevel;
203    ///
204    /// assert!(LogLevel::Trace.includes_trace());
205    /// assert!(!LogLevel::Debug.includes_trace());
206    /// ```
207    #[must_use]
208    pub const fn includes_trace(&self) -> bool {
209        matches!(self, Self::Trace)
210    }
211}
212
213impl Default for LogLevel {
214    fn default() -> Self {
215        Self::Info
216    }
217}
218
219impl fmt::Display for LogLevel {
220    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
221        match self {
222            Self::Silent => write!(f, "silent"),
223            Self::Error => write!(f, "error"),
224            Self::Warn => write!(f, "warn"),
225            Self::Info => write!(f, "info"),
226            Self::Debug => write!(f, "debug"),
227            Self::Trace => write!(f, "trace"),
228        }
229    }
230}
231
232impl FromStr for LogLevel {
233    type Err = String;
234
235    fn from_str(s: &str) -> Result<Self, Self::Err> {
236        match s.to_lowercase().as_str() {
237            "silent" => Ok(Self::Silent),
238            "error" => Ok(Self::Error),
239            "warn" | "warning" => Ok(Self::Warn),
240            "info" => Ok(Self::Info),
241            "debug" | "verbose" => Ok(Self::Debug),
242            "trace" => Ok(Self::Trace),
243            _ => Err(format!(
244                "Invalid log level '{s}'. Valid options: silent, error, warn, info, debug, verbose, trace"
245            )),
246        }
247    }
248}
249
250/// Wrapper for OutputFormat to implement Clap traits.
251///
252/// This type wraps `OutputFormat` to provide Clap integration while keeping
253/// the core `OutputFormat` type independent of CLI concerns.
254///
255/// # Examples
256///
257/// ```rust
258/// use sublime_cli_tools::cli::OutputFormatArg;
259/// use sublime_cli_tools::output::OutputFormat;
260/// use std::str::FromStr;
261///
262/// let arg = OutputFormatArg::from_str("json").unwrap();
263/// assert_eq!(arg.0, OutputFormat::Json);
264/// ```
265#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
266pub struct OutputFormatArg(pub OutputFormat);
267
268impl OutputFormatArg {
269    /// Creates a new OutputFormatArg from OutputFormat.
270    ///
271    /// # Examples
272    ///
273    /// ```rust
274    /// use sublime_cli_tools::cli::OutputFormatArg;
275    /// use sublime_cli_tools::output::OutputFormat;
276    ///
277    /// let arg = OutputFormatArg::new(OutputFormat::Json);
278    /// assert_eq!(arg.0, OutputFormat::Json);
279    /// ```
280    #[must_use]
281    pub const fn new(format: OutputFormat) -> Self {
282        Self(format)
283    }
284
285    /// Returns the inner OutputFormat.
286    ///
287    /// # Examples
288    ///
289    /// ```rust
290    /// use sublime_cli_tools::cli::OutputFormatArg;
291    /// use sublime_cli_tools::output::OutputFormat;
292    ///
293    /// let arg = OutputFormatArg::new(OutputFormat::Json);
294    /// assert_eq!(arg.into_inner(), OutputFormat::Json);
295    /// ```
296    #[must_use]
297    pub const fn into_inner(self) -> OutputFormat {
298        self.0
299    }
300
301    /// Returns a reference to the inner OutputFormat.
302    ///
303    /// # Examples
304    ///
305    /// ```rust
306    /// use sublime_cli_tools::cli::OutputFormatArg;
307    /// use sublime_cli_tools::output::OutputFormat;
308    ///
309    /// let arg = OutputFormatArg::new(OutputFormat::Json);
310    /// assert_eq!(*arg.as_inner(), OutputFormat::Json);
311    /// ```
312    #[must_use]
313    pub const fn as_inner(&self) -> &OutputFormat {
314        &self.0
315    }
316}
317
318impl fmt::Display for OutputFormatArg {
319    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
320        write!(f, "{}", self.0)
321    }
322}
323
324impl FromStr for OutputFormatArg {
325    type Err = String;
326
327    fn from_str(s: &str) -> Result<Self, Self::Err> {
328        OutputFormat::from_str(s).map(Self)
329    }
330}
331
332impl ValueEnum for OutputFormatArg {
333    fn value_variants<'a>() -> &'a [Self] {
334        &[
335            Self(OutputFormat::Human),
336            Self(OutputFormat::Json),
337            Self(OutputFormat::JsonCompact),
338            Self(OutputFormat::Quiet),
339        ]
340    }
341
342    fn to_possible_value(&self) -> Option<clap::builder::PossibleValue> {
343        Some(match self.0 {
344            OutputFormat::Human => clap::builder::PossibleValue::new("human")
345                .help("Human-readable output with colors and tables"),
346            OutputFormat::Json => {
347                clap::builder::PossibleValue::new("json").help("Pretty-printed JSON output")
348            }
349            OutputFormat::JsonCompact => clap::builder::PossibleValue::new("json-compact")
350                .help("Compact JSON output (single line)"),
351            OutputFormat::Quiet => {
352                clap::builder::PossibleValue::new("quiet").help("Minimal output")
353            }
354        })
355    }
356}
357
358impl From<OutputFormat> for OutputFormatArg {
359    fn from(format: OutputFormat) -> Self {
360        Self(format)
361    }
362}
363
364impl From<OutputFormatArg> for OutputFormat {
365    fn from(arg: OutputFormatArg) -> Self {
366        arg.0
367    }
368}