Initial commit

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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()

View 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}"

View 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()

View 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()

View 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

View 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

View File

@@ -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

View 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

View 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

View 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