Initial commit
This commit is contained in:
467
skills/fire-patterns/SKILL.md
Normal file
467
skills/fire-patterns/SKILL.md
Normal file
@@ -0,0 +1,467 @@
|
||||
---
|
||||
name: fire-patterns
|
||||
description: Auto-generated CLI patterns using Google Fire with class-based structure, docstring parsing, and nested classes. Use when building Python CLI applications, creating Fire CLIs, implementing auto-generated commands, designing class-based CLIs, or when user mentions Fire, Google Fire, CLI generation, docstring commands, nested command groups, or Python command-line tools.
|
||||
allowed-tools: Read, Write, Edit, Bash
|
||||
---
|
||||
|
||||
# fire-patterns
|
||||
|
||||
Provides patterns for building Python CLI applications using Google Fire with automatic command generation from class methods, docstring-based help text, nested class structures for command groups, and rich console output integration.
|
||||
|
||||
## Core Patterns
|
||||
|
||||
### 1. Class-Based Fire CLI with Docstring Parsing
|
||||
|
||||
Fire automatically generates CLI commands from class methods and extracts help text from docstrings:
|
||||
|
||||
```python
|
||||
import fire
|
||||
from rich.console import Console
|
||||
|
||||
console = Console()
|
||||
|
||||
class MyCLI:
|
||||
"""A powerful CLI tool with auto-generated commands"""
|
||||
|
||||
def __init__(self):
|
||||
self.version = "1.0.0"
|
||||
self.config = {}
|
||||
|
||||
def init(self, template='basic'):
|
||||
"""Initialize a new project
|
||||
|
||||
Args:
|
||||
template: Project template to use (default: basic)
|
||||
"""
|
||||
console.print(f"[green]✓[/green] Initializing project with {template} template...")
|
||||
return {"status": "success", "template": template}
|
||||
|
||||
def deploy(self, environment, force=False, mode='safe'):
|
||||
"""Deploy to specified environment
|
||||
|
||||
Args:
|
||||
environment: Target environment (dev, staging, prod)
|
||||
force: Force deployment without confirmation (default: False)
|
||||
mode: Deployment mode - fast, safe, or rollback (default: safe)
|
||||
"""
|
||||
console.print(f"[cyan]Deploying to {environment} in {mode} mode[/cyan]")
|
||||
if force:
|
||||
console.print("[yellow]⚠ Force mode enabled - skipping confirmation[/yellow]")
|
||||
return {"environment": environment, "mode": mode, "forced": force}
|
||||
|
||||
if __name__ == '__main__':
|
||||
fire.Fire(MyCLI)
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
python mycli.py init --template=react
|
||||
python mycli.py deploy production --force
|
||||
python mycli.py deploy staging --mode=fast
|
||||
python mycli.py --help # Auto-generated from docstrings
|
||||
```
|
||||
|
||||
### 2. Nested Class Structure for Command Groups
|
||||
|
||||
Organize related commands using nested classes:
|
||||
|
||||
```python
|
||||
import fire
|
||||
from rich.console import Console
|
||||
|
||||
console = Console()
|
||||
|
||||
class MyCLI:
|
||||
"""Main CLI application"""
|
||||
|
||||
def __init__(self):
|
||||
self.version = "1.0.0"
|
||||
|
||||
class Config:
|
||||
"""Manage configuration settings"""
|
||||
|
||||
def get(self, key):
|
||||
"""Get configuration value
|
||||
|
||||
Args:
|
||||
key: Configuration key to retrieve
|
||||
"""
|
||||
value = self._load_config().get(key)
|
||||
console.print(f"[blue]Config[/blue] {key}: {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 list(self):
|
||||
"""List all configuration values"""
|
||||
config = self._load_config()
|
||||
console.print("[bold]Configuration:[/bold]")
|
||||
for key, value in config.items():
|
||||
console.print(f" {key}: {value}")
|
||||
|
||||
@staticmethod
|
||||
def _load_config():
|
||||
# Load configuration from file
|
||||
return {}
|
||||
|
||||
@staticmethod
|
||||
def _save_config(config):
|
||||
# Save configuration to file
|
||||
pass
|
||||
|
||||
class Database:
|
||||
"""Database management commands"""
|
||||
|
||||
def migrate(self, direction='up'):
|
||||
"""Run database migrations
|
||||
|
||||
Args:
|
||||
direction: Migration direction - up or down (default: up)
|
||||
"""
|
||||
console.print(f"[cyan]Running migrations {direction}...[/cyan]")
|
||||
|
||||
def seed(self, dataset='default'):
|
||||
"""Seed database with test data
|
||||
|
||||
Args:
|
||||
dataset: Dataset to use for seeding (default: default)
|
||||
"""
|
||||
console.print(f"[green]Seeding database with {dataset} dataset[/green]")
|
||||
|
||||
def reset(self, confirm=False):
|
||||
"""Reset database to initial state
|
||||
|
||||
Args:
|
||||
confirm: Confirm destructive operation (default: False)
|
||||
"""
|
||||
if not confirm:
|
||||
console.print("[red]⚠ Use --confirm to reset database[/red]")
|
||||
return
|
||||
console.print("[yellow]Resetting database...[/yellow]")
|
||||
|
||||
if __name__ == '__main__':
|
||||
fire.Fire(MyCLI)
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
python mycli.py config get database_url
|
||||
python mycli.py config set api_key abc123
|
||||
python mycli.py config list
|
||||
python mycli.py database migrate
|
||||
python mycli.py database seed --dataset=production
|
||||
python mycli.py database reset --confirm
|
||||
```
|
||||
|
||||
### 3. Multiple Return Types and Output Formatting
|
||||
|
||||
Fire handles different return types automatically:
|
||||
|
||||
```python
|
||||
import fire
|
||||
from rich.console import Console
|
||||
from rich.table import Table
|
||||
import json
|
||||
|
||||
console = Console()
|
||||
|
||||
class MyCLI:
|
||||
"""CLI with rich output formatting"""
|
||||
|
||||
def status(self):
|
||||
"""Show application status (returns dict)"""
|
||||
return {
|
||||
"status": "running",
|
||||
"version": "1.0.0",
|
||||
"uptime": "24h",
|
||||
"active_users": 42
|
||||
}
|
||||
|
||||
def list_items(self):
|
||||
"""List items with table formatting (returns list)"""
|
||||
items = [
|
||||
{"id": 1, "name": "Item A", "status": "active"},
|
||||
{"id": 2, "name": "Item B", "status": "pending"},
|
||||
{"id": 3, "name": "Item C", "status": "completed"}
|
||||
]
|
||||
|
||||
table = Table(title="Items")
|
||||
table.add_column("ID", style="cyan")
|
||||
table.add_column("Name", style="magenta")
|
||||
table.add_column("Status", style="green")
|
||||
|
||||
for item in items:
|
||||
table.add_row(str(item['id']), item['name'], item['status'])
|
||||
|
||||
console.print(table)
|
||||
return items
|
||||
|
||||
def export(self, format='json'):
|
||||
"""Export data in specified format
|
||||
|
||||
Args:
|
||||
format: Output format - json, yaml, or text (default: json)
|
||||
"""
|
||||
data = {"items": [1, 2, 3], "total": 3}
|
||||
|
||||
if format == 'json':
|
||||
return json.dumps(data, indent=2)
|
||||
elif format == 'yaml':
|
||||
return f"items:\n - 1\n - 2\n - 3\ntotal: 3"
|
||||
else:
|
||||
return f"Total items: {data['total']}"
|
||||
|
||||
if __name__ == '__main__':
|
||||
fire.Fire(MyCLI)
|
||||
```
|
||||
|
||||
### 4. Property-Based Access and Chaining
|
||||
|
||||
Use properties and method chaining with Fire:
|
||||
|
||||
```python
|
||||
import fire
|
||||
from rich.console import Console
|
||||
|
||||
console = Console()
|
||||
|
||||
class MyCLI:
|
||||
"""CLI with property access"""
|
||||
|
||||
def __init__(self):
|
||||
self._version = "1.0.0"
|
||||
self._debug = False
|
||||
|
||||
@property
|
||||
def version(self):
|
||||
"""Get application version"""
|
||||
return self._version
|
||||
|
||||
@property
|
||||
def debug(self):
|
||||
"""Get debug mode status"""
|
||||
return self._debug
|
||||
|
||||
def info(self):
|
||||
"""Display application information"""
|
||||
console.print(f"[bold]Application Info[/bold]")
|
||||
console.print(f"Version: {self.version}")
|
||||
console.print(f"Debug: {self.debug}")
|
||||
return {"version": self.version, "debug": self.debug}
|
||||
|
||||
if __name__ == '__main__':
|
||||
fire.Fire(MyCLI)
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
python mycli.py version # Access property directly
|
||||
python mycli.py debug # Access property directly
|
||||
python mycli.py info # Call method
|
||||
```
|
||||
|
||||
## Available Templates
|
||||
|
||||
Use the following templates for generating Fire CLI applications:
|
||||
|
||||
- **basic-fire-cli.py.template** - Simple single-class Fire CLI
|
||||
- **nested-fire-cli.py.template** - Multi-class CLI with command groups
|
||||
- **rich-fire-cli.py.template** - Fire CLI with rich console output
|
||||
- **typed-fire-cli.py.template** - Type-annotated Fire CLI
|
||||
- **config-fire-cli.py.template** - Fire CLI with configuration management
|
||||
- **multi-command-fire-cli.py.template** - Complex multi-command Fire CLI
|
||||
|
||||
## Available Scripts
|
||||
|
||||
- **generate-fire-cli.sh** - Generate Fire CLI from specification
|
||||
- **validate-fire-cli.py** - Validate Fire CLI structure and docstrings
|
||||
- **extract-commands.py** - Extract command structure from Fire CLI
|
||||
- **test-fire-cli.py** - Test Fire CLI commands programmatically
|
||||
|
||||
## Key Fire CLI Principles
|
||||
|
||||
### Automatic Command Generation
|
||||
|
||||
Fire automatically generates CLI commands from:
|
||||
- Public methods (commands)
|
||||
- Method parameters (command arguments and flags)
|
||||
- Docstrings (help text and argument descriptions)
|
||||
- Nested classes (command groups)
|
||||
- Properties (read-only values)
|
||||
|
||||
### Docstring Parsing
|
||||
|
||||
Fire parses docstrings to generate help text:
|
||||
|
||||
```python
|
||||
def command(self, arg1, arg2='default'):
|
||||
"""Command description shown in help
|
||||
|
||||
Args:
|
||||
arg1: Description of arg1 (shown in help)
|
||||
arg2: Description of arg2 (shown in help)
|
||||
|
||||
Returns:
|
||||
Description of return value
|
||||
"""
|
||||
pass
|
||||
```
|
||||
|
||||
### Boolean Flags
|
||||
|
||||
Fire converts boolean parameters to flags:
|
||||
|
||||
```python
|
||||
def deploy(self, force=False, verbose=False):
|
||||
"""Deploy with optional flags"""
|
||||
pass
|
||||
|
||||
# Usage:
|
||||
# python cli.py deploy --force --verbose
|
||||
# python cli.py deploy --noforce # Explicitly set to False
|
||||
```
|
||||
|
||||
### Default Values
|
||||
|
||||
Default parameter values become default flag values:
|
||||
|
||||
```python
|
||||
def init(self, template='basic', port=8000):
|
||||
"""Initialize with defaults"""
|
||||
pass
|
||||
|
||||
# Usage:
|
||||
# python cli.py init # Uses defaults
|
||||
# python cli.py init --template=react # Override template
|
||||
# python cli.py init --port=3000 # Override port
|
||||
```
|
||||
|
||||
## Integration with Rich Console
|
||||
|
||||
Enhance Fire CLIs with rich formatting:
|
||||
|
||||
```python
|
||||
from rich.console import Console
|
||||
from rich.progress import track
|
||||
from rich.panel import Panel
|
||||
import fire
|
||||
import time
|
||||
|
||||
console = Console()
|
||||
|
||||
class MyCLI:
|
||||
"""Rich-enhanced Fire CLI"""
|
||||
|
||||
def process(self, items=100):
|
||||
"""Process items with progress bar
|
||||
|
||||
Args:
|
||||
items: Number of items to process
|
||||
"""
|
||||
console.print(Panel("[bold green]Processing Started[/bold green]"))
|
||||
|
||||
for i in track(range(items), description="Processing..."):
|
||||
time.sleep(0.01) # Simulate work
|
||||
|
||||
console.print("[green]✓[/green] Processing complete!")
|
||||
|
||||
if __name__ == '__main__':
|
||||
fire.Fire(MyCLI)
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Clear Docstrings**: Write comprehensive docstrings for auto-generated help
|
||||
2. **Nested Classes**: Use nested classes for logical command grouping
|
||||
3. **Default Values**: Provide sensible defaults for all optional parameters
|
||||
4. **Type Hints**: Use type annotations for better IDE support
|
||||
5. **Return Values**: Return data structures that Fire can serialize
|
||||
6. **Rich Output**: Use rich console for enhanced terminal output
|
||||
7. **Validation**: Validate inputs within methods, not in Fire setup
|
||||
8. **Error Handling**: Use try-except blocks and return error messages
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Confirmation Prompts
|
||||
|
||||
```python
|
||||
def delete(self, resource, confirm=False):
|
||||
"""Delete resource with confirmation
|
||||
|
||||
Args:
|
||||
resource: Resource to delete
|
||||
confirm: Skip confirmation prompt
|
||||
"""
|
||||
if not confirm:
|
||||
console.print("[yellow]Use --confirm to delete[/yellow]")
|
||||
return
|
||||
|
||||
console.print(f"[red]Deleting {resource}...[/red]")
|
||||
```
|
||||
|
||||
### Environment Selection
|
||||
|
||||
```python
|
||||
from enum import Enum
|
||||
|
||||
class Environment(str, Enum):
|
||||
DEV = "dev"
|
||||
STAGING = "staging"
|
||||
PROD = "prod"
|
||||
|
||||
def deploy(self, env: Environment):
|
||||
"""Deploy to environment
|
||||
|
||||
Args:
|
||||
env: Target environment (dev, staging, prod)
|
||||
"""
|
||||
console.print(f"Deploying to {env.value}")
|
||||
```
|
||||
|
||||
### Verbose Mode
|
||||
|
||||
```python
|
||||
def __init__(self):
|
||||
self.verbose = False
|
||||
|
||||
def build(self, verbose=False):
|
||||
"""Build project
|
||||
|
||||
Args:
|
||||
verbose: Enable verbose output
|
||||
"""
|
||||
self.verbose = verbose
|
||||
if self.verbose:
|
||||
console.print("[dim]Verbose mode enabled[/dim]")
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
- Python 3.7+
|
||||
- google-fire package: `pip install fire`
|
||||
- rich package (optional): `pip install rich`
|
||||
- Type hints support for better IDE integration
|
||||
|
||||
## Examples
|
||||
|
||||
See `examples/` directory for complete working examples:
|
||||
- `basic-cli.md` - Simple Fire CLI walkthrough
|
||||
- `nested-commands.md` - Multi-level command structure
|
||||
- `rich-integration.md` - Rich console integration examples
|
||||
- `advanced-patterns.md` - Complex Fire CLI patterns
|
||||
|
||||
---
|
||||
|
||||
**Purpose**: Generate maintainable Python CLI applications with automatic command generation
|
||||
**Framework**: Google Fire
|
||||
**Key Feature**: Zero boilerplate - commands auto-generated from class methods
|
||||
418
skills/fire-patterns/examples/advanced-patterns.md
Normal file
418
skills/fire-patterns/examples/advanced-patterns.md
Normal 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
|
||||
172
skills/fire-patterns/examples/basic-cli.md
Normal file
172
skills/fire-patterns/examples/basic-cli.md
Normal 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
|
||||
262
skills/fire-patterns/examples/nested-commands.md
Normal file
262
skills/fire-patterns/examples/nested-commands.md
Normal 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.
|
||||
322
skills/fire-patterns/examples/rich-integration.md
Normal file
322
skills/fire-patterns/examples/rich-integration.md
Normal 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
|
||||
208
skills/fire-patterns/scripts/extract-commands.py
Executable file
208
skills/fire-patterns/scripts/extract-commands.py
Executable file
@@ -0,0 +1,208 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Extract command structure from Fire CLI for documentation"""
|
||||
|
||||
import ast
|
||||
import sys
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import List, Dict, Optional
|
||||
|
||||
|
||||
class CommandExtractor:
|
||||
"""Extract command structure from Fire CLI Python files"""
|
||||
|
||||
def __init__(self, filepath: Path):
|
||||
self.filepath = filepath
|
||||
self.tree = None
|
||||
self.commands = []
|
||||
|
||||
def extract(self) -> List[Dict]:
|
||||
"""Extract all commands from Fire CLI"""
|
||||
try:
|
||||
content = self.filepath.read_text()
|
||||
self.tree = ast.parse(content)
|
||||
except Exception as e:
|
||||
print(f"Error parsing file: {e}", file=sys.stderr)
|
||||
return []
|
||||
|
||||
# Find all classes
|
||||
for node in self.tree.body:
|
||||
if isinstance(node, ast.ClassDef):
|
||||
self._extract_from_class(node)
|
||||
|
||||
return self.commands
|
||||
|
||||
def _extract_from_class(self, class_node: ast.ClassDef, parent_path: str = ""):
|
||||
"""Extract commands from a class"""
|
||||
class_name = class_node.name
|
||||
class_doc = ast.get_docstring(class_node) or ""
|
||||
|
||||
current_path = f"{parent_path}.{class_name}" if parent_path else class_name
|
||||
|
||||
for item in class_node.body:
|
||||
if isinstance(item, ast.FunctionDef):
|
||||
# Skip private methods
|
||||
if item.name.startswith('_'):
|
||||
continue
|
||||
|
||||
command = self._extract_command(item, current_path)
|
||||
if command:
|
||||
self.commands.append(command)
|
||||
|
||||
elif isinstance(item, ast.ClassDef):
|
||||
# Nested class - recurse
|
||||
self._extract_from_class(item, current_path)
|
||||
|
||||
def _extract_command(self, func_node: ast.FunctionDef, class_path: str) -> Optional[Dict]:
|
||||
"""Extract command information from function"""
|
||||
func_name = func_node.name
|
||||
docstring = ast.get_docstring(func_node) or ""
|
||||
|
||||
# Parse docstring for description and args
|
||||
description = ""
|
||||
args_help = {}
|
||||
|
||||
if docstring:
|
||||
lines = docstring.split('\n')
|
||||
desc_lines = []
|
||||
in_args = False
|
||||
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
if line.startswith('Args:'):
|
||||
in_args = True
|
||||
continue
|
||||
elif line.startswith('Returns:') or line.startswith('Raises:'):
|
||||
in_args = False
|
||||
continue
|
||||
|
||||
if not in_args and line:
|
||||
desc_lines.append(line)
|
||||
elif in_args and line:
|
||||
# Parse arg line: "arg_name: description"
|
||||
if ':' in line:
|
||||
arg_name, arg_desc = line.split(':', 1)
|
||||
args_help[arg_name.strip()] = arg_desc.strip()
|
||||
|
||||
description = ' '.join(desc_lines)
|
||||
|
||||
# Extract arguments
|
||||
args = []
|
||||
for arg in func_node.args.args:
|
||||
if arg.arg == 'self':
|
||||
continue
|
||||
|
||||
arg_info = {
|
||||
'name': arg.arg,
|
||||
'type': self._get_type_annotation(arg),
|
||||
'help': args_help.get(arg.arg, ''),
|
||||
}
|
||||
|
||||
# Check for default value
|
||||
defaults_offset = len(func_node.args.args) - len(func_node.args.defaults)
|
||||
arg_index = func_node.args.args.index(arg)
|
||||
if arg_index >= defaults_offset:
|
||||
default_index = arg_index - defaults_offset
|
||||
default_value = self._get_default_value(func_node.args.defaults[default_index])
|
||||
arg_info['default'] = default_value
|
||||
arg_info['required'] = False
|
||||
else:
|
||||
arg_info['required'] = True
|
||||
|
||||
args.append(arg_info)
|
||||
|
||||
return {
|
||||
'name': func_name,
|
||||
'path': f"{class_path}.{func_name}",
|
||||
'description': description,
|
||||
'arguments': args
|
||||
}
|
||||
|
||||
def _get_type_annotation(self, arg: ast.arg) -> Optional[str]:
|
||||
"""Extract type annotation from argument"""
|
||||
if arg.annotation:
|
||||
return ast.unparse(arg.annotation)
|
||||
return None
|
||||
|
||||
def _get_default_value(self, node) -> str:
|
||||
"""Extract default value from AST node"""
|
||||
try:
|
||||
return ast.unparse(node)
|
||||
except:
|
||||
return repr(node)
|
||||
|
||||
def print_tree(self):
|
||||
"""Print command tree in human-readable format"""
|
||||
print(f"\n{'='*60}")
|
||||
print(f"Fire CLI Commands: {self.filepath.name}")
|
||||
print(f"{'='*60}\n")
|
||||
|
||||
for cmd in self.commands:
|
||||
print(f"📌 {cmd['path']}")
|
||||
if cmd['description']:
|
||||
print(f" {cmd['description']}")
|
||||
if cmd['arguments']:
|
||||
print(f" Arguments:")
|
||||
for arg in cmd['arguments']:
|
||||
required = "required" if arg['required'] else "optional"
|
||||
type_str = f": {arg['type']}" if arg['type'] else ""
|
||||
default_str = f" = {arg['default']}" if 'default' in arg else ""
|
||||
help_str = f" - {arg['help']}" if arg['help'] else ""
|
||||
print(f" • {arg['name']}{type_str}{default_str} ({required}){help_str}")
|
||||
print()
|
||||
|
||||
def export_json(self) -> str:
|
||||
"""Export commands as JSON"""
|
||||
return json.dumps(self.commands, indent=2)
|
||||
|
||||
def export_markdown(self) -> str:
|
||||
"""Export commands as Markdown"""
|
||||
lines = [f"# {self.filepath.name} Commands\n"]
|
||||
|
||||
for cmd in self.commands:
|
||||
lines.append(f"## `{cmd['path']}`\n")
|
||||
|
||||
if cmd['description']:
|
||||
lines.append(f"{cmd['description']}\n")
|
||||
|
||||
if cmd['arguments']:
|
||||
lines.append("### Arguments\n")
|
||||
for arg in cmd['arguments']:
|
||||
required = "**required**" if arg['required'] else "*optional*"
|
||||
type_str = f" (`{arg['type']}`)" if arg['type'] else ""
|
||||
default_str = f" Default: `{arg['default']}`" if 'default' in arg else ""
|
||||
|
||||
lines.append(f"- `{arg['name']}`{type_str} - {required}{default_str}")
|
||||
if arg['help']:
|
||||
lines.append(f" - {arg['help']}")
|
||||
|
||||
lines.append("")
|
||||
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: extract-commands.py <fire-cli-file.py> [--json|--markdown]")
|
||||
sys.exit(1)
|
||||
|
||||
filepath = Path(sys.argv[1])
|
||||
output_format = sys.argv[2] if len(sys.argv) > 2 else '--tree'
|
||||
|
||||
if not filepath.exists():
|
||||
print(f"Error: File not found: {filepath}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
extractor = CommandExtractor(filepath)
|
||||
extractor.extract()
|
||||
|
||||
if output_format == '--json':
|
||||
print(extractor.export_json())
|
||||
elif output_format == '--markdown':
|
||||
print(extractor.export_markdown())
|
||||
else:
|
||||
extractor.print_tree()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
179
skills/fire-patterns/scripts/generate-fire-cli.sh
Executable file
179
skills/fire-patterns/scripts/generate-fire-cli.sh
Executable file
@@ -0,0 +1,179 @@
|
||||
#!/bin/bash
|
||||
# Generate Fire CLI from specification
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
TEMPLATES_DIR="$(dirname "$SCRIPT_DIR")/templates"
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Function to display usage
|
||||
usage() {
|
||||
cat << EOF
|
||||
Usage: $(basename "$0") [OPTIONS]
|
||||
|
||||
Generate a Python Fire CLI application from templates.
|
||||
|
||||
OPTIONS:
|
||||
-n, --name NAME CLI name (required)
|
||||
-d, --description DESC CLI description (required)
|
||||
-t, --template TYPE Template type: basic, nested, rich, typed, config, multi (default: basic)
|
||||
-o, --output FILE Output file path (required)
|
||||
-c, --class-name NAME Main class name (default: CLI)
|
||||
-v, --version VERSION Version number (default: 1.0.0)
|
||||
-h, --help Show this help message
|
||||
|
||||
TEMPLATE TYPES:
|
||||
basic - Simple single-class Fire CLI
|
||||
nested - Multi-class CLI with command groups
|
||||
rich - Fire CLI with rich console output
|
||||
typed - Type-annotated Fire CLI with full type hints
|
||||
config - Fire CLI with comprehensive configuration management
|
||||
multi - Complex multi-command Fire CLI
|
||||
|
||||
EXAMPLES:
|
||||
$(basename "$0") -n mycli -d "My CLI tool" -o mycli.py
|
||||
$(basename "$0") -n deploy-tool -d "Deployment CLI" -t nested -o deploy.py
|
||||
$(basename "$0") -n mytool -d "Advanced tool" -t typed -c MyTool -o tool.py
|
||||
|
||||
EOF
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Parse command line arguments
|
||||
CLI_NAME=""
|
||||
DESCRIPTION=""
|
||||
TEMPLATE="basic"
|
||||
OUTPUT_FILE=""
|
||||
CLASS_NAME="CLI"
|
||||
VERSION="1.0.0"
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-n|--name)
|
||||
CLI_NAME="$2"
|
||||
shift 2
|
||||
;;
|
||||
-d|--description)
|
||||
DESCRIPTION="$2"
|
||||
shift 2
|
||||
;;
|
||||
-t|--template)
|
||||
TEMPLATE="$2"
|
||||
shift 2
|
||||
;;
|
||||
-o|--output)
|
||||
OUTPUT_FILE="$2"
|
||||
shift 2
|
||||
;;
|
||||
-c|--class-name)
|
||||
CLASS_NAME="$2"
|
||||
shift 2
|
||||
;;
|
||||
-v|--version)
|
||||
VERSION="$2"
|
||||
shift 2
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
;;
|
||||
*)
|
||||
echo -e "${RED}Error: Unknown option $1${NC}"
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Validate required arguments
|
||||
if [[ -z "$CLI_NAME" ]]; then
|
||||
echo -e "${RED}Error: CLI name is required${NC}"
|
||||
usage
|
||||
fi
|
||||
|
||||
if [[ -z "$DESCRIPTION" ]]; then
|
||||
echo -e "${RED}Error: Description is required${NC}"
|
||||
usage
|
||||
fi
|
||||
|
||||
if [[ -z "$OUTPUT_FILE" ]]; then
|
||||
echo -e "${RED}Error: Output file is required${NC}"
|
||||
usage
|
||||
fi
|
||||
|
||||
# Validate template type
|
||||
TEMPLATE_FILE=""
|
||||
case $TEMPLATE in
|
||||
basic)
|
||||
TEMPLATE_FILE="$TEMPLATES_DIR/basic-fire-cli.py.template"
|
||||
;;
|
||||
nested)
|
||||
TEMPLATE_FILE="$TEMPLATES_DIR/nested-fire-cli.py.template"
|
||||
;;
|
||||
rich)
|
||||
TEMPLATE_FILE="$TEMPLATES_DIR/rich-fire-cli.py.template"
|
||||
;;
|
||||
typed)
|
||||
TEMPLATE_FILE="$TEMPLATES_DIR/typed-fire-cli.py.template"
|
||||
;;
|
||||
config)
|
||||
TEMPLATE_FILE="$TEMPLATES_DIR/config-fire-cli.py.template"
|
||||
;;
|
||||
multi)
|
||||
TEMPLATE_FILE="$TEMPLATES_DIR/multi-command-fire-cli.py.template"
|
||||
;;
|
||||
*)
|
||||
echo -e "${RED}Error: Invalid template type: $TEMPLATE${NC}"
|
||||
echo "Valid types: basic, nested, rich, typed, config, multi"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ ! -f "$TEMPLATE_FILE" ]]; then
|
||||
echo -e "${RED}Error: Template file not found: $TEMPLATE_FILE${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Prepare variables
|
||||
CLI_NAME_LOWER=$(echo "$CLI_NAME" | tr '[:upper:]' '[:lower:]' | tr ' ' '-')
|
||||
DEFAULT_PROJECT_NAME="my-project"
|
||||
SUBCOMMAND_GROUP_NAME="Resources"
|
||||
SUBCOMMAND_GROUP_DESCRIPTION="Resource management commands"
|
||||
SUBCOMMAND_GROUP_NAME_LOWER="resources"
|
||||
RESOURCE_NAME="resource"
|
||||
|
||||
echo -e "${BLUE}Generating Fire CLI...${NC}"
|
||||
echo -e "${BLUE} Name: ${NC}$CLI_NAME"
|
||||
echo -e "${BLUE} Description: ${NC}$DESCRIPTION"
|
||||
echo -e "${BLUE} Template: ${NC}$TEMPLATE"
|
||||
echo -e "${BLUE} Class: ${NC}$CLASS_NAME"
|
||||
echo -e "${BLUE} Version: ${NC}$VERSION"
|
||||
echo -e "${BLUE} Output: ${NC}$OUTPUT_FILE"
|
||||
|
||||
# Generate CLI by replacing template variables
|
||||
sed -e "s/{{CLI_NAME}}/$CLI_NAME/g" \
|
||||
-e "s/{{CLI_DESCRIPTION}}/$DESCRIPTION/g" \
|
||||
-e "s/{{CLASS_NAME}}/$CLASS_NAME/g" \
|
||||
-e "s/{{CLI_NAME_LOWER}}/$CLI_NAME_LOWER/g" \
|
||||
-e "s/{{VERSION}}/$VERSION/g" \
|
||||
-e "s/{{DEFAULT_PROJECT_NAME}}/$DEFAULT_PROJECT_NAME/g" \
|
||||
-e "s/{{SUBCOMMAND_GROUP_NAME}}/$SUBCOMMAND_GROUP_NAME/g" \
|
||||
-e "s/{{SUBCOMMAND_GROUP_DESCRIPTION}}/$SUBCOMMAND_GROUP_DESCRIPTION/g" \
|
||||
-e "s/{{SUBCOMMAND_GROUP_NAME_LOWER}}/$SUBCOMMAND_GROUP_NAME_LOWER/g" \
|
||||
-e "s/{{RESOURCE_NAME}}/$RESOURCE_NAME/g" \
|
||||
"$TEMPLATE_FILE" > "$OUTPUT_FILE"
|
||||
|
||||
# Make executable
|
||||
chmod +x "$OUTPUT_FILE"
|
||||
|
||||
echo -e "${GREEN}✓ Generated Fire CLI: $OUTPUT_FILE${NC}"
|
||||
echo -e "${YELLOW}Next steps:${NC}"
|
||||
echo -e " 1. Review and customize: ${BLUE}$OUTPUT_FILE${NC}"
|
||||
echo -e " 2. Install dependencies: ${BLUE}pip install fire rich${NC}"
|
||||
echo -e " 3. Test the CLI: ${BLUE}python $OUTPUT_FILE --help${NC}"
|
||||
echo -e " 4. Validate: ${BLUE}$SCRIPT_DIR/validate-fire-cli.py $OUTPUT_FILE${NC}"
|
||||
208
skills/fire-patterns/scripts/test-fire-cli.py
Executable file
208
skills/fire-patterns/scripts/test-fire-cli.py
Executable file
@@ -0,0 +1,208 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Test Fire CLI commands programmatically"""
|
||||
|
||||
import sys
|
||||
import importlib.util
|
||||
import inspect
|
||||
from pathlib import Path
|
||||
from typing import Any, List, Dict
|
||||
import json
|
||||
|
||||
|
||||
class FireCLITester:
|
||||
"""Test Fire CLI commands without running them"""
|
||||
|
||||
def __init__(self, filepath: Path):
|
||||
self.filepath = filepath
|
||||
self.module = None
|
||||
self.cli_class = None
|
||||
|
||||
def load_cli(self) -> bool:
|
||||
"""Load CLI module dynamically"""
|
||||
try:
|
||||
spec = importlib.util.spec_from_file_location("cli_module", self.filepath)
|
||||
self.module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(self.module)
|
||||
|
||||
# Find main CLI class (first class in module)
|
||||
for name, obj in inspect.getmembers(self.module):
|
||||
if inspect.isclass(obj) and obj.__module__ == self.module.__name__:
|
||||
self.cli_class = obj
|
||||
break
|
||||
|
||||
if not self.cli_class:
|
||||
print("Error: No CLI class found in module", file=sys.stderr)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error loading CLI module: {e}", file=sys.stderr)
|
||||
return False
|
||||
|
||||
def get_commands(self) -> Dict[str, Any]:
|
||||
"""Get all available commands"""
|
||||
if not self.cli_class:
|
||||
return {}
|
||||
|
||||
commands = {}
|
||||
instance = self.cli_class()
|
||||
|
||||
# Get methods from main class
|
||||
for name, method in inspect.getmembers(instance, predicate=inspect.ismethod):
|
||||
if not name.startswith('_'):
|
||||
commands[name] = {
|
||||
'type': 'method',
|
||||
'signature': str(inspect.signature(method)),
|
||||
'doc': inspect.getdoc(method) or 'No documentation'
|
||||
}
|
||||
|
||||
# Get nested classes (command groups)
|
||||
for name, obj in inspect.getmembers(self.cli_class):
|
||||
if inspect.isclass(obj) and not name.startswith('_'):
|
||||
commands[name] = {
|
||||
'type': 'command_group',
|
||||
'doc': inspect.getdoc(obj) or 'No documentation',
|
||||
'methods': {}
|
||||
}
|
||||
|
||||
# Get methods from nested class
|
||||
for method_name, method in inspect.getmembers(obj, predicate=inspect.isfunction):
|
||||
if not method_name.startswith('_'):
|
||||
commands[name]['methods'][method_name] = {
|
||||
'signature': str(inspect.signature(method)),
|
||||
'doc': inspect.getdoc(method) or 'No documentation'
|
||||
}
|
||||
|
||||
return commands
|
||||
|
||||
def test_instantiation(self) -> bool:
|
||||
"""Test if CLI class can be instantiated"""
|
||||
try:
|
||||
instance = self.cli_class()
|
||||
print("✅ CLI class instantiation: PASSED")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ CLI class instantiation: FAILED - {e}")
|
||||
return False
|
||||
|
||||
def test_method_signatures(self) -> bool:
|
||||
"""Test if all methods have valid signatures"""
|
||||
try:
|
||||
instance = self.cli_class()
|
||||
errors = []
|
||||
|
||||
for name, method in inspect.getmembers(instance, predicate=inspect.ismethod):
|
||||
if name.startswith('_'):
|
||||
continue
|
||||
|
||||
try:
|
||||
sig = inspect.signature(method)
|
||||
# Check for invalid parameter types
|
||||
for param_name, param in sig.parameters.items():
|
||||
if param.kind == inspect.Parameter.VAR_KEYWORD:
|
||||
errors.append(f"Method '{name}' uses **kwargs (works but not recommended)")
|
||||
except Exception as e:
|
||||
errors.append(f"Method '{name}' signature error: {e}")
|
||||
|
||||
if errors:
|
||||
print("⚠️ Method signatures: WARNINGS")
|
||||
for error in errors:
|
||||
print(f" • {error}")
|
||||
return True # Warnings, not failures
|
||||
else:
|
||||
print("✅ Method signatures: PASSED")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Method signatures: FAILED - {e}")
|
||||
return False
|
||||
|
||||
def test_docstrings(self) -> bool:
|
||||
"""Test if all public methods have docstrings"""
|
||||
try:
|
||||
instance = self.cli_class()
|
||||
missing = []
|
||||
|
||||
for name, method in inspect.getmembers(instance, predicate=inspect.ismethod):
|
||||
if name.startswith('_'):
|
||||
continue
|
||||
|
||||
doc = inspect.getdoc(method)
|
||||
if not doc:
|
||||
missing.append(name)
|
||||
|
||||
if missing:
|
||||
print("⚠️ Docstrings: WARNINGS")
|
||||
print(f" Missing docstrings for: {', '.join(missing)}")
|
||||
return True # Warnings, not failures
|
||||
else:
|
||||
print("✅ Docstrings: PASSED")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Docstrings: FAILED - {e}")
|
||||
return False
|
||||
|
||||
def print_summary(self):
|
||||
"""Print CLI summary"""
|
||||
commands = self.get_commands()
|
||||
|
||||
print(f"\n{'='*60}")
|
||||
print(f"Fire CLI Test Report: {self.filepath.name}")
|
||||
print(f"{'='*60}\n")
|
||||
|
||||
print(f"CLI Class: {self.cli_class.__name__}")
|
||||
print(f"Total Commands: {len(commands)}\n")
|
||||
|
||||
print("Available Commands:")
|
||||
for cmd_name, cmd_info in commands.items():
|
||||
if cmd_info['type'] == 'method':
|
||||
print(f" • {cmd_name}{cmd_info['signature']}")
|
||||
elif cmd_info['type'] == 'command_group':
|
||||
print(f" • {cmd_name}/ (command group)")
|
||||
for method_name, method_info in cmd_info['methods'].items():
|
||||
print(f" ◦ {method_name}{method_info['signature']}")
|
||||
|
||||
print()
|
||||
|
||||
def run_tests(self) -> bool:
|
||||
"""Run all tests"""
|
||||
print(f"\nTesting Fire CLI: {self.filepath.name}\n")
|
||||
|
||||
results = []
|
||||
results.append(self.test_instantiation())
|
||||
results.append(self.test_method_signatures())
|
||||
results.append(self.test_docstrings())
|
||||
|
||||
print()
|
||||
return all(results)
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: test-fire-cli.py <fire-cli-file.py> [--summary]")
|
||||
sys.exit(1)
|
||||
|
||||
filepath = Path(sys.argv[1])
|
||||
show_summary = '--summary' in sys.argv
|
||||
|
||||
if not filepath.exists():
|
||||
print(f"Error: File not found: {filepath}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
tester = FireCLITester(filepath)
|
||||
|
||||
if not tester.load_cli():
|
||||
sys.exit(1)
|
||||
|
||||
if show_summary:
|
||||
tester.print_summary()
|
||||
else:
|
||||
passed = tester.run_tests()
|
||||
tester.print_summary()
|
||||
sys.exit(0 if passed else 1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
194
skills/fire-patterns/scripts/validate-fire-cli.py
Executable file
194
skills/fire-patterns/scripts/validate-fire-cli.py
Executable file
@@ -0,0 +1,194 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Validate Fire CLI structure and docstrings"""
|
||||
|
||||
import ast
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import List, Dict, Tuple
|
||||
|
||||
|
||||
class FireCLIValidator:
|
||||
"""Validates Fire CLI Python files for proper structure"""
|
||||
|
||||
def __init__(self, filepath: Path):
|
||||
self.filepath = filepath
|
||||
self.errors: List[str] = []
|
||||
self.warnings: List[str] = []
|
||||
self.tree = None
|
||||
|
||||
def validate(self) -> bool:
|
||||
"""Run all validation checks"""
|
||||
if not self._parse_file():
|
||||
return False
|
||||
|
||||
self._check_fire_import()
|
||||
self._check_main_class()
|
||||
self._check_docstrings()
|
||||
self._check_fire_call()
|
||||
self._check_method_signatures()
|
||||
|
||||
return len(self.errors) == 0
|
||||
|
||||
def _parse_file(self) -> bool:
|
||||
"""Parse Python file into AST"""
|
||||
try:
|
||||
content = self.filepath.read_text()
|
||||
self.tree = ast.parse(content)
|
||||
return True
|
||||
except SyntaxError as e:
|
||||
self.errors.append(f"Syntax error: {e}")
|
||||
return False
|
||||
except Exception as e:
|
||||
self.errors.append(f"Failed to parse file: {e}")
|
||||
return False
|
||||
|
||||
def _check_fire_import(self):
|
||||
"""Check for fire import"""
|
||||
has_fire_import = False
|
||||
for node in ast.walk(self.tree):
|
||||
if isinstance(node, ast.Import):
|
||||
for alias in node.names:
|
||||
if alias.name == 'fire':
|
||||
has_fire_import = True
|
||||
break
|
||||
elif isinstance(node, ast.ImportFrom):
|
||||
if node.module == 'fire':
|
||||
has_fire_import = True
|
||||
break
|
||||
|
||||
if not has_fire_import:
|
||||
self.errors.append("Missing 'import fire' statement")
|
||||
|
||||
def _check_main_class(self):
|
||||
"""Check for main CLI class"""
|
||||
classes = [node for node in self.tree.body if isinstance(node, ast.ClassDef)]
|
||||
|
||||
if not classes:
|
||||
self.errors.append("No classes found - Fire CLI requires at least one class")
|
||||
return
|
||||
|
||||
main_class = classes[0] # Assume first class is main
|
||||
|
||||
# Check class docstring
|
||||
docstring = ast.get_docstring(main_class)
|
||||
if not docstring:
|
||||
self.warnings.append(f"Class '{main_class.name}' missing docstring")
|
||||
|
||||
# Check for __init__ method
|
||||
has_init = any(
|
||||
isinstance(node, ast.FunctionDef) and node.name == '__init__'
|
||||
for node in main_class.body
|
||||
)
|
||||
|
||||
if not has_init:
|
||||
self.warnings.append(f"Class '{main_class.name}' missing __init__ method")
|
||||
|
||||
def _check_docstrings(self):
|
||||
"""Check method docstrings"""
|
||||
for node in ast.walk(self.tree):
|
||||
if isinstance(node, ast.ClassDef):
|
||||
for item in node.body:
|
||||
if isinstance(item, ast.FunctionDef):
|
||||
# Skip private methods
|
||||
if item.name.startswith('_'):
|
||||
continue
|
||||
|
||||
docstring = ast.get_docstring(item)
|
||||
if not docstring:
|
||||
self.warnings.append(
|
||||
f"Method '{item.name}' missing docstring "
|
||||
"(used for Fire help text)"
|
||||
)
|
||||
else:
|
||||
# Check for Args section in docstring
|
||||
if item.args.args and len(item.args.args) > 1: # Skip 'self'
|
||||
if 'Args:' not in docstring:
|
||||
self.warnings.append(
|
||||
f"Method '{item.name}' docstring missing 'Args:' section"
|
||||
)
|
||||
|
||||
def _check_fire_call(self):
|
||||
"""Check for fire.Fire() call"""
|
||||
has_fire_call = False
|
||||
|
||||
for node in ast.walk(self.tree):
|
||||
if isinstance(node, ast.Call):
|
||||
if isinstance(node.func, ast.Attribute):
|
||||
if (isinstance(node.func.value, ast.Name) and
|
||||
node.func.value.id == 'fire' and
|
||||
node.func.attr == 'Fire'):
|
||||
has_fire_call = True
|
||||
break
|
||||
|
||||
if not has_fire_call:
|
||||
self.errors.append("Missing 'fire.Fire()' call (required to run CLI)")
|
||||
|
||||
def _check_method_signatures(self):
|
||||
"""Check method signatures for Fire compatibility"""
|
||||
for node in ast.walk(self.tree):
|
||||
if isinstance(node, ast.ClassDef):
|
||||
for item in node.body:
|
||||
if isinstance(item, ast.FunctionDef):
|
||||
# Skip private and special methods
|
||||
if item.name.startswith('_'):
|
||||
continue
|
||||
|
||||
# Check for *args or **kwargs (Fire handles these but warn)
|
||||
if item.args.vararg or item.args.kwarg:
|
||||
self.warnings.append(
|
||||
f"Method '{item.name}' uses *args or **kwargs - "
|
||||
"Fire will handle these, but explicit params are clearer"
|
||||
)
|
||||
|
||||
def print_results(self):
|
||||
"""Print validation results"""
|
||||
print(f"\n{'='*60}")
|
||||
print(f"Fire CLI Validation: {self.filepath.name}")
|
||||
print(f"{'='*60}\n")
|
||||
|
||||
if self.errors:
|
||||
print("❌ ERRORS:")
|
||||
for error in self.errors:
|
||||
print(f" • {error}")
|
||||
print()
|
||||
|
||||
if self.warnings:
|
||||
print("⚠️ WARNINGS:")
|
||||
for warning in self.warnings:
|
||||
print(f" • {warning}")
|
||||
print()
|
||||
|
||||
if not self.errors and not self.warnings:
|
||||
print("✅ All checks passed!")
|
||||
elif not self.errors:
|
||||
print(f"✅ Validation passed with {len(self.warnings)} warning(s)")
|
||||
else:
|
||||
print(f"❌ Validation failed with {len(self.errors)} error(s)")
|
||||
|
||||
print()
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print("Usage: validate-fire-cli.py <fire-cli-file.py>")
|
||||
sys.exit(1)
|
||||
|
||||
filepath = Path(sys.argv[1])
|
||||
|
||||
if not filepath.exists():
|
||||
print(f"Error: File not found: {filepath}")
|
||||
sys.exit(1)
|
||||
|
||||
if not filepath.suffix == '.py':
|
||||
print(f"Error: File must be a Python file (.py): {filepath}")
|
||||
sys.exit(1)
|
||||
|
||||
validator = FireCLIValidator(filepath)
|
||||
is_valid = validator.validate()
|
||||
validator.print_results()
|
||||
|
||||
sys.exit(0 if is_valid else 1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
59
skills/fire-patterns/templates/basic-fire-cli.py.template
Normal file
59
skills/fire-patterns/templates/basic-fire-cli.py.template
Normal file
@@ -0,0 +1,59 @@
|
||||
"""{{CLI_NAME}} - {{CLI_DESCRIPTION}}
|
||||
|
||||
Basic Fire CLI template with single-class structure.
|
||||
"""
|
||||
import fire
|
||||
from rich.console import Console
|
||||
|
||||
console = Console()
|
||||
|
||||
|
||||
class {{CLASS_NAME}}:
|
||||
"""{{CLI_DESCRIPTION}}"""
|
||||
|
||||
def __init__(self):
|
||||
self.version = "{{VERSION}}"
|
||||
self.verbose = False
|
||||
|
||||
def init(self, name='{{DEFAULT_PROJECT_NAME}}'):
|
||||
"""Initialize a new project
|
||||
|
||||
Args:
|
||||
name: Project name (default: {{DEFAULT_PROJECT_NAME}})
|
||||
"""
|
||||
console.print(f"[green]✓[/green] Initializing project: {name}")
|
||||
console.print(f"[dim]Version: {self.version}[/dim]")
|
||||
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]")
|
||||
# Add build logic here
|
||||
console.print("[green]✓[/green] Build complete!")
|
||||
|
||||
def version_info(self):
|
||||
"""Display version information"""
|
||||
console.print(f"[bold]{{CLI_NAME}}[/bold] version {self.version}")
|
||||
return {"version": self.version}
|
||||
|
||||
|
||||
def main():
|
||||
fire.Fire({{CLASS_NAME}})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
|
||||
# Usage examples:
|
||||
# python {{CLI_NAME_LOWER}}.py init --name=my-project
|
||||
# python {{CLI_NAME_LOWER}}.py build --verbose
|
||||
# python {{CLI_NAME_LOWER}}.py version-info
|
||||
228
skills/fire-patterns/templates/config-fire-cli.py.template
Normal file
228
skills/fire-patterns/templates/config-fire-cli.py.template
Normal file
@@ -0,0 +1,228 @@
|
||||
"""{{CLI_NAME}} - {{CLI_DESCRIPTION}}
|
||||
|
||||
Fire CLI with comprehensive configuration management.
|
||||
"""
|
||||
import fire
|
||||
from rich.console import Console
|
||||
from pathlib import Path
|
||||
import json
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
console = Console()
|
||||
|
||||
|
||||
class ConfigManager:
|
||||
"""Configuration management with file persistence"""
|
||||
|
||||
def __init__(self, config_file: Path):
|
||||
self.config_file = config_file
|
||||
self._ensure_config_exists()
|
||||
|
||||
def _ensure_config_exists(self) -> None:
|
||||
"""Create config file if it doesn't exist"""
|
||||
self.config_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
if not self.config_file.exists():
|
||||
self.config_file.write_text(json.dumps({}, indent=2))
|
||||
|
||||
def load(self) -> Dict[str, Any]:
|
||||
"""Load configuration from file"""
|
||||
try:
|
||||
return json.loads(self.config_file.read_text())
|
||||
except Exception as e:
|
||||
console.print(f"[red]Error loading config: {e}[/red]")
|
||||
return {}
|
||||
|
||||
def save(self, config: Dict[str, Any]) -> None:
|
||||
"""Save configuration to file"""
|
||||
try:
|
||||
self.config_file.write_text(json.dumps(config, indent=2))
|
||||
except Exception as e:
|
||||
console.print(f"[red]Error saving config: {e}[/red]")
|
||||
|
||||
|
||||
class {{CLASS_NAME}}:
|
||||
"""{{CLI_DESCRIPTION}}"""
|
||||
|
||||
def __init__(self):
|
||||
self.version = "{{VERSION}}"
|
||||
self.config_file = Path.home() / ".{{CLI_NAME_LOWER}}" / "config.json"
|
||||
self.config_manager = ConfigManager(self.config_file)
|
||||
|
||||
class Config:
|
||||
"""Configuration management commands"""
|
||||
|
||||
def __init__(self, parent):
|
||||
self.parent = parent
|
||||
self.manager = parent.config_manager
|
||||
|
||||
def get(self, key: str, default: Optional[str] = None) -> Optional[str]:
|
||||
"""Get configuration value
|
||||
|
||||
Args:
|
||||
key: Configuration key to retrieve
|
||||
default: Default value if key not found
|
||||
"""
|
||||
config = self.manager.load()
|
||||
value = config.get(key, default)
|
||||
|
||||
if value is None:
|
||||
console.print(f"[yellow]Key '{key}' not found[/yellow]")
|
||||
else:
|
||||
console.print(f"[blue]{key}[/blue]: {value}")
|
||||
|
||||
return value
|
||||
|
||||
def set(self, key: str, value: str) -> None:
|
||||
"""Set configuration value
|
||||
|
||||
Args:
|
||||
key: Configuration key to set
|
||||
value: Configuration value
|
||||
"""
|
||||
config = self.manager.load()
|
||||
config[key] = value
|
||||
self.manager.save(config)
|
||||
console.print(f"[green]✓[/green] Set {key} = {value}")
|
||||
|
||||
def unset(self, key: str) -> None:
|
||||
"""Remove configuration key
|
||||
|
||||
Args:
|
||||
key: Configuration key to remove
|
||||
"""
|
||||
config = self.manager.load()
|
||||
if key in config:
|
||||
del config[key]
|
||||
self.manager.save(config)
|
||||
console.print(f"[green]✓[/green] Removed {key}")
|
||||
else:
|
||||
console.print(f"[yellow]Key '{key}' not found[/yellow]")
|
||||
|
||||
def list(self) -> Dict[str, Any]:
|
||||
"""List all configuration values"""
|
||||
config = self.manager.load()
|
||||
|
||||
if not config:
|
||||
console.print("[yellow]No configuration values set[/yellow]")
|
||||
return {}
|
||||
|
||||
console.print("[bold]Configuration:[/bold]")
|
||||
for key, value in sorted(config.items()):
|
||||
console.print(f" [blue]{key}[/blue]: {value}")
|
||||
|
||||
return config
|
||||
|
||||
def reset(self, confirm: bool = False) -> None:
|
||||
"""Reset configuration to defaults
|
||||
|
||||
Args:
|
||||
confirm: Confirm reset operation
|
||||
"""
|
||||
if not confirm:
|
||||
console.print("[yellow]Use --confirm to reset configuration[/yellow]")
|
||||
return
|
||||
|
||||
self.manager.save({})
|
||||
console.print("[green]✓[/green] Configuration reset")
|
||||
|
||||
def path(self) -> str:
|
||||
"""Show configuration file path"""
|
||||
console.print(f"[blue]Config file:[/blue] {self.parent.config_file}")
|
||||
return str(self.parent.config_file)
|
||||
|
||||
def edit(self) -> None:
|
||||
"""Open configuration file in editor"""
|
||||
import os
|
||||
editor = os.environ.get('EDITOR', 'vim')
|
||||
console.print(f"[dim]Opening {self.parent.config_file} in {editor}[/dim]")
|
||||
os.system(f"{editor} {self.parent.config_file}")
|
||||
|
||||
def validate(self) -> bool:
|
||||
"""Validate configuration file"""
|
||||
try:
|
||||
config = self.manager.load()
|
||||
console.print("[green]✓[/green] Configuration is valid")
|
||||
console.print(f"[dim]Found {len(config)} keys[/dim]")
|
||||
return True
|
||||
except Exception as e:
|
||||
console.print(f"[red]✗ Configuration is invalid: {e}[/red]")
|
||||
return False
|
||||
|
||||
def export(self, output_file: Optional[str] = None) -> str:
|
||||
"""Export configuration to file
|
||||
|
||||
Args:
|
||||
output_file: Output file path (default: stdout)
|
||||
"""
|
||||
config = self.manager.load()
|
||||
output = json.dumps(config, indent=2)
|
||||
|
||||
if output_file:
|
||||
Path(output_file).write_text(output)
|
||||
console.print(f"[green]✓[/green] Exported to {output_file}")
|
||||
else:
|
||||
console.print(output)
|
||||
|
||||
return output
|
||||
|
||||
def import_config(self, input_file: str, merge: bool = False) -> None:
|
||||
"""Import configuration from file
|
||||
|
||||
Args:
|
||||
input_file: Input file path
|
||||
merge: Merge with existing config (default: False)
|
||||
"""
|
||||
try:
|
||||
new_config = json.loads(Path(input_file).read_text())
|
||||
|
||||
if merge:
|
||||
config = self.manager.load()
|
||||
config.update(new_config)
|
||||
else:
|
||||
config = new_config
|
||||
|
||||
self.manager.save(config)
|
||||
console.print(f"[green]✓[/green] Imported configuration from {input_file}")
|
||||
|
||||
except Exception as e:
|
||||
console.print(f"[red]Error importing config: {e}[/red]")
|
||||
|
||||
def __init__(self):
|
||||
self.version = "{{VERSION}}"
|
||||
self.config_file = Path.home() / ".{{CLI_NAME_LOWER}}" / "config.json"
|
||||
self.config_manager = ConfigManager(self.config_file)
|
||||
self.config = self.Config(self)
|
||||
|
||||
def info(self) -> Dict[str, Any]:
|
||||
"""Display CLI information"""
|
||||
console.print(f"[bold]{{CLI_NAME}}[/bold] v{self.version}")
|
||||
console.print(f"Config file: {self.config_file}")
|
||||
|
||||
config = self.config_manager.load()
|
||||
console.print(f"Config keys: {len(config)}")
|
||||
|
||||
return {
|
||||
"version": self.version,
|
||||
"config_file": str(self.config_file),
|
||||
"config_keys": len(config)
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
fire.Fire({{CLASS_NAME}})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
|
||||
# Usage examples:
|
||||
# python {{CLI_NAME_LOWER}}.py config get api_key
|
||||
# python {{CLI_NAME_LOWER}}.py config set api_key abc123
|
||||
# python {{CLI_NAME_LOWER}}.py config unset api_key
|
||||
# python {{CLI_NAME_LOWER}}.py config list
|
||||
# python {{CLI_NAME_LOWER}}.py config reset --confirm
|
||||
# python {{CLI_NAME_LOWER}}.py config path
|
||||
# python {{CLI_NAME_LOWER}}.py config validate
|
||||
# python {{CLI_NAME_LOWER}}.py config export output.json
|
||||
# python {{CLI_NAME_LOWER}}.py config import-config input.json --merge
|
||||
@@ -0,0 +1,268 @@
|
||||
"""{{CLI_NAME}} - {{CLI_DESCRIPTION}}
|
||||
|
||||
Complex multi-command Fire CLI with multiple command groups.
|
||||
"""
|
||||
import fire
|
||||
from rich.console import Console
|
||||
from rich.table import Table
|
||||
from pathlib import Path
|
||||
from typing import Optional, List
|
||||
import json
|
||||
|
||||
console = Console()
|
||||
|
||||
|
||||
class {{CLASS_NAME}}:
|
||||
"""{{CLI_DESCRIPTION}}"""
|
||||
|
||||
def __init__(self):
|
||||
self.version = "{{VERSION}}"
|
||||
self.config_file = Path.home() / ".{{CLI_NAME_LOWER}}" / "config.json"
|
||||
|
||||
class Project:
|
||||
"""Project management commands"""
|
||||
|
||||
def create(self, name: str, template: str = 'default', path: Optional[str] = None):
|
||||
"""Create a new project
|
||||
|
||||
Args:
|
||||
name: Project name
|
||||
template: Project template (default: default)
|
||||
path: Project path (default: current directory)
|
||||
"""
|
||||
project_path = Path(path) if path else Path.cwd() / name
|
||||
console.print(f"[green]✓[/green] Creating project: {name}")
|
||||
console.print(f"[dim]Template: {template}[/dim]")
|
||||
console.print(f"[dim]Path: {project_path}[/dim]")
|
||||
|
||||
def delete(self, name: str, confirm: bool = False):
|
||||
"""Delete a project
|
||||
|
||||
Args:
|
||||
name: Project name
|
||||
confirm: Confirm deletion
|
||||
"""
|
||||
if not confirm:
|
||||
console.print("[yellow]Use --confirm to delete project[/yellow]")
|
||||
return
|
||||
|
||||
console.print(f"[red]Deleting project: {name}[/red]")
|
||||
|
||||
def list(self):
|
||||
"""List all projects"""
|
||||
projects = [
|
||||
{"name": "project-a", "status": "active", "version": "1.0.0"},
|
||||
{"name": "project-b", "status": "archived", "version": "2.1.0"},
|
||||
]
|
||||
|
||||
table = Table(title="Projects")
|
||||
table.add_column("Name", style="cyan")
|
||||
table.add_column("Status", style="green")
|
||||
table.add_column("Version", style="yellow")
|
||||
|
||||
for proj in projects:
|
||||
table.add_row(proj['name'], proj['status'], proj['version'])
|
||||
|
||||
console.print(table)
|
||||
return projects
|
||||
|
||||
class Build:
|
||||
"""Build management commands"""
|
||||
|
||||
def start(self, target: str, parallel: bool = False, workers: int = 4):
|
||||
"""Start build process
|
||||
|
||||
Args:
|
||||
target: Build target
|
||||
parallel: Enable parallel builds
|
||||
workers: Number of parallel workers
|
||||
"""
|
||||
console.print(f"[cyan]Building target: {target}[/cyan]")
|
||||
if parallel:
|
||||
console.print(f"[dim]Parallel mode with {workers} workers[/dim]")
|
||||
|
||||
def clean(self, deep: bool = False):
|
||||
"""Clean build artifacts
|
||||
|
||||
Args:
|
||||
deep: Perform deep clean (includes cache)
|
||||
"""
|
||||
console.print("[yellow]Cleaning build artifacts...[/yellow]")
|
||||
if deep:
|
||||
console.print("[dim]Deep clean: removing cache[/dim]")
|
||||
|
||||
def status(self):
|
||||
"""Show build status"""
|
||||
console.print("[bold]Build Status:[/bold]")
|
||||
console.print(" Last build: [green]Success[/green]")
|
||||
console.print(" Artifacts: 42 files")
|
||||
|
||||
class Deploy:
|
||||
"""Deployment commands"""
|
||||
|
||||
def start(
|
||||
self,
|
||||
environment: str,
|
||||
service: Optional[str] = None,
|
||||
force: bool = False,
|
||||
mode: str = 'safe'
|
||||
):
|
||||
"""Deploy to environment
|
||||
|
||||
Args:
|
||||
environment: Target environment (dev, staging, prod)
|
||||
service: Specific service to deploy (default: all)
|
||||
force: Force deployment
|
||||
mode: Deployment mode (fast, safe, rollback)
|
||||
"""
|
||||
console.print(f"[cyan]Deploying to {environment}[/cyan]")
|
||||
if service:
|
||||
console.print(f"[dim]Service: {service}[/dim]")
|
||||
console.print(f"[dim]Mode: {mode}[/dim]")
|
||||
if force:
|
||||
console.print("[yellow]⚠ Force mode enabled[/yellow]")
|
||||
|
||||
def rollback(self, environment: str, version: Optional[str] = None):
|
||||
"""Rollback deployment
|
||||
|
||||
Args:
|
||||
environment: Target environment
|
||||
version: Version to rollback to (default: previous)
|
||||
"""
|
||||
target = version or "previous"
|
||||
console.print(f"[yellow]Rolling back {environment} to {target}[/yellow]")
|
||||
|
||||
def status(self, environment: str):
|
||||
"""Show deployment status
|
||||
|
||||
Args:
|
||||
environment: Target environment
|
||||
"""
|
||||
console.print(f"[bold]Deployment Status: {environment}[/bold]")
|
||||
console.print(" Status: [green]Active[/green]")
|
||||
console.print(" Version: 1.2.3")
|
||||
console.print(" Last deployed: 2 hours ago")
|
||||
|
||||
def history(self, environment: str, limit: int = 10):
|
||||
"""Show deployment history
|
||||
|
||||
Args:
|
||||
environment: Target environment
|
||||
limit: Number of records to show
|
||||
"""
|
||||
console.print(f"[bold]Deployment History: {environment}[/bold]")
|
||||
for i in range(limit):
|
||||
console.print(f" {i+1}. Version 1.{i}.0 - 2 days ago")
|
||||
|
||||
class Database:
|
||||
"""Database management commands"""
|
||||
|
||||
def migrate(self, direction: str = 'up', steps: Optional[int] = None):
|
||||
"""Run database migrations
|
||||
|
||||
Args:
|
||||
direction: Migration direction (up, down)
|
||||
steps: Number of migrations to run (default: all)
|
||||
"""
|
||||
console.print(f"[cyan]Running migrations {direction}[/cyan]")
|
||||
if steps:
|
||||
console.print(f"[dim]Steps: {steps}[/dim]")
|
||||
|
||||
def seed(self, dataset: str = 'default'):
|
||||
"""Seed database
|
||||
|
||||
Args:
|
||||
dataset: Dataset to use (default, test, production)
|
||||
"""
|
||||
console.print(f"[green]Seeding database with {dataset} dataset[/green]")
|
||||
|
||||
def reset(self, confirm: bool = False):
|
||||
"""Reset database
|
||||
|
||||
Args:
|
||||
confirm: Confirm reset operation
|
||||
"""
|
||||
if not confirm:
|
||||
console.print("[yellow]Use --confirm to reset database[/yellow]")
|
||||
return
|
||||
|
||||
console.print("[red]Resetting database...[/red]")
|
||||
|
||||
def backup(self, output: Optional[str] = None):
|
||||
"""Backup database
|
||||
|
||||
Args:
|
||||
output: Output file path (default: auto-generated)
|
||||
"""
|
||||
filename = output or f"backup-{self._timestamp()}.sql"
|
||||
console.print(f"[cyan]Creating backup: {filename}[/cyan]")
|
||||
|
||||
@staticmethod
|
||||
def _timestamp():
|
||||
from datetime import datetime
|
||||
return datetime.now().strftime("%Y%m%d-%H%M%S")
|
||||
|
||||
class Config:
|
||||
"""Configuration commands"""
|
||||
|
||||
def __init__(self, parent):
|
||||
self.parent = parent
|
||||
|
||||
def get(self, key: str):
|
||||
"""Get configuration value"""
|
||||
console.print(f"[blue]{key}[/blue]: value")
|
||||
|
||||
def set(self, key: str, value: str):
|
||||
"""Set configuration value"""
|
||||
console.print(f"[green]✓[/green] Set {key} = {value}")
|
||||
|
||||
def list(self):
|
||||
"""List all configuration"""
|
||||
console.print("[bold]Configuration:[/bold]")
|
||||
console.print(" api_key: abc123")
|
||||
console.print(" endpoint: https://api.example.com")
|
||||
|
||||
def __init__(self):
|
||||
self.version = "{{VERSION}}"
|
||||
self.config_file = Path.home() / ".{{CLI_NAME_LOWER}}" / "config.json"
|
||||
self.project = self.Project()
|
||||
self.build = self.Build()
|
||||
self.deploy = self.Deploy()
|
||||
self.database = self.Database()
|
||||
self.config = self.Config(self)
|
||||
|
||||
def version_info(self):
|
||||
"""Display version information"""
|
||||
console.print(f"[bold]{{CLI_NAME}}[/bold] version {self.version}")
|
||||
return {"version": self.version}
|
||||
|
||||
def info(self):
|
||||
"""Display CLI information"""
|
||||
console.print(f"[bold]{{CLI_NAME}}[/bold] v{self.version}")
|
||||
console.print(f"Config: {self.config_file}")
|
||||
console.print("\n[bold]Available Commands:[/bold]")
|
||||
console.print(" project - Project management")
|
||||
console.print(" build - Build management")
|
||||
console.print(" deploy - Deployment commands")
|
||||
console.print(" database - Database operations")
|
||||
console.print(" config - Configuration management")
|
||||
|
||||
|
||||
def main():
|
||||
fire.Fire({{CLASS_NAME}})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
|
||||
# Usage examples:
|
||||
# python {{CLI_NAME_LOWER}}.py project create my-app --template=react
|
||||
# python {{CLI_NAME_LOWER}}.py project list
|
||||
# python {{CLI_NAME_LOWER}}.py build start production --parallel
|
||||
# python {{CLI_NAME_LOWER}}.py build clean --deep
|
||||
# python {{CLI_NAME_LOWER}}.py deploy start staging --service=api
|
||||
# python {{CLI_NAME_LOWER}}.py deploy rollback production --version=1.2.0
|
||||
# python {{CLI_NAME_LOWER}}.py database migrate --direction=up
|
||||
# python {{CLI_NAME_LOWER}}.py database backup
|
||||
# python {{CLI_NAME_LOWER}}.py config get api_key
|
||||
148
skills/fire-patterns/templates/nested-fire-cli.py.template
Normal file
148
skills/fire-patterns/templates/nested-fire-cli.py.template
Normal file
@@ -0,0 +1,148 @@
|
||||
"""{{CLI_NAME}} - {{CLI_DESCRIPTION}}
|
||||
|
||||
Nested Fire CLI template with command groups.
|
||||
"""
|
||||
import fire
|
||||
from rich.console import Console
|
||||
from pathlib import Path
|
||||
import json
|
||||
|
||||
console = Console()
|
||||
|
||||
|
||||
class {{CLASS_NAME}}:
|
||||
"""{{CLI_DESCRIPTION}}"""
|
||||
|
||||
def __init__(self):
|
||||
self.version = "{{VERSION}}"
|
||||
self.config_file = Path.home() / ".{{CLI_NAME_LOWER}}" / "config.json"
|
||||
|
||||
class Config:
|
||||
"""Configuration management commands"""
|
||||
|
||||
def __init__(self, parent):
|
||||
self.parent = parent
|
||||
|
||||
def get(self, key):
|
||||
"""Get configuration value
|
||||
|
||||
Args:
|
||||
key: Configuration key to retrieve
|
||||
"""
|
||||
config = self._load_config()
|
||||
value = config.get(key)
|
||||
if value is None:
|
||||
console.print(f"[yellow]Key '{key}' not found[/yellow]")
|
||||
else:
|
||||
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 list(self):
|
||||
"""List all configuration values"""
|
||||
config = self._load_config()
|
||||
if not config:
|
||||
console.print("[yellow]No configuration values set[/yellow]")
|
||||
return
|
||||
|
||||
console.print("[bold]Configuration:[/bold]")
|
||||
for key, value in config.items():
|
||||
console.print(f" [blue]{key}[/blue]: {value}")
|
||||
return config
|
||||
|
||||
def reset(self):
|
||||
"""Reset configuration to defaults"""
|
||||
self._save_config({})
|
||||
console.print("[green]✓[/green] Configuration reset")
|
||||
|
||||
def _load_config(self):
|
||||
"""Load configuration from file"""
|
||||
if not self.parent.config_file.exists():
|
||||
return {}
|
||||
try:
|
||||
return json.loads(self.parent.config_file.read_text())
|
||||
except Exception as e:
|
||||
console.print(f"[red]Error loading config: {e}[/red]")
|
||||
return {}
|
||||
|
||||
def _save_config(self, config):
|
||||
"""Save configuration to file"""
|
||||
self.parent.config_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
self.parent.config_file.write_text(json.dumps(config, indent=2))
|
||||
|
||||
class {{SUBCOMMAND_GROUP_NAME}}:
|
||||
"""{{SUBCOMMAND_GROUP_DESCRIPTION}}"""
|
||||
|
||||
def create(self, name, template='default'):
|
||||
"""Create new {{RESOURCE_NAME}}
|
||||
|
||||
Args:
|
||||
name: {{RESOURCE_NAME}} name
|
||||
template: Template to use (default: default)
|
||||
"""
|
||||
console.print(f"[cyan]Creating {{RESOURCE_NAME}}: {name}[/cyan]")
|
||||
console.print(f"[dim]Using template: {template}[/dim]")
|
||||
console.print("[green]✓[/green] {{RESOURCE_NAME}} created successfully")
|
||||
|
||||
def delete(self, name, confirm=False):
|
||||
"""Delete {{RESOURCE_NAME}}
|
||||
|
||||
Args:
|
||||
name: {{RESOURCE_NAME}} name
|
||||
confirm: Confirm deletion (default: False)
|
||||
"""
|
||||
if not confirm:
|
||||
console.print("[yellow]⚠ Use --confirm to delete {{RESOURCE_NAME}}[/yellow]")
|
||||
return
|
||||
|
||||
console.print(f"[red]Deleting {{RESOURCE_NAME}}: {name}[/red]")
|
||||
console.print("[green]✓[/green] {{RESOURCE_NAME}} deleted")
|
||||
|
||||
def list(self):
|
||||
"""List all {{RESOURCE_NAME}}s"""
|
||||
console.print("[bold]{{RESOURCE_NAME}}s:[/bold]")
|
||||
# Add list logic here
|
||||
items = ["item1", "item2", "item3"]
|
||||
for item in items:
|
||||
console.print(f" • {item}")
|
||||
return items
|
||||
|
||||
def __init__(self):
|
||||
self.version = "{{VERSION}}"
|
||||
self.config_file = Path.home() / ".{{CLI_NAME_LOWER}}" / "config.json"
|
||||
self.config = self.Config(self)
|
||||
self.{{SUBCOMMAND_GROUP_NAME_LOWER}} = self.{{SUBCOMMAND_GROUP_NAME}}()
|
||||
|
||||
def info(self):
|
||||
"""Display CLI information"""
|
||||
console.print(f"[bold]{{CLI_NAME}}[/bold] v{self.version}")
|
||||
console.print(f"Config file: {self.config_file}")
|
||||
return {"version": self.version, "config_file": str(self.config_file)}
|
||||
|
||||
|
||||
def main():
|
||||
fire.Fire({{CLASS_NAME}})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
|
||||
# Usage examples:
|
||||
# python {{CLI_NAME_LOWER}}.py config get api_key
|
||||
# python {{CLI_NAME_LOWER}}.py config set api_key abc123
|
||||
# python {{CLI_NAME_LOWER}}.py config list
|
||||
# python {{CLI_NAME_LOWER}}.py {{SUBCOMMAND_GROUP_NAME_LOWER}} create my-item
|
||||
# python {{CLI_NAME_LOWER}}.py {{SUBCOMMAND_GROUP_NAME_LOWER}} delete my-item --confirm
|
||||
# python {{CLI_NAME_LOWER}}.py {{SUBCOMMAND_GROUP_NAME_LOWER}} list
|
||||
161
skills/fire-patterns/templates/rich-fire-cli.py.template
Normal file
161
skills/fire-patterns/templates/rich-fire-cli.py.template
Normal file
@@ -0,0 +1,161 @@
|
||||
"""{{CLI_NAME}} - {{CLI_DESCRIPTION}}
|
||||
|
||||
Fire CLI with rich console formatting and output.
|
||||
"""
|
||||
import fire
|
||||
from rich.console import Console
|
||||
from rich.table import Table
|
||||
from rich.panel import Panel
|
||||
from rich.progress import track
|
||||
from rich.tree import Tree
|
||||
import time
|
||||
|
||||
console = Console()
|
||||
|
||||
|
||||
class {{CLASS_NAME}}:
|
||||
"""{{CLI_DESCRIPTION}}"""
|
||||
|
||||
def __init__(self):
|
||||
self.version = "{{VERSION}}"
|
||||
|
||||
def status(self):
|
||||
"""Display application status with rich formatting"""
|
||||
panel = Panel(
|
||||
"[bold green]Application Running[/bold green]\n"
|
||||
f"Version: {self.version}\n"
|
||||
"Status: [green]Active[/green]",
|
||||
title="{{CLI_NAME}} Status",
|
||||
border_style="green"
|
||||
)
|
||||
console.print(panel)
|
||||
|
||||
return {
|
||||
"status": "active",
|
||||
"version": self.version
|
||||
}
|
||||
|
||||
def list_items(self, format='table'):
|
||||
"""List items with formatted output
|
||||
|
||||
Args:
|
||||
format: Output format - table, tree, or json (default: table)
|
||||
"""
|
||||
items = [
|
||||
{"id": 1, "name": "Item Alpha", "status": "active", "count": 42},
|
||||
{"id": 2, "name": "Item Beta", "status": "pending", "count": 23},
|
||||
{"id": 3, "name": "Item Gamma", "status": "completed", "count": 67},
|
||||
]
|
||||
|
||||
if format == 'table':
|
||||
table = Table(title="{{CLI_NAME}} Items", 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("Count", justify="right", style="blue")
|
||||
|
||||
for item in items:
|
||||
status_color = {
|
||||
"active": "green",
|
||||
"pending": "yellow",
|
||||
"completed": "blue"
|
||||
}.get(item['status'], "white")
|
||||
|
||||
table.add_row(
|
||||
str(item['id']),
|
||||
item['name'],
|
||||
f"[{status_color}]{item['status']}[/{status_color}]",
|
||||
str(item['count'])
|
||||
)
|
||||
|
||||
console.print(table)
|
||||
|
||||
elif format == 'tree':
|
||||
tree = Tree("{{CLI_NAME}} Items")
|
||||
for item in items:
|
||||
branch = tree.add(f"[bold]{item['name']}[/bold]")
|
||||
branch.add(f"ID: {item['id']}")
|
||||
branch.add(f"Status: {item['status']}")
|
||||
branch.add(f"Count: {item['count']}")
|
||||
|
||||
console.print(tree)
|
||||
|
||||
else: # json
|
||||
import json
|
||||
output = json.dumps(items, indent=2)
|
||||
console.print(output)
|
||||
|
||||
return items
|
||||
|
||||
def process(self, count=100, delay=0.01):
|
||||
"""Process items with progress bar
|
||||
|
||||
Args:
|
||||
count: Number of items to process (default: 100)
|
||||
delay: Delay between items in seconds (default: 0.01)
|
||||
"""
|
||||
console.print(Panel(
|
||||
f"[bold cyan]Processing {count} items[/bold cyan]",
|
||||
border_style="cyan"
|
||||
))
|
||||
|
||||
results = []
|
||||
for i in track(range(count), description="Processing..."):
|
||||
time.sleep(delay)
|
||||
results.append(i)
|
||||
|
||||
console.print("[green]✓[/green] Processing complete!")
|
||||
console.print(f"[dim]Processed {len(results)} items[/dim]")
|
||||
|
||||
return {"processed": len(results)}
|
||||
|
||||
def build(self, target='production', verbose=False):
|
||||
"""Build project with formatted output
|
||||
|
||||
Args:
|
||||
target: Build target environment (default: production)
|
||||
verbose: Show detailed build information (default: False)
|
||||
"""
|
||||
console.print(Panel(
|
||||
f"[bold]Building for {target}[/bold]",
|
||||
title="Build Process",
|
||||
border_style="blue"
|
||||
))
|
||||
|
||||
steps = [
|
||||
"Cleaning build directory",
|
||||
"Installing dependencies",
|
||||
"Compiling source files",
|
||||
"Running tests",
|
||||
"Packaging artifacts"
|
||||
]
|
||||
|
||||
for step in steps:
|
||||
console.print(f"[cyan]→[/cyan] {step}...")
|
||||
if verbose:
|
||||
console.print(f" [dim]Details for: {step}[/dim]")
|
||||
time.sleep(0.3)
|
||||
|
||||
console.print("\n[green]✓[/green] Build successful!")
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"target": target,
|
||||
"steps_completed": len(steps)
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
fire.Fire({{CLASS_NAME}})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
|
||||
# Usage examples:
|
||||
# python {{CLI_NAME_LOWER}}.py status
|
||||
# python {{CLI_NAME_LOWER}}.py list-items
|
||||
# python {{CLI_NAME_LOWER}}.py list-items --format=tree
|
||||
# python {{CLI_NAME_LOWER}}.py process --count=50
|
||||
# python {{CLI_NAME_LOWER}}.py build --target=staging --verbose
|
||||
206
skills/fire-patterns/templates/typed-fire-cli.py.template
Normal file
206
skills/fire-patterns/templates/typed-fire-cli.py.template
Normal file
@@ -0,0 +1,206 @@
|
||||
"""{{CLI_NAME}} - {{CLI_DESCRIPTION}}
|
||||
|
||||
Type-annotated Fire CLI template with full type hints.
|
||||
"""
|
||||
import fire
|
||||
from rich.console import Console
|
||||
from typing import Optional, List, Dict, Any, Literal
|
||||
from pathlib import Path
|
||||
from enum import Enum
|
||||
|
||||
console = Console()
|
||||
|
||||
|
||||
class Environment(str, Enum):
|
||||
"""Deployment environment options"""
|
||||
DEV = "dev"
|
||||
STAGING = "staging"
|
||||
PRODUCTION = "production"
|
||||
|
||||
|
||||
class OutputFormat(str, Enum):
|
||||
"""Output format options"""
|
||||
JSON = "json"
|
||||
YAML = "yaml"
|
||||
TEXT = "text"
|
||||
|
||||
|
||||
class {{CLASS_NAME}}:
|
||||
"""{{CLI_DESCRIPTION}}"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.version: str = "{{VERSION}}"
|
||||
self.verbose: bool = False
|
||||
|
||||
def init(
|
||||
self,
|
||||
name: str,
|
||||
template: str = 'default',
|
||||
path: Optional[Path] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""Initialize a new project
|
||||
|
||||
Args:
|
||||
name: Project name
|
||||
template: Template to use (default: default)
|
||||
path: Project path (default: current directory)
|
||||
|
||||
Returns:
|
||||
Dictionary with initialization status
|
||||
"""
|
||||
project_path = path or Path.cwd() / name
|
||||
console.print(f"[green]✓[/green] Initializing project: {name}")
|
||||
console.print(f"[dim]Template: {template}[/dim]")
|
||||
console.print(f"[dim]Path: {project_path}[/dim]")
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"project": name,
|
||||
"template": template,
|
||||
"path": str(project_path)
|
||||
}
|
||||
|
||||
def deploy(
|
||||
self,
|
||||
environment: Environment,
|
||||
force: bool = False,
|
||||
mode: Literal['fast', 'safe', 'rollback'] = 'safe',
|
||||
services: Optional[List[str]] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""Deploy to specified environment
|
||||
|
||||
Args:
|
||||
environment: Target environment (dev, staging, production)
|
||||
force: Force deployment without confirmation (default: False)
|
||||
mode: Deployment mode - fast, safe, or rollback (default: safe)
|
||||
services: List of services to deploy (default: all)
|
||||
|
||||
Returns:
|
||||
Dictionary with deployment status
|
||||
"""
|
||||
console.print(f"[cyan]Deploying to {environment.value} in {mode} mode[/cyan]")
|
||||
|
||||
if force:
|
||||
console.print("[yellow]⚠ Force mode enabled[/yellow]")
|
||||
|
||||
if services:
|
||||
console.print(f"[dim]Services: {', '.join(services)}[/dim]")
|
||||
else:
|
||||
console.print("[dim]Deploying all services[/dim]")
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"environment": environment.value,
|
||||
"mode": mode,
|
||||
"force": force,
|
||||
"services": services or ["all"]
|
||||
}
|
||||
|
||||
def export(
|
||||
self,
|
||||
output_format: OutputFormat = OutputFormat.JSON,
|
||||
output_file: Optional[Path] = None,
|
||||
pretty: bool = True
|
||||
) -> str:
|
||||
"""Export data in specified format
|
||||
|
||||
Args:
|
||||
output_format: Output format (json, yaml, text)
|
||||
output_file: Output file path (default: stdout)
|
||||
pretty: Pretty-print output (default: True)
|
||||
|
||||
Returns:
|
||||
Exported data as string
|
||||
"""
|
||||
data = {
|
||||
"version": self.version,
|
||||
"items": [1, 2, 3],
|
||||
"total": 3
|
||||
}
|
||||
|
||||
if output_format == OutputFormat.JSON:
|
||||
import json
|
||||
output = json.dumps(data, indent=2 if pretty else None)
|
||||
elif output_format == OutputFormat.YAML:
|
||||
output = "version: {}\nitems:\n - 1\n - 2\n - 3\ntotal: 3".format(self.version)
|
||||
else:
|
||||
output = f"Version: {self.version}\nTotal items: {data['total']}"
|
||||
|
||||
if output_file:
|
||||
output_file.write_text(output)
|
||||
console.print(f"[green]✓[/green] Exported to {output_file}")
|
||||
else:
|
||||
console.print(output)
|
||||
|
||||
return output
|
||||
|
||||
def build(
|
||||
self,
|
||||
targets: List[str],
|
||||
parallel: bool = False,
|
||||
max_workers: int = 4,
|
||||
verbose: bool = False
|
||||
) -> Dict[str, Any]:
|
||||
"""Build specified targets
|
||||
|
||||
Args:
|
||||
targets: List of build targets
|
||||
parallel: Enable parallel builds (default: False)
|
||||
max_workers: Maximum parallel workers (default: 4)
|
||||
verbose: Enable verbose output (default: False)
|
||||
|
||||
Returns:
|
||||
Dictionary with build results
|
||||
"""
|
||||
self.verbose = verbose
|
||||
|
||||
console.print(f"[cyan]Building targets: {', '.join(targets)}[/cyan]")
|
||||
|
||||
if parallel:
|
||||
console.print(f"[dim]Parallel mode with {max_workers} workers[/dim]")
|
||||
|
||||
if self.verbose:
|
||||
console.print("[dim]Verbose mode enabled[/dim]")
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"targets": targets,
|
||||
"parallel": parallel,
|
||||
"max_workers": max_workers
|
||||
}
|
||||
|
||||
def config_get(self, key: str) -> Optional[str]:
|
||||
"""Get configuration value
|
||||
|
||||
Args:
|
||||
key: Configuration key
|
||||
|
||||
Returns:
|
||||
Configuration value or None
|
||||
"""
|
||||
# Mock configuration
|
||||
config = {"api_key": "abc123", "endpoint": "https://api.example.com"}
|
||||
value = config.get(key)
|
||||
|
||||
if value:
|
||||
console.print(f"[blue]{key}[/blue]: {value}")
|
||||
else:
|
||||
console.print(f"[yellow]Key '{key}' not found[/yellow]")
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def main() -> None:
|
||||
fire.Fire({{CLASS_NAME}})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
|
||||
# Usage examples:
|
||||
# python {{CLI_NAME_LOWER}}.py init my-project --template=react
|
||||
# python {{CLI_NAME_LOWER}}.py deploy production --force --mode=fast
|
||||
# python {{CLI_NAME_LOWER}}.py export --output-format=yaml
|
||||
# python {{CLI_NAME_LOWER}}.py build web api --parallel --max-workers=8
|
||||
# python {{CLI_NAME_LOWER}}.py config-get api_key
|
||||
Reference in New Issue
Block a user