Initial commit
This commit is contained in:
66
skills/clap-patterns/templates/basic-parser.rs
Normal file
66
skills/clap-patterns/templates/basic-parser.rs
Normal file
@@ -0,0 +1,66 @@
|
||||
/// Basic Parser Template with Clap Derive Macros
|
||||
///
|
||||
/// This template demonstrates:
|
||||
/// - Parser derive macro
|
||||
/// - Argument attributes (short, long, default_value)
|
||||
/// - PathBuf for file handling
|
||||
/// - Boolean flags
|
||||
/// - Doc comments as help text
|
||||
|
||||
use clap::Parser;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(name = "myapp")]
|
||||
#[command(author = "Your Name <you@example.com>")]
|
||||
#[command(version = "1.0.0")]
|
||||
#[command(about = "A simple CLI application", long_about = None)]
|
||||
struct Cli {
|
||||
/// Input file to process
|
||||
#[arg(short, long, value_name = "FILE")]
|
||||
input: PathBuf,
|
||||
|
||||
/// Optional output file
|
||||
#[arg(short, long)]
|
||||
output: Option<PathBuf>,
|
||||
|
||||
/// Enable verbose output
|
||||
#[arg(short, long)]
|
||||
verbose: bool,
|
||||
|
||||
/// Number of items to process
|
||||
#[arg(short = 'c', long, default_value_t = 10)]
|
||||
count: usize,
|
||||
|
||||
/// Dry run mode (don't make changes)
|
||||
#[arg(short = 'n', long)]
|
||||
dry_run: bool,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let cli = Cli::parse();
|
||||
|
||||
if cli.verbose {
|
||||
println!("Input file: {:?}", cli.input);
|
||||
println!("Output file: {:?}", cli.output);
|
||||
println!("Count: {}", cli.count);
|
||||
println!("Dry run: {}", cli.dry_run);
|
||||
}
|
||||
|
||||
// Check if input file exists
|
||||
if !cli.input.exists() {
|
||||
eprintln!("Error: Input file does not exist: {:?}", cli.input);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
// Your processing logic here
|
||||
println!("Processing {} with count {}...", cli.input.display(), cli.count);
|
||||
|
||||
if let Some(output) = cli.output {
|
||||
if !cli.dry_run {
|
||||
println!("Would write to: {}", output.display());
|
||||
} else {
|
||||
println!("Dry run: Skipping write to {}", output.display());
|
||||
}
|
||||
}
|
||||
}
|
||||
112
skills/clap-patterns/templates/builder-pattern.rs
Normal file
112
skills/clap-patterns/templates/builder-pattern.rs
Normal file
@@ -0,0 +1,112 @@
|
||||
/// Builder Pattern Template (Manual API)
|
||||
///
|
||||
/// This template demonstrates the builder API for advanced use cases:
|
||||
/// - Dynamic CLI construction
|
||||
/// - Runtime configuration
|
||||
/// - Custom help templates
|
||||
/// - Complex validation logic
|
||||
///
|
||||
/// Note: Prefer derive macros unless you need this level of control.
|
||||
|
||||
use clap::{Arg, ArgAction, ArgMatches, Command};
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn build_cli() -> Command {
|
||||
Command::new("advanced-cli")
|
||||
.version("1.0.0")
|
||||
.author("Your Name <you@example.com>")
|
||||
.about("Advanced CLI using builder pattern")
|
||||
.arg(
|
||||
Arg::new("input")
|
||||
.short('i')
|
||||
.long("input")
|
||||
.value_name("FILE")
|
||||
.help("Input file to process")
|
||||
.required(true)
|
||||
.value_parser(clap::value_parser!(PathBuf)),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("output")
|
||||
.short('o')
|
||||
.long("output")
|
||||
.value_name("FILE")
|
||||
.help("Output file (optional)")
|
||||
.value_parser(clap::value_parser!(PathBuf)),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("verbose")
|
||||
.short('v')
|
||||
.long("verbose")
|
||||
.help("Enable verbose output")
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("count")
|
||||
.short('c')
|
||||
.long("count")
|
||||
.value_name("NUM")
|
||||
.help("Number of items to process")
|
||||
.default_value("10")
|
||||
.value_parser(clap::value_parser!(usize)),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("format")
|
||||
.short('f')
|
||||
.long("format")
|
||||
.value_name("FORMAT")
|
||||
.help("Output format")
|
||||
.value_parser(["json", "yaml", "toml"])
|
||||
.default_value("json"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("tags")
|
||||
.short('t')
|
||||
.long("tag")
|
||||
.value_name("TAG")
|
||||
.help("Tags to apply (can be specified multiple times)")
|
||||
.action(ArgAction::Append),
|
||||
)
|
||||
}
|
||||
|
||||
fn process_args(matches: &ArgMatches) {
|
||||
let input = matches.get_one::<PathBuf>("input").unwrap();
|
||||
let output = matches.get_one::<PathBuf>("output");
|
||||
let verbose = matches.get_flag("verbose");
|
||||
let count = *matches.get_one::<usize>("count").unwrap();
|
||||
let format = matches.get_one::<String>("format").unwrap();
|
||||
let tags: Vec<_> = matches
|
||||
.get_many::<String>("tags")
|
||||
.unwrap_or_default()
|
||||
.map(|s| s.as_str())
|
||||
.collect();
|
||||
|
||||
if verbose {
|
||||
println!("Configuration:");
|
||||
println!(" Input: {:?}", input);
|
||||
println!(" Output: {:?}", output);
|
||||
println!(" Count: {}", count);
|
||||
println!(" Format: {}", format);
|
||||
println!(" Tags: {:?}", tags);
|
||||
}
|
||||
|
||||
// Your processing logic here
|
||||
println!("Processing {} items from {}", count, input.display());
|
||||
|
||||
if !tags.is_empty() {
|
||||
println!("Applying tags: {}", tags.join(", "));
|
||||
}
|
||||
|
||||
if let Some(output_path) = output {
|
||||
println!("Writing {} format to {}", format, output_path.display());
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let matches = build_cli().get_matches();
|
||||
process_args(&matches);
|
||||
}
|
||||
|
||||
// Example usage:
|
||||
//
|
||||
// cargo run -- -i input.txt -o output.json -v -c 20 -f yaml -t alpha -t beta
|
||||
// cargo run -- --input data.txt --format toml --tag important
|
||||
99
skills/clap-patterns/templates/env-variables.rs
Normal file
99
skills/clap-patterns/templates/env-variables.rs
Normal file
@@ -0,0 +1,99 @@
|
||||
/// Environment Variable Integration Template
|
||||
///
|
||||
/// This template demonstrates:
|
||||
/// - Reading from environment variables
|
||||
/// - Fallback to CLI arguments
|
||||
/// - Default values
|
||||
/// - Sensitive data handling (API keys, tokens)
|
||||
|
||||
use clap::Parser;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(name = "envapp")]
|
||||
#[command(about = "CLI with environment variable support")]
|
||||
struct Cli {
|
||||
/// API key (or set API_KEY env var)
|
||||
///
|
||||
/// Sensitive data like API keys should preferably be set via environment
|
||||
/// variables to avoid exposing them in shell history or process lists.
|
||||
#[arg(long, env = "API_KEY", hide_env_values = true)]
|
||||
api_key: String,
|
||||
|
||||
/// Database URL (or set DATABASE_URL env var)
|
||||
#[arg(long, env = "DATABASE_URL")]
|
||||
database_url: String,
|
||||
|
||||
/// Log level: debug, info, warn, error
|
||||
///
|
||||
/// Defaults to "info" if not provided via CLI or LOG_LEVEL env var.
|
||||
#[arg(long, env = "LOG_LEVEL", default_value = "info")]
|
||||
log_level: String,
|
||||
|
||||
/// Configuration file path
|
||||
///
|
||||
/// Reads from CONFIG_FILE env var, or uses default if not specified.
|
||||
#[arg(long, env = "CONFIG_FILE", default_value = "config.toml")]
|
||||
config: PathBuf,
|
||||
|
||||
/// Number of workers (default from env or 4)
|
||||
#[arg(long, env = "WORKER_COUNT", default_value_t = 4)]
|
||||
workers: usize,
|
||||
|
||||
/// Enable debug mode
|
||||
///
|
||||
/// Can be set via DEBUG=1 or --debug flag
|
||||
#[arg(long, env = "DEBUG", value_parser = clap::value_parser!(bool))]
|
||||
debug: bool,
|
||||
|
||||
/// Host to bind to
|
||||
#[arg(long, env = "HOST", default_value = "127.0.0.1")]
|
||||
host: String,
|
||||
|
||||
/// Port to listen on
|
||||
#[arg(short, long, env = "PORT", default_value_t = 8080)]
|
||||
port: u16,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let cli = Cli::parse();
|
||||
|
||||
println!("Configuration loaded:");
|
||||
println!(" Database URL: {}", cli.database_url);
|
||||
println!(" API Key: {}...", &cli.api_key[..4.min(cli.api_key.len())]);
|
||||
println!(" Log level: {}", cli.log_level);
|
||||
println!(" Config file: {}", cli.config.display());
|
||||
println!(" Workers: {}", cli.workers);
|
||||
println!(" Debug mode: {}", cli.debug);
|
||||
println!(" Host: {}", cli.host);
|
||||
println!(" Port: {}", cli.port);
|
||||
|
||||
// Initialize logging based on log_level
|
||||
match cli.log_level.to_lowercase().as_str() {
|
||||
"debug" => println!("Log level set to DEBUG"),
|
||||
"info" => println!("Log level set to INFO"),
|
||||
"warn" => println!("Log level set to WARN"),
|
||||
"error" => println!("Log level set to ERROR"),
|
||||
_ => println!("Unknown log level: {}", cli.log_level),
|
||||
}
|
||||
|
||||
// Your application logic here
|
||||
println!("\nStarting application...");
|
||||
println!("Listening on {}:{}", cli.host, cli.port);
|
||||
}
|
||||
|
||||
// Example usage:
|
||||
//
|
||||
// 1. Set environment variables:
|
||||
// export API_KEY="sk-1234567890abcdef"
|
||||
// export DATABASE_URL="postgres://localhost/mydb"
|
||||
// export LOG_LEVEL="debug"
|
||||
// export WORKER_COUNT="8"
|
||||
// cargo run
|
||||
//
|
||||
// 2. Override with CLI arguments:
|
||||
// cargo run -- --api-key "other-key" --workers 16
|
||||
//
|
||||
// 3. Mix environment and CLI:
|
||||
// export DATABASE_URL="postgres://localhost/mydb"
|
||||
// cargo run -- --api-key "sk-1234" --debug
|
||||
290
skills/clap-patterns/templates/full-featured-cli.rs
Normal file
290
skills/clap-patterns/templates/full-featured-cli.rs
Normal file
@@ -0,0 +1,290 @@
|
||||
/// 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
|
||||
139
skills/clap-patterns/templates/subcommands.rs
Normal file
139
skills/clap-patterns/templates/subcommands.rs
Normal file
@@ -0,0 +1,139 @@
|
||||
/// Subcommand Template with Clap
|
||||
///
|
||||
/// This template demonstrates:
|
||||
/// - Subcommand derive macro
|
||||
/// - Nested command structure
|
||||
/// - Per-subcommand arguments
|
||||
/// - Enum-based command routing
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(name = "git-like")]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
#[command(propagate_version = true)]
|
||||
struct Cli {
|
||||
/// Enable verbose output
|
||||
#[arg(global = true, short, long)]
|
||||
verbose: bool,
|
||||
|
||||
#[command(subcommand)]
|
||||
command: Commands,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum Commands {
|
||||
/// Initialize a new repository
|
||||
Init {
|
||||
/// Directory to initialize
|
||||
#[arg(value_name = "DIR", default_value = ".")]
|
||||
path: PathBuf,
|
||||
|
||||
/// Create a bare repository
|
||||
#[arg(long)]
|
||||
bare: bool,
|
||||
},
|
||||
|
||||
/// Add files to staging area
|
||||
Add {
|
||||
/// Files to add
|
||||
#[arg(value_name = "FILE", required = true)]
|
||||
files: Vec<PathBuf>,
|
||||
|
||||
/// Add all files
|
||||
#[arg(short = 'A', long)]
|
||||
all: bool,
|
||||
},
|
||||
|
||||
/// Commit staged changes
|
||||
Commit {
|
||||
/// Commit message
|
||||
#[arg(short, long)]
|
||||
message: String,
|
||||
|
||||
/// Amend previous commit
|
||||
#[arg(long)]
|
||||
amend: bool,
|
||||
},
|
||||
|
||||
/// Remote repository operations
|
||||
Remote {
|
||||
#[command(subcommand)]
|
||||
command: RemoteCommands,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum RemoteCommands {
|
||||
/// Add a new remote
|
||||
Add {
|
||||
/// Remote name
|
||||
name: String,
|
||||
|
||||
/// Remote URL
|
||||
url: String,
|
||||
},
|
||||
|
||||
/// Remove a remote
|
||||
Remove {
|
||||
/// Remote name
|
||||
name: String,
|
||||
},
|
||||
|
||||
/// List all remotes
|
||||
List {
|
||||
/// Show URLs
|
||||
#[arg(short, long)]
|
||||
verbose: bool,
|
||||
},
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let cli = Cli::parse();
|
||||
|
||||
match &cli.command {
|
||||
Commands::Init { path, bare } => {
|
||||
if cli.verbose {
|
||||
println!("Initializing repository at {:?}", path);
|
||||
}
|
||||
println!(
|
||||
"Initialized {} repository in {}",
|
||||
if *bare { "bare" } else { "normal" },
|
||||
path.display()
|
||||
);
|
||||
}
|
||||
|
||||
Commands::Add { files, all } => {
|
||||
if *all {
|
||||
println!("Adding all files");
|
||||
} else {
|
||||
println!("Adding {} file(s)", files.len());
|
||||
if cli.verbose {
|
||||
for file in files {
|
||||
println!(" - {}", file.display());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Commands::Commit { message, amend } => {
|
||||
if *amend {
|
||||
println!("Amending previous commit");
|
||||
}
|
||||
println!("Committing with message: {}", message);
|
||||
}
|
||||
|
||||
Commands::Remote { command } => match command {
|
||||
RemoteCommands::Add { name, url } => {
|
||||
println!("Adding remote '{}' -> {}", name, url);
|
||||
}
|
||||
RemoteCommands::Remove { name } => {
|
||||
println!("Removing remote '{}'", name);
|
||||
}
|
||||
RemoteCommands::List { verbose } => {
|
||||
println!("Listing remotes{}", if *verbose { " (verbose)" } else { "" });
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
143
skills/clap-patterns/templates/value-enum.rs
Normal file
143
skills/clap-patterns/templates/value-enum.rs
Normal file
@@ -0,0 +1,143 @@
|
||||
/// ValueEnum Template for Type-Safe Options
|
||||
///
|
||||
/// This template demonstrates:
|
||||
/// - ValueEnum trait for constrained choices
|
||||
/// - Type-safe option selection
|
||||
/// - Automatic validation and help text
|
||||
/// - Pattern matching on enums
|
||||
|
||||
use clap::{Parser, ValueEnum};
|
||||
|
||||
/// Output format options
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
|
||||
enum Format {
|
||||
/// JavaScript Object Notation
|
||||
Json,
|
||||
/// YAML Ain't Markup Language
|
||||
Yaml,
|
||||
/// Tom's Obvious, Minimal Language
|
||||
Toml,
|
||||
/// Comma-Separated Values
|
||||
Csv,
|
||||
}
|
||||
|
||||
/// Log level options
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
|
||||
enum LogLevel {
|
||||
/// Detailed debug information
|
||||
Debug,
|
||||
/// General information
|
||||
Info,
|
||||
/// Warning messages
|
||||
Warn,
|
||||
/// Error messages only
|
||||
Error,
|
||||
}
|
||||
|
||||
/// Color output mode
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
|
||||
enum ColorMode {
|
||||
/// Always use colors
|
||||
Always,
|
||||
/// Never use colors
|
||||
Never,
|
||||
/// Automatically detect (default)
|
||||
Auto,
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(name = "converter")]
|
||||
#[command(about = "Convert data between formats with type-safe options")]
|
||||
struct Cli {
|
||||
/// Input file
|
||||
input: std::path::PathBuf,
|
||||
|
||||
/// Output format
|
||||
#[arg(short, long, value_enum, default_value_t = Format::Json)]
|
||||
format: Format,
|
||||
|
||||
/// Log level
|
||||
#[arg(short, long, value_enum, default_value_t = LogLevel::Info)]
|
||||
log_level: LogLevel,
|
||||
|
||||
/// Color mode for output
|
||||
#[arg(long, value_enum, default_value_t = ColorMode::Auto)]
|
||||
color: ColorMode,
|
||||
|
||||
/// Pretty print output (for supported formats)
|
||||
#[arg(short, long)]
|
||||
pretty: bool,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let cli = Cli::parse();
|
||||
|
||||
// Configure logging based on log level
|
||||
match cli.log_level {
|
||||
LogLevel::Debug => println!("🔍 Debug logging enabled"),
|
||||
LogLevel::Info => println!("ℹ️ Info logging enabled"),
|
||||
LogLevel::Warn => println!("⚠️ Warning logging enabled"),
|
||||
LogLevel::Error => println!("❌ Error logging only"),
|
||||
}
|
||||
|
||||
// Check color mode
|
||||
let use_colors = match cli.color {
|
||||
ColorMode::Always => true,
|
||||
ColorMode::Never => false,
|
||||
ColorMode::Auto => atty::is(atty::Stream::Stdout),
|
||||
};
|
||||
|
||||
if use_colors {
|
||||
println!("🎨 Color output enabled");
|
||||
}
|
||||
|
||||
// Process based on format
|
||||
println!("Converting {} to {:?}", cli.input.display(), cli.format);
|
||||
|
||||
match cli.format {
|
||||
Format::Json => {
|
||||
println!("Converting to JSON{}", if cli.pretty { " (pretty)" } else { "" });
|
||||
// JSON conversion logic here
|
||||
}
|
||||
Format::Yaml => {
|
||||
println!("Converting to YAML");
|
||||
// YAML conversion logic here
|
||||
}
|
||||
Format::Toml => {
|
||||
println!("Converting to TOML");
|
||||
// TOML conversion logic here
|
||||
}
|
||||
Format::Csv => {
|
||||
println!("Converting to CSV");
|
||||
// CSV conversion logic here
|
||||
}
|
||||
}
|
||||
|
||||
println!("✓ Conversion complete");
|
||||
}
|
||||
|
||||
// Helper function to check if stdout is a terminal (for color auto-detection)
|
||||
mod atty {
|
||||
pub enum Stream {
|
||||
Stdout,
|
||||
}
|
||||
|
||||
pub fn is(_stream: Stream) -> bool {
|
||||
// Simple implementation - checks if stdout is a TTY
|
||||
#[cfg(unix)]
|
||||
{
|
||||
unsafe { libc::isatty(libc::STDOUT_FILENO) != 0 }
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Example usage:
|
||||
//
|
||||
// cargo run -- input.txt --format json --log-level debug
|
||||
// cargo run -- data.yml --format toml --color always --pretty
|
||||
// cargo run -- config.json --format yaml --log-level warn
|
||||
109
skills/clap-patterns/templates/value-parser.rs
Normal file
109
skills/clap-patterns/templates/value-parser.rs
Normal file
@@ -0,0 +1,109 @@
|
||||
/// Value Parser Template with Custom Validation
|
||||
///
|
||||
/// This template demonstrates:
|
||||
/// - Custom value parsers
|
||||
/// - Range validation
|
||||
/// - Format validation (regex)
|
||||
/// - Error handling with helpful messages
|
||||
|
||||
use clap::Parser;
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
const PORT_RANGE: RangeInclusive<usize> = 1..=65535;
|
||||
|
||||
/// Parse and validate port number
|
||||
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()
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Validate email format (basic validation)
|
||||
fn validate_email(s: &str) -> Result<String, String> {
|
||||
if s.contains('@') && s.contains('.') && s.len() > 5 {
|
||||
Ok(s.to_string())
|
||||
} else {
|
||||
Err(format!("`{}` is not a valid email address", s))
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse percentage (0-100)
|
||||
fn parse_percentage(s: &str) -> Result<u8, String> {
|
||||
let value: u8 = s
|
||||
.parse()
|
||||
.map_err(|_| format!("`{}` isn't a valid number", s))?;
|
||||
|
||||
if value <= 100 {
|
||||
Ok(value)
|
||||
} else {
|
||||
Err("percentage must be between 0 and 100".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// Validate directory exists
|
||||
fn validate_directory(s: &str) -> Result<std::path::PathBuf, String> {
|
||||
let path = std::path::PathBuf::from(s);
|
||||
|
||||
if path.exists() && path.is_dir() {
|
||||
Ok(path)
|
||||
} else {
|
||||
Err(format!("directory does not exist: {}", s))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(name = "validator")]
|
||||
#[command(about = "CLI with custom value parsers and validation")]
|
||||
struct Cli {
|
||||
/// Port number (1-65535)
|
||||
#[arg(short, long, value_parser = port_in_range)]
|
||||
port: u16,
|
||||
|
||||
/// Email address
|
||||
#[arg(short, long, value_parser = validate_email)]
|
||||
email: String,
|
||||
|
||||
/// Success threshold percentage (0-100)
|
||||
#[arg(short, long, value_parser = parse_percentage, default_value = "80")]
|
||||
threshold: u8,
|
||||
|
||||
/// Working directory (must exist)
|
||||
#[arg(short, long, value_parser = validate_directory)]
|
||||
workdir: Option<std::path::PathBuf>,
|
||||
|
||||
/// Number of retries (1-10)
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
default_value = "3",
|
||||
value_parser = clap::value_parser!(u8).range(1..=10)
|
||||
)]
|
||||
retries: u8,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let cli = Cli::parse();
|
||||
|
||||
println!("Configuration:");
|
||||
println!(" Port: {}", cli.port);
|
||||
println!(" Email: {}", cli.email);
|
||||
println!(" Threshold: {}%", cli.threshold);
|
||||
println!(" Retries: {}", cli.retries);
|
||||
|
||||
if let Some(workdir) = cli.workdir {
|
||||
println!(" Working directory: {}", workdir.display());
|
||||
}
|
||||
|
||||
// Your application logic here
|
||||
println!("\nValidation passed! All inputs are valid.");
|
||||
}
|
||||
Reference in New Issue
Block a user