Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 09:04:14 +08:00
commit 70c36b5eff
248 changed files with 47482 additions and 0 deletions

View File

@@ -0,0 +1,249 @@
---
name: clap-patterns
description: Modern type-safe Rust CLI patterns with Clap derive macros, Parser trait, Subcommand enums, validation, and value parsers. Use when building CLI applications, creating Clap commands, implementing type-safe Rust CLIs, or when user mentions Clap, CLI patterns, Rust command-line, derive macros, Parser trait, Subcommands, or command-line interfaces.
allowed-tools: Read, Write, Edit, Bash
---
# clap-patterns
Provides modern type-safe Rust CLI patterns using Clap 4.x with derive macros, Parser trait, Subcommand enums, custom validation, value parsers, and environment variable integration for building maintainable command-line applications.
## Core Patterns
### 1. Basic Parser with Derive Macros
Use derive macros for automatic CLI parsing with type safety:
```rust
use clap::Parser;
#[derive(Parser)]
#[command(name = "myapp")]
#[command(author, version, about, long_about = None)]
struct Cli {
/// Input file path
#[arg(short, long, value_name = "FILE")]
input: std::path::PathBuf,
/// Optional output file
#[arg(short, long)]
output: Option<std::path::PathBuf>,
/// Verbose mode
#[arg(short, long)]
verbose: bool,
/// Number of items to process
#[arg(short, long, default_value_t = 10)]
count: usize,
}
fn main() {
let cli = Cli::parse();
if cli.verbose {
println!("Processing: {:?}", cli.input);
}
}
```
### 2. Subcommand Enums
Organize complex CLIs with nested subcommands:
```rust
use clap::{Parser, Subcommand};
#[derive(Parser)]
#[command(name = "git")]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
/// Add files to staging
Add {
/// Files to add
#[arg(value_name = "FILE")]
files: Vec<String>,
},
/// Commit changes
Commit {
/// Commit message
#[arg(short, long)]
message: String,
},
}
```
### 3. Value Parsers and Validation
Implement custom parsing and validation:
```rust
use clap::Parser;
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!("`{s}` isn't a valid port number"))?;
if PORT_RANGE.contains(&port) {
Ok(port as u16)
} else {
Err(format!("port not in range {}-{}", PORT_RANGE.start(), PORT_RANGE.end()))
}
}
#[derive(Parser)]
struct Cli {
/// Port to listen on
#[arg(short, long, value_parser = port_in_range)]
port: u16,
}
```
### 4. Environment Variable Integration
Support environment variables with fallback:
```rust
use clap::Parser;
#[derive(Parser)]
struct Cli {
/// API key (or set API_KEY env var)
#[arg(long, env = "API_KEY")]
api_key: String,
/// Database URL
#[arg(long, env = "DATABASE_URL")]
database_url: String,
/// Optional log level
#[arg(long, env = "LOG_LEVEL", default_value = "info")]
log_level: String,
}
```
### 5. ValueEnum for Constrained Choices
Use ValueEnum for type-safe option selection:
```rust
use clap::{Parser, ValueEnum};
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
enum Format {
Json,
Yaml,
Toml,
}
#[derive(Parser)]
struct Cli {
/// Output format
#[arg(value_enum, short, long, default_value_t = Format::Json)]
format: Format,
}
```
## Available Templates
The following Rust templates demonstrate Clap patterns:
- **basic-parser.rs**: Simple CLI with Parser derive macro
- **subcommands.rs**: Multi-level subcommand structure
- **value-parser.rs**: Custom validation with value parsers
- **env-variables.rs**: Environment variable integration
- **value-enum.rs**: Type-safe enums for options
- **builder-pattern.rs**: Manual builder API (for complex cases)
- **full-featured-cli.rs**: Complete CLI with all patterns
## Available Scripts
Helper scripts for Clap development:
- **generate-completions.sh**: Generate shell completions (bash, zsh, fish)
- **validate-cargo.sh**: Check Cargo.toml for correct Clap dependencies
- **test-cli.sh**: Test CLI with various argument combinations
## Usage Instructions
1. **Choose the appropriate template** based on your CLI complexity:
- Simple single-command → `basic-parser.rs`
- Multiple subcommands → `subcommands.rs`
- Need validation → `value-parser.rs`
- Environment config → `env-variables.rs`
2. **Add Clap to Cargo.toml**:
```toml
[dependencies]
clap = { version = "4.5", features = ["derive", "env"] }
```
3. **Implement your CLI** using the selected template as a starting point
4. **Generate completions** using the provided script for better UX
## Best Practices
- Use derive macros for most cases (cleaner, less boilerplate)
- Add help text with doc comments (shows in `--help`)
- Validate early with value parsers
- Use ValueEnum for constrained choices
- Support environment variables for sensitive data
- Provide sensible defaults with `default_value_t`
- Use PathBuf for file/directory arguments
- Add version and author metadata
## Common Patterns
### Multiple Values
```rust
#[arg(short, long, num_args = 1..)]
files: Vec<PathBuf>,
```
### Required Unless Present
```rust
#[arg(long, required_unless_present = "config")]
database_url: Option<String>,
```
### Conflicting Arguments
```rust
#[arg(long, conflicts_with = "json")]
yaml: bool,
```
### Global Arguments (for subcommands)
```rust
#[arg(global = true, short, long)]
verbose: bool,
```
## Testing Your CLI
Run the test script to validate your CLI:
```bash
bash scripts/test-cli.sh your-binary
```
This tests:
- Help output (`--help`)
- Version flag (`--version`)
- Invalid arguments
- Subcommand routing
- Environment variable precedence
## References
- Templates: `skills/clap-patterns/templates/`
- Scripts: `skills/clap-patterns/scripts/`
- Examples: `skills/clap-patterns/examples/`
- Clap Documentation: https://docs.rs/clap/latest/clap/

View File

@@ -0,0 +1,164 @@
# Clap Quick Start Guide
This guide will help you build your first Clap CLI application in minutes.
## Prerequisites
- Rust installed (1.70.0 or newer)
- Cargo (comes with Rust)
## Step 1: Create a New Project
```bash
cargo new my-cli
cd my-cli
```
## Step 2: Add Clap Dependency
Edit `Cargo.toml`:
```toml
[dependencies]
clap = { version = "4.5", features = ["derive"] }
```
## Step 3: Write Your First CLI
Replace `src/main.rs` with:
```rust
use clap::Parser;
/// Simple program to greet a person
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Args {
/// Name of the person to greet
#[arg(short, long)]
name: String,
/// Number of times to greet
#[arg(short, long, default_value_t = 1)]
count: u8,
}
fn main() {
let args = Args::parse();
for _ in 0..args.count {
println!("Hello {}!", args.name)
}
}
```
## Step 4: Build and Run
```bash
# Build the project
cargo build --release
# Run with arguments
./target/release/my-cli --name Alice --count 3
# Check help output
./target/release/my-cli --help
```
## Expected Output
```
$ ./target/release/my-cli --name Alice --count 3
Hello Alice!
Hello Alice!
Hello Alice!
```
## Help Output
```
$ ./target/release/my-cli --help
Simple program to greet a person
Usage: my-cli --name <NAME> [--count <COUNT>]
Options:
-n, --name <NAME> Name of the person to greet
-c, --count <COUNT> Number of times to greet [default: 1]
-h, --help Print help
-V, --version Print version
```
## Next Steps
1. **Add Subcommands**: See `subcommands.rs` template
2. **Add Validation**: See `value-parser.rs` template
3. **Environment Variables**: See `env-variables.rs` template
4. **Type-Safe Options**: See `value-enum.rs` template
## Common Patterns
### Optional Arguments
```rust
#[arg(short, long)]
output: Option<String>,
```
### Multiple Values
```rust
#[arg(short, long, num_args = 1..)]
files: Vec<PathBuf>,
```
### Boolean Flags
```rust
#[arg(short, long)]
verbose: bool,
```
### With Default Value
```rust
#[arg(short, long, default_value = "config.toml")]
config: String,
```
### Required Unless Present
```rust
#[arg(long, required_unless_present = "config")]
database_url: Option<String>,
```
## Troubleshooting
### "Parser trait not found"
Add the import:
```rust
use clap::Parser;
```
### "derive feature not enabled"
Update `Cargo.toml`:
```toml
clap = { version = "4.5", features = ["derive"] }
```
### Help text not showing
Add doc comments above fields:
```rust
/// This shows up in --help output
#[arg(short, long)]
```
## Resources
- Full templates: `skills/clap-patterns/templates/`
- Helper scripts: `skills/clap-patterns/scripts/`
- Official docs: https://docs.rs/clap/latest/clap/

View File

@@ -0,0 +1,474 @@
# Real-World Clap CLI Example
A complete, production-ready CLI application demonstrating best practices.
## Project Structure
```
my-tool/
├── Cargo.toml
├── src/
│ ├── main.rs # CLI definition and entry point
│ ├── commands/ # Command implementations
│ │ ├── mod.rs
│ │ ├── init.rs
│ │ ├── build.rs
│ │ └── deploy.rs
│ ├── config.rs # Configuration management
│ └── utils.rs # Helper functions
├── tests/
│ └── cli_tests.rs # Integration tests
└── completions/ # Generated shell completions
```
## Cargo.toml
```toml
[package]
name = "my-tool"
version = "1.0.0"
edition = "2021"
[dependencies]
clap = { version = "4.5", features = ["derive", "env", "cargo"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
anyhow = "1.0"
tokio = { version = "1", features = ["full"] }
colored = "2.0"
[dev-dependencies]
assert_cmd = "2.0"
predicates = "3.0"
```
## main.rs - Complete CLI Definition
```rust
use clap::{Parser, Subcommand, ValueEnum};
use std::path::PathBuf;
mod commands;
mod config;
mod utils;
#[derive(Parser)]
#[command(name = "my-tool")]
#[command(author, version, about = "A production-ready CLI tool", long_about = None)]
#[command(propagate_version = true)]
struct Cli {
/// Configuration file
#[arg(
short,
long,
env = "MY_TOOL_CONFIG",
global = true,
default_value = "config.json"
)]
config: PathBuf,
/// Enable verbose output
#[arg(short, long, global = true)]
verbose: bool,
/// Output format
#[arg(short = 'F', long, value_enum, global = true, default_value_t = OutputFormat::Text)]
format: OutputFormat,
/// Log file path
#[arg(long, env = "MY_TOOL_LOG", global = true)]
log_file: Option<PathBuf>,
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
/// Initialize a new project
Init {
/// Project directory
#[arg(default_value = ".")]
path: PathBuf,
/// Project name
#[arg(short, long)]
name: Option<String>,
/// Project template
#[arg(short, long, value_enum, default_value_t = Template::Default)]
template: Template,
/// Skip interactive prompts
#[arg(short = 'y', long)]
yes: bool,
/// Git repository URL
#[arg(short, long)]
git: Option<String>,
},
/// Build the project
Build {
/// Build profile
#[arg(short, long, value_enum, default_value_t = Profile::Debug)]
profile: Profile,
/// 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: PathBuf,
/// Clean before building
#[arg(long)]
clean: bool,
/// Watch for changes
#[arg(short, long)]
watch: bool,
},
/// Deploy to environment
Deploy {
/// Target environment
#[arg(value_enum)]
environment: Environment,
/// Deployment version/tag
#[arg(short, long)]
version: String,
/// Dry run (don't actually deploy)
#[arg(short = 'n', long)]
dry_run: bool,
/// Skip pre-deployment checks
#[arg(long)]
skip_checks: bool,
/// Deployment timeout in seconds
#[arg(short, long, default_value_t = 300)]
timeout: u64,
/// Rollback on failure
#[arg(long)]
rollback: bool,
},
/// Manage configuration
Config {
#[command(subcommand)]
action: ConfigAction,
},
/// Generate shell completions
Completions {
/// Shell type
#[arg(value_enum)]
shell: Shell,
/// Output directory
#[arg(short, long, default_value = "completions")]
output: PathBuf,
},
}
#[derive(Subcommand)]
enum ConfigAction {
/// Show current configuration
Show,
/// Set a configuration value
Set {
/// Configuration key
key: String,
/// Configuration value
value: String,
},
/// Get a configuration value
Get {
/// Configuration key
key: String,
},
/// Reset configuration to defaults
Reset {
/// Confirm reset
#[arg(short = 'y', long)]
yes: bool,
},
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
enum OutputFormat {
Text,
Json,
Yaml,
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
enum Template {
Default,
Minimal,
Full,
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
enum Profile {
Debug,
Release,
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
enum Environment {
Dev,
Staging,
Prod,
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
enum Shell {
Bash,
Zsh,
Fish,
PowerShell,
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let cli = Cli::parse();
// Initialize logging
if let Some(log_file) = &cli.log_file {
utils::init_file_logging(log_file, cli.verbose)?;
} else {
utils::init_console_logging(cli.verbose);
}
// Load configuration
let config = config::load(&cli.config)?;
// Execute command
match &cli.command {
Commands::Init {
path,
name,
template,
yes,
git,
} => {
commands::init::execute(path, name.as_deref(), *template, *yes, git.as_deref()).await?;
}
Commands::Build {
profile,
jobs,
target,
clean,
watch,
} => {
commands::build::execute(*profile, *jobs, target, *clean, *watch).await?;
}
Commands::Deploy {
environment,
version,
dry_run,
skip_checks,
timeout,
rollback,
} => {
commands::deploy::execute(
*environment,
version,
*dry_run,
*skip_checks,
*timeout,
*rollback,
)
.await?;
}
Commands::Config { action } => match action {
ConfigAction::Show => config::show(&config, cli.format),
ConfigAction::Set { key, value } => config::set(&cli.config, key, value)?,
ConfigAction::Get { key } => config::get(&config, key, cli.format)?,
ConfigAction::Reset { yes } => config::reset(&cli.config, *yes)?,
},
Commands::Completions { shell, output } => {
commands::completions::generate(*shell, output)?;
}
}
Ok(())
}
```
## Key Features Demonstrated
### 1. Global Arguments
Arguments available to all subcommands:
```rust
#[arg(short, long, global = true)]
verbose: bool,
```
### 2. Environment Variables
Fallback to environment variables:
```rust
#[arg(short, long, env = "MY_TOOL_CONFIG")]
config: PathBuf,
```
### 3. Validation
Numeric range validation:
```rust
#[arg(short, long, value_parser = clap::value_parser!(u8).range(1..=32))]
jobs: u8,
```
### 4. Type-Safe Enums
Constrained choices with ValueEnum:
```rust
#[derive(ValueEnum)]
enum Environment {
Dev,
Staging,
Prod,
}
```
### 5. Nested Subcommands
Multi-level command structure:
```rust
Config {
#[command(subcommand)]
action: ConfigAction,
}
```
### 6. Default Values
Sensible defaults for all options:
```rust
#[arg(short, long, default_value = "config.json")]
config: PathBuf,
```
## Integration Tests
`tests/cli_tests.rs`:
```rust
use assert_cmd::Command;
use predicates::prelude::*;
#[test]
fn test_help() {
let mut cmd = Command::cargo_bin("my-tool").unwrap();
cmd.arg("--help")
.assert()
.success()
.stdout(predicate::str::contains("A production-ready CLI tool"));
}
#[test]
fn test_version() {
let mut cmd = Command::cargo_bin("my-tool").unwrap();
cmd.arg("--version")
.assert()
.success()
.stdout(predicate::str::contains("1.0.0"));
}
#[test]
fn test_init_command() {
let mut cmd = Command::cargo_bin("my-tool").unwrap();
cmd.arg("init")
.arg("--name")
.arg("test-project")
.arg("--yes")
.assert()
.success();
}
```
## Building for Production
```bash
# Build release binary
cargo build --release
# Run tests
cargo test
# Generate completions
./target/release/my-tool completions bash
./target/release/my-tool completions zsh
./target/release/my-tool completions fish
# Install locally
cargo install --path .
```
## Distribution
### Cross-Platform Binaries
Use `cross` for cross-compilation:
```bash
cargo install cross
cross build --release --target x86_64-unknown-linux-gnu
cross build --release --target x86_64-pc-windows-gnu
cross build --release --target x86_64-apple-darwin
```
### Package for Distribution
```bash
# Linux/macOS tar.gz
tar czf my-tool-linux-x64.tar.gz -C target/release my-tool
# Windows zip
zip my-tool-windows-x64.zip target/release/my-tool.exe
```
## Best Practices Checklist
- ✓ Clear, descriptive help text
- ✓ Sensible default values
- ✓ Environment variable support
- ✓ Input validation
- ✓ Type-safe options (ValueEnum)
- ✓ Global arguments for common options
- ✓ Proper error handling (anyhow)
- ✓ Integration tests
- ✓ Shell completion generation
- ✓ Version information
- ✓ Verbose/quiet modes
- ✓ Configuration file support
- ✓ Dry-run mode for destructive operations
## Resources
- Full templates: `skills/clap-patterns/templates/`
- Validation examples: `examples/validation-examples.md`
- Test scripts: `scripts/test-cli.sh`

View File

@@ -0,0 +1,300 @@
# Clap Validation Examples
Comprehensive examples for validating CLI input with Clap value parsers.
## 1. Port Number Validation
Validate port numbers are in the valid range (1-65535):
```rust
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()
))
}
}
#[derive(Parser)]
struct Cli {
#[arg(short, long, value_parser = port_in_range)]
port: u16,
}
```
**Usage:**
```bash
$ my-cli --port 8080 # ✓ Valid
$ my-cli --port 80000 # ❌ Error: port not in range 1-65535
$ my-cli --port abc # ❌ Error: `abc` isn't a valid port number
```
## 2. Email Validation
Basic email format validation:
```rust
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))
}
}
#[derive(Parser)]
struct Cli {
#[arg(short, long, value_parser = validate_email)]
email: String,
}
```
## 3. File/Directory Existence
Validate that files or directories exist:
```rust
fn file_exists(s: &str) -> Result<PathBuf, String> {
let path = PathBuf::from(s);
if path.exists() && path.is_file() {
Ok(path)
} else {
Err(format!("file does not exist: {}", s))
}
}
fn dir_exists(s: &str) -> Result<PathBuf, String> {
let path = PathBuf::from(s);
if path.exists() && path.is_dir() {
Ok(path)
} else {
Err(format!("directory does not exist: {}", s))
}
}
#[derive(Parser)]
struct Cli {
#[arg(short, long, value_parser = file_exists)]
input: PathBuf,
#[arg(short, long, value_parser = dir_exists)]
output_dir: PathBuf,
}
```
## 4. URL Validation
Validate URL format:
```rust
fn validate_url(s: &str) -> Result<String, String> {
if s.starts_with("http://") || s.starts_with("https://") {
Ok(s.to_string())
} else {
Err(format!("`{}` must start with http:// or https://", s))
}
}
#[derive(Parser)]
struct Cli {
#[arg(short, long, value_parser = validate_url)]
endpoint: String,
}
```
## 5. Numeric Range Validation
Use built-in range validation:
```rust
#[derive(Parser)]
struct Cli {
/// Port (1-65535)
#[arg(long, value_parser = clap::value_parser!(u16).range(1..=65535))]
port: u16,
/// Threads (1-32)
#[arg(long, value_parser = clap::value_parser!(u8).range(1..=32))]
threads: u8,
/// Percentage (0-100)
#[arg(long, value_parser = clap::value_parser!(u8).range(0..=100))]
percentage: u8,
}
```
## 6. Regex Pattern Validation
Validate against regex patterns:
```rust
use regex::Regex;
fn validate_version(s: &str) -> Result<String, String> {
let re = Regex::new(r"^\d+\.\d+\.\d+$").unwrap();
if re.is_match(s) {
Ok(s.to_string())
} else {
Err(format!("`{}` is not a valid semantic version (e.g., 1.2.3)", s))
}
}
#[derive(Parser)]
struct Cli {
#[arg(short, long, value_parser = validate_version)]
version: String,
}
```
**Note:** Add `regex = "1"` to `Cargo.toml` for this example.
## 7. Multiple Validation Rules
Combine multiple validation rules:
```rust
fn validate_username(s: &str) -> Result<String, String> {
// Must be 3-20 characters
if s.len() < 3 || s.len() > 20 {
return Err("username must be 3-20 characters".to_string());
}
// Must start with letter
if !s.chars().next().unwrap().is_alphabetic() {
return Err("username must start with a letter".to_string());
}
// Only alphanumeric and underscore
if !s.chars().all(|c| c.is_alphanumeric() || c == '_') {
return Err("username can only contain letters, numbers, and underscores".to_string());
}
Ok(s.to_string())
}
#[derive(Parser)]
struct Cli {
#[arg(short, long, value_parser = validate_username)]
username: String,
}
```
## 8. Conditional Validation
Validate based on other arguments:
```rust
#[derive(Parser)]
struct Cli {
/// Enable SSL
#[arg(long)]
ssl: bool,
/// SSL certificate (required if --ssl is set)
#[arg(long, required_if_eq("ssl", "true"))]
cert: Option<PathBuf>,
/// SSL key (required if --ssl is set)
#[arg(long, required_if_eq("ssl", "true"))]
key: Option<PathBuf>,
}
```
## 9. Mutually Exclusive Arguments
Ensure only one option is provided:
```rust
#[derive(Parser)]
struct Cli {
/// Use JSON format
#[arg(long, conflicts_with = "yaml")]
json: bool,
/// Use YAML format
#[arg(long, conflicts_with = "json")]
yaml: bool,
}
```
## 10. Custom Type with FromStr
Implement `FromStr` for automatic parsing:
```rust
use std::str::FromStr;
struct IpPort {
ip: std::net::IpAddr,
port: u16,
}
impl FromStr for IpPort {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts: Vec<&str> = s.split(':').collect();
if parts.len() != 2 {
return Err("format must be IP:PORT (e.g., 127.0.0.1:8080)".to_string());
}
let ip = parts[0]
.parse()
.map_err(|_| format!("invalid IP address: {}", parts[0]))?;
let port = parts[1]
.parse()
.map_err(|_| format!("invalid port: {}", parts[1]))?;
Ok(IpPort { ip, port })
}
}
#[derive(Parser)]
struct Cli {
/// Bind address (IP:PORT)
#[arg(short, long)]
bind: IpPort,
}
```
**Usage:**
```bash
$ my-cli --bind 127.0.0.1:8080 # ✓ Valid
$ my-cli --bind 192.168.1.1:3000 # ✓ Valid
$ my-cli --bind invalid # ❌ Error
```
## Testing Validation
Use the provided test script:
```bash
bash scripts/test-cli.sh ./target/debug/my-cli validation
```
## Best Practices
1. **Provide Clear Error Messages**: Tell users what went wrong and how to fix it
2. **Validate Early**: Use value parsers instead of validating after parsing
3. **Use Type System**: Leverage Rust's type system for compile-time safety
4. **Document Constraints**: Add constraints to help text
5. **Test Edge Cases**: Test boundary values and invalid inputs
## Resources
- Value parser template: `templates/value-parser.rs`
- Test script: `scripts/test-cli.sh`
- Clap docs: https://docs.rs/clap/latest/clap/

View File

@@ -0,0 +1,94 @@
#!/usr/bin/env bash
# Generate shell completions for Clap CLI applications
#
# Usage: ./generate-completions.sh <binary-name> [output-dir]
#
# This script generates shell completion scripts for bash, zsh, fish, and powershell.
# The CLI binary must support the --generate-completions flag (built with Clap).
set -euo pipefail
BINARY="${1:-}"
OUTPUT_DIR="${2:-completions}"
if [ -z "$BINARY" ]; then
echo "Error: Binary name required"
echo "Usage: $0 <binary-name> [output-dir]"
exit 1
fi
# Create output directory
mkdir -p "$OUTPUT_DIR"
echo "Generating shell completions for: $BINARY"
echo "Output directory: $OUTPUT_DIR"
echo ""
# Check if binary exists
if ! command -v "$BINARY" &> /dev/null; then
echo "Warning: Binary '$BINARY' not found in PATH"
echo "Make sure to build and install it first: cargo install --path ."
exit 1
fi
# Generate completions for each shell
for shell in bash zsh fish powershell elvish; do
echo "Generating $shell completions..."
case "$shell" in
bash)
"$BINARY" --generate-completion "$shell" > "$OUTPUT_DIR/${BINARY}.bash" 2>/dev/null || {
echo " ⚠️ Failed (CLI may not support --generate-completion)"
continue
}
echo " ✓ Generated: $OUTPUT_DIR/${BINARY}.bash"
;;
zsh)
"$BINARY" --generate-completion "$shell" > "$OUTPUT_DIR/_${BINARY}" 2>/dev/null || {
echo " ⚠️ Failed"
continue
}
echo " ✓ Generated: $OUTPUT_DIR/_${BINARY}"
;;
fish)
"$BINARY" --generate-completion "$shell" > "$OUTPUT_DIR/${BINARY}.fish" 2>/dev/null || {
echo " ⚠️ Failed"
continue
}
echo " ✓ Generated: $OUTPUT_DIR/${BINARY}.fish"
;;
powershell)
"$BINARY" --generate-completion "$shell" > "$OUTPUT_DIR/_${BINARY}.ps1" 2>/dev/null || {
echo " ⚠️ Failed"
continue
}
echo " ✓ Generated: $OUTPUT_DIR/_${BINARY}.ps1"
;;
elvish)
"$BINARY" --generate-completion "$shell" > "$OUTPUT_DIR/${BINARY}.elv" 2>/dev/null || {
echo " ⚠️ Failed"
continue
}
echo " ✓ Generated: $OUTPUT_DIR/${BINARY}.elv"
;;
esac
done
echo ""
echo "✓ Completion generation complete!"
echo ""
echo "Installation instructions:"
echo ""
echo "Bash:"
echo " sudo cp $OUTPUT_DIR/${BINARY}.bash /etc/bash_completion.d/"
echo " Or: echo 'source $PWD/$OUTPUT_DIR/${BINARY}.bash' >> ~/.bashrc"
echo ""
echo "Zsh:"
echo " cp $OUTPUT_DIR/_${BINARY} /usr/local/share/zsh/site-functions/"
echo " Or add to fpath: fpath=($PWD/$OUTPUT_DIR \$fpath)"
echo ""
echo "Fish:"
echo " cp $OUTPUT_DIR/${BINARY}.fish ~/.config/fish/completions/"
echo ""
echo "PowerShell:"
echo " Add to profile: . $PWD/$OUTPUT_DIR/_${BINARY}.ps1"

View File

@@ -0,0 +1,159 @@
#!/usr/bin/env bash
# Test a Clap CLI application with various argument combinations
#
# Usage: ./test-cli.sh <binary-path> [test-suite]
#
# Test suites: basic, subcommands, validation, env, all (default)
set -euo pipefail
BINARY="${1:-}"
TEST_SUITE="${2:-all}"
if [ -z "$BINARY" ]; then
echo "Error: Binary path required"
echo "Usage: $0 <binary-path> [test-suite]"
echo ""
echo "Test suites:"
echo " basic - Test help, version, basic flags"
echo " subcommands - Test subcommand routing"
echo " validation - Test input validation"
echo " env - Test environment variables"
echo " all - Run all tests (default)"
exit 1
fi
if [ ! -x "$BINARY" ]; then
echo "Error: Binary not found or not executable: $BINARY"
exit 1
fi
PASS=0
FAIL=0
run_test() {
local name="$1"
local expected_exit="$2"
shift 2
echo -n "Testing: $name ... "
if "$BINARY" "$@" &>/dev/null; then
actual_exit=0
else
actual_exit=$?
fi
if [ "$actual_exit" -eq "$expected_exit" ]; then
echo "✓ PASS"
((PASS++))
else
echo "❌ FAIL (expected exit $expected_exit, got $actual_exit)"
((FAIL++))
fi
}
test_basic() {
echo ""
echo "=== Basic Tests ==="
run_test "Help output" 0 --help
run_test "Version output" 0 --version
run_test "Short help" 0 -h
run_test "Invalid flag" 1 --invalid-flag
run_test "No arguments (might fail for some CLIs)" 0
}
test_subcommands() {
echo ""
echo "=== Subcommand Tests ==="
run_test "Subcommand help" 0 help
run_test "Invalid subcommand" 1 invalid-command
# Try common subcommands
for cmd in init add build test deploy; do
if "$BINARY" help 2>&1 | grep -q "$cmd"; then
run_test "Subcommand '$cmd' help" 0 "$cmd" --help
fi
done
}
test_validation() {
echo ""
echo "=== Validation Tests ==="
# Test file arguments with non-existent files
run_test "Non-existent file" 1 --input /nonexistent/file.txt
# Test numeric ranges
run_test "Invalid number" 1 --count abc
run_test "Negative number" 1 --count -5
# Test conflicting flags
if "$BINARY" --help 2>&1 | grep -q "conflicts with"; then
echo " (Found conflicting arguments in help text)"
fi
}
test_env() {
echo ""
echo "=== Environment Variable Tests ==="
# Check if binary supports environment variables
if "$BINARY" --help 2>&1 | grep -q "\[env:"; then
echo "✓ Environment variable support detected"
# Extract env vars from help text
ENV_VARS=$("$BINARY" --help 2>&1 | grep -o '\[env: [A-Z_]*\]' | sed 's/\[env: \(.*\)\]/\1/' || true)
if [ -n "$ENV_VARS" ]; then
echo "Found environment variables:"
echo "$ENV_VARS" | while read -r var; do
echo " - $var"
done
fi
else
echo " No environment variable support detected"
fi
}
# Run requested test suite
case "$TEST_SUITE" in
basic)
test_basic
;;
subcommands)
test_subcommands
;;
validation)
test_validation
;;
env)
test_env
;;
all)
test_basic
test_subcommands
test_validation
test_env
;;
*)
echo "Error: Unknown test suite: $TEST_SUITE"
exit 1
;;
esac
echo ""
echo "=== Test Summary ==="
echo "Passed: $PASS"
echo "Failed: $FAIL"
echo ""
if [ "$FAIL" -gt 0 ]; then
echo "❌ Some tests failed"
exit 1
else
echo "✓ All tests passed!"
exit 0
fi

View File

@@ -0,0 +1,113 @@
#!/usr/bin/env bash
# Validate Cargo.toml for correct Clap configuration
#
# Usage: ./validate-cargo.sh [path-to-Cargo.toml]
#
# Checks:
# - Clap dependency exists
# - Clap version is 4.x or newer
# - Required features are enabled (derive)
# - Optional features (env, cargo) are present if needed
set -euo pipefail
CARGO_TOML="${1:-Cargo.toml}"
if [ ! -f "$CARGO_TOML" ]; then
echo "❌ Error: $CARGO_TOML not found"
exit 1
fi
echo "Validating Clap configuration in: $CARGO_TOML"
echo ""
# Check if clap is listed as a dependency
if ! grep -q "clap" "$CARGO_TOML"; then
echo "❌ Clap not found in dependencies"
echo ""
echo "Add to $CARGO_TOML:"
echo ""
echo '[dependencies]'
echo 'clap = { version = "4.5", features = ["derive"] }'
exit 1
fi
echo "✓ Clap dependency found"
# Extract clap version
VERSION=$(grep -A 5 '^\[dependencies\]' "$CARGO_TOML" | grep 'clap' | head -1)
# Check version
if echo "$VERSION" | grep -q '"4\.' || echo "$VERSION" | grep -q "'4\."; then
echo "✓ Clap version 4.x detected"
elif echo "$VERSION" | grep -q '"3\.' || echo "$VERSION" | grep -q "'3\."; then
echo "⚠️ Warning: Clap version 3.x detected"
echo " Consider upgrading to 4.x for latest features"
else
echo "⚠️ Warning: Could not determine Clap version"
fi
# Check for derive feature
if echo "$VERSION" | grep -q 'features.*derive' || echo "$VERSION" | grep -q 'derive.*features'; then
echo "✓ 'derive' feature enabled"
else
echo "❌ 'derive' feature not found"
echo " Add: features = [\"derive\"]"
exit 1
fi
# Check for optional but recommended features
echo ""
echo "Optional features:"
if echo "$VERSION" | grep -q '"env"' || echo "$VERSION" | grep -q "'env'"; then
echo "✓ 'env' feature enabled (environment variable support)"
else
echo " 'env' feature not enabled"
echo " Add for environment variable support: features = [\"derive\", \"env\"]"
fi
if echo "$VERSION" | grep -q '"cargo"' || echo "$VERSION" | grep -q "'cargo'"; then
echo "✓ 'cargo' feature enabled (automatic version from Cargo.toml)"
else
echo " 'cargo' feature not enabled"
echo " Add for automatic version: features = [\"derive\", \"cargo\"]"
fi
if echo "$VERSION" | grep -q '"color"' || echo "$VERSION" | grep -q "'color'"; then
echo "✓ 'color' feature enabled (colored output)"
else
echo " 'color' feature not enabled"
echo " Add for colored help: features = [\"derive\", \"color\"]"
fi
echo ""
# Check for common patterns in src/
if [ -d "src" ]; then
echo "Checking source files for Clap usage patterns..."
if grep -r "use clap::Parser" src/ &>/dev/null; then
echo "✓ Parser trait usage found"
fi
if grep -r "use clap::Subcommand" src/ &>/dev/null; then
echo "✓ Subcommand trait usage found"
fi
if grep -r "use clap::ValueEnum" src/ &>/dev/null; then
echo "✓ ValueEnum trait usage found"
fi
if grep -r "#\[derive(Parser)\]" src/ &>/dev/null; then
echo "✓ Parser derive macro usage found"
fi
fi
echo ""
echo "✓ Validation complete!"
echo ""
echo "Recommended Cargo.toml configuration:"
echo ""
echo '[dependencies]'
echo 'clap = { version = "4.5", features = ["derive", "env", "cargo"] }'

View 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());
}
}
}

View 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

View 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

View 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

View 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 { "" });
}
},
}
}

View 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

View 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.");
}