adrscope/cli/
handlers.rs

1//! Command handlers that execute use cases.
2
3use std::io::{self, Write};
4
5use crate::application::{
6    GenerateOptions, GenerateUseCase, StatsOptions, StatsUseCase, ValidateOptions, ValidateUseCase,
7    WikiOptions, WikiUseCase,
8};
9use crate::cli::args::{Cli, Commands, GenerateArgs, StatsArgs, ValidateArgs, WikiArgs};
10use crate::domain::Severity;
11use crate::error::Result;
12use crate::infrastructure::RealFileSystem;
13
14/// Runs the CLI with the parsed arguments.
15///
16/// # Errors
17///
18/// Returns an error if the command execution fails.
19pub fn run(cli: Cli) -> Result<i32> {
20    match cli.command {
21        Commands::Generate(args) => handle_generate(args, cli.verbose),
22        Commands::Wiki(args) => handle_wiki(args, cli.verbose),
23        Commands::Validate(args) => handle_validate(args, cli.verbose),
24        Commands::Stats(args) => handle_stats(args, cli.verbose),
25    }
26}
27
28fn handle_generate(args: GenerateArgs, verbose: bool) -> Result<i32> {
29    let fs = RealFileSystem::new();
30    let use_case = GenerateUseCase::new(fs);
31
32    let options = GenerateOptions::new(&args.input)
33        .with_output(&args.output)
34        .with_title(&args.title)
35        .with_theme(args.theme.into())
36        .with_pattern(&args.pattern);
37
38    if verbose {
39        eprintln!("Scanning for ADRs in: {}", args.input);
40    }
41
42    let result = use_case.execute(&options)?;
43
44    // Report parse errors
45    if result.has_errors() {
46        eprintln!("\nWarnings:");
47        for (path, error) in &result.parse_errors {
48            eprintln!("  {} - {}", path.display(), error);
49        }
50    }
51
52    println!(
53        "Generated {} with {} ADRs",
54        result.output_path, result.adr_count
55    );
56
57    Ok(0)
58}
59
60fn handle_wiki(args: WikiArgs, verbose: bool) -> Result<i32> {
61    let fs = RealFileSystem::new();
62    let use_case = WikiUseCase::new(fs);
63
64    let mut options = WikiOptions::new(&args.input)
65        .with_output_dir(&args.output)
66        .with_pattern(&args.pattern);
67
68    if let Some(url) = &args.pages_url {
69        options = options.with_pages_url(url);
70    }
71
72    if verbose {
73        eprintln!("Scanning for ADRs in: {}", args.input);
74    }
75
76    let result = use_case.execute(&options)?;
77
78    // Report parse errors
79    if result.has_errors() {
80        eprintln!("\nWarnings:");
81        for (path, error) in &result.parse_errors {
82            eprintln!("  {} - {}", path.display(), error);
83        }
84    }
85
86    println!(
87        "Generated {} wiki files in {} from {} ADRs",
88        result.generated_files.len(),
89        result.output_dir,
90        result.adr_count
91    );
92
93    if verbose {
94        eprintln!("\nGenerated files:");
95        for file in &result.generated_files {
96            eprintln!("  {file}");
97        }
98    }
99
100    Ok(0)
101}
102
103fn handle_validate(args: ValidateArgs, verbose: bool) -> Result<i32> {
104    let fs = RealFileSystem::new();
105    let use_case = ValidateUseCase::new(fs);
106
107    let options = ValidateOptions::new(&args.input)
108        .with_pattern(&args.pattern)
109        .with_strict(args.strict);
110
111    if verbose {
112        eprintln!("Validating ADRs in: {}", args.input);
113    }
114
115    let result = use_case.execute(&options)?;
116
117    // Report parse errors
118    for (path, error) in &result.parse_errors {
119        eprintln!("ERROR: {} - {}", path.display(), error);
120    }
121
122    // Report validation issues
123    let mut stdout = io::stdout();
124    for (path, issue) in result.all_issues() {
125        let prefix = match issue.severity {
126            Severity::Error => "ERROR",
127            Severity::Warning => "WARNING",
128        };
129        let _ = writeln!(
130            stdout,
131            "{}: {} - {} [{}]",
132            prefix,
133            path.display(),
134            issue.message,
135            issue.rule
136        );
137    }
138
139    // Summary
140    println!(
141        "\nValidation complete: {} errors, {} warnings",
142        result.total_errors, result.total_warnings
143    );
144
145    if result.passed {
146        println!("All checks passed.");
147        Ok(0)
148    } else {
149        println!("Validation failed.");
150        Ok(1)
151    }
152}
153
154fn handle_stats(args: StatsArgs, verbose: bool) -> Result<i32> {
155    let fs = RealFileSystem::new();
156    let use_case = StatsUseCase::new(fs);
157
158    let options = StatsOptions::new(&args.input)
159        .with_pattern(&args.pattern)
160        .with_format(args.format.into());
161
162    if verbose {
163        eprintln!("Computing statistics for ADRs in: {}", args.input);
164    }
165
166    let result = use_case.execute(&options)?;
167
168    // Report parse errors
169    if result.has_errors() {
170        eprintln!("\nWarnings:");
171        for (path, error) in &result.parse_errors {
172            eprintln!("  {} - {}", path.display(), error);
173        }
174        eprintln!();
175    }
176
177    println!("{}", result.output);
178
179    Ok(0)
180}
181
182#[cfg(test)]
183mod tests {
184    use super::*;
185
186    // Integration tests would require file system setup,
187    // so we primarily test through the use cases directly.
188    // These handler functions are thin wrappers.
189
190    #[test]
191    fn test_handler_functions_exist() {
192        // Verify that all handler functions are properly defined
193        // by checking they can be referenced
194        let _: fn(GenerateArgs, bool) -> Result<i32> = handle_generate;
195        let _: fn(WikiArgs, bool) -> Result<i32> = handle_wiki;
196        let _: fn(ValidateArgs, bool) -> Result<i32> = handle_validate;
197        let _: fn(StatsArgs, bool) -> Result<i32> = handle_stats;
198    }
199}