adrscope/cli/
args.rs

1//! Command-line argument definitions using clap derive.
2
3use clap::{Parser, Subcommand, ValueEnum};
4
5/// ADRScope - Generate self-contained HTML viewers for Architecture Decision Records.
6#[derive(Parser, Debug)]
7#[command(name = "adrscope")]
8#[command(author, version, about, long_about = None)]
9pub struct Cli {
10    /// Enable verbose output.
11    #[arg(short, long, global = true)]
12    pub verbose: bool,
13
14    /// The command to run.
15    #[command(subcommand)]
16    pub command: Commands,
17}
18
19/// Available commands.
20#[derive(Subcommand, Debug)]
21pub enum Commands {
22    /// Generate a self-contained HTML viewer.
23    Generate(GenerateArgs),
24
25    /// Generate GitHub Wiki pages.
26    Wiki(WikiArgs),
27
28    /// Validate ADR files.
29    Validate(ValidateArgs),
30
31    /// Show ADR statistics.
32    Stats(StatsArgs),
33}
34
35/// Arguments for the generate command.
36#[derive(Parser, Debug)]
37pub struct GenerateArgs {
38    /// Input directory containing ADR files.
39    #[arg(short, long, default_value = "docs/decisions")]
40    pub input: String,
41
42    /// Output HTML file path.
43    #[arg(short, long, default_value = "adrs.html")]
44    pub output: String,
45
46    /// Page title.
47    #[arg(short, long, default_value = "Architecture Decision Records")]
48    pub title: String,
49
50    /// Theme preference.
51    #[arg(long, value_enum, default_value = "auto")]
52    pub theme: ThemeArg,
53
54    /// Glob pattern for matching ADR files.
55    #[arg(short, long, default_value = "**/*.md")]
56    pub pattern: String,
57}
58
59/// Arguments for the wiki command.
60#[derive(Parser, Debug)]
61pub struct WikiArgs {
62    /// Input directory containing ADR files.
63    #[arg(short, long, default_value = "docs/decisions")]
64    pub input: String,
65
66    /// Output directory for wiki files.
67    #[arg(short, long, default_value = "wiki")]
68    pub output: String,
69
70    /// URL to the GitHub Pages viewer (for cross-linking).
71    #[arg(long)]
72    pub pages_url: Option<String>,
73
74    /// Glob pattern for matching ADR files.
75    #[arg(short, long, default_value = "**/*.md")]
76    pub pattern: String,
77}
78
79/// Arguments for the validate command.
80#[derive(Parser, Debug)]
81pub struct ValidateArgs {
82    /// Input directory containing ADR files.
83    #[arg(short, long, default_value = "docs/decisions")]
84    pub input: String,
85
86    /// Glob pattern for matching ADR files.
87    #[arg(short, long, default_value = "**/*.md")]
88    pub pattern: String,
89
90    /// Fail on warnings (strict mode).
91    #[arg(long)]
92    pub strict: bool,
93}
94
95/// Arguments for the stats command.
96#[derive(Parser, Debug)]
97pub struct StatsArgs {
98    /// Input directory containing ADR files.
99    #[arg(short, long, default_value = "docs/decisions")]
100    pub input: String,
101
102    /// Glob pattern for matching ADR files.
103    #[arg(short, long, default_value = "**/*.md")]
104    pub pattern: String,
105
106    /// Output format.
107    #[arg(short, long, value_enum, default_value = "text")]
108    pub format: FormatArg,
109}
110
111/// Theme argument for CLI.
112#[derive(ValueEnum, Clone, Debug, Default)]
113pub enum ThemeArg {
114    /// Light theme.
115    Light,
116    /// Dark theme.
117    Dark,
118    /// Auto (follows system preference).
119    #[default]
120    Auto,
121}
122
123impl From<ThemeArg> for crate::infrastructure::Theme {
124    fn from(arg: ThemeArg) -> Self {
125        match arg {
126            ThemeArg::Light => Self::Light,
127            ThemeArg::Dark => Self::Dark,
128            ThemeArg::Auto => Self::Auto,
129        }
130    }
131}
132
133/// Output format argument for CLI.
134#[derive(ValueEnum, Clone, Debug, Default)]
135pub enum FormatArg {
136    /// Human-readable text.
137    #[default]
138    Text,
139    /// JSON format.
140    Json,
141    /// Markdown format.
142    Markdown,
143}
144
145impl From<FormatArg> for crate::application::stats::StatsFormat {
146    fn from(arg: FormatArg) -> Self {
147        match arg {
148            FormatArg::Text => Self::Text,
149            FormatArg::Json => Self::Json,
150            FormatArg::Markdown => Self::Markdown,
151        }
152    }
153}
154
155#[cfg(test)]
156mod tests {
157    use super::*;
158    use clap::CommandFactory;
159
160    #[test]
161    fn test_cli_parses() {
162        // Verify that the CLI structure is valid
163        Cli::command().debug_assert();
164    }
165
166    #[test]
167    fn test_generate_defaults() {
168        let args = GenerateArgs {
169            input: "docs/decisions".to_string(),
170            output: "adrs.html".to_string(),
171            title: "ADRs".to_string(),
172            theme: ThemeArg::Auto,
173            pattern: "**/*.md".to_string(),
174        };
175
176        assert_eq!(args.input, "docs/decisions");
177        assert_eq!(args.output, "adrs.html");
178    }
179
180    #[test]
181    fn test_theme_conversion() {
182        use crate::infrastructure::Theme;
183
184        assert!(matches!(Theme::from(ThemeArg::Light), Theme::Light));
185        assert!(matches!(Theme::from(ThemeArg::Dark), Theme::Dark));
186        assert!(matches!(Theme::from(ThemeArg::Auto), Theme::Auto));
187    }
188
189    #[test]
190    fn test_format_conversion() {
191        use crate::application::stats::StatsFormat;
192
193        assert!(matches!(
194            StatsFormat::from(FormatArg::Text),
195            StatsFormat::Text
196        ));
197        assert!(matches!(
198            StatsFormat::from(FormatArg::Json),
199            StatsFormat::Json
200        ));
201        assert!(matches!(
202            StatsFormat::from(FormatArg::Markdown),
203            StatsFormat::Markdown
204        ));
205    }
206}