Files
gh-vanman2024-cli-builder-p…/skills/clap-patterns/templates/full-featured-cli.rs
2025-11-30 09:04:14 +08:00

291 lines
7.2 KiB
Rust

/// Full-Featured CLI Template
///
/// This template combines all patterns:
/// - Parser derive with subcommands
/// - ValueEnum for type-safe options
/// - Environment variable support
/// - Custom value parsers
/// - Global arguments
/// - Comprehensive help text
use clap::{Parser, Subcommand, ValueEnum};
use std::path::PathBuf;
#[derive(Parser)]
#[command(name = "myapp")]
#[command(author = "Your Name <you@example.com>")]
#[command(version = "1.0.0")]
#[command(about = "A full-featured CLI application", long_about = None)]
#[command(propagate_version = true)]
struct Cli {
/// Configuration file path
#[arg(short, long, env = "CONFIG_FILE", global = true)]
config: Option<PathBuf>,
/// Enable verbose output
#[arg(short, long, global = true)]
verbose: bool,
/// Output format
#[arg(short, long, value_enum, global = true, default_value_t = Format::Text)]
format: Format,
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
/// Initialize a new project
Init {
/// Project directory
#[arg(default_value = ".")]
path: PathBuf,
/// Project template
#[arg(short, long, value_enum, default_value_t = Template::Basic)]
template: Template,
/// Skip interactive prompts
#[arg(short = 'y', long)]
yes: bool,
},
/// Build the project
Build {
/// Build mode
#[arg(short, long, value_enum, default_value_t = BuildMode::Debug)]
mode: BuildMode,
/// Number of parallel jobs
#[arg(short, long, value_parser = clap::value_parser!(u8).range(1..=32), default_value_t = 4)]
jobs: u8,
/// Target directory
#[arg(short, long, default_value = "target")]
target_dir: PathBuf,
/// Clean before building
#[arg(long)]
clean: bool,
},
/// Test the project
Test {
/// Test name pattern
pattern: Option<String>,
/// Run ignored tests
#[arg(long)]
ignored: bool,
/// Number of test threads
#[arg(long, value_parser = clap::value_parser!(usize).range(1..))]
test_threads: Option<usize>,
/// Show output for passing tests
#[arg(long)]
nocapture: bool,
},
/// Deploy the project
Deploy {
/// Deployment environment
#[arg(value_enum)]
environment: Environment,
/// Skip pre-deployment checks
#[arg(long)]
skip_checks: bool,
/// Deployment tag/version
#[arg(short, long)]
tag: Option<String>,
/// Deployment configuration
#[command(subcommand)]
config: Option<DeployConfig>,
},
}
#[derive(Subcommand)]
enum DeployConfig {
/// Configure database settings
Database {
/// Database URL
#[arg(long, env = "DATABASE_URL")]
url: String,
/// Run migrations
#[arg(long)]
migrate: bool,
},
/// Configure server settings
Server {
/// Server host
#[arg(long, default_value = "0.0.0.0")]
host: String,
/// Server port
#[arg(long, default_value_t = 8080, value_parser = port_in_range)]
port: u16,
/// Number of workers
#[arg(long, default_value_t = 4)]
workers: usize,
},
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
enum Format {
/// Human-readable text
Text,
/// JSON output
Json,
/// YAML output
Yaml,
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
enum Template {
/// Basic template
Basic,
/// Full-featured template
Full,
/// Minimal template
Minimal,
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
enum BuildMode {
/// Debug build with symbols
Debug,
/// Release build with optimizations
Release,
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
enum Environment {
/// Development environment
Dev,
/// Staging environment
Staging,
/// Production environment
Prod,
}
use std::ops::RangeInclusive;
const PORT_RANGE: RangeInclusive<usize> = 1..=65535;
fn port_in_range(s: &str) -> Result<u16, String> {
let port: usize = s
.parse()
.map_err(|_| format!("`{}` isn't a valid port number", s))?;
if PORT_RANGE.contains(&port) {
Ok(port as u16)
} else {
Err(format!(
"port not in range {}-{}",
PORT_RANGE.start(),
PORT_RANGE.end()
))
}
}
fn main() {
let cli = Cli::parse();
if cli.verbose {
println!("Verbose mode enabled");
if let Some(config) = &cli.config {
println!("Using config: {}", config.display());
}
println!("Output format: {:?}", cli.format);
}
match &cli.command {
Commands::Init { path, template, yes } => {
println!("Initializing project at {}", path.display());
println!("Template: {:?}", template);
if *yes {
println!("Skipping prompts");
}
}
Commands::Build {
mode,
jobs,
target_dir,
clean,
} => {
if *clean {
println!("Cleaning target directory");
}
println!("Building in {:?} mode", mode);
println!("Using {} parallel jobs", jobs);
println!("Target directory: {}", target_dir.display());
}
Commands::Test {
pattern,
ignored,
test_threads,
nocapture,
} => {
println!("Running tests");
if let Some(pat) = pattern {
println!("Pattern: {}", pat);
}
if *ignored {
println!("Including ignored tests");
}
if let Some(threads) = test_threads {
println!("Test threads: {}", threads);
}
if *nocapture {
println!("Showing test output");
}
}
Commands::Deploy {
environment,
skip_checks,
tag,
config,
} => {
println!("Deploying to {:?}", environment);
if *skip_checks {
println!("⚠️ Skipping pre-deployment checks");
}
if let Some(version) = tag {
println!("Version: {}", version);
}
if let Some(deploy_config) = config {
match deploy_config {
DeployConfig::Database { url, migrate } => {
println!("Database URL: {}", url);
if *migrate {
println!("Running migrations");
}
}
DeployConfig::Server { host, port, workers } => {
println!("Server: {}:{}", host, port);
println!("Workers: {}", workers);
}
}
}
}
}
}
// Example usage:
//
// myapp init --template full
// myapp build --mode release --jobs 8 --clean
// myapp test integration --test-threads 4
// myapp deploy prod --tag v1.0.0 server --host 0.0.0.0 --port 443 --workers 16