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}