Initial commit
This commit is contained in:
201
skills/argparse-patterns/SKILL.md
Normal file
201
skills/argparse-patterns/SKILL.md
Normal 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
|
||||
473
skills/argparse-patterns/examples/advanced-parsing.md
Normal file
473
skills/argparse-patterns/examples/advanced-parsing.md
Normal 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
|
||||
230
skills/argparse-patterns/examples/basic-usage.md
Normal file
230
skills/argparse-patterns/examples/basic-usage.md
Normal 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`
|
||||
370
skills/argparse-patterns/examples/nested-commands.md
Normal file
370
skills/argparse-patterns/examples/nested-commands.md
Normal 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
|
||||
283
skills/argparse-patterns/examples/subcommands.md
Normal file
283
skills/argparse-patterns/examples/subcommands.md
Normal 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`
|
||||
424
skills/argparse-patterns/examples/validation-patterns.md
Normal file
424
skills/argparse-patterns/examples/validation-patterns.md
Normal 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`
|
||||
151
skills/argparse-patterns/scripts/convert-to-click.sh
Executable file
151
skills/argparse-patterns/scripts/convert-to-click.sh
Executable 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
|
||||
213
skills/argparse-patterns/scripts/generate-parser.sh
Executable file
213
skills/argparse-patterns/scripts/generate-parser.sh
Executable 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
|
||||
149
skills/argparse-patterns/scripts/test-parser.sh
Executable file
149
skills/argparse-patterns/scripts/test-parser.sh
Executable 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
|
||||
173
skills/argparse-patterns/scripts/validate-parser.sh
Executable file
173
skills/argparse-patterns/scripts/validate-parser.sh
Executable 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
|
||||
201
skills/argparse-patterns/templates/argparse-to-commander.ts
Normal file
201
skills/argparse-patterns/templates/argparse-to-commander.ts
Normal 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
|
||||
*/
|
||||
243
skills/argparse-patterns/templates/argument-groups.py
Executable file
243
skills/argparse-patterns/templates/argument-groups.py
Executable 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())
|
||||
93
skills/argparse-patterns/templates/basic-parser.py
Executable file
93
skills/argparse-patterns/templates/basic-parser.py
Executable 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())
|
||||
162
skills/argparse-patterns/templates/boolean-flags.py
Executable file
162
skills/argparse-patterns/templates/boolean-flags.py
Executable 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())
|
||||
197
skills/argparse-patterns/templates/choices-validation.py
Executable file
197
skills/argparse-patterns/templates/choices-validation.py
Executable 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())
|
||||
188
skills/argparse-patterns/templates/custom-actions.py
Executable file
188
skills/argparse-patterns/templates/custom-actions.py
Executable 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())
|
||||
175
skills/argparse-patterns/templates/mutually-exclusive.py
Executable file
175
skills/argparse-patterns/templates/mutually-exclusive.py
Executable 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())
|
||||
219
skills/argparse-patterns/templates/nested-subparser.py
Executable file
219
skills/argparse-patterns/templates/nested-subparser.py
Executable 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)
|
||||
123
skills/argparse-patterns/templates/subparser-pattern.py
Executable file
123
skills/argparse-patterns/templates/subparser-pattern.py
Executable 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)
|
||||
257
skills/argparse-patterns/templates/type-coercion.py
Executable file
257
skills/argparse-patterns/templates/type-coercion.py
Executable 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())
|
||||
164
skills/argparse-patterns/templates/variadic-args.py
Executable file
164
skills/argparse-patterns/templates/variadic-args.py
Executable 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())
|
||||
Reference in New Issue
Block a user