Skip to content

Commit e4140a0

Browse files
Copilotjosecelano
andcommitted
feat: [#115] create CLI binary with check and list commands
Co-authored-by: josecelano <58816+josecelano@users.noreply.github.com>
1 parent a837e79 commit e4140a0

File tree

3 files changed

+252
-2
lines changed

3 files changed

+252
-2
lines changed

packages/dependency-installer/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,12 @@ license = "MIT"
99
name = "torrust_dependency_installer"
1010
path = "src/lib.rs"
1111

12+
[[bin]]
13+
name = "dependency-installer"
14+
path = "src/bin/dependency-installer.rs"
15+
1216
[dependencies]
17+
clap = { version = "4.0", features = [ "derive" ] }
1318
thiserror = "1.0"
1419
tracing = "0.1"
1520
tracing-subscriber = { version = "0.3", features = [ "env-filter" ] }

packages/dependency-installer/README.md

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,75 @@ This package can detect the following development dependencies:
2020

2121
## Usage
2222

23-
### Checking Dependencies
23+
### CLI Binary
24+
25+
The package provides a `dependency-installer` binary for command-line usage:
26+
27+
```bash
28+
# Check all dependencies
29+
dependency-installer check
30+
31+
# Check specific tool
32+
dependency-installer check --tool opentofu
33+
34+
# List all tools with status
35+
dependency-installer list
36+
37+
# Enable verbose logging
38+
dependency-installer check --verbose
39+
40+
# Get help
41+
dependency-installer --help
42+
dependency-installer check --help
43+
```
44+
45+
#### Exit Codes
46+
47+
- **0**: Success (all checks passed)
48+
- **1**: Missing dependencies
49+
- **2**: Invalid arguments
50+
- **3**: Internal error
51+
52+
#### Examples
53+
54+
```bash
55+
# Check all dependencies
56+
$ dependency-installer check
57+
Checking dependencies...
58+
59+
✓ cargo-machete: installed
60+
✗ OpenTofu: not installed
61+
✗ Ansible: not installed
62+
✓ LXD: installed
63+
64+
Missing 2 out of 4 required dependencies
65+
66+
# Check specific tool (with aliases)
67+
$ dependency-installer check --tool tofu
68+
✗ OpenTofu: not installed
69+
70+
# List all tools
71+
$ dependency-installer list
72+
Available tools:
73+
74+
- cargo-machete (installed)
75+
- OpenTofu (not installed)
76+
- Ansible (not installed)
77+
- LXD (installed)
78+
```
79+
80+
#### Tool Aliases
81+
82+
The CLI accepts multiple aliases for tools:
83+
84+
- `cargo-machete` or `machete`
85+
- `opentofu` or `tofu`
86+
- `ansible`
87+
- `lxd`
88+
89+
### Library Usage
90+
91+
#### Checking Dependencies
2492

2593
```rust
2694
use torrust_dependency_installer::DependencyManager;
@@ -39,7 +107,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
39107
}
40108
```
41109

42-
### Using Individual Detectors
110+
#### Using Individual Detectors
43111

44112
```rust
45113
use torrust_dependency_installer::{ToolDetector, OpenTofuDetector};
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
//! CLI binary for managing development dependencies
2+
//!
3+
//! This binary provides commands to check and list development dependencies
4+
//! required for E2E tests in the Torrust Tracker Deployer project.
5+
//!
6+
//! # Exit Codes
7+
//!
8+
//! - 0: Success (all checks passed)
9+
//! - 1: Missing dependencies
10+
//! - 2: Invalid arguments
11+
//! - 3: Internal error
12+
13+
use std::process;
14+
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+
}
45+
46+
fn main() {
47+
let exit_code = match run() {
48+
Ok(()) => 0,
49+
Err(e) => {
50+
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+
}
61+
}
62+
};
63+
64+
process::exit(exit_code);
65+
}
66+
67+
fn run() -> Result<(), Box<dyn std::error::Error>> {
68+
let cli = Cli::parse();
69+
70+
// Initialize tracing based on verbose flag
71+
if cli.verbose {
72+
std::env::set_var("RUST_LOG", "debug");
73+
}
74+
torrust_dependency_installer::init_tracing();
75+
76+
let manager = DependencyManager::new();
77+
78+
match cli.command {
79+
Commands::Check { tool } => handle_check(&manager, tool),
80+
Commands::List => handle_list(&manager),
81+
}
82+
}
83+
84+
fn handle_check(
85+
manager: &DependencyManager,
86+
tool: Option<String>,
87+
) -> Result<(), Box<dyn std::error::Error>> {
88+
match tool {
89+
Some(tool_name) => check_specific_tool(manager, &tool_name),
90+
None => check_all_tools(manager),
91+
}
92+
}
93+
94+
fn check_all_tools(manager: &DependencyManager) -> Result<(), Box<dyn std::error::Error>> {
95+
info!("Checking all dependencies");
96+
println!("Checking dependencies...\n");
97+
98+
let results = manager.check_all()?;
99+
let mut missing_count = 0;
100+
101+
for result in &results {
102+
if result.installed {
103+
println!("✓ {}: installed", result.tool);
104+
} else {
105+
println!("✗ {}: not installed", result.tool);
106+
missing_count += 1;
107+
}
108+
}
109+
110+
println!();
111+
if missing_count > 0 {
112+
let msg = format!(
113+
"Missing {missing_count} out of {} required dependencies",
114+
results.len()
115+
);
116+
error!("{}", msg);
117+
println!("{msg}");
118+
Err(msg.into())
119+
} else {
120+
info!("All dependencies are installed");
121+
println!("All dependencies are installed");
122+
Ok(())
123+
}
124+
}
125+
126+
fn check_specific_tool(
127+
manager: &DependencyManager,
128+
tool_name: &str,
129+
) -> Result<(), Box<dyn std::error::Error>> {
130+
info!(tool = tool_name, "Checking specific tool");
131+
132+
// Parse tool name to Dependency enum
133+
let dep = parse_tool_name(tool_name)?;
134+
let detector = manager.get_detector(dep);
135+
136+
let installed = detector.is_installed()?;
137+
138+
if installed {
139+
info!(tool = detector.name(), "Tool is installed");
140+
println!("✓ {}: installed", detector.name());
141+
Ok(())
142+
} else {
143+
let msg = format!("{}: not installed", detector.name());
144+
error!(tool = detector.name(), "Tool is not installed");
145+
println!("✗ {msg}");
146+
Err(msg.into())
147+
}
148+
}
149+
150+
fn handle_list(manager: &DependencyManager) -> Result<(), Box<dyn std::error::Error>> {
151+
info!("Listing all available tools");
152+
println!("Available tools:\n");
153+
154+
let results = manager.check_all()?;
155+
for result in results {
156+
let status = if result.installed {
157+
"installed"
158+
} else {
159+
"not installed"
160+
};
161+
println!("- {} ({status})", result.tool);
162+
}
163+
164+
Ok(())
165+
}
166+
167+
fn parse_tool_name(name: &str) -> Result<Dependency, String> {
168+
match name.to_lowercase().as_str() {
169+
"cargo-machete" | "machete" => Ok(Dependency::CargoMachete),
170+
"opentofu" | "tofu" => Ok(Dependency::OpenTofu),
171+
"ansible" => Ok(Dependency::Ansible),
172+
"lxd" => Ok(Dependency::Lxd),
173+
_ => Err(format!(
174+
"Unknown tool: {name}. Available: cargo-machete, opentofu, ansible, lxd"
175+
)),
176+
}
177+
}

0 commit comments

Comments
 (0)