Skip to content

Commit 7e461ed

Browse files
committed
refactor: [#116] extract handlers module with check and list submodules
1 parent 7da8994 commit 7e461ed

File tree

7 files changed

+220
-154
lines changed

7 files changed

+220
-154
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//! Application logic for the dependency installer CLI
2+
//!
3+
//! This module contains the core application logic for running the CLI.
4+
5+
use clap::Parser;
6+
7+
use crate::cli::{Cli, Commands};
8+
use crate::DependencyManager;
9+
10+
/// Run the CLI application
11+
///
12+
/// # Errors
13+
///
14+
/// Returns an error if:
15+
/// - Dependencies are missing
16+
/// - Invalid tool name is provided
17+
/// - Internal error occurs during dependency checking
18+
pub fn run() -> Result<(), Box<dyn std::error::Error>> {
19+
let cli = Cli::parse();
20+
21+
// Initialize tracing based on verbose flag
22+
// Must set environment variable before calling init_tracing()
23+
if cli.verbose {
24+
std::env::set_var("RUST_LOG", "debug");
25+
}
26+
27+
crate::init_tracing();
28+
29+
let manager = DependencyManager::new();
30+
31+
match cli.command {
32+
Commands::Check { tool } => crate::handlers::check::handle_check(&manager, tool),
33+
Commands::List => crate::handlers::list::handle_list(&manager),
34+
}
35+
}

packages/dependency-installer/src/bin/dependency-installer.rs

Lines changed: 17 additions & 154 deletions
Original file line numberDiff line numberDiff line change
@@ -12,171 +12,34 @@
1212
1313
use std::process;
1414

15-
use clap::{Parser, Subcommand};
16-
use torrust_dependency_installer::{Dependency, DependencyManager};
17-
use tracing::{error, info};
18-
19-
/// Manage development dependencies for E2E tests
20-
#[derive(Parser)]
21-
#[command(name = "dependency-installer")]
22-
#[command(version)]
23-
#[command(about = "Manage development dependencies for E2E tests", long_about = None)]
24-
struct Cli {
25-
#[command(subcommand)]
26-
command: Commands,
27-
28-
/// Enable verbose output
29-
#[arg(short, long, global = true)]
30-
verbose: bool,
31-
}
32-
33-
#[derive(Subcommand)]
34-
enum Commands {
35-
/// Check if dependencies are installed
36-
Check {
37-
/// Specific tool to check (if omitted, checks all)
38-
#[arg(short, long)]
39-
tool: Option<String>,
40-
},
41-
42-
/// List all available tools and their status
43-
List,
44-
}
15+
use torrust_dependency_installer::app;
4516

4617
fn main() {
47-
let exit_code = match run() {
18+
let exit_code = match app::run() {
4819
Ok(()) => 0,
4920
Err(e) => {
5021
eprintln!("Error: {e}");
51-
52-
// Determine exit code based on error type
53-
let error_msg = e.to_string();
54-
if error_msg.contains("not installed") || error_msg.contains("Missing") {
55-
1 // Missing dependency
56-
} else if error_msg.contains("Unknown tool") || error_msg.contains("invalid") {
57-
2 // Invalid argument
58-
} else {
59-
3 // Internal error
60-
}
22+
error_to_exit_code(e.as_ref())
6123
}
6224
};
6325

6426
process::exit(exit_code);
6527
}
6628

67-
fn run() -> Result<(), Box<dyn std::error::Error>> {
68-
let cli = Cli::parse();
69-
70-
// Initialize tracing based on verbose flag
71-
// Must set environment variable before calling init_tracing()
72-
if cli.verbose {
73-
std::env::set_var("RUST_LOG", "debug");
74-
}
75-
torrust_dependency_installer::init_tracing();
76-
77-
let manager = DependencyManager::new();
78-
79-
match cli.command {
80-
Commands::Check { tool } => handle_check(&manager, tool),
81-
Commands::List => handle_list(&manager),
82-
}
83-
}
84-
85-
fn handle_check(
86-
manager: &DependencyManager,
87-
tool: Option<String>,
88-
) -> Result<(), Box<dyn std::error::Error>> {
89-
match tool {
90-
Some(tool_name) => check_specific_tool(manager, &tool_name),
91-
None => check_all_tools(manager),
92-
}
93-
}
94-
95-
fn check_all_tools(manager: &DependencyManager) -> Result<(), Box<dyn std::error::Error>> {
96-
info!("Checking all dependencies");
97-
println!("Checking dependencies...\n");
98-
99-
let results = manager.check_all()?;
100-
let mut missing_count = 0;
101-
102-
for result in &results {
103-
if result.installed {
104-
println!("✓ {}: installed", result.tool);
105-
} else {
106-
println!("✗ {}: not installed", result.tool);
107-
missing_count += 1;
108-
}
109-
}
110-
111-
println!();
112-
if missing_count > 0 {
113-
let msg = format!(
114-
"Missing {missing_count} out of {} required dependencies",
115-
results.len()
116-
);
117-
error!("{}", msg);
118-
eprintln!("{msg}");
119-
Err(msg.into())
120-
} else {
121-
info!("All dependencies are installed");
122-
println!("All dependencies are installed");
123-
Ok(())
124-
}
125-
}
126-
127-
fn check_specific_tool(
128-
manager: &DependencyManager,
129-
tool_name: &str,
130-
) -> Result<(), Box<dyn std::error::Error>> {
131-
info!(tool = tool_name, "Checking specific tool");
132-
133-
// Parse tool name to Dependency enum
134-
let dep = parse_tool_name(tool_name)?;
135-
let detector = manager.get_detector(dep);
136-
137-
let installed = detector.is_installed()?;
138-
139-
if installed {
140-
info!(tool = detector.name(), "Tool is installed");
141-
println!("✓ {}: installed", detector.name());
142-
Ok(())
29+
/// Determine exit code based on error type
30+
///
31+
/// # Exit Codes
32+
///
33+
/// - 1: Missing dependencies (contains "not installed" or "Missing")
34+
/// - 2: Invalid arguments (contains "Unknown tool" or "invalid")
35+
/// - 3: Internal error (all other errors)
36+
fn error_to_exit_code(error: &dyn std::error::Error) -> i32 {
37+
let error_msg = error.to_string();
38+
if error_msg.contains("not installed") || error_msg.contains("Missing") {
39+
1 // Missing dependency
40+
} else if error_msg.contains("Unknown tool") || error_msg.contains("invalid") {
41+
2 // Invalid argument
14342
} else {
144-
let msg = format!("{}: not installed", detector.name());
145-
error!(tool = detector.name(), "Tool is not installed");
146-
eprintln!("✗ {msg}");
147-
Err(msg.into())
148-
}
149-
}
150-
151-
fn handle_list(manager: &DependencyManager) -> Result<(), Box<dyn std::error::Error>> {
152-
info!("Listing all available tools");
153-
println!("Available tools:\n");
154-
155-
let results = manager.check_all()?;
156-
for result in results {
157-
let status = if result.installed {
158-
"installed"
159-
} else {
160-
"not installed"
161-
};
162-
println!("- {} ({status})", result.tool);
163-
}
164-
165-
Ok(())
166-
}
167-
168-
fn parse_tool_name(name: &str) -> Result<Dependency, String> {
169-
match name.to_lowercase().as_str() {
170-
"cargo-machete" | "machete" => Ok(Dependency::CargoMachete),
171-
"opentofu" | "tofu" => Ok(Dependency::OpenTofu),
172-
"ansible" => Ok(Dependency::Ansible),
173-
"lxd" => Ok(Dependency::Lxd),
174-
_ => {
175-
// List of available tools - should be kept in sync with the match arms above
176-
const AVAILABLE_TOOLS: &str = "cargo-machete, opentofu, ansible, lxd";
177-
Err(format!(
178-
"Unknown tool: {name}. Available: {AVAILABLE_TOOLS}"
179-
))
180-
}
43+
3 // Internal error
18144
}
18245
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//! CLI argument parsing structures
2+
//!
3+
//! This module defines the command-line interface structure and commands
4+
//! for the dependency installer application.
5+
6+
use clap::{Parser, Subcommand};
7+
8+
/// Manage development dependencies for E2E tests
9+
#[derive(Parser)]
10+
#[command(name = "dependency-installer")]
11+
#[command(version)]
12+
#[command(about = "Manage development dependencies for E2E tests", long_about = None)]
13+
pub struct Cli {
14+
#[command(subcommand)]
15+
pub command: Commands,
16+
17+
/// Enable verbose output
18+
#[arg(short, long, global = true)]
19+
pub verbose: bool,
20+
}
21+
22+
#[derive(Subcommand)]
23+
pub enum Commands {
24+
/// Check if dependencies are installed
25+
Check {
26+
/// Specific tool to check (if omitted, checks all)
27+
#[arg(short, long)]
28+
tool: Option<String>,
29+
},
30+
31+
/// List all available tools and their status
32+
List,
33+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
//! Check command handler
2+
//!
3+
//! This module handles checking whether dependencies are installed.
4+
5+
use tracing::{error, info};
6+
7+
use crate::{Dependency, DependencyManager};
8+
9+
/// Handle the check command
10+
///
11+
/// # Errors
12+
///
13+
/// Returns an error if:
14+
/// - Dependencies are missing
15+
/// - Invalid tool name is provided
16+
/// - Internal error occurs during dependency checking
17+
pub fn handle_check(
18+
manager: &DependencyManager,
19+
tool: Option<String>,
20+
) -> Result<(), Box<dyn std::error::Error>> {
21+
match tool {
22+
Some(tool_name) => check_specific_tool(manager, &tool_name),
23+
None => check_all_tools(manager),
24+
}
25+
}
26+
27+
fn check_all_tools(manager: &DependencyManager) -> Result<(), Box<dyn std::error::Error>> {
28+
info!("Checking all dependencies");
29+
println!("Checking dependencies...\n");
30+
31+
let results = manager.check_all()?;
32+
let mut missing_count = 0;
33+
34+
for result in &results {
35+
if result.installed {
36+
println!("✓ {}: installed", result.tool);
37+
} else {
38+
println!("✗ {}: not installed", result.tool);
39+
missing_count += 1;
40+
}
41+
}
42+
43+
println!();
44+
if missing_count > 0 {
45+
let msg = format!(
46+
"Missing {missing_count} out of {} required dependencies",
47+
results.len()
48+
);
49+
error!("{}", msg);
50+
eprintln!("{msg}");
51+
Err(msg.into())
52+
} else {
53+
info!("All dependencies are installed");
54+
println!("All dependencies are installed");
55+
Ok(())
56+
}
57+
}
58+
59+
fn check_specific_tool(
60+
manager: &DependencyManager,
61+
tool_name: &str,
62+
) -> Result<(), Box<dyn std::error::Error>> {
63+
info!(tool = tool_name, "Checking specific tool");
64+
65+
// Parse tool name to Dependency enum
66+
let dep = parse_tool_name(tool_name)?;
67+
let detector = manager.get_detector(dep);
68+
69+
let installed = detector.is_installed()?;
70+
71+
if installed {
72+
info!(tool = detector.name(), "Tool is installed");
73+
println!("✓ {}: installed", detector.name());
74+
Ok(())
75+
} else {
76+
let msg = format!("{}: not installed", detector.name());
77+
error!(tool = detector.name(), "Tool is not installed");
78+
eprintln!("✗ {msg}");
79+
Err(msg.into())
80+
}
81+
}
82+
83+
fn parse_tool_name(name: &str) -> Result<Dependency, String> {
84+
match name.to_lowercase().as_str() {
85+
"cargo-machete" | "machete" => Ok(Dependency::CargoMachete),
86+
"opentofu" | "tofu" => Ok(Dependency::OpenTofu),
87+
"ansible" => Ok(Dependency::Ansible),
88+
"lxd" => Ok(Dependency::Lxd),
89+
_ => {
90+
// List of available tools - should be kept in sync with the match arms above
91+
const AVAILABLE_TOOLS: &str = "cargo-machete, opentofu, ansible, lxd";
92+
Err(format!(
93+
"Unknown tool: {name}. Available: {AVAILABLE_TOOLS}"
94+
))
95+
}
96+
}
97+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//! List command handler
2+
//!
3+
//! This module handles listing all available dependencies and their status.
4+
5+
use tracing::info;
6+
7+
use crate::DependencyManager;
8+
9+
/// Handle the list command
10+
///
11+
/// # Errors
12+
///
13+
/// Returns an error if dependency checking fails
14+
pub fn handle_list(manager: &DependencyManager) -> Result<(), Box<dyn std::error::Error>> {
15+
info!("Listing all available tools");
16+
println!("Available tools:\n");
17+
18+
let results = manager.check_all()?;
19+
for result in results {
20+
let status = if result.installed {
21+
"installed"
22+
} else {
23+
"not installed"
24+
};
25+
println!("- {} ({status})", result.tool);
26+
}
27+
28+
Ok(())
29+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
//! Command handlers for the dependency installer CLI
2+
//!
3+
//! This module contains handlers for different CLI commands.
4+
5+
pub mod check;
6+
pub mod list;

0 commit comments

Comments
 (0)