Initial commit

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

View File

@@ -0,0 +1,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