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,201 @@
---
name: argparse-patterns
description: Standard library Python argparse examples with subparsers, choices, actions, and nested command patterns. Use when building Python CLIs without external dependencies, implementing argument parsing, creating subcommands, or when user mentions argparse, standard library CLI, subparsers, argument validation, or nested commands.
allowed-tools: Read, Write, Edit, Bash
---
# argparse-patterns
Python's built-in argparse module for CLI argument parsing - no external dependencies required.
## Overview
Provides comprehensive argparse patterns using only Python standard library. Includes subparsers for nested commands, choices for validation, custom actions, argument groups, and mutually exclusive options.
## Instructions
### Basic Parser Setup
1. Import argparse and create parser with description
2. Add version info with `action='version'`
3. Set formatter_class for better help formatting
4. Parse arguments with `parser.parse_args()`
### Subparsers (Nested Commands)
1. Use `parser.add_subparsers(dest='command')` to create command groups
2. Add individual commands with `subparsers.add_parser('command-name')`
3. Each subparser can have its own arguments and options
4. Nest subparsers for multi-level commands (e.g., `mycli config get key`)
### Choices and Validation
1. Use `choices=['opt1', 'opt2']` to restrict values
2. Implement custom validation with type functions
3. Add validators using argparse types
4. Set defaults with `default=value`
### Actions
1. `store_true/store_false` - Boolean flags
2. `store_const` - Store constant value
3. `append` - Collect multiple values
4. `count` - Count flag occurrences
5. `version` - Display version and exit
6. Custom actions with Action subclass
### Argument Types
1. Positional arguments - Required by default
2. Optional arguments - Prefix with `--` or `-`
3. Type coercion - `type=int`, `type=float`, `type=pathlib.Path`
4. Nargs - `'?'` (optional), `'*'` (zero or more), `'+'` (one or more)
## Available Templates
### Python Templates
- **basic-parser.py** - Simple parser with arguments and options
- **subparser-pattern.py** - Single-level subcommands
- **nested-subparser.py** - Multi-level nested commands (e.g., git config get)
- **choices-validation.py** - Argument choices and validation
- **boolean-flags.py** - Boolean flag patterns
- **custom-actions.py** - Custom action classes
- **mutually-exclusive.py** - Mutually exclusive groups
- **argument-groups.py** - Organizing related arguments
- **type-coercion.py** - Custom type converters
- **variadic-args.py** - Variable argument patterns
### TypeScript Templates
- **argparse-to-commander.ts** - argparse patterns translated to commander.js
- **argparse-to-yargs.ts** - argparse patterns translated to yargs
- **parser-comparison.ts** - Side-by-side argparse vs Node.js patterns
## Available Scripts
- **generate-parser.sh** - Generate argparse parser from specifications
- **validate-parser.sh** - Validate parser structure and completeness
- **test-parser.sh** - Test parser with various argument combinations
- **convert-to-click.sh** - Convert argparse code to Click decorators
## Examples
See `examples/` directory for comprehensive patterns:
- **basic-usage.md** - Simple CLI with arguments
- **subcommands.md** - Multi-command CLI (like git, docker)
- **nested-commands.md** - Deep command hierarchies
- **validation-patterns.md** - Argument validation strategies
- **advanced-parsing.md** - Complex parsing scenarios
## Common Patterns
### Pattern 1: Simple CLI with Options
```python
parser = argparse.ArgumentParser(description='Deploy application')
parser.add_argument('--env', choices=['dev', 'staging', 'prod'], default='dev')
parser.add_argument('--force', action='store_true')
args = parser.parse_args()
```
### Pattern 2: Subcommands (git-like)
```python
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='command')
deploy_cmd = subparsers.add_parser('deploy')
deploy_cmd.add_argument('environment')
config_cmd = subparsers.add_parser('config')
```
### Pattern 3: Nested Subcommands (git config get/set)
```python
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='command')
config = subparsers.add_parser('config')
config_subs = config.add_subparsers(dest='config_command')
config_get = config_subs.add_parser('get')
config_get.add_argument('key')
config_set = config_subs.add_parser('set')
config_set.add_argument('key')
config_set.add_argument('value')
```
### Pattern 4: Mutually Exclusive Options
```python
group = parser.add_mutually_exclusive_group()
group.add_argument('--json', action='store_true')
group.add_argument('--yaml', action='store_true')
```
### Pattern 5: Custom Validation
```python
def validate_port(value):
ivalue = int(value)
if ivalue < 1 or ivalue > 65535:
raise argparse.ArgumentTypeError(f"{value} is not a valid port")
return ivalue
parser.add_argument('--port', type=validate_port, default=8080)
```
## Best Practices
1. **Always provide help text** - Use `help=` for every argument
2. **Set sensible defaults** - Use `default=` to avoid None values
3. **Use choices for fixed options** - Better than manual validation
4. **Group related arguments** - Use `add_argument_group()` for clarity
5. **Handle missing subcommands** - Check if `args.command` is None
6. **Use type coercion** - Prefer `type=int` over manual conversion
7. **Provide examples** - Use `epilog=` for usage examples
## Advantages Over External Libraries
- **No dependencies** - Built into Python standard library
- **Stable API** - Won't break with updates
- **Universal** - Works everywhere Python works
- **Well documented** - Extensive official documentation
- **Lightweight** - No installation or import overhead
## When to Use argparse
Use argparse when:
- Building simple to medium complexity CLIs
- Avoiding external dependencies is important
- Working in restricted environments
- Learning CLI patterns (clear, explicit API)
Consider alternatives when:
- Need decorator-based syntax (use Click/Typer)
- Want type safety and auto-completion (use Typer)
- Rapid prototyping from existing code (use Fire)
## Integration
This skill integrates with:
- `cli-setup` agent - Initialize Python CLI projects
- `cli-feature-impl` agent - Implement command logic
- `cli-verifier-python` agent - Validate argparse structure
- `click-patterns` skill - Compare with Click patterns
- `typer-patterns` skill - Compare with Typer patterns
## Requirements
- Python 3.7+ (argparse included in standard library)
- No external dependencies required
- Works on all platforms (Windows, macOS, Linux)
---
**Purpose**: Standard library Python CLI argument parsing patterns
**Used by**: Python CLI projects prioritizing zero dependencies

View File

@@ -0,0 +1,473 @@
# Advanced argparse Patterns
Complex argument parsing scenarios and advanced techniques.
## Templates Reference
- `templates/custom-actions.py`
- `templates/mutually-exclusive.py`
- `templates/argument-groups.py`
- `templates/variadic-args.py`
## Overview
Advanced patterns:
- Custom action classes
- Mutually exclusive groups
- Argument groups (organization)
- Variadic arguments (nargs)
- Environment variable fallback
- Config file integration
- Subparser inheritance
## 1. Custom Actions
Create custom argument processing logic.
### Simple Custom Action
```python
class UpperCaseAction(argparse.Action):
"""Convert value to uppercase."""
def __call__(self, parser, namespace, values, option_string=None):
setattr(namespace, self.dest, values.upper())
parser.add_argument('--name', action=UpperCaseAction)
```
### Key-Value Action
```python
class KeyValueAction(argparse.Action):
"""Parse key=value pairs."""
def __call__(self, parser, namespace, values, option_string=None):
if '=' not in values:
parser.error(f"Must be key=value format: {values}")
key, value = values.split('=', 1)
items = getattr(namespace, self.dest, {}) or {}
items[key] = value
setattr(namespace, self.dest, items)
parser.add_argument(
'--env', '-e',
action=KeyValueAction,
help='Environment variable (key=value)'
)
# Usage: --env API_KEY=abc123 --env DB_URL=postgres://...
```
### Load File Action
```python
class LoadFileAction(argparse.Action):
"""Load and parse file content."""
def __call__(self, parser, namespace, values, option_string=None):
try:
with open(values, 'r') as f:
content = f.read()
setattr(namespace, self.dest, content)
except Exception as e:
parser.error(f"Cannot load file {values}: {e}")
parser.add_argument('--config', action=LoadFileAction)
```
## 2. Mutually Exclusive Groups
Ensure only one option from a group is used.
### Basic Exclusivity
```python
group = parser.add_mutually_exclusive_group()
group.add_argument('--json', help='Output as JSON')
group.add_argument('--yaml', help='Output as YAML')
group.add_argument('--xml', help='Output as XML')
# Valid: --json output.json
# Valid: --yaml output.yaml
# Invalid: --json output.json --yaml output.yaml
```
### Required Exclusive Group
```python
mode_group = parser.add_mutually_exclusive_group(required=True)
mode_group.add_argument('--create', metavar='NAME')
mode_group.add_argument('--update', metavar='NAME')
mode_group.add_argument('--delete', metavar='NAME')
mode_group.add_argument('--list', action='store_true')
# Must specify exactly one: create, update, delete, or list
```
### Multiple Exclusive Groups
```python
# Output format group
output = parser.add_mutually_exclusive_group()
output.add_argument('--json', action='store_true')
output.add_argument('--yaml', action='store_true')
# Verbosity group
verbosity = parser.add_mutually_exclusive_group()
verbosity.add_argument('--verbose', action='store_true')
verbosity.add_argument('--quiet', action='store_true')
# Can use one from each group:
# Valid: --json --verbose
# Invalid: --json --yaml
```
## 3. Argument Groups
Organize arguments for better help display.
```python
parser = argparse.ArgumentParser()
# Server configuration
server_group = parser.add_argument_group(
'server configuration',
'Options for configuring the web server'
)
server_group.add_argument('--host', default='127.0.0.1')
server_group.add_argument('--port', type=int, default=8080)
server_group.add_argument('--workers', type=int, default=4)
# Database configuration
db_group = parser.add_argument_group(
'database configuration',
'Options for database connection'
)
db_group.add_argument('--db-host', default='localhost')
db_group.add_argument('--db-port', type=int, default=5432)
db_group.add_argument('--db-name', required=True)
# Logging configuration
log_group = parser.add_argument_group(
'logging configuration',
'Options for logging and monitoring'
)
log_group.add_argument('--log-level',
choices=['debug', 'info', 'warning', 'error'],
default='info')
log_group.add_argument('--log-file', help='Log to file')
```
**Help output groups arguments logically.**
## 4. Variadic Arguments (nargs)
Handle variable number of arguments.
### Optional Single Argument (?)
```python
parser.add_argument(
'--output',
nargs='?',
const='default.json', # Used if flag present, no value
default=None, # Used if flag not present
help='Output file'
)
# --output → 'default.json'
# --output file.json → 'file.json'
# (no flag) → None
```
### Zero or More (*)
```python
parser.add_argument(
'--include',
nargs='*',
default=[],
help='Include patterns'
)
# --include → []
# --include *.py → ['*.py']
# --include *.py *.md → ['*.py', '*.md']
```
### One or More (+)
```python
parser.add_argument(
'files',
nargs='+',
help='Input files (at least one required)'
)
# file1.txt → ['file1.txt']
# file1.txt file2.txt → ['file1.txt', 'file2.txt']
# (no files) → Error: required
```
### Exact Number
```python
parser.add_argument(
'--range',
nargs=2,
type=int,
metavar=('START', 'END'),
help='Range as start end'
)
# --range 1 10 → [1, 10]
# --range 1 → Error: expected 2
```
### Remainder
```python
parser.add_argument(
'--command',
nargs=argparse.REMAINDER,
help='Pass-through command and args'
)
# mycli --command python script.py --arg1 --arg2
# → command = ['python', 'script.py', '--arg1', '--arg2']
```
## 5. Environment Variable Fallback
```python
import os
parser = argparse.ArgumentParser()
parser.add_argument(
'--api-key',
default=os.environ.get('API_KEY'),
help='API key (default: $API_KEY)'
)
parser.add_argument(
'--db-url',
default=os.environ.get('DATABASE_URL'),
help='Database URL (default: $DATABASE_URL)'
)
# Precedence: CLI arg > Environment variable > Default
```
## 6. Config File Integration
```python
import configparser
def load_config(config_file):
"""Load configuration from INI file."""
config = configparser.ConfigParser()
config.read(config_file)
return config
parser = argparse.ArgumentParser()
parser.add_argument('--config', help='Config file')
parser.add_argument('--host', help='Server host')
parser.add_argument('--port', type=int, help='Server port')
args = parser.parse_args()
# Load config file if specified
if args.config:
config = load_config(args.config)
# Use config values as defaults if not specified on CLI
if not args.host:
args.host = config.get('server', 'host', fallback='127.0.0.1')
if not args.port:
args.port = config.getint('server', 'port', fallback=8080)
```
## 7. Parent Parsers (Inheritance)
Share common arguments across subcommands.
```python
# Parent parser with common arguments
parent_parser = argparse.ArgumentParser(add_help=False)
parent_parser.add_argument('--verbose', action='store_true')
parent_parser.add_argument('--config', help='Config file')
# Main parser
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='command')
# Subcommands inherit from parent
deploy_parser = subparsers.add_parser(
'deploy',
parents=[parent_parser],
help='Deploy application'
)
deploy_parser.add_argument('environment')
build_parser = subparsers.add_parser(
'build',
parents=[parent_parser],
help='Build application'
)
build_parser.add_argument('--target')
# Both subcommands have --verbose and --config
```
## 8. Argument Defaults from Dict
```python
defaults = {
'host': '127.0.0.1',
'port': 8080,
'workers': 4,
'timeout': 30.0
}
parser = argparse.ArgumentParser()
parser.add_argument('--host')
parser.add_argument('--port', type=int)
parser.add_argument('--workers', type=int)
parser.add_argument('--timeout', type=float)
# Set all defaults at once
parser.set_defaults(**defaults)
```
## 9. Namespace Manipulation
```python
# Pre-populate namespace
defaults = argparse.Namespace(
host='127.0.0.1',
port=8080,
debug=False
)
args = parser.parse_args(namespace=defaults)
# Or modify after parsing
args = parser.parse_args()
args.computed_value = args.value1 + args.value2
```
## 10. Conditional Arguments
```python
args = parser.parse_args()
# Add conditional validation
if args.ssl and not (args.cert and args.key):
parser.error("--ssl requires both --cert and --key")
# Add computed values
if args.workers == 'auto':
import os
args.workers = os.cpu_count()
```
## Complete Advanced Example
```python
#!/usr/bin/env python3
import argparse
import os
import sys
class KeyValueAction(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
key, value = values.split('=', 1)
items = getattr(namespace, self.dest, {}) or {}
items[key] = value
setattr(namespace, self.dest, items)
def main():
# Parent parser for common args
parent = argparse.ArgumentParser(add_help=False)
parent.add_argument('--verbose', action='store_true')
parent.add_argument('--config', help='Config file')
# Main parser
parser = argparse.ArgumentParser(
description='Advanced argparse patterns'
)
subparsers = parser.add_subparsers(dest='command', required=True)
# Deploy command
deploy = subparsers.add_parser(
'deploy',
parents=[parent],
help='Deploy application'
)
# Mutually exclusive group
format_group = deploy.add_mutually_exclusive_group()
format_group.add_argument('--json', action='store_true')
format_group.add_argument('--yaml', action='store_true')
# Custom action
deploy.add_argument(
'--env', '-e',
action=KeyValueAction,
help='Environment variable'
)
# Variadic arguments
deploy.add_argument(
'targets',
nargs='+',
help='Deployment targets'
)
# Environment fallback
deploy.add_argument(
'--api-key',
default=os.environ.get('API_KEY'),
help='API key'
)
args = parser.parse_args()
# Post-parse validation
if args.command == 'deploy':
if not args.api_key:
parser.error("API key required (use --api-key or $API_KEY)")
return 0
if __name__ == '__main__':
sys.exit(main())
```
## Best Practices
1. **Use parent parsers** for shared arguments
2. **Use argument groups** for organization
3. **Use mutually exclusive groups** when appropriate
4. **Validate after parsing** for complex logic
5. **Provide environment fallbacks** for sensitive data
6. **Use custom actions** for complex transformations
7. **Document nargs behavior** in help text
## Next Steps
- Review template files for complete implementations
- Test patterns with `scripts/test-parser.sh`
- Compare with Click/Typer alternatives

View File

@@ -0,0 +1,230 @@
# Basic argparse Usage
Simple CLI with positional and optional arguments using Python's standard library.
## Template Reference
`templates/basic-parser.py`
## Overview
Demonstrates fundamental argparse patterns:
- Positional arguments (required)
- Optional arguments with flags
- Boolean flags
- Type coercion
- Default values
- Help text generation
## Quick Start
```bash
# View help
python basic-parser.py --help
# Basic usage
python basic-parser.py deploy my-app
# With optional arguments
python basic-parser.py deploy my-app --env staging --timeout 60
# Boolean flags
python basic-parser.py deploy my-app --force
# Verbose mode (count occurrences)
python basic-parser.py deploy my-app -vvv
```
## Key Patterns
### 1. Create Parser
```python
import argparse
parser = argparse.ArgumentParser(
description='Deploy application to specified environment',
formatter_class=argparse.RawDescriptionHelpFormatter
)
```
**Why `RawDescriptionHelpFormatter`?**
- Preserves formatting in epilog (usage examples)
- Better control over help text layout
### 2. Add Version
```python
parser.add_argument(
'--version',
action='version',
version='%(prog)s 1.0.0'
)
```
**Usage:** `python mycli.py --version`
### 3. Positional Arguments
```python
parser.add_argument(
'app_name',
help='Name of the application to deploy'
)
```
**Required by default** - no flag needed, just the value.
### 4. Optional Arguments
```python
parser.add_argument(
'--env', '-e',
default='development',
help='Deployment environment (default: %(default)s)'
)
```
**Note:** `%(default)s` automatically shows default value in help.
### 5. Type Coercion
```python
parser.add_argument(
'--timeout', '-t',
type=int,
default=30,
help='Timeout in seconds'
)
```
**Automatic validation** - argparse will error if non-integer provided.
### 6. Boolean Flags
```python
parser.add_argument(
'--force', '-f',
action='store_true',
help='Force deployment without confirmation'
)
```
**Result:**
- Present: `args.force = True`
- Absent: `args.force = False`
### 7. Count Action
```python
parser.add_argument(
'--verbose', '-v',
action='count',
default=0,
help='Increase verbosity (-v, -vv, -vvv)'
)
```
**Usage:**
- `-v`: verbosity = 1
- `-vv`: verbosity = 2
- `-vvv`: verbosity = 3
## Complete Example
```python
#!/usr/bin/env python3
import argparse
import sys
def main():
parser = argparse.ArgumentParser(
description='Simple deployment tool'
)
parser.add_argument('--version', action='version', version='1.0.0')
parser.add_argument('app_name', help='Application name')
parser.add_argument('--env', default='dev', help='Environment')
parser.add_argument('--timeout', type=int, default=30, help='Timeout')
parser.add_argument('--force', action='store_true', help='Force')
args = parser.parse_args()
print(f"Deploying {args.app_name} to {args.env}")
print(f"Timeout: {args.timeout}s")
print(f"Force: {args.force}")
return 0
if __name__ == '__main__':
sys.exit(main())
```
## Help Output
```
usage: basic-parser.py [-h] [--version] [--env ENV] [--timeout TIMEOUT]
[--force] [--verbose]
action app_name
Deploy application to specified environment
positional arguments:
action Action to perform
app_name Name of the application to deploy
optional arguments:
-h, --help show this help message and exit
--version show program's version number and exit
--env ENV, -e ENV Deployment environment (default: development)
--timeout TIMEOUT, -t TIMEOUT
Timeout in seconds (default: 30)
--force, -f Force deployment without confirmation
--verbose, -v Increase verbosity (-v, -vv, -vvv)
```
## Common Mistakes
### ❌ Wrong: Accessing before parsing
```python
args = parser.parse_args()
print(args.env) # ✓ Correct
```
```python
print(args.env) # ✗ Wrong - args doesn't exist yet
args = parser.parse_args()
```
### ❌ Wrong: Not checking boolean flags
```python
if args.force: # ✓ Correct
print("Force mode")
```
```python
if args.force == True: # ✗ Unnecessary comparison
print("Force mode")
```
### ❌ Wrong: Manual type conversion
```python
parser.add_argument('--port', type=int) # ✓ Let argparse handle it
```
```python
parser.add_argument('--port')
port = int(args.port) # ✗ Manual conversion (error-prone)
```
## Next Steps
- **Subcommands:** See `subcommands.md`
- **Validation:** See `validation-patterns.md`
- **Advanced:** See `advanced-parsing.md`

View File

@@ -0,0 +1,370 @@
# Nested Subcommands
Multi-level command hierarchies like `git config get` or `kubectl config view`.
## Template Reference
`templates/nested-subparser.py`
## Overview
Create deep command structures:
- `mycli config get key`
- `mycli config set key value`
- `mycli deploy start production`
- `mycli deploy stop production`
## Quick Start
```bash
# Two-level commands
python nested-subparser.py config get database_url
python nested-subparser.py config set api_key abc123
python nested-subparser.py config list
# Deploy subcommands
python nested-subparser.py deploy start production --replicas 3
python nested-subparser.py deploy stop staging
```
## Architecture
```
mycli
├── config
│ ├── get <key>
│ ├── set <key> <value>
│ ├── list
│ └── delete <key>
└── deploy
├── start <environment>
├── stop <environment>
└── restart <environment>
```
## Implementation Pattern
### 1. Main Parser
```python
parser = argparse.ArgumentParser(description='Multi-level CLI')
subparsers = parser.add_subparsers(dest='command', required=True)
```
### 2. First-Level Subcommand
```python
# Create 'config' command group
config_parser = subparsers.add_parser(
'config',
help='Manage configuration'
)
# Create second-level subparsers under 'config'
config_subparsers = config_parser.add_subparsers(
dest='config_command',
required=True
)
```
### 3. Second-Level Subcommands
```python
# config get
config_get = config_subparsers.add_parser('get', help='Get value')
config_get.add_argument('key', help='Configuration key')
config_get.set_defaults(func=config_get_handler)
# config set
config_set = config_subparsers.add_parser('set', help='Set value')
config_set.add_argument('key', help='Configuration key')
config_set.add_argument('value', help='Configuration value')
config_set.add_argument('--force', action='store_true')
config_set.set_defaults(func=config_set_handler)
```
## Complete Example
```python
#!/usr/bin/env python3
import argparse
import sys
# Config handlers
def config_get(args):
print(f"Getting: {args.key}")
return 0
def config_set(args):
print(f"Setting: {args.key} = {args.value}")
return 0
# Deploy handlers
def deploy_start(args):
print(f"Starting deployment to {args.environment}")
return 0
def main():
parser = argparse.ArgumentParser(description='Nested CLI')
subparsers = parser.add_subparsers(dest='command', required=True)
# === Config group ===
config_parser = subparsers.add_parser('config', help='Configuration')
config_subs = config_parser.add_subparsers(
dest='config_command',
required=True
)
# config get
get_parser = config_subs.add_parser('get')
get_parser.add_argument('key')
get_parser.set_defaults(func=config_get)
# config set
set_parser = config_subs.add_parser('set')
set_parser.add_argument('key')
set_parser.add_argument('value')
set_parser.set_defaults(func=config_set)
# === Deploy group ===
deploy_parser = subparsers.add_parser('deploy', help='Deployment')
deploy_subs = deploy_parser.add_subparsers(
dest='deploy_command',
required=True
)
# deploy start
start_parser = deploy_subs.add_parser('start')
start_parser.add_argument('environment')
start_parser.set_defaults(func=deploy_start)
# Parse and dispatch
args = parser.parse_args()
return args.func(args)
if __name__ == '__main__':
sys.exit(main())
```
## Accessing Nested Commands
```python
args = parser.parse_args()
# Top-level command
print(args.command) # 'config' or 'deploy'
# Second-level command
if args.command == 'config':
print(args.config_command) # 'get', 'set', 'list', 'delete'
elif args.command == 'deploy':
print(args.deploy_command) # 'start', 'stop', 'restart'
```
## Help Output
### Top-Level Help
```
usage: mycli [-h] {config,deploy} ...
positional arguments:
{config,deploy}
config Manage configuration
deploy Manage deployments
```
### Second-Level Help
```bash
$ python mycli.py config --help
usage: mycli config [-h] {get,set,list,delete} ...
positional arguments:
{get,set,list,delete}
get Get configuration value
set Set configuration value
list List all configuration
delete Delete configuration value
```
### Third-Level Help
```bash
$ python mycli.py config set --help
usage: mycli config set [-h] [-f] key value
positional arguments:
key Configuration key
value Configuration value
optional arguments:
-h, --help show this help message and exit
-f, --force Overwrite existing value
```
## Dispatch Pattern
### Option 1: Manual Switch
```python
args = parser.parse_args()
if args.command == 'config':
if args.config_command == 'get':
config_get(args)
elif args.config_command == 'set':
config_set(args)
elif args.command == 'deploy':
if args.deploy_command == 'start':
deploy_start(args)
```
### Option 2: Function Dispatch (Recommended)
```python
# Set handlers when creating parsers
config_get.set_defaults(func=config_get_handler)
config_set.set_defaults(func=config_set_handler)
deploy_start.set_defaults(func=deploy_start_handler)
# Simple dispatch
args = parser.parse_args()
return args.func(args)
```
## Best Practices
### 1. Consistent Naming
```python
# ✓ Good - consistent dest naming
config_parser.add_subparsers(dest='config_command')
deploy_parser.add_subparsers(dest='deploy_command')
```
### 2. Set Required
```python
# ✓ Good - require subcommand
config_subs = config_parser.add_subparsers(
dest='config_command',
required=True
)
```
### 3. Provide Help
```python
# ✓ Good - descriptive help at each level
config_parser = subparsers.add_parser(
'config',
help='Manage configuration',
description='Configuration management commands'
)
```
### 4. Use set_defaults
```python
# ✓ Good - easy dispatch
get_parser.set_defaults(func=config_get)
```
## How Deep Should You Go?
### ✓ Good: 2-3 Levels
```
mycli config get key
mycli deploy start production
```
### ⚠️ Consider alternatives: 4+ Levels
```
mycli server database config get key # Too deep
```
**Alternatives:**
- Flatten: `mycli db-config-get key`
- Split: Separate CLI tools
- Use flags: `mycli config get key --scope=server --type=database`
## Common Mistakes
### ❌ Wrong: Same dest name
```python
# Both use 'command' - second overwrites first
config_subs = config_parser.add_subparsers(dest='command')
deploy_subs = deploy_parser.add_subparsers(dest='command')
```
```python
# ✓ Correct - unique dest names
config_subs = config_parser.add_subparsers(dest='config_command')
deploy_subs = deploy_parser.add_subparsers(dest='deploy_command')
```
### ❌ Wrong: Accessing wrong level
```python
args = parser.parse_args(['config', 'get', 'key'])
print(args.command) # ✓ 'config'
print(args.config_command) # ✓ 'get'
print(args.deploy_command) # ✗ Error - not set
```
### ❌ Wrong: Not checking hierarchy
```python
# ✗ Assumes deploy command
print(args.deploy_command)
```
```python
# ✓ Check first
if args.command == 'deploy':
print(args.deploy_command)
```
## Real-World Examples
### Git-style
```
git config --global user.name "Name"
git remote add origin url
git branch --list
```
### Kubectl-style
```
kubectl config view
kubectl get pods --namespace default
kubectl logs pod-name --follow
```
### Docker-style
```
docker container ls
docker image build -t name .
docker network create name
```
## Next Steps
- **Validation:** See `validation-patterns.md`
- **Advanced:** See `advanced-parsing.md`
- **Compare frameworks:** See templates for Click/Typer equivalents

View File

@@ -0,0 +1,283 @@
# Subcommands with argparse
Multi-command CLI like `git`, `docker`, or `kubectl` using subparsers.
## Template Reference
`templates/subparser-pattern.py`
## Overview
Create CLIs with multiple commands:
- `mycli init` - Initialize project
- `mycli deploy production` - Deploy to environment
- `mycli status` - Show status
Each subcommand has its own arguments and options.
## Quick Start
```bash
# View main help
python subparser-pattern.py --help
# View subcommand help
python subparser-pattern.py init --help
python subparser-pattern.py deploy --help
# Execute subcommands
python subparser-pattern.py init --template react
python subparser-pattern.py deploy production --force
python subparser-pattern.py status --format json
```
## Key Patterns
### 1. Create Subparsers
```python
parser = argparse.ArgumentParser(description='Multi-command CLI')
subparsers = parser.add_subparsers(
dest='command', # Store command name in args.command
help='Available commands',
required=True # At least one command required (Python 3.7+)
)
```
**Important:** Set `dest='command'` to access which command was used.
### 2. Add Subcommand
```python
init_parser = subparsers.add_parser(
'init',
help='Initialize a new project',
description='Initialize a new project with specified template'
)
init_parser.add_argument('--template', default='basic')
init_parser.add_argument('--path', default='.')
```
Each subcommand is a separate parser with its own arguments.
### 3. Set Command Handler
```python
def cmd_init(args):
"""Initialize project."""
print(f"Initializing with {args.template} template...")
init_parser.set_defaults(func=cmd_init)
```
**Dispatch pattern:**
```python
args = parser.parse_args()
return args.func(args) # Call the appropriate handler
```
### 4. Subcommand with Choices
```python
deploy_parser = subparsers.add_parser('deploy')
deploy_parser.add_argument(
'environment',
choices=['development', 'staging', 'production'],
help='Target environment'
)
deploy_parser.add_argument(
'--mode',
choices=['fast', 'safe', 'rollback'],
default='safe'
)
```
## Complete Example
```python
#!/usr/bin/env python3
import argparse
import sys
def cmd_init(args):
print(f"Initializing with {args.template} template")
return 0
def cmd_deploy(args):
print(f"Deploying to {args.environment}")
return 0
def main():
parser = argparse.ArgumentParser(description='My CLI Tool')
parser.add_argument('--version', action='version', version='1.0.0')
subparsers = parser.add_subparsers(dest='command', required=True)
# Init command
init_parser = subparsers.add_parser('init', help='Initialize project')
init_parser.add_argument('--template', default='basic')
init_parser.set_defaults(func=cmd_init)
# Deploy command
deploy_parser = subparsers.add_parser('deploy', help='Deploy app')
deploy_parser.add_argument(
'environment',
choices=['dev', 'staging', 'prod']
)
deploy_parser.set_defaults(func=cmd_deploy)
# Parse and dispatch
args = parser.parse_args()
return args.func(args)
if __name__ == '__main__':
sys.exit(main())
```
## Help Output
### Main Help
```
usage: mycli [-h] [--version] {init,deploy,status} ...
Multi-command CLI tool
positional arguments:
{init,deploy,status} Available commands
init Initialize a new project
deploy Deploy application to environment
status Show deployment status
optional arguments:
-h, --help show this help message and exit
--version show program's version number and exit
```
### Subcommand Help
```bash
$ python mycli.py deploy --help
usage: mycli deploy [-h] [-f] [-m {fast,safe,rollback}]
{development,staging,production}
positional arguments:
{development,staging,production}
Target environment
optional arguments:
-h, --help show this help message and exit
-f, --force Force deployment without confirmation
-m {fast,safe,rollback}, --mode {fast,safe,rollback}
Deployment mode (default: safe)
```
## Accessing Parsed Values
```python
args = parser.parse_args()
# Which command was used?
print(args.command) # 'init', 'deploy', or 'status'
# Command-specific arguments
if args.command == 'deploy':
print(args.environment) # 'production'
print(args.force) # True/False
print(args.mode) # 'safe'
```
## Common Patterns
### Pattern 1: Switch on Command
```python
args = parser.parse_args()
if args.command == 'init':
init_project(args)
elif args.command == 'deploy':
deploy_app(args)
elif args.command == 'status':
show_status(args)
```
### Pattern 2: Function Dispatch (Better)
```python
# Set handlers
init_parser.set_defaults(func=cmd_init)
deploy_parser.set_defaults(func=cmd_deploy)
# Dispatch
args = parser.parse_args()
return args.func(args)
```
### Pattern 3: Check if Command Provided
```python
args = parser.parse_args()
if not args.command:
parser.print_help()
sys.exit(1)
```
**Note:** Use `required=True` in `add_subparsers()` to make this automatic.
## Common Mistakes
### ❌ Wrong: Forgetting dest
```python
subparsers = parser.add_subparsers(dest='command') # ✓ Can check args.command
```
```python
subparsers = parser.add_subparsers() # ✗ Can't access which command
```
### ❌ Wrong: Accessing wrong argument
```python
# deploy_parser defines 'environment'
# init_parser defines 'template'
args = parser.parse_args(['deploy', 'prod'])
print(args.environment) # ✓ Correct
print(args.template) # ✗ Error - not defined for deploy
```
### ❌ Wrong: No required=True (Python 3.7+)
```python
subparsers = parser.add_subparsers(dest='command', required=True) # ✓
```
```python
subparsers = parser.add_subparsers(dest='command') # ✗ Command optional
# User can run: python mycli.py (no command)
```
## Nested Subcommands
For multi-level commands like `git config get`, see:
- `nested-commands.md`
- `templates/nested-subparser.py`
## Next Steps
- **Nested Commands:** See `nested-commands.md`
- **Validation:** See `validation-patterns.md`
- **Complex CLIs:** See `advanced-parsing.md`

View File

@@ -0,0 +1,424 @@
# Validation Patterns with argparse
Custom validators, type checking, and error handling.
## Template Reference
`templates/choices-validation.py`
## Overview
Robust argument validation:
- Built-in choices validation
- Custom type validators
- Range validation
- Pattern matching (regex)
- File/path validation
## Quick Start
```bash
# Valid inputs
python choices-validation.py --log-level debug --port 8080
python choices-validation.py --region us-east-1 --email user@example.com
# Invalid inputs (will error)
python choices-validation.py --log-level invalid # Not in choices
python choices-validation.py --port 99999 # Out of range
python choices-validation.py --email invalid # Invalid format
```
## Validation Methods
### 1. Choices (Built-in)
```python
parser.add_argument(
'--log-level',
choices=['debug', 'info', 'warning', 'error', 'critical'],
default='info',
help='Logging level'
)
```
**Automatic validation** - argparse rejects invalid values.
### 2. Custom Type Validator
```python
def validate_port(value):
"""Validate port number is 1-65535."""
try:
ivalue = int(value)
except ValueError:
raise argparse.ArgumentTypeError(f"{value} is not a valid integer")
if ivalue < 1 or ivalue > 65535:
raise argparse.ArgumentTypeError(
f"{value} is not a valid port (must be 1-65535)"
)
return ivalue
parser.add_argument(
'--port',
type=validate_port,
default=8080,
help='Server port (1-65535)'
)
```
### 3. Regex Pattern Validation
```python
import re
def validate_email(value):
"""Validate email address format."""
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
if not re.match(pattern, value):
raise argparse.ArgumentTypeError(
f"{value} is not a valid email address"
)
return value
parser.add_argument('--email', type=validate_email)
```
### 4. IP Address Validation
```python
def validate_ip(value):
"""Validate IPv4 address."""
pattern = r'^(\d{1,3}\.){3}\d{1,3}$'
if not re.match(pattern, value):
raise argparse.ArgumentTypeError(
f"{value} is not a valid IP address"
)
# Check each octet is 0-255
octets = [int(x) for x in value.split('.')]
if any(o < 0 or o > 255 for o in octets):
raise argparse.ArgumentTypeError(
f"{value} contains invalid octets"
)
return value
parser.add_argument('--host', type=validate_ip)
```
### 5. Path Validation
```python
from pathlib import Path
def validate_path_exists(value):
"""Validate path exists."""
path = Path(value)
if not path.exists():
raise argparse.ArgumentTypeError(
f"Path does not exist: {value}"
)
return path
parser.add_argument('--config', type=validate_path_exists)
```
### 6. Range Validation Factory
```python
def validate_range(min_val, max_val):
"""Factory function for range validators."""
def validator(value):
try:
ivalue = int(value)
except ValueError:
raise argparse.ArgumentTypeError(
f"{value} is not a valid integer"
)
if ivalue < min_val or ivalue > max_val:
raise argparse.ArgumentTypeError(
f"{value} must be between {min_val} and {max_val}"
)
return ivalue
return validator
# Usage
parser.add_argument(
'--workers',
type=validate_range(1, 32),
default=4,
help='Number of workers (1-32)'
)
```
## Complete Validation Example
```python
#!/usr/bin/env python3
import argparse
import re
import sys
from pathlib import Path
def validate_port(value):
ivalue = int(value)
if not (1 <= ivalue <= 65535):
raise argparse.ArgumentTypeError(
f"Port must be 1-65535, got {value}"
)
return ivalue
def validate_email(value):
if not re.match(r'^[\w\.-]+@[\w\.-]+\.\w+$', value):
raise argparse.ArgumentTypeError(
f"Invalid email: {value}"
)
return value
def main():
parser = argparse.ArgumentParser(
description='Validation examples'
)
# Choices
parser.add_argument(
'--env',
choices=['dev', 'staging', 'prod'],
required=True,
help='Environment (required)'
)
# Custom validators
parser.add_argument('--port', type=validate_port, default=8080)
parser.add_argument('--email', type=validate_email)
# Path validation
parser.add_argument(
'--config',
type=lambda x: Path(x) if Path(x).exists() else
parser.error(f"File not found: {x}"),
help='Config file (must exist)'
)
args = parser.parse_args()
print(f"Environment: {args.env}")
print(f"Port: {args.port}")
if args.email:
print(f"Email: {args.email}")
return 0
if __name__ == '__main__':
sys.exit(main())
```
## Post-Parse Validation
Sometimes you need to validate relationships between arguments:
```python
args = parser.parse_args()
# Validate argument combinations
if args.ssl and not (args.cert and args.key):
parser.error("--ssl requires both --cert and --key")
if args.output and args.output.exists() and not args.force:
parser.error(f"Output file exists: {args.output}. Use --force to overwrite")
# Validate argument ranges
if args.start_date > args.end_date:
parser.error("Start date must be before end date")
```
## Error Messages
### Built-in Error Format
```bash
$ python mycli.py --env invalid
usage: mycli.py [-h] --env {dev,staging,prod}
mycli.py: error: argument --env: invalid choice: 'invalid'
(choose from 'dev', 'staging', 'prod')
```
### Custom Error Format
```python
def validate_port(value):
try:
ivalue = int(value)
except ValueError:
raise argparse.ArgumentTypeError(
f"Port must be an integer (got '{value}')"
)
if ivalue < 1 or ivalue > 65535:
raise argparse.ArgumentTypeError(
f"Port {ivalue} is out of range (valid: 1-65535)"
)
return ivalue
```
```bash
$ python mycli.py --port 99999
usage: mycli.py [-h] [--port PORT]
mycli.py: error: argument --port: Port 99999 is out of range (valid: 1-65535)
```
## Common Validation Patterns
### URL Validation
```python
import re
def validate_url(value):
pattern = r'^https?://[\w\.-]+\.\w+(:\d+)?(/.*)?$'
if not re.match(pattern, value):
raise argparse.ArgumentTypeError(f"Invalid URL: {value}")
return value
```
### Date Validation
```python
from datetime import datetime
def validate_date(value):
try:
return datetime.strptime(value, '%Y-%m-%d').date()
except ValueError:
raise argparse.ArgumentTypeError(
f"Invalid date: {value} (expected YYYY-MM-DD)"
)
```
### File Extension Validation
```python
def validate_json_file(value):
path = Path(value)
if path.suffix != '.json':
raise argparse.ArgumentTypeError(
f"File must have .json extension: {value}"
)
return path
```
### Percentage Validation
```python
def validate_percentage(value):
try:
pct = float(value.rstrip('%'))
except ValueError:
raise argparse.ArgumentTypeError(f"Invalid percentage: {value}")
if not (0 <= pct <= 100):
raise argparse.ArgumentTypeError(
f"Percentage must be 0-100: {value}"
)
return pct
```
## Best Practices
### ✓ Do: Fail Early
```python
# Validate during parsing
parser.add_argument('--port', type=validate_port)
# Not after parsing
args = parser.parse_args()
if not valid_port(args.port): # ✗ Too late
sys.exit(1)
```
### ✓ Do: Provide Clear Messages
```python
# ✓ Clear, actionable error
raise argparse.ArgumentTypeError(
f"Port {value} is out of range (valid: 1-65535)"
)
# ✗ Vague error
raise argparse.ArgumentTypeError("Invalid port")
```
### ✓ Do: Use Choices When Possible
```python
# ✓ Let argparse handle it
parser.add_argument('--env', choices=['dev', 'staging', 'prod'])
# ✗ Manual validation
parser.add_argument('--env')
if args.env not in ['dev', 'staging', 'prod']:
parser.error("Invalid environment")
```
### ✓ Do: Validate Type Before Range
```python
def validate_port(value):
# First ensure it's an integer
try:
ivalue = int(value)
except ValueError:
raise argparse.ArgumentTypeError(f"Not an integer: {value}")
# Then check range
if not (1 <= ivalue <= 65535):
raise argparse.ArgumentTypeError(f"Out of range: {ivalue}")
return ivalue
```
## Testing Validation
```python
import pytest
from io import StringIO
import sys
def test_valid_port():
"""Test valid port number."""
parser = create_parser()
args = parser.parse_args(['--port', '8080'])
assert args.port == 8080
def test_invalid_port():
"""Test invalid port number."""
parser = create_parser()
with pytest.raises(SystemExit):
parser.parse_args(['--port', '99999'])
def test_invalid_choice():
"""Test invalid choice."""
parser = create_parser()
with pytest.raises(SystemExit):
parser.parse_args(['--env', 'invalid'])
```
## Next Steps
- **Advanced Patterns:** See `advanced-parsing.md`
- **Type Coercion:** See `templates/type-coercion.py`
- **Custom Actions:** See `templates/custom-actions.py`

View File

@@ -0,0 +1,151 @@
#!/usr/bin/env bash
# Convert argparse code to Click decorators
set -euo pipefail
usage() {
cat <<EOF
Convert argparse parser to Click decorators
Usage: $(basename "$0") ARGPARSE_FILE [OUTPUT_FILE]
Performs basic conversion from argparse to Click:
- ArgumentParser → @click.group() or @click.command()
- add_argument() → @click.option() or @click.argument()
- add_subparsers() → @group.command()
- choices=[] → type=click.Choice([])
- action='store_true' → is_flag=True
Note: This is a basic converter. Manual refinement may be needed.
Examples:
$(basename "$0") mycli.py mycli_click.py
$(basename "$0") basic-parser.py
EOF
exit 1
}
if [ $# -eq 0 ]; then
usage
fi
ARGPARSE_FILE="$1"
OUTPUT_FILE="${2:-}"
if [ ! -f "$ARGPARSE_FILE" ]; then
echo "Error: File not found: $ARGPARSE_FILE"
exit 1
fi
echo "Converting argparse to Click: $ARGPARSE_FILE"
convert_to_click() {
cat <<'EOF'
#!/usr/bin/env python3
"""
Converted from argparse to Click
This is a basic conversion. You may need to adjust:
- Argument order and grouping
- Type conversions
- Custom validators
- Error handling
"""
import click
@click.group()
@click.version_option(version='1.0.0')
@click.pass_context
def cli(ctx):
"""CLI tool converted from argparse"""
ctx.ensure_object(dict)
# Convert your subcommands here
# Example pattern:
#
# @cli.command()
# @click.argument('target')
# @click.option('--env', type=click.Choice(['dev', 'staging', 'prod']), default='dev')
# @click.option('--force', is_flag=True, help='Force operation')
# def deploy(target, env, force):
# """Deploy to environment"""
# click.echo(f"Deploying {target} to {env}")
# if force:
# click.echo("Force mode enabled")
if __name__ == '__main__':
cli()
EOF
echo ""
echo "# Detected argparse patterns:"
echo ""
# Detect subcommands
if grep -q "add_subparsers(" "$ARGPARSE_FILE"; then
echo "# Subcommands found:"
grep -oP "add_parser\('\K[^']+(?=')" "$ARGPARSE_FILE" | while read -r cmd; do
echo "# - $cmd"
done
echo ""
fi
# Detect arguments
if grep -q "add_argument(" "$ARGPARSE_FILE"; then
echo "# Arguments found:"
grep "add_argument(" "$ARGPARSE_FILE" | grep -oP "'[^']+'" | head -n1 | while read -r arg; do
echo "# $arg"
done
echo ""
fi
# Detect choices
if grep -q "choices=" "$ARGPARSE_FILE"; then
echo "# Choices found (convert to click.Choice):"
grep -oP "choices=\[\K[^\]]+(?=\])" "$ARGPARSE_FILE" | while read -r choices; do
echo "# [$choices]"
done
echo ""
fi
# Provide conversion hints
cat <<'EOF'
# Conversion Guide:
#
# argparse → Click
# ----------------------------------|--------------------------------
# parser.add_argument('arg') → @click.argument('arg')
# parser.add_argument('--opt') → @click.option('--opt')
# action='store_true' → is_flag=True
# choices=['a', 'b'] → type=click.Choice(['a', 'b'])
# type=int → type=int
# required=True → required=True
# default='value' → default='value'
# help='...' → help='...'
#
# For nested subcommands:
# Use @group.command() decorator
#
# For more info: https://click.palletsprojects.com/
EOF
}
# Output
if [ -n "$OUTPUT_FILE" ]; then
convert_to_click > "$OUTPUT_FILE"
chmod +x "$OUTPUT_FILE"
echo "Converted to Click: $OUTPUT_FILE"
echo ""
echo "Next steps:"
echo " 1. Review the generated file"
echo " 2. Add your command implementations"
echo " 3. Install Click: pip install click"
echo " 4. Test: python $OUTPUT_FILE --help"
else
convert_to_click
fi

View File

@@ -0,0 +1,213 @@
#!/usr/bin/env bash
# Generate argparse parser from specification
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
TEMPLATES_DIR="$(dirname "$SCRIPT_DIR")/templates"
usage() {
cat <<EOF
Generate argparse parser from specification
Usage: $(basename "$0") [OPTIONS]
Options:
-n, --name NAME Parser name (required)
-d, --description DESC Parser description
-s, --subcommands Include subcommands
-c, --choices Include choice validation
-g, --groups Include argument groups
-o, --output FILE Output file (default: stdout)
-h, --help Show this help
Examples:
$(basename "$0") -n mycli -d "My CLI tool" -o mycli.py
$(basename "$0") -n deploy -s -c -o deploy.py
EOF
exit 1
}
# Parse arguments
NAME=""
DESCRIPTION=""
SUBCOMMANDS=false
CHOICES=false
GROUPS=false
OUTPUT=""
while [[ $# -gt 0 ]]; do
case $1 in
-n|--name)
NAME="$2"
shift 2
;;
-d|--description)
DESCRIPTION="$2"
shift 2
;;
-s|--subcommands)
SUBCOMMANDS=true
shift
;;
-c|--choices)
CHOICES=true
shift
;;
-g|--groups)
GROUPS=true
shift
;;
-o|--output)
OUTPUT="$2"
shift 2
;;
-h|--help)
usage
;;
*)
echo "Error: Unknown option $1"
usage
;;
esac
done
if [ -z "$NAME" ]; then
echo "Error: --name is required"
usage
fi
# Set defaults
DESCRIPTION="${DESCRIPTION:-$NAME CLI tool}"
# Generate parser
generate_parser() {
cat <<EOF
#!/usr/bin/env python3
"""
$DESCRIPTION
Generated by generate-parser.sh
"""
import argparse
import sys
def main():
parser = argparse.ArgumentParser(
description='$DESCRIPTION',
formatter_class=argparse.RawDescriptionHelpFormatter
)
parser.add_argument(
'--version',
action='version',
version='1.0.0'
)
parser.add_argument(
'--verbose', '-v',
action='store_true',
help='Enable verbose output'
)
EOF
if [ "$GROUPS" = true ]; then
cat <<EOF
# Configuration group
config_group = parser.add_argument_group(
'configuration',
'Configuration options'
)
config_group.add_argument(
'--config',
help='Configuration file'
)
EOF
fi
if [ "$SUBCOMMANDS" = true ]; then
cat <<EOF
# Create subparsers
subparsers = parser.add_subparsers(
dest='command',
help='Available commands',
required=True
)
# Example subcommand
cmd_parser = subparsers.add_parser(
'run',
help='Run the application'
)
cmd_parser.add_argument(
'target',
help='Target to run'
)
EOF
if [ "$CHOICES" = true ]; then
cat <<EOF
cmd_parser.add_argument(
'--env',
choices=['development', 'staging', 'production'],
default='development',
help='Environment (default: %(default)s)'
)
EOF
fi
else
cat <<EOF
# Arguments
parser.add_argument(
'target',
help='Target to process'
)
EOF
if [ "$CHOICES" = true ]; then
cat <<EOF
parser.add_argument(
'--env',
choices=['development', 'staging', 'production'],
default='development',
help='Environment (default: %(default)s)'
)
EOF
fi
fi
cat <<EOF
# Parse arguments
args = parser.parse_args()
# Display configuration
if args.verbose:
print("Verbose mode enabled")
print(f"Arguments: {args}")
return 0
if __name__ == '__main__':
sys.exit(main())
EOF
}
# Output
if [ -n "$OUTPUT" ]; then
generate_parser > "$OUTPUT"
chmod +x "$OUTPUT"
echo "Generated parser: $OUTPUT"
else
generate_parser
fi

View File

@@ -0,0 +1,149 @@
#!/usr/bin/env bash
# Test argparse parser with various argument combinations
set -euo pipefail
usage() {
cat <<EOF
Test argparse parser with various arguments
Usage: $(basename "$0") PARSER_FILE
Tests:
- Help display (--help)
- Version display (--version)
- Missing required arguments
- Invalid choices
- Type validation
- Subcommands (if present)
Examples:
$(basename "$0") mycli.py
$(basename "$0") ../templates/basic-parser.py
EOF
exit 1
}
if [ $# -eq 0 ]; then
usage
fi
PARSER_FILE="$1"
if [ ! -f "$PARSER_FILE" ]; then
echo "Error: File not found: $PARSER_FILE"
exit 1
fi
# Make executable if needed
if [ ! -x "$PARSER_FILE" ]; then
chmod +x "$PARSER_FILE"
fi
echo "Testing argparse parser: $PARSER_FILE"
echo ""
PASSED=0
FAILED=0
run_test() {
local description="$1"
shift
local expected_result="$1"
shift
echo -n "Testing: $description ... "
if "$PARSER_FILE" "$@" >/dev/null 2>&1; then
result="success"
else
result="failure"
fi
if [ "$result" = "$expected_result" ]; then
echo "✓ PASS"
((PASSED++))
else
echo "✗ FAIL (expected $expected_result, got $result)"
((FAILED++))
fi
}
# Test --help
run_test "Help display" "success" --help
# Test --version
if grep -q "action='version'" "$PARSER_FILE"; then
run_test "Version display" "success" --version
fi
# Test with no arguments
run_test "No arguments" "failure"
# Test invalid option
run_test "Invalid option" "failure" --invalid-option
# Detect and test subcommands
if grep -q "add_subparsers(" "$PARSER_FILE"; then
echo ""
echo "Subcommands detected, testing subcommand patterns..."
# Try to extract subcommand names
subcommands=$(grep -oP "add_parser\('\K[^']+(?=')" "$PARSER_FILE" || true)
if [ -n "$subcommands" ]; then
for cmd in $subcommands; do
run_test "Subcommand: $cmd --help" "success" "$cmd" --help
done
fi
fi
# Test choices if present
if grep -q "choices=\[" "$PARSER_FILE"; then
echo ""
echo "Choices validation detected, testing..."
# Extract valid and invalid choices
valid_choice=$(grep -oP "choices=\[\s*'([^']+)" "$PARSER_FILE" | head -n1 | grep -oP "'[^']+'" | tr -d "'" || echo "valid")
invalid_choice="invalid_choice_12345"
if grep -q "add_subparsers(" "$PARSER_FILE" && [ -n "$subcommands" ]; then
first_cmd=$(echo "$subcommands" | head -n1)
run_test "Valid choice" "success" "$first_cmd" target --env "$valid_choice" 2>/dev/null || true
run_test "Invalid choice" "failure" "$first_cmd" target --env "$invalid_choice" 2>/dev/null || true
fi
fi
# Test type validation if present
if grep -q "type=int" "$PARSER_FILE"; then
echo ""
echo "Type validation detected, testing..."
run_test "Valid integer" "success" --port 8080 2>/dev/null || true
run_test "Invalid integer" "failure" --port invalid 2>/dev/null || true
fi
# Test boolean flags if present
if grep -q "action='store_true'" "$PARSER_FILE"; then
echo ""
echo "Boolean flags detected, testing..."
run_test "Boolean flag present" "success" --verbose 2>/dev/null || true
fi
# Summary
echo ""
echo "Test Summary:"
echo " Passed: $PASSED"
echo " Failed: $FAILED"
echo " Total: $((PASSED + FAILED))"
if [ $FAILED -eq 0 ]; then
echo ""
echo "✓ All tests passed"
exit 0
else
echo ""
echo "✗ Some tests failed"
exit 1
fi

View File

@@ -0,0 +1,173 @@
#!/usr/bin/env bash
# Validate argparse parser structure and completeness
set -euo pipefail
usage() {
cat <<EOF
Validate argparse parser structure
Usage: $(basename "$0") PARSER_FILE
Checks:
- Valid Python syntax
- Imports argparse
- Creates ArgumentParser
- Has main() function
- Calls parse_args()
- Has proper shebang
- Has help text
- Has version info
Examples:
$(basename "$0") mycli.py
$(basename "$0") ../templates/basic-parser.py
EOF
exit 1
}
if [ $# -eq 0 ]; then
usage
fi
PARSER_FILE="$1"
if [ ! -f "$PARSER_FILE" ]; then
echo "Error: File not found: $PARSER_FILE"
exit 1
fi
echo "Validating argparse parser: $PARSER_FILE"
echo ""
ERRORS=0
WARNINGS=0
# Check shebang
if head -n1 "$PARSER_FILE" | grep -q '^#!/usr/bin/env python'; then
echo "✓ Has proper Python shebang"
else
echo "✗ Missing or invalid shebang"
((ERRORS++))
fi
# Check syntax
if python3 -m py_compile "$PARSER_FILE" 2>/dev/null; then
echo "✓ Valid Python syntax"
else
echo "✗ Invalid Python syntax"
((ERRORS++))
fi
# Check imports
if grep -q "import argparse" "$PARSER_FILE"; then
echo "✓ Imports argparse"
else
echo "✗ Does not import argparse"
((ERRORS++))
fi
# Check ArgumentParser creation
if grep -q "ArgumentParser(" "$PARSER_FILE"; then
echo "✓ Creates ArgumentParser"
else
echo "✗ Does not create ArgumentParser"
((ERRORS++))
fi
# Check main function
if grep -q "^def main(" "$PARSER_FILE"; then
echo "✓ Has main() function"
else
echo "⚠ No main() function found"
((WARNINGS++))
fi
# Check parse_args call
if grep -q "\.parse_args()" "$PARSER_FILE"; then
echo "✓ Calls parse_args()"
else
echo "✗ Does not call parse_args()"
((ERRORS++))
fi
# Check version
if grep -q "action='version'" "$PARSER_FILE"; then
echo "✓ Has version info"
else
echo "⚠ No version info found"
((WARNINGS++))
fi
# Check help text
if grep -q "help=" "$PARSER_FILE"; then
echo "✓ Has help text for arguments"
else
echo "⚠ No help text found"
((WARNINGS++))
fi
# Check description
if grep -q "description=" "$PARSER_FILE"; then
echo "✓ Has parser description"
else
echo "⚠ No parser description"
((WARNINGS++))
fi
# Check if executable
if [ -x "$PARSER_FILE" ]; then
echo "✓ File is executable"
else
echo "⚠ File is not executable (run: chmod +x $PARSER_FILE)"
((WARNINGS++))
fi
# Check subparsers if present
if grep -q "add_subparsers(" "$PARSER_FILE"; then
echo "✓ Has subparsers"
# Check if dest is set
if grep -q "add_subparsers(.*dest=" "$PARSER_FILE"; then
echo " ✓ Subparsers have dest set"
else
echo " ⚠ Subparsers missing dest parameter"
((WARNINGS++))
fi
fi
# Check for choices
if grep -q "choices=" "$PARSER_FILE"; then
echo "✓ Uses choices for validation"
fi
# Check for type coercion
if grep -q "type=" "$PARSER_FILE"; then
echo "✓ Uses type coercion"
fi
# Check for argument groups
if grep -q "add_argument_group(" "$PARSER_FILE"; then
echo "✓ Uses argument groups"
fi
# Check for mutually exclusive groups
if grep -q "add_mutually_exclusive_group(" "$PARSER_FILE"; then
echo "✓ Uses mutually exclusive groups"
fi
# Summary
echo ""
echo "Validation Summary:"
echo " Errors: $ERRORS"
echo " Warnings: $WARNINGS"
if [ $ERRORS -eq 0 ]; then
echo ""
echo "✓ Parser validation passed"
exit 0
else
echo ""
echo "✗ Parser validation failed"
exit 1
fi

View File

@@ -0,0 +1,201 @@
#!/usr/bin/env node
/**
* argparse patterns translated to commander.js
*
* Shows equivalent patterns between Python argparse and Node.js commander
*
* Usage:
* npm install commander
* node argparse-to-commander.ts deploy production --force
*/
import { Command, Option } from 'commander';
const program = new Command();
// ===== Basic Configuration (like ArgumentParser) =====
program
.name('mycli')
.description('A powerful CLI tool')
.version('1.0.0');
// ===== Subcommands (like add_subparsers) =====
// Init command (like subparsers.add_parser('init'))
program
.command('init')
.description('Initialize a new project')
.option('-t, --template <type>', 'project template', 'basic')
.option('-p, --path <path>', 'project path', '.')
.action((options) => {
console.log(`Initializing project with ${options.template} template...`);
console.log(`Path: ${options.path}`);
});
// Deploy command with choices (like choices=[...])
program
.command('deploy <environment>')
.description('Deploy to specified environment')
.addOption(
new Option('-m, --mode <mode>', 'deployment mode')
.choices(['fast', 'safe', 'rollback'])
.default('safe')
)
.option('-f, --force', 'force deployment', false)
.action((environment, options) => {
console.log(`Deploying to ${environment} in ${options.mode} mode`);
if (options.force) {
console.log('Warning: Force mode enabled');
}
});
// ===== Nested Subcommands (like nested add_subparsers) =====
const config = program
.command('config')
.description('Manage configuration');
config
.command('get <key>')
.description('Get configuration value')
.action((key) => {
console.log(`Getting config: ${key}`);
});
config
.command('set <key> <value>')
.description('Set configuration value')
.option('-f, --force', 'overwrite existing value')
.action((key, value, options) => {
console.log(`Setting ${key} = ${value}`);
if (options.force) {
console.log('(Overwriting existing value)');
}
});
config
.command('list')
.description('List all configuration values')
.option('--format <format>', 'output format', 'text')
.action((options) => {
console.log(`Listing configuration (format: ${options.format})`);
});
// ===== Boolean Flags (like action='store_true') =====
program
.command('build')
.description('Build the project')
.option('--verbose', 'enable verbose output')
.option('--debug', 'enable debug mode')
.option('--no-cache', 'disable cache (enabled by default)')
.action((options) => {
console.log('Building project...');
console.log(`Verbose: ${options.verbose || false}`);
console.log(`Debug: ${options.debug || false}`);
console.log(`Cache: ${options.cache}`);
});
// ===== Type Coercion (like type=int, type=float) =====
program
.command('server')
.description('Start server')
.option('-p, --port <number>', 'server port', parseInt, 8080)
.option('-t, --timeout <seconds>', 'timeout in seconds', parseFloat, 30.0)
.option('-w, --workers <number>', 'number of workers', parseInt, 4)
.action((options) => {
console.log(`Starting server on port ${options.port}`);
console.log(`Timeout: ${options.timeout}s`);
console.log(`Workers: ${options.workers}`);
});
// ===== Variadic Arguments (like nargs='+') =====
program
.command('process <files...>')
.description('Process multiple files')
.option('--format <format>', 'output format', 'json')
.action((files, options) => {
console.log(`Processing ${files.length} file(s):`);
files.forEach((file) => console.log(` - ${file}`));
console.log(`Output format: ${options.format}`);
});
// ===== Mutually Exclusive Options =====
// Note: Commander doesn't have built-in mutually exclusive groups
// You need to validate manually
program
.command('export')
.description('Export data')
.option('--json <file>', 'export as JSON')
.option('--yaml <file>', 'export as YAML')
.option('--xml <file>', 'export as XML')
.action((options) => {
const formats = [options.json, options.yaml, options.xml].filter(Boolean);
if (formats.length > 1) {
console.error('Error: --json, --yaml, and --xml are mutually exclusive');
process.exit(1);
}
if (options.json) {
console.log(`Exporting as JSON to ${options.json}`);
} else if (options.yaml) {
console.log(`Exporting as YAML to ${options.yaml}`);
} else if (options.xml) {
console.log(`Exporting as XML to ${options.xml}`);
}
});
// ===== Required Options (like required=True) =====
program
.command('login')
.description('Login to service')
.requiredOption('--username <username>', 'username for authentication')
.requiredOption('--password <password>', 'password for authentication')
.option('--token <token>', 'authentication token (alternative to password)')
.action((options) => {
console.log(`Logging in as ${options.username}`);
});
// ===== Custom Validation =====
function validatePort(value: string): number {
const port = parseInt(value, 10);
if (isNaN(port) || port < 1 || port > 65535) {
throw new Error(`Invalid port: ${value} (must be 1-65535)`);
}
return port;
}
program
.command('connect')
.description('Connect to server')
.option('-p, --port <number>', 'server port', validatePort, 8080)
.action((options) => {
console.log(`Connecting to port ${options.port}`);
});
// ===== Argument Groups (display organization) =====
// Note: Commander doesn't have argument groups for help display
// You can organize with comments or separate commands
// ===== Parse Arguments =====
program.parse();
/**
* COMPARISON SUMMARY:
*
* argparse Pattern | commander.js Equivalent
* ---------------------------------|--------------------------------
* ArgumentParser() | new Command()
* add_argument() | .option() or .argument()
* add_subparsers() | .command()
* choices=[...] | .choices([...])
* action='store_true' | .option('--flag')
* action='store_false' | .option('--no-flag')
* type=int | parseInt
* type=float | parseFloat
* nargs='+' | <arg...>
* nargs='*' | [arg...]
* required=True | .requiredOption()
* default=value | option(..., default)
* help='...' | .description('...')
* mutually_exclusive_group() | Manual validation
* add_argument_group() | Organize with subcommands
*/

View File

@@ -0,0 +1,243 @@
#!/usr/bin/env python3
"""
Argument groups for better organization and help output.
Usage:
python argument-groups.py --host 192.168.1.1 --port 8080 --ssl
python argument-groups.py --db-host localhost --db-port 5432 --db-name mydb
python argument-groups.py --log-level debug --log-file app.log
"""
import argparse
import sys
def main():
parser = argparse.ArgumentParser(
description='Organized arguments with groups',
formatter_class=argparse.RawDescriptionHelpFormatter
)
# ===== Server Configuration Group =====
server_group = parser.add_argument_group(
'server configuration',
'Options for configuring the web server'
)
server_group.add_argument(
'--host',
default='127.0.0.1',
help='Server host address (default: %(default)s)'
)
server_group.add_argument(
'--port', '-p',
type=int,
default=8080,
help='Server port (default: %(default)s)'
)
server_group.add_argument(
'--workers',
type=int,
default=4,
help='Number of worker processes (default: %(default)s)'
)
server_group.add_argument(
'--ssl',
action='store_true',
help='Enable SSL/TLS'
)
server_group.add_argument(
'--cert',
help='Path to SSL certificate (required if --ssl is set)'
)
server_group.add_argument(
'--key',
help='Path to SSL private key (required if --ssl is set)'
)
# ===== Database Configuration Group =====
db_group = parser.add_argument_group(
'database configuration',
'Options for database connection'
)
db_group.add_argument(
'--db-host',
default='localhost',
help='Database host (default: %(default)s)'
)
db_group.add_argument(
'--db-port',
type=int,
default=5432,
help='Database port (default: %(default)s)'
)
db_group.add_argument(
'--db-name',
required=True,
help='Database name (required)'
)
db_group.add_argument(
'--db-user',
help='Database username'
)
db_group.add_argument(
'--db-password',
help='Database password'
)
db_group.add_argument(
'--db-pool-size',
type=int,
default=10,
help='Database connection pool size (default: %(default)s)'
)
# ===== Logging Configuration Group =====
log_group = parser.add_argument_group(
'logging configuration',
'Options for logging and monitoring'
)
log_group.add_argument(
'--log-level',
choices=['debug', 'info', 'warning', 'error', 'critical'],
default='info',
help='Logging level (default: %(default)s)'
)
log_group.add_argument(
'--log-file',
help='Log to file instead of stdout'
)
log_group.add_argument(
'--log-format',
choices=['text', 'json'],
default='text',
help='Log format (default: %(default)s)'
)
log_group.add_argument(
'--access-log',
action='store_true',
help='Enable access logging'
)
# ===== Cache Configuration Group =====
cache_group = parser.add_argument_group(
'cache configuration',
'Options for caching layer'
)
cache_group.add_argument(
'--cache-backend',
choices=['redis', 'memcached', 'memory'],
default='memory',
help='Cache backend (default: %(default)s)'
)
cache_group.add_argument(
'--cache-host',
default='localhost',
help='Cache server host (default: %(default)s)'
)
cache_group.add_argument(
'--cache-port',
type=int,
default=6379,
help='Cache server port (default: %(default)s)'
)
cache_group.add_argument(
'--cache-ttl',
type=int,
default=300,
help='Default cache TTL in seconds (default: %(default)s)'
)
# ===== Security Configuration Group =====
security_group = parser.add_argument_group(
'security configuration',
'Security and authentication options'
)
security_group.add_argument(
'--auth-required',
action='store_true',
help='Require authentication for all requests'
)
security_group.add_argument(
'--jwt-secret',
help='JWT secret key'
)
security_group.add_argument(
'--cors-origins',
nargs='+',
help='Allowed CORS origins'
)
security_group.add_argument(
'--rate-limit',
type=int,
default=100,
help='Rate limit (requests per minute, default: %(default)s)'
)
# Parse arguments
args = parser.parse_args()
# Validate SSL configuration
if args.ssl and (not args.cert or not args.key):
parser.error("--cert and --key are required when --ssl is enabled")
# Display configuration
print("Configuration Summary:")
print("\nServer:")
print(f" Host: {args.host}:{args.port}")
print(f" Workers: {args.workers}")
print(f" SSL: {'Enabled' if args.ssl else 'Disabled'}")
if args.ssl:
print(f" Certificate: {args.cert}")
print(f" Key: {args.key}")
print("\nDatabase:")
print(f" Host: {args.db_host}:{args.db_port}")
print(f" Database: {args.db_name}")
print(f" User: {args.db_user or '(not set)'}")
print(f" Pool Size: {args.db_pool_size}")
print("\nLogging:")
print(f" Level: {args.log_level}")
print(f" File: {args.log_file or 'stdout'}")
print(f" Format: {args.log_format}")
print(f" Access Log: {'Enabled' if args.access_log else 'Disabled'}")
print("\nCache:")
print(f" Backend: {args.cache_backend}")
print(f" Host: {args.cache_host}:{args.cache_port}")
print(f" TTL: {args.cache_ttl}s")
print("\nSecurity:")
print(f" Auth Required: {'Yes' if args.auth_required else 'No'}")
print(f" CORS Origins: {args.cors_origins or '(not set)'}")
print(f" Rate Limit: {args.rate_limit} req/min")
return 0
if __name__ == '__main__':
sys.exit(main())

View File

@@ -0,0 +1,93 @@
#!/usr/bin/env python3
"""
Basic argparse parser with common argument types.
Usage:
python basic-parser.py --help
python basic-parser.py deploy app1 --env production --force
python basic-parser.py deploy app2 --env staging --timeout 60
"""
import argparse
import sys
def main():
parser = argparse.ArgumentParser(
description='Deploy application to specified environment',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog='''
Examples:
%(prog)s deploy my-app --env production
%(prog)s deploy my-app --env staging --force
%(prog)s deploy my-app --env dev --timeout 120
'''
)
# Version info
parser.add_argument(
'--version',
action='version',
version='%(prog)s 1.0.0'
)
# Required positional argument
parser.add_argument(
'action',
help='Action to perform'
)
parser.add_argument(
'app_name',
help='Name of the application to deploy'
)
# Optional arguments with different types
parser.add_argument(
'--env', '-e',
default='development',
help='Deployment environment (default: %(default)s)'
)
parser.add_argument(
'--timeout', '-t',
type=int,
default=30,
help='Timeout in seconds (default: %(default)s)'
)
# Boolean flag
parser.add_argument(
'--force', '-f',
action='store_true',
help='Force deployment without confirmation'
)
# Verbose flag (count occurrences)
parser.add_argument(
'--verbose', '-v',
action='count',
default=0,
help='Increase verbosity (-v, -vv, -vvv)'
)
# Parse arguments
args = parser.parse_args()
# Use parsed arguments
print(f"Action: {args.action}")
print(f"App Name: {args.app_name}")
print(f"Environment: {args.env}")
print(f"Timeout: {args.timeout}s")
print(f"Force: {args.force}")
print(f"Verbosity Level: {args.verbose}")
# Example validation
if args.timeout < 1:
parser.error("Timeout must be at least 1 second")
return 0
if __name__ == '__main__':
sys.exit(main())

View File

@@ -0,0 +1,162 @@
#!/usr/bin/env python3
"""
Boolean flag patterns with store_true, store_false, and count actions.
Usage:
python boolean-flags.py --verbose
python boolean-flags.py -vvv --debug --force
python boolean-flags.py --no-cache --quiet
"""
import argparse
import sys
def main():
parser = argparse.ArgumentParser(
description='Boolean flag patterns',
formatter_class=argparse.RawDescriptionHelpFormatter
)
# ===== store_true (False by default) =====
parser.add_argument(
'--verbose',
action='store_true',
help='Enable verbose output'
)
parser.add_argument(
'--debug',
action='store_true',
help='Enable debug mode'
)
parser.add_argument(
'--force', '-f',
action='store_true',
help='Force operation without confirmation'
)
parser.add_argument(
'--dry-run',
action='store_true',
help='Perform a dry run without making changes'
)
# ===== store_false (True by default) =====
parser.add_argument(
'--no-cache',
action='store_false',
dest='cache',
help='Disable caching (enabled by default)'
)
parser.add_argument(
'--no-color',
action='store_false',
dest='color',
help='Disable colored output (enabled by default)'
)
# ===== count action (count occurrences) =====
parser.add_argument(
'-v',
action='count',
default=0,
dest='verbosity',
help='Increase verbosity (-v, -vv, -vvv)'
)
parser.add_argument(
'-q', '--quiet',
action='count',
default=0,
help='Decrease verbosity (-q, -qq, -qqq)'
)
# ===== store_const action =====
parser.add_argument(
'--fast',
action='store_const',
const='fast',
dest='mode',
help='Use fast mode'
)
parser.add_argument(
'--safe',
action='store_const',
const='safe',
dest='mode',
help='Use safe mode (default)'
)
parser.set_defaults(mode='safe')
# ===== Combined short flags =====
parser.add_argument(
'-a', '--all',
action='store_true',
help='Process all items'
)
parser.add_argument(
'-r', '--recursive',
action='store_true',
help='Process recursively'
)
parser.add_argument(
'-i', '--interactive',
action='store_true',
help='Run in interactive mode'
)
# Parse arguments
args = parser.parse_args()
# Calculate effective verbosity
effective_verbosity = args.verbosity - args.quiet
# Display configuration
print("Boolean Flags Configuration:")
print(f" Verbose: {args.verbose}")
print(f" Debug: {args.debug}")
print(f" Force: {args.force}")
print(f" Dry Run: {args.dry_run}")
print(f" Cache: {args.cache}")
print(f" Color: {args.color}")
print(f" Verbosity Level: {effective_verbosity}")
print(f" Mode: {args.mode}")
print(f" All: {args.all}")
print(f" Recursive: {args.recursive}")
print(f" Interactive: {args.interactive}")
# Example usage based on flags
if args.debug:
print("\nDebug mode enabled - showing detailed information")
if args.dry_run:
print("\nDry run mode - no changes will be made")
if effective_verbosity > 0:
print(f"\nVerbosity level: {effective_verbosity}")
if effective_verbosity >= 3:
print("Maximum verbosity - showing everything")
elif effective_verbosity < 0:
print(f"\nQuiet level: {abs(effective_verbosity)}")
if args.force:
print("\nForce mode - skipping confirmations")
if not args.cache:
print("\nCache disabled")
if not args.color:
print("\nColor output disabled")
return 0
if __name__ == '__main__':
sys.exit(main())

View File

@@ -0,0 +1,197 @@
#!/usr/bin/env python3
"""
Argument choices and custom validation patterns.
Usage:
python choices-validation.py --log-level debug
python choices-validation.py --port 8080 --host 192.168.1.1
python choices-validation.py --region us-east-1 --instance-type t2.micro
"""
import argparse
import re
import sys
from pathlib import Path
def validate_port(value):
"""Custom validator for port numbers."""
try:
ivalue = int(value)
except ValueError:
raise argparse.ArgumentTypeError(f"{value} is not a valid integer")
if ivalue < 1 or ivalue > 65535:
raise argparse.ArgumentTypeError(
f"{value} is not a valid port (must be 1-65535)"
)
return ivalue
def validate_ip(value):
"""Custom validator for IP addresses."""
pattern = r'^(\d{1,3}\.){3}\d{1,3}$'
if not re.match(pattern, value):
raise argparse.ArgumentTypeError(f"{value} is not a valid IP address")
# Check each octet is 0-255
octets = [int(x) for x in value.split('.')]
if any(o < 0 or o > 255 for o in octets):
raise argparse.ArgumentTypeError(
f"{value} contains invalid octets (must be 0-255)"
)
return value
def validate_email(value):
"""Custom validator for email addresses."""
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
if not re.match(pattern, value):
raise argparse.ArgumentTypeError(f"{value} is not a valid email address")
return value
def validate_path_exists(value):
"""Custom validator to check if path exists."""
path = Path(value)
if not path.exists():
raise argparse.ArgumentTypeError(f"Path does not exist: {value}")
return path
def validate_range(min_val, max_val):
"""Factory function for range validators."""
def validator(value):
try:
ivalue = int(value)
except ValueError:
raise argparse.ArgumentTypeError(f"{value} is not a valid integer")
if ivalue < min_val or ivalue > max_val:
raise argparse.ArgumentTypeError(
f"{value} must be between {min_val} and {max_val}"
)
return ivalue
return validator
def main():
parser = argparse.ArgumentParser(
description='Demonstrate choices and validation patterns',
formatter_class=argparse.RawDescriptionHelpFormatter
)
# ===== String Choices =====
parser.add_argument(
'--log-level',
choices=['debug', 'info', 'warning', 'error', 'critical'],
default='info',
help='Logging level (default: %(default)s)'
)
parser.add_argument(
'--region',
choices=[
'us-east-1', 'us-west-1', 'us-west-2',
'eu-west-1', 'eu-central-1',
'ap-southeast-1', 'ap-northeast-1'
],
default='us-east-1',
help='AWS region (default: %(default)s)'
)
parser.add_argument(
'--format',
choices=['json', 'yaml', 'toml', 'xml'],
default='json',
help='Output format (default: %(default)s)'
)
# ===== Custom Validators =====
parser.add_argument(
'--port',
type=validate_port,
default=8080,
help='Server port (1-65535, default: %(default)s)'
)
parser.add_argument(
'--host',
type=validate_ip,
default='127.0.0.1',
help='Server host IP (default: %(default)s)'
)
parser.add_argument(
'--email',
type=validate_email,
help='Email address for notifications'
)
parser.add_argument(
'--config',
type=validate_path_exists,
help='Path to configuration file (must exist)'
)
# ===== Range Validators =====
parser.add_argument(
'--workers',
type=validate_range(1, 32),
default=4,
help='Number of worker processes (1-32, default: %(default)s)'
)
parser.add_argument(
'--timeout',
type=validate_range(1, 3600),
default=30,
help='Request timeout in seconds (1-3600, default: %(default)s)'
)
# ===== Integer Choices =====
parser.add_argument(
'--instance-type',
choices=['t2.micro', 't2.small', 't2.medium', 't3.large'],
default='t2.micro',
help='EC2 instance type (default: %(default)s)'
)
# ===== Type Coercion =====
parser.add_argument(
'--memory',
type=float,
default=1.0,
help='Memory limit in GB (default: %(default)s)'
)
parser.add_argument(
'--retry-count',
type=int,
default=3,
help='Number of retries (default: %(default)s)'
)
# Parse arguments
args = parser.parse_args()
# Display parsed values
print("Configuration:")
print(f" Log Level: {args.log_level}")
print(f" Region: {args.region}")
print(f" Format: {args.format}")
print(f" Port: {args.port}")
print(f" Host: {args.host}")
print(f" Email: {args.email}")
print(f" Config: {args.config}")
print(f" Workers: {args.workers}")
print(f" Timeout: {args.timeout}s")
print(f" Instance Type: {args.instance_type}")
print(f" Memory: {args.memory}GB")
print(f" Retry Count: {args.retry_count}")
return 0
if __name__ == '__main__':
sys.exit(main())

View File

@@ -0,0 +1,188 @@
#!/usr/bin/env python3
"""
Custom action classes for advanced argument processing.
Usage:
python custom-actions.py --env-file .env
python custom-actions.py --key API_KEY --key DB_URL
python custom-actions.py --range 1-10 --range 20-30
"""
import argparse
import sys
from pathlib import Path
class LoadEnvFileAction(argparse.Action):
"""Custom action to load environment variables from file."""
def __call__(self, parser, namespace, values, option_string=None):
env_file = Path(values)
if not env_file.exists():
parser.error(f"Environment file does not exist: {values}")
env_vars = {}
with open(env_file, 'r') as f:
for line in f:
line = line.strip()
if line and not line.startswith('#'):
if '=' in line:
key, value = line.split('=', 1)
env_vars[key.strip()] = value.strip()
setattr(namespace, self.dest, env_vars)
class KeyValueAction(argparse.Action):
"""Custom action to parse key=value pairs."""
def __call__(self, parser, namespace, values, option_string=None):
if '=' not in values:
parser.error(f"Argument must be in key=value format: {values}")
key, value = values.split('=', 1)
items = getattr(namespace, self.dest, None) or {}
items[key] = value
setattr(namespace, self.dest, items)
class RangeAction(argparse.Action):
"""Custom action to parse ranges like 1-10."""
def __call__(self, parser, namespace, values, option_string=None):
if '-' not in values:
parser.error(f"Range must be in format start-end: {values}")
try:
start, end = values.split('-')
start = int(start)
end = int(end)
except ValueError:
parser.error(f"Invalid range format: {values}")
if start > end:
parser.error(f"Start must be less than or equal to end: {values}")
ranges = getattr(namespace, self.dest, None) or []
ranges.append((start, end))
setattr(namespace, self.dest, ranges)
class AppendUniqueAction(argparse.Action):
"""Custom action to append unique values only."""
def __call__(self, parser, namespace, values, option_string=None):
items = getattr(namespace, self.dest, None) or []
if values not in items:
items.append(values)
setattr(namespace, self.dest, items)
class ValidateAndStoreAction(argparse.Action):
"""Custom action that validates before storing."""
def __call__(self, parser, namespace, values, option_string=None):
# Custom validation logic
if values.startswith('test-'):
print(f"Warning: Using test value: {values}")
# Transform value
transformed = values.upper()
setattr(namespace, self.dest, transformed)
class IncrementAction(argparse.Action):
"""Custom action to increment a value."""
def __init__(self, option_strings, dest, default=0, **kwargs):
super().__init__(option_strings, dest, nargs=0, default=default, **kwargs)
def __call__(self, parser, namespace, values, option_string=None):
current = getattr(namespace, self.dest, self.default)
setattr(namespace, self.dest, current + 1)
def main():
parser = argparse.ArgumentParser(
description='Custom action demonstrations',
formatter_class=argparse.RawDescriptionHelpFormatter
)
# Load environment file
parser.add_argument(
'--env-file',
action=LoadEnvFileAction,
help='Load environment variables from file'
)
# Key-value pairs
parser.add_argument(
'--config', '-c',
action=KeyValueAction,
help='Configuration in key=value format (can be used multiple times)'
)
# Range parsing
parser.add_argument(
'--range', '-r',
action=RangeAction,
help='Range in start-end format (e.g., 1-10)'
)
# Unique append
parser.add_argument(
'--tag',
action=AppendUniqueAction,
help='Add unique tag (duplicates ignored)'
)
# Validate and transform
parser.add_argument(
'--key',
action=ValidateAndStoreAction,
help='Key to transform to uppercase'
)
# Custom increment
parser.add_argument(
'--increment',
action=IncrementAction,
help='Increment counter'
)
# Parse arguments
args = parser.parse_args()
# Display results
print("Custom Actions Results:")
if args.env_file:
print(f"\nEnvironment Variables:")
for key, value in args.env_file.items():
print(f" {key}={value}")
if args.config:
print(f"\nConfiguration:")
for key, value in args.config.items():
print(f" {key}={value}")
if args.range:
print(f"\nRanges:")
for start, end in args.range:
print(f" {start}-{end} (includes {end - start + 1} values)")
if args.tag:
print(f"\nUnique Tags: {', '.join(args.tag)}")
if args.key:
print(f"\nTransformed Key: {args.key}")
if args.increment:
print(f"\nIncrement Count: {args.increment}")
return 0
if __name__ == '__main__':
sys.exit(main())

View File

@@ -0,0 +1,175 @@
#!/usr/bin/env python3
"""
Mutually exclusive argument groups.
Usage:
python mutually-exclusive.py --json output.json
python mutually-exclusive.py --yaml output.yaml
python mutually-exclusive.py --verbose
python mutually-exclusive.py --quiet
python mutually-exclusive.py --create resource
python mutually-exclusive.py --delete resource
"""
import argparse
import sys
def main():
parser = argparse.ArgumentParser(
description='Mutually exclusive argument groups',
formatter_class=argparse.RawDescriptionHelpFormatter
)
# ===== Output Format (mutually exclusive) =====
output_group = parser.add_mutually_exclusive_group()
output_group.add_argument(
'--json',
metavar='FILE',
help='Output in JSON format'
)
output_group.add_argument(
'--yaml',
metavar='FILE',
help='Output in YAML format'
)
output_group.add_argument(
'--xml',
metavar='FILE',
help='Output in XML format'
)
# ===== Verbosity (mutually exclusive) =====
verbosity_group = parser.add_mutually_exclusive_group()
verbosity_group.add_argument(
'--verbose', '-v',
action='store_true',
help='Increase verbosity'
)
verbosity_group.add_argument(
'--quiet', '-q',
action='store_true',
help='Suppress output'
)
# ===== Operation Mode (mutually exclusive, required) =====
operation_group = parser.add_mutually_exclusive_group(required=True)
operation_group.add_argument(
'--create',
metavar='RESOURCE',
help='Create a resource'
)
operation_group.add_argument(
'--update',
metavar='RESOURCE',
help='Update a resource'
)
operation_group.add_argument(
'--delete',
metavar='RESOURCE',
help='Delete a resource'
)
operation_group.add_argument(
'--list',
action='store_true',
help='List all resources'
)
# ===== Authentication Method (mutually exclusive) =====
auth_group = parser.add_mutually_exclusive_group()
auth_group.add_argument(
'--token',
metavar='TOKEN',
help='Authenticate with token'
)
auth_group.add_argument(
'--api-key',
metavar='KEY',
help='Authenticate with API key'
)
auth_group.add_argument(
'--credentials',
metavar='FILE',
help='Authenticate with credentials file'
)
# ===== Deployment Strategy (mutually exclusive with default) =====
strategy_group = parser.add_mutually_exclusive_group()
strategy_group.add_argument(
'--rolling',
action='store_true',
help='Use rolling deployment'
)
strategy_group.add_argument(
'--blue-green',
action='store_true',
help='Use blue-green deployment'
)
strategy_group.add_argument(
'--canary',
action='store_true',
help='Use canary deployment'
)
# Set default strategy if none specified
parser.set_defaults(rolling=False, blue_green=False, canary=False)
# Parse arguments
args = parser.parse_args()
# Display configuration
print("Mutually Exclusive Groups Configuration:")
# Output format
if args.json:
print(f" Output Format: JSON to {args.json}")
elif args.yaml:
print(f" Output Format: YAML to {args.yaml}")
elif args.xml:
print(f" Output Format: XML to {args.xml}")
else:
print(" Output Format: None (default stdout)")
# Verbosity
if args.verbose:
print(" Verbosity: Verbose")
elif args.quiet:
print(" Verbosity: Quiet")
else:
print(" Verbosity: Normal")
# Operation
if args.create:
print(f" Operation: Create {args.create}")
elif args.update:
print(f" Operation: Update {args.update}")
elif args.delete:
print(f" Operation: Delete {args.delete}")
elif args.list:
print(" Operation: List resources")
# Authentication
if args.token:
print(f" Auth Method: Token")
elif args.api_key:
print(f" Auth Method: API Key")
elif args.credentials:
print(f" Auth Method: Credentials file ({args.credentials})")
else:
print(" Auth Method: None")
# Deployment strategy
if args.rolling:
print(" Deployment: Rolling")
elif args.blue_green:
print(" Deployment: Blue-Green")
elif args.canary:
print(" Deployment: Canary")
else:
print(" Deployment: Default")
return 0
if __name__ == '__main__':
sys.exit(main())

View File

@@ -0,0 +1,219 @@
#!/usr/bin/env python3
"""
Nested subcommands pattern (like git config get/set, kubectl config view).
Usage:
python nested-subparser.py config get database_url
python nested-subparser.py config set api_key abc123
python nested-subparser.py config list
python nested-subparser.py deploy start production --replicas 3
python nested-subparser.py deploy stop production
"""
import argparse
import sys
# Config command handlers
def config_get(args):
"""Get configuration value."""
print(f"Getting config: {args.key}")
# Simulate getting config
print(f"{args.key} = example_value")
def config_set(args):
"""Set configuration value."""
print(f"Setting config: {args.key} = {args.value}")
if args.force:
print("(Overwriting existing value)")
def config_list(args):
"""List all configuration values."""
print(f"Listing all configuration (format: {args.format})")
def config_delete(args):
"""Delete configuration value."""
if not args.force:
response = input(f"Delete {args.key}? (y/n): ")
if response.lower() != 'y':
print("Cancelled")
return 1
print(f"Deleted: {args.key}")
# Deploy command handlers
def deploy_start(args):
"""Start deployment."""
print(f"Starting deployment to {args.environment}")
print(f"Replicas: {args.replicas}")
print(f"Wait: {args.wait}")
def deploy_stop(args):
"""Stop deployment."""
print(f"Stopping deployment in {args.environment}")
def deploy_restart(args):
"""Restart deployment."""
print(f"Restarting deployment in {args.environment}")
if args.hard:
print("(Hard restart)")
def main():
# Main parser
parser = argparse.ArgumentParser(
description='Multi-level CLI tool with nested subcommands',
formatter_class=argparse.RawDescriptionHelpFormatter
)
parser.add_argument('--version', action='version', version='1.0.0')
# Top-level subparsers
subparsers = parser.add_subparsers(
dest='command',
help='Top-level commands',
required=True
)
# ===== Config command group =====
config_parser = subparsers.add_parser(
'config',
help='Manage configuration',
description='Configuration management commands'
)
# Config subcommands
config_subparsers = config_parser.add_subparsers(
dest='config_command',
help='Config operations',
required=True
)
# config get
config_get_parser = config_subparsers.add_parser(
'get',
help='Get configuration value'
)
config_get_parser.add_argument('key', help='Configuration key')
config_get_parser.set_defaults(func=config_get)
# config set
config_set_parser = config_subparsers.add_parser(
'set',
help='Set configuration value'
)
config_set_parser.add_argument('key', help='Configuration key')
config_set_parser.add_argument('value', help='Configuration value')
config_set_parser.add_argument(
'--force', '-f',
action='store_true',
help='Overwrite existing value'
)
config_set_parser.set_defaults(func=config_set)
# config list
config_list_parser = config_subparsers.add_parser(
'list',
help='List all configuration values'
)
config_list_parser.add_argument(
'--format',
choices=['text', 'json', 'yaml'],
default='text',
help='Output format (default: %(default)s)'
)
config_list_parser.set_defaults(func=config_list)
# config delete
config_delete_parser = config_subparsers.add_parser(
'delete',
help='Delete configuration value'
)
config_delete_parser.add_argument('key', help='Configuration key')
config_delete_parser.add_argument(
'--force', '-f',
action='store_true',
help='Delete without confirmation'
)
config_delete_parser.set_defaults(func=config_delete)
# ===== Deploy command group =====
deploy_parser = subparsers.add_parser(
'deploy',
help='Manage deployments',
description='Deployment management commands'
)
# Deploy subcommands
deploy_subparsers = deploy_parser.add_subparsers(
dest='deploy_command',
help='Deploy operations',
required=True
)
# deploy start
deploy_start_parser = deploy_subparsers.add_parser(
'start',
help='Start deployment'
)
deploy_start_parser.add_argument(
'environment',
choices=['development', 'staging', 'production'],
help='Target environment'
)
deploy_start_parser.add_argument(
'--replicas', '-r',
type=int,
default=1,
help='Number of replicas (default: %(default)s)'
)
deploy_start_parser.add_argument(
'--wait',
action='store_true',
help='Wait for deployment to complete'
)
deploy_start_parser.set_defaults(func=deploy_start)
# deploy stop
deploy_stop_parser = deploy_subparsers.add_parser(
'stop',
help='Stop deployment'
)
deploy_stop_parser.add_argument(
'environment',
choices=['development', 'staging', 'production'],
help='Target environment'
)
deploy_stop_parser.set_defaults(func=deploy_stop)
# deploy restart
deploy_restart_parser = deploy_subparsers.add_parser(
'restart',
help='Restart deployment'
)
deploy_restart_parser.add_argument(
'environment',
choices=['development', 'staging', 'production'],
help='Target environment'
)
deploy_restart_parser.add_argument(
'--hard',
action='store_true',
help='Perform hard restart'
)
deploy_restart_parser.set_defaults(func=deploy_restart)
# Parse arguments
args = parser.parse_args()
# Call the appropriate command function
return args.func(args)
if __name__ == '__main__':
sys.exit(main() or 0)

View File

@@ -0,0 +1,123 @@
#!/usr/bin/env python3
"""
Single-level subcommands pattern (like docker, kubectl).
Usage:
python subparser-pattern.py init --template react
python subparser-pattern.py deploy production --force
python subparser-pattern.py status --format json
"""
import argparse
import sys
def cmd_init(args):
"""Initialize a new project."""
print(f"Initializing project with {args.template} template...")
print(f"Path: {args.path}")
def cmd_deploy(args):
"""Deploy application."""
print(f"Deploying to {args.environment} in {args.mode} mode")
if args.force:
print("Warning: Force mode enabled")
def cmd_status(args):
"""Show deployment status."""
print(f"Status format: {args.format}")
print("Fetching status...")
def main():
# Main parser
parser = argparse.ArgumentParser(
description='Multi-command CLI tool',
formatter_class=argparse.RawDescriptionHelpFormatter
)
parser.add_argument(
'--version',
action='version',
version='1.0.0'
)
# Create subparsers
subparsers = parser.add_subparsers(
dest='command',
help='Available commands',
required=True # Python 3.7+
)
# Init command
init_parser = subparsers.add_parser(
'init',
help='Initialize a new project',
description='Initialize a new project with specified template'
)
init_parser.add_argument(
'--template', '-t',
default='basic',
help='Project template (default: %(default)s)'
)
init_parser.add_argument(
'--path', '-p',
default='.',
help='Project path (default: %(default)s)'
)
init_parser.set_defaults(func=cmd_init)
# Deploy command
deploy_parser = subparsers.add_parser(
'deploy',
help='Deploy application to environment',
description='Deploy application to specified environment'
)
deploy_parser.add_argument(
'environment',
choices=['development', 'staging', 'production'],
help='Target environment'
)
deploy_parser.add_argument(
'--force', '-f',
action='store_true',
help='Force deployment without confirmation'
)
deploy_parser.add_argument(
'--mode', '-m',
choices=['fast', 'safe', 'rollback'],
default='safe',
help='Deployment mode (default: %(default)s)'
)
deploy_parser.set_defaults(func=cmd_deploy)
# Status command
status_parser = subparsers.add_parser(
'status',
help='Show deployment status',
description='Display current deployment status'
)
status_parser.add_argument(
'--format',
choices=['text', 'json', 'yaml'],
default='text',
help='Output format (default: %(default)s)'
)
status_parser.add_argument(
'--service',
action='append',
help='Filter by service (can be used multiple times)'
)
status_parser.set_defaults(func=cmd_status)
# Parse arguments
args = parser.parse_args()
# Call the appropriate command function
return args.func(args)
if __name__ == '__main__':
sys.exit(main() or 0)

View File

@@ -0,0 +1,257 @@
#!/usr/bin/env python3
"""
Type coercion and custom type converters.
Usage:
python type-coercion.py --port 8080 --timeout 30.5 --date 2024-01-15
python type-coercion.py --url https://api.example.com --size 1.5GB
"""
import argparse
import sys
from datetime import datetime
from pathlib import Path
import re
def parse_date(value):
"""Parse date in YYYY-MM-DD format."""
try:
return datetime.strptime(value, '%Y-%m-%d').date()
except ValueError:
raise argparse.ArgumentTypeError(
f"Invalid date format: {value} (expected YYYY-MM-DD)"
)
def parse_datetime(value):
"""Parse datetime in ISO format."""
try:
return datetime.fromisoformat(value)
except ValueError:
raise argparse.ArgumentTypeError(
f"Invalid datetime format: {value} (expected ISO format)"
)
def parse_url(value):
"""Parse and validate URL."""
pattern = r'^https?://[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(/.*)?$'
if not re.match(pattern, value):
raise argparse.ArgumentTypeError(f"Invalid URL: {value}")
return value
def parse_size(value):
"""Parse size with units (e.g., 1.5GB, 500MB)."""
pattern = r'^(\d+\.?\d*)(B|KB|MB|GB|TB)$'
match = re.match(pattern, value, re.IGNORECASE)
if not match:
raise argparse.ArgumentTypeError(
f"Invalid size format: {value} (expected number with unit)"
)
size, unit = match.groups()
size = float(size)
units = {'B': 1, 'KB': 1024, 'MB': 1024**2, 'GB': 1024**3, 'TB': 1024**4}
return int(size * units[unit.upper()])
def parse_duration(value):
"""Parse duration (e.g., 1h, 30m, 90s)."""
pattern = r'^(\d+)(s|m|h|d)$'
match = re.match(pattern, value, re.IGNORECASE)
if not match:
raise argparse.ArgumentTypeError(
f"Invalid duration format: {value} (expected number with s/m/h/d)"
)
amount, unit = match.groups()
amount = int(amount)
units = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}
return amount * units[unit.lower()]
def parse_percentage(value):
"""Parse percentage (0-100)."""
try:
pct = float(value.rstrip('%'))
except ValueError:
raise argparse.ArgumentTypeError(f"Invalid percentage: {value}")
if pct < 0 or pct > 100:
raise argparse.ArgumentTypeError(
f"Percentage must be between 0 and 100: {value}"
)
return pct
def parse_comma_separated(value):
"""Parse comma-separated list."""
return [item.strip() for item in value.split(',') if item.strip()]
def parse_key_value_pairs(value):
"""Parse semicolon-separated key=value pairs."""
pairs = {}
for pair in value.split(';'):
if '=' in pair:
key, val = pair.split('=', 1)
pairs[key.strip()] = val.strip()
return pairs
def main():
parser = argparse.ArgumentParser(
description='Type coercion demonstrations',
formatter_class=argparse.RawDescriptionHelpFormatter
)
# ===== Built-in Types =====
parser.add_argument(
'--port',
type=int,
default=8080,
help='Port number (integer)'
)
parser.add_argument(
'--timeout',
type=float,
default=30.0,
help='Timeout in seconds (float)'
)
parser.add_argument(
'--config',
type=Path,
help='Configuration file path'
)
parser.add_argument(
'--output',
type=argparse.FileType('w'),
help='Output file (opened for writing)'
)
parser.add_argument(
'--input',
type=argparse.FileType('r'),
help='Input file (opened for reading)'
)
# ===== Custom Types =====
parser.add_argument(
'--date',
type=parse_date,
help='Date in YYYY-MM-DD format'
)
parser.add_argument(
'--datetime',
type=parse_datetime,
help='Datetime in ISO format'
)
parser.add_argument(
'--url',
type=parse_url,
help='URL to connect to'
)
parser.add_argument(
'--size',
type=parse_size,
help='Size with unit (e.g., 1.5GB, 500MB)'
)
parser.add_argument(
'--duration',
type=parse_duration,
help='Duration (e.g., 1h, 30m, 90s)'
)
parser.add_argument(
'--percentage',
type=parse_percentage,
help='Percentage (0-100)'
)
parser.add_argument(
'--tags',
type=parse_comma_separated,
help='Comma-separated tags'
)
parser.add_argument(
'--env',
type=parse_key_value_pairs,
help='Environment variables as key=value;key2=value2'
)
# ===== List Types =====
parser.add_argument(
'--ids',
type=int,
nargs='+',
help='List of integer IDs'
)
parser.add_argument(
'--ratios',
type=float,
nargs='*',
help='List of float ratios'
)
# Parse arguments
args = parser.parse_args()
# Display parsed values
print("Type Coercion Results:")
print("\nBuilt-in Types:")
print(f" Port (int): {args.port} - type: {type(args.port).__name__}")
print(f" Timeout (float): {args.timeout} - type: {type(args.timeout).__name__}")
if args.config:
print(f" Config (Path): {args.config} - type: {type(args.config).__name__}")
print("\nCustom Types:")
if args.date:
print(f" Date: {args.date} - type: {type(args.date).__name__}")
if args.datetime:
print(f" Datetime: {args.datetime}")
if args.url:
print(f" URL: {args.url}")
if args.size:
print(f" Size: {args.size} bytes ({args.size / (1024**3):.2f} GB)")
if args.duration:
print(f" Duration: {args.duration} seconds ({args.duration / 3600:.2f} hours)")
if args.percentage is not None:
print(f" Percentage: {args.percentage}%")
if args.tags:
print(f" Tags: {args.tags}")
if args.env:
print(f" Environment:")
for key, value in args.env.items():
print(f" {key} = {value}")
print("\nList Types:")
if args.ids:
print(f" IDs: {args.ids}")
if args.ratios:
print(f" Ratios: {args.ratios}")
# Clean up file handles
if args.output:
args.output.close()
if args.input:
args.input.close()
return 0
if __name__ == '__main__':
sys.exit(main())

View File

@@ -0,0 +1,164 @@
#!/usr/bin/env python3
"""
Variadic argument patterns (nargs: ?, *, +, number).
Usage:
python variadic-args.py file1.txt file2.txt file3.txt
python variadic-args.py --output result.json file1.txt file2.txt
python variadic-args.py --include *.py --exclude test_*.py
"""
import argparse
import sys
from pathlib import Path
def main():
parser = argparse.ArgumentParser(
description='Variadic argument patterns',
formatter_class=argparse.RawDescriptionHelpFormatter
)
# ===== nargs='?' (optional, 0 or 1) =====
parser.add_argument(
'--output',
nargs='?',
const='default.json', # Used if flag present but no value
default=None, # Used if flag not present
help='Output file (default: stdout, or default.json if flag present)'
)
parser.add_argument(
'--config',
nargs='?',
const='config.yaml',
help='Configuration file (default: config.yaml if flag present)'
)
# ===== nargs='*' (zero or more) =====
parser.add_argument(
'--include',
nargs='*',
default=[],
help='Include patterns (zero or more)'
)
parser.add_argument(
'--exclude',
nargs='*',
default=[],
help='Exclude patterns (zero or more)'
)
parser.add_argument(
'--tags',
nargs='*',
metavar='TAG',
help='Tags to apply'
)
# ===== nargs='+' (one or more, required) =====
parser.add_argument(
'files',
nargs='+',
type=Path,
help='Input files (at least one required)'
)
parser.add_argument(
'--servers',
nargs='+',
metavar='SERVER',
help='Server addresses (at least one required if specified)'
)
# ===== nargs=N (exact number) =====
parser.add_argument(
'--coordinates',
nargs=2,
type=float,
metavar=('LAT', 'LON'),
help='Coordinates as latitude longitude'
)
parser.add_argument(
'--range',
nargs=2,
type=int,
metavar=('START', 'END'),
help='Range as start end'
)
parser.add_argument(
'--rgb',
nargs=3,
type=int,
metavar=('R', 'G', 'B'),
help='RGB color values (0-255)'
)
# ===== Remainder arguments =====
parser.add_argument(
'--command',
nargs=argparse.REMAINDER,
help='Command and arguments to pass through'
)
# Parse arguments
args = parser.parse_args()
# Display results
print("Variadic Arguments Results:")
print("\nnargs='?' (optional):")
print(f" Output: {args.output}")
print(f" Config: {args.config}")
print("\nnargs='*' (zero or more):")
print(f" Include Patterns: {args.include if args.include else '(none)'}")
print(f" Exclude Patterns: {args.exclude if args.exclude else '(none)'}")
print(f" Tags: {args.tags if args.tags else '(none)'}")
print("\nnargs='+' (one or more):")
print(f" Files ({len(args.files)}):")
for f in args.files:
print(f" - {f}")
if args.servers:
print(f" Servers ({len(args.servers)}):")
for s in args.servers:
print(f" - {s}")
print("\nnargs=N (exact number):")
if args.coordinates:
lat, lon = args.coordinates
print(f" Coordinates: {lat}, {lon}")
if args.range:
start, end = args.range
print(f" Range: {start} to {end}")
if args.rgb:
r, g, b = args.rgb
print(f" RGB Color: rgb({r}, {g}, {b})")
print("\nRemaining arguments:")
if args.command:
print(f" Command: {' '.join(args.command)}")
# Example usage
print("\nExample Processing:")
print(f"Processing {len(args.files)} file(s)...")
if args.include:
print(f"Including patterns: {', '.join(args.include)}")
if args.exclude:
print(f"Excluding patterns: {', '.join(args.exclude)}")
if args.output:
print(f"Output will be written to: {args.output}")
else:
print("Output will be written to: stdout")
return 0
if __name__ == '__main__':
sys.exit(main())