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,418 @@
# Advanced Fire CLI Patterns
This guide covers advanced patterns and techniques for building sophisticated Fire CLIs.
## Pattern 1: Configuration Management with Persistence
Implement persistent configuration storage:
```python
import fire
from rich.console import Console
from pathlib import Path
import json
console = Console()
class ConfigManager:
"""Handle configuration file I/O"""
def __init__(self, config_path: Path):
self.config_path = config_path
self._ensure_exists()
def _ensure_exists(self):
"""Create config file if missing"""
self.config_path.parent.mkdir(parents=True, exist_ok=True)
if not self.config_path.exists():
self.save({})
def load(self) -> dict:
"""Load configuration"""
try:
return json.loads(self.config_path.read_text())
except Exception as e:
console.print(f"[red]Error loading config: {e}[/red]")
return {}
def save(self, config: dict):
"""Save configuration"""
self.config_path.write_text(json.dumps(config, indent=2))
class MyCLI:
def __init__(self):
self.config_manager = ConfigManager(
Path.home() / ".mycli" / "config.json"
)
def configure(self, key, value):
"""Set configuration value"""
config = self.config_manager.load()
config[key] = value
self.config_manager.save(config)
console.print(f"[green]✓[/green] Set {key} = {value}")
```
## Pattern 2: Environment-Based Configuration
Handle multiple environments:
```python
from enum import Enum
class Environment(str, Enum):
DEV = "dev"
STAGING = "staging"
PRODUCTION = "production"
class MyCLI:
def __init__(self):
self.current_env = Environment.DEV
def set_env(self, env: Environment):
"""Switch environment
Args:
env: Target environment (dev, staging, production)
"""
self.current_env = env
console.print(f"[cyan]Environment: {env.value}[/cyan]")
def deploy(self):
"""Deploy to current environment"""
console.print(f"Deploying to {self.current_env.value}")
# Usage:
# python cli.py set-env staging
# python cli.py deploy
```
## Pattern 3: Property Access for Read-Only Values
Use properties for values that should be readable but not settable:
```python
class MyCLI:
def __init__(self):
self._version = "1.0.0"
self._config_path = Path.home() / ".mycli"
@property
def version(self):
"""Get CLI version"""
return self._version
@property
def config_path(self):
"""Get configuration path"""
return str(self._config_path)
# Usage:
# python cli.py version # Returns "1.0.0"
# python cli.py config-path # Returns path
```
## Pattern 4: Validation and Error Handling
Implement robust validation:
```python
from pathlib import Path
class MyCLI:
def deploy(self, path: str, confirm=False):
"""Deploy from path
Args:
path: Path to deployment files
confirm: Skip confirmation prompt
"""
deploy_path = Path(path)
# Validate path exists
if not deploy_path.exists():
console.print(f"[red]Error: Path not found: {path}[/red]")
return {"status": "error", "message": "Path not found"}
# Validate path is directory
if not deploy_path.is_dir():
console.print(f"[red]Error: Not a directory: {path}[/red]")
return {"status": "error", "message": "Not a directory"}
# Require confirmation for sensitive operations
if not confirm:
console.print("[yellow]⚠ Use --confirm to proceed[/yellow]")
return {"status": "cancelled"}
# Perform deployment
console.print(f"[green]Deploying from {path}...[/green]")
return {"status": "success"}
```
## Pattern 5: Chaining Commands
Create chainable command patterns:
```python
class Builder:
"""Build pipeline with chaining"""
def __init__(self):
self.steps = []
def clean(self):
"""Add clean step"""
self.steps.append("clean")
return self # Return self for chaining
def build(self):
"""Add build step"""
self.steps.append("build")
return self
def test(self):
"""Add test step"""
self.steps.append("test")
return self
def execute(self):
"""Execute pipeline"""
for step in self.steps:
console.print(f"[cyan]Running: {step}[/cyan]")
return {"steps": self.steps}
class MyCLI:
def __init__(self):
self.builder = Builder()
# Usage:
# python cli.py builder clean
# python cli.py builder build
# python cli.py builder clean build test execute
```
## Pattern 6: Context Managers for Resources
Use context managers for resource handling:
```python
from contextlib import contextmanager
class MyCLI:
@contextmanager
def _deployment_context(self, env):
"""Context manager for deployments"""
console.print(f"[cyan]Starting deployment to {env}...[/cyan]")
try:
yield
console.print("[green]✓[/green] Deployment successful")
except Exception as e:
console.print(f"[red]✗ Deployment failed: {e}[/red]")
raise
finally:
console.print("[dim]Cleanup complete[/dim]")
def deploy(self, env='staging'):
"""Deploy with context management"""
with self._deployment_context(env):
# Deployment logic here
console.print(" [dim]Building...[/dim]")
console.print(" [dim]Uploading...[/dim]")
```
## Pattern 7: Plugin Architecture
Create extensible CLI with plugins:
```python
from abc import ABC, abstractmethod
from typing import List
class Plugin(ABC):
"""Base plugin class"""
@abstractmethod
def execute(self, *args, **kwargs):
"""Execute plugin"""
pass
class DatabasePlugin(Plugin):
"""Database operations plugin"""
def execute(self, operation):
console.print(f"[cyan]Database: {operation}[/cyan]")
class CachePlugin(Plugin):
"""Cache operations plugin"""
def execute(self, operation):
console.print(f"[yellow]Cache: {operation}[/yellow]")
class MyCLI:
def __init__(self):
self.plugins: List[Plugin] = [
DatabasePlugin(),
CachePlugin()
]
def run_plugins(self, operation):
"""Execute operation on all plugins
Args:
operation: Operation to run
"""
for plugin in self.plugins:
plugin.execute(operation)
```
## Pattern 8: Async Operations
Handle async operations in Fire CLI:
```python
import asyncio
from typing import List
class MyCLI:
def fetch(self, urls: List[str]):
"""Fetch multiple URLs concurrently
Args:
urls: List of URLs to fetch
"""
async def fetch_all():
tasks = [self._fetch_one(url) for url in urls]
return await asyncio.gather(*tasks)
results = asyncio.run(fetch_all())
return {"fetched": len(results)}
async def _fetch_one(self, url):
"""Fetch single URL"""
# Simulated async fetch
await asyncio.sleep(0.1)
return url
# Usage:
# python cli.py fetch https://example.com https://google.com
```
## Pattern 9: Dry Run Mode
Implement dry-run capability:
```python
class MyCLI:
def __init__(self):
self.dry_run = False
def deploy(self, env, dry_run=False):
"""Deploy to environment
Args:
env: Target environment
dry_run: Show what would happen without executing
"""
self.dry_run = dry_run
if self.dry_run:
console.print("[yellow]DRY RUN MODE[/yellow]")
self._execute("Build project", lambda: self._build())
self._execute("Run tests", lambda: self._test())
self._execute("Upload files", lambda: self._upload(env))
def _execute(self, description, action):
"""Execute or simulate action"""
if self.dry_run:
console.print(f"[dim]Would: {description}[/dim]")
else:
console.print(f"[cyan]{description}...[/cyan]")
action()
def _build(self):
pass # Build logic
def _test(self):
pass # Test logic
def _upload(self, env):
pass # Upload logic
```
## Pattern 10: Logging Integration
Integrate structured logging:
```python
import logging
from pathlib import Path
class MyCLI:
def __init__(self):
self._setup_logging()
def _setup_logging(self):
"""Configure logging"""
log_file = Path.home() / ".mycli" / "cli.log"
log_file.parent.mkdir(parents=True, exist_ok=True)
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(log_file),
logging.StreamHandler()
]
)
self.logger = logging.getLogger(__name__)
def deploy(self, env):
"""Deploy with logging"""
self.logger.info(f"Starting deployment to {env}")
try:
console.print(f"[cyan]Deploying to {env}...[/cyan]")
# Deployment logic
self.logger.info("Deployment successful")
console.print("[green]✓[/green] Deployed!")
except Exception as e:
self.logger.error(f"Deployment failed: {e}")
console.print(f"[red]✗ Error: {e}[/red]")
raise
```
## Pattern 11: Interactive Confirmation
Add interactive prompts:
```python
class MyCLI:
def delete(self, resource, force=False):
"""Delete resource with confirmation
Args:
resource: Resource to delete
force: Skip confirmation
"""
if not force:
console.print(f"[yellow]⚠ Delete {resource}?[/yellow]")
console.print("[dim]Use --force to skip this prompt[/dim]")
return
console.print(f"[red]Deleting {resource}...[/red]")
# Delete logic here
console.print("[green]✓[/green] Deleted")
```
## Best Practices Summary
1. **Error Handling**: Always validate inputs and handle errors gracefully
2. **Confirmation**: Require confirmation for destructive operations
3. **Dry Run**: Implement dry-run mode for risky operations
4. **Logging**: Log important actions to file for audit trail
5. **Type Hints**: Use type hints for better IDE support
6. **Properties**: Use properties for read-only values
7. **Context Managers**: Use context managers for resource cleanup
8. **Enums**: Use Enums for constrained choices
9. **Async**: Use asyncio for concurrent operations
10. **Plugins**: Design for extensibility with plugin architecture

View File

@@ -0,0 +1,172 @@
# Basic Fire CLI Example
This example demonstrates creating a simple Fire CLI with basic commands.
## Generate Basic CLI
```bash
./scripts/generate-fire-cli.sh \
--name "TaskManager" \
--description "Simple task management CLI" \
--template basic \
--output task_manager.py
```
## Generated CLI Structure
```python
import fire
from rich.console import Console
console = Console()
class TaskManager:
"""Simple task management CLI"""
def __init__(self):
self.version = "1.0.0"
self.verbose = False
def init(self, name='my-project'):
"""Initialize a new project
Args:
name: Project name (default: my-project)
"""
console.print(f"[green]✓[/green] Initializing project: {name}")
return {"status": "success", "project": name}
def build(self, verbose=False):
"""Build the project
Args:
verbose: Enable verbose output (default: False)
"""
self.verbose = verbose
if self.verbose:
console.print("[dim]Verbose mode enabled[/dim]")
console.print("[cyan]Building project...[/cyan]")
console.print("[green]✓[/green] Build complete!")
if __name__ == '__main__':
fire.Fire(TaskManager)
```
## Usage Examples
### Display Help
```bash
python task_manager.py --help
```
Output:
```
NAME
task_manager.py
SYNOPSIS
task_manager.py COMMAND
COMMANDS
COMMAND is one of the following:
init
Initialize a new project
build
Build the project
version_info
Display version information
```
### Initialize Project
```bash
python task_manager.py init
# Uses default name 'my-project'
python task_manager.py init --name=my-app
# Custom project name
```
### Build with Verbose Mode
```bash
python task_manager.py build --verbose
```
### Get Version Information
```bash
python task_manager.py version-info
```
## Key Features
1. **Automatic Help Generation**: Fire generates help text from docstrings
2. **Type Conversion**: Fire automatically converts string arguments to correct types
3. **Default Values**: Parameter defaults become CLI defaults
4. **Boolean Flags**: `verbose=False` becomes `--verbose` flag
5. **Rich Output**: Integration with rich console for colored output
## Common Patterns
### Boolean Flags
```python
def deploy(self, force=False, dry_run=False):
"""Deploy application
Args:
force: Force deployment
dry_run: Perform dry run only
"""
pass
# Usage:
# python cli.py deploy --force
# python cli.py deploy --dry-run
# python cli.py deploy --noforce # Explicit False
```
### Required vs Optional Arguments
```python
def create(self, name, template='default'):
"""Create resource
Args:
name: Resource name (required)
template: Template to use (optional)
"""
pass
# Usage:
# python cli.py create my-resource
# python cli.py create my-resource --template=advanced
```
### Returning Values
```python
def status(self):
"""Get status"""
return {
"running": True,
"version": "1.0.0",
"uptime": "24h"
}
# Fire will display the returned dict
```
## Next Steps
1. Add more commands as methods
2. Use rich console for better output
3. Add configuration management
4. Implement nested classes for command groups
5. Add type hints for better IDE support

View File

@@ -0,0 +1,262 @@
# Nested Commands Example
This example demonstrates using nested classes to organize related commands into groups.
## Generate Nested CLI
```bash
./scripts/generate-fire-cli.sh \
--name "DeployTool" \
--description "Deployment management tool" \
--template nested \
--output deploy_tool.py
```
## Command Structure
```
deploy_tool.py
├── config # Configuration group
│ ├── get # Get config value
│ ├── set # Set config value
│ ├── list # List all config
│ └── reset # Reset config
├── resources # Resources group
│ ├── create # Create resource
│ ├── delete # Delete resource
│ └── list # List resources
└── info # Display info
```
## Usage Examples
### Configuration Management
```bash
# Set configuration value
python deploy_tool.py config set api_key abc123
# Get configuration value
python deploy_tool.py config get api_key
# Output: api_key: abc123
# List all configuration
python deploy_tool.py config list
# Output:
# Configuration:
# api_key: abc123
# endpoint: https://api.example.com
# Reset configuration
python deploy_tool.py config reset --confirm
```
### Resource Management
```bash
# Create resource with default template
python deploy_tool.py resources create my-resource
# Create resource with custom template
python deploy_tool.py resources create my-resource --template=advanced
# List all resources
python deploy_tool.py resources list
# Output:
# Resources:
# • item1
# • item2
# • item3
# Delete resource (requires confirmation)
python deploy_tool.py resources delete my-resource
# Output: ⚠ Use --confirm to delete resource
python deploy_tool.py resources delete my-resource --confirm
# Output: ✓ resource deleted
```
### Display Information
```bash
python deploy_tool.py info
# Output:
# DeployTool v1.0.0
# Config file: /home/user/.deploytool/config.json
```
## Implementation Pattern
### Main CLI Class
```python
class DeployTool:
"""Main CLI application"""
def __init__(self):
self.version = "1.0.0"
self.config_file = Path.home() / ".deploytool" / "config.json"
# Initialize nested command groups
self.config = self.Config(self)
self.resources = self.Resources()
def info(self):
"""Display CLI information"""
console.print(f"[bold]DeployTool[/bold] v{self.version}")
return {"version": self.version}
```
### Nested Command Group
```python
class Config:
"""Configuration management commands"""
def __init__(self, parent):
self.parent = parent # Access to main CLI instance
def get(self, key):
"""Get configuration value
Args:
key: Configuration key to retrieve
"""
config = self._load_config()
value = config.get(key)
console.print(f"[blue]{key}[/blue]: {value}")
return value
def set(self, key, value):
"""Set configuration value
Args:
key: Configuration key to set
value: Configuration value
"""
config = self._load_config()
config[key] = value
self._save_config(config)
console.print(f"[green]✓[/green] Set {key} = {value}")
def _load_config(self):
"""Private helper method (not exposed as command)"""
if not self.parent.config_file.exists():
return {}
return json.loads(self.parent.config_file.read_text())
def _save_config(self, config):
"""Private helper method (not exposed as command)"""
self.parent.config_file.parent.mkdir(parents=True, exist_ok=True)
self.parent.config_file.write_text(json.dumps(config, indent=2))
```
## Key Concepts
### Parent Access
Nested classes can access the parent CLI instance:
```python
class Config:
def __init__(self, parent):
self.parent = parent # Store parent reference
def some_command(self):
# Access parent properties
version = self.parent.version
config_file = self.parent.config_file
```
### Private Methods
Methods starting with `_` are not exposed as CLI commands:
```python
def list(self):
"""Public command - accessible via CLI"""
pass
def _load_config(self):
"""Private helper - not accessible via CLI"""
pass
```
### Multiple Nesting Levels
You can nest command groups multiple levels deep:
```python
class CLI:
class Database:
"""Database commands"""
class Migration:
"""Migration subcommands"""
def up(self):
"""Run migrations up"""
pass
def down(self):
"""Run migrations down"""
pass
# Usage:
# python cli.py database migration up
# python cli.py database migration down
```
## Help Navigation
### Top-Level Help
```bash
python deploy_tool.py --help
```
Shows all command groups and top-level commands.
### Group-Level Help
```bash
python deploy_tool.py config --help
```
Shows all commands in the `config` group.
### Command-Level Help
```bash
python deploy_tool.py config set --help
```
Shows help for specific command including arguments.
## Best Practices
1. **Logical Grouping**: Group related commands together
2. **Clear Names**: Use descriptive names for groups and commands
3. **Parent Access**: Use parent reference to share state
4. **Private Helpers**: Use `_` prefix for helper methods
5. **Comprehensive Docs**: Document each command group and command
6. **Shallow Nesting**: Keep nesting to 2-3 levels maximum
## Advanced Pattern: Shared Context
```python
class CLI:
def __init__(self):
self.context = {"verbose": False, "config": {}}
class Commands:
def __init__(self, parent):
self.parent = parent
def run(self, verbose=False):
"""Run command"""
self.parent.context["verbose"] = verbose
if verbose:
console.print("[dim]Verbose mode enabled[/dim]")
```
This allows sharing state across command groups through the parent context.

View File

@@ -0,0 +1,322 @@
# Rich Console Integration Example
This example shows how to integrate Fire CLI with Rich library for beautiful terminal output.
## Generate Rich CLI
```bash
./scripts/generate-fire-cli.sh \
--name "Monitor" \
--description "System monitoring CLI" \
--template rich \
--output monitor.py
```
## Rich Features
### Tables
Display data in formatted tables:
```python
from rich.table import Table
def list_items(self):
"""List items with table formatting"""
items = [
{"id": 1, "name": "Service A", "status": "active", "uptime": "99.9%"},
{"id": 2, "name": "Service B", "status": "down", "uptime": "0%"},
{"id": 3, "name": "Service C", "status": "pending", "uptime": "N/A"},
]
table = Table(title="System Services", show_header=True, header_style="bold magenta")
table.add_column("ID", style="cyan", width=6)
table.add_column("Name", style="green")
table.add_column("Status", style="yellow")
table.add_column("Uptime", justify="right", style="blue")
for item in items:
# Dynamic styling based on status
status_style = {
"active": "green",
"down": "red",
"pending": "yellow"
}.get(item['status'], "white")
table.add_row(
str(item['id']),
item['name'],
f"[{status_style}]{item['status']}[/{status_style}]",
item['uptime']
)
console.print(table)
return items
```
Usage:
```bash
python monitor.py list-items
```
Output:
```
System Services
┏━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━┓
┃ ID ┃ Name ┃ Status ┃ Uptime ┃
┡━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━┩
│ 1 │ Service A │ active │ 99.9% │
│ 2 │ Service B │ down │ 0% │
│ 3 │ Service C │ pending │ N/A │
└──────┴───────────┴─────────┴────────┘
```
### Progress Bars
Show progress for long-running operations:
```python
from rich.progress import track
import time
def process(self, count=100):
"""Process items with progress bar
Args:
count: Number of items to process
"""
console.print("[cyan]Starting processing...[/cyan]")
results = []
for i in track(range(count), description="Processing..."):
time.sleep(0.01) # Simulate work
results.append(i)
console.print("[green]✓[/green] Processing complete!")
return {"processed": len(results)}
```
Usage:
```bash
python monitor.py process --count=50
```
Output:
```
Starting processing...
Processing... ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 0:00:00
✓ Processing complete!
```
### Panels
Display information in bordered panels:
```python
from rich.panel import Panel
def status(self):
"""Display system status"""
panel = Panel(
"[bold green]System Running[/bold green]\n"
"Version: 1.0.0\n"
"Uptime: 24 hours\n"
"Load: [yellow]0.45[/yellow]\n"
"Memory: [blue]45%[/blue]",
title="System Status",
border_style="green"
)
console.print(panel)
return {
"status": "running",
"version": "1.0.0",
"uptime": "24h"
}
```
Usage:
```bash
python monitor.py status
```
Output:
```
╭─────── System Status ────────╮
│ System Running │
│ Version: 1.0.0 │
│ Uptime: 24 hours │
│ Load: 0.45 │
│ Memory: 45% │
╰──────────────────────────────╯
```
### Trees
Display hierarchical data:
```python
from rich.tree import Tree
def show_structure(self):
"""Display project structure"""
tree = Tree("📁 Project Root")
src = tree.add("📁 src")
src.add("📄 main.py")
src.add("📄 config.py")
tests = tree.add("📁 tests")
tests.add("📄 test_main.py")
tests.add("📄 test_config.py")
tree.add("📄 README.md")
tree.add("📄 requirements.txt")
console.print(tree)
```
Usage:
```bash
python monitor.py show-structure
```
Output:
```
📁 Project Root
├── 📁 src
│ ├── 📄 main.py
│ └── 📄 config.py
├── 📁 tests
│ ├── 📄 test_main.py
│ └── 📄 test_config.py
├── 📄 README.md
└── 📄 requirements.txt
```
### Styled Output
Use rich markup for styled text:
```python
def deploy(self, environment):
"""Deploy to environment
Args:
environment: Target environment
"""
console.print(f"[bold cyan]Deploying to {environment}...[/bold cyan]")
console.print("[dim]Step 1: Building...[/dim]")
console.print("[dim]Step 2: Testing...[/dim]")
console.print("[dim]Step 3: Uploading...[/dim]")
console.print("[green]✓[/green] Deployment complete!")
# Error example
if environment == "production":
console.print("[red]⚠ Production deployment requires approval[/red]")
```
### Color Palette
Common rich colors and styles:
```python
# Colors
console.print("[red]Error message[/red]")
console.print("[green]Success message[/green]")
console.print("[yellow]Warning message[/yellow]")
console.print("[blue]Info message[/blue]")
console.print("[cyan]Action message[/cyan]")
console.print("[magenta]Highlight[/magenta]")
# Styles
console.print("[bold]Bold text[/bold]")
console.print("[dim]Dimmed text[/dim]")
console.print("[italic]Italic text[/italic]")
console.print("[underline]Underlined text[/underline]")
# Combinations
console.print("[bold red]Bold red text[/bold red]")
console.print("[dim yellow]Dimmed yellow text[/dim yellow]")
# Emojis
console.print("✓ Success")
console.print("✗ Failure")
console.print("⚠ Warning")
console.print(" Info")
console.print("→ Next step")
console.print("🚀 Deploy")
console.print("📦 Package")
```
## Complete Rich CLI Example
```python
import fire
from rich.console import Console
from rich.table import Table
from rich.panel import Panel
from rich.progress import track
import time
console = Console()
class Monitor:
"""System monitoring CLI with rich output"""
def __init__(self):
self.version = "1.0.0"
def status(self):
"""Display comprehensive status"""
# Header panel
console.print(Panel(
"[bold green]System Online[/bold green]",
title="Monitor Status",
border_style="green"
))
# Services table
table = Table(title="Services")
table.add_column("Service", style="cyan")
table.add_column("Status", style="green")
table.add_column("Load", justify="right")
table.add_row("API", "[green]●[/green] Running", "45%")
table.add_row("DB", "[green]●[/green] Running", "23%")
table.add_row("Cache", "[yellow]●[/yellow] Degraded", "78%")
console.print(table)
def deploy(self, env='staging'):
"""Deploy with visual feedback"""
console.print(Panel(
f"[bold]Deploying to {env}[/bold]",
border_style="cyan"
))
steps = ["Build", "Test", "Upload", "Verify"]
for step in track(steps, description="Deploying..."):
time.sleep(0.5)
console.print("[green]✓[/green] Deployment successful!")
if __name__ == '__main__':
fire.Fire(Monitor)
```
## Installation
```bash
pip install fire rich
```
## Best Practices
1. **Use Console Instance**: Create one `Console()` instance and reuse it
2. **Consistent Colors**: Use consistent colors for similar message types
3. **Progress for Long Tasks**: Always show progress for operations >1 second
4. **Tables for Lists**: Use tables instead of plain text for structured data
5. **Panels for Sections**: Use panels to separate different output sections
6. **Emojis Sparingly**: Use emojis to enhance, not clutter
7. **Test in Different Terminals**: Rich output varies by terminal capabilities