This commit includes cleanup from model refactoring and terminal client
modularization for better code organization and maintainability.
## Game Models Refactor
**Removed RunnerState class:**
- Eliminated separate RunnerState model (was redundant)
- Replaced runners: List[RunnerState] with direct base references:
- on_first: Optional[LineupPlayerState]
- on_second: Optional[LineupPlayerState]
- on_third: Optional[LineupPlayerState]
- Updated helper methods:
- get_runner_at_base() now returns LineupPlayerState directly
- get_all_runners() returns List[Tuple[int, LineupPlayerState]]
- is_runner_on_X() simplified to direct None checks
**Benefits:**
- Matches database structure (plays table has on_first_id, etc.)
- Simpler state management (direct references vs list management)
- Better type safety (LineupPlayerState vs generic runner)
- Easier to work with in game engine logic
**Updated files:**
- app/models/game_models.py - Removed RunnerState, updated GameState
- app/core/play_resolver.py - Use get_all_runners() instead of state.runners
- app/core/validators.py - Updated runner access patterns
- tests/unit/models/test_game_models.py - Updated test assertions
- tests/unit/core/test_play_resolver.py - Updated test data
- tests/unit/core/test_validators.py - Updated test data
## Terminal Client Refactor
**Modularization (DRY principle):**
Created separate modules for better code organization:
1. **terminal_client/commands.py** (10,243 bytes)
- Shared command functions for game operations
- Used by both CLI (main.py) and REPL (repl.py)
- Functions: submit_defensive_decision, submit_offensive_decision,
resolve_play, quick_play_sequence
- Single source of truth for command logic
2. **terminal_client/arg_parser.py** (7,280 bytes)
- Centralized argument parsing and validation
- Handles defensive/offensive decision arguments
- Validates formats (alignment, depths, hold runners, steal attempts)
3. **terminal_client/completions.py** (10,357 bytes)
- TAB completion support for REPL mode
- Command completions, option completions, dynamic completions
- Game ID completions, defensive/offensive option suggestions
4. **terminal_client/help_text.py** (10,839 bytes)
- Centralized help text and command documentation
- Detailed command descriptions
- Usage examples for all commands
**Updated main modules:**
- terminal_client/main.py - Simplified by using shared commands module
- terminal_client/repl.py - Cleaner with shared functions and completions
**Benefits:**
- DRY: Behavior consistent between CLI and REPL modes
- Maintainability: Changes in one place affect both interfaces
- Testability: Can test commands module independently
- Organization: Clear separation of concerns
## Documentation
**New files:**
- app/models/visual_model_relationships.md
- Visual documentation of model relationships
- Helps understand data flow between models
- terminal_client/update_docs/ (6 phase documentation files)
- Phased documentation for terminal client evolution
- Historical context for implementation decisions
## Tests
**New test files:**
- tests/unit/terminal_client/__init__.py
- tests/unit/terminal_client/test_arg_parser.py
- tests/unit/terminal_client/test_commands.py
- tests/unit/terminal_client/test_completions.py
- tests/unit/terminal_client/test_help_text.py
**Updated tests:**
- Integration tests updated for new runner model
- Unit tests updated for model changes
- All tests passing with new structure
## Summary
- ✅ Simplified game state model (removed RunnerState)
- ✅ Better alignment with database structure
- ✅ Modularized terminal client (DRY principle)
- ✅ Shared command logic between CLI and REPL
- ✅ Comprehensive test coverage
- ✅ Improved documentation
Total changes: 26 files modified/created
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
665 lines
22 KiB
Markdown
665 lines
22 KiB
Markdown
● Terminal Client Improvement Plan - Part 4: Detailed Help System
|
|
|
|
Overview
|
|
|
|
Enhance the REPL help system with detailed documentation, examples, and better formatting. This makes the terminal
|
|
client self-documenting and easier to use for new developers.
|
|
|
|
Files to Create
|
|
|
|
1. Create backend/terminal_client/help_text.py
|
|
|
|
"""
|
|
Help text and documentation for terminal client commands.
|
|
|
|
Provides detailed, formatted help text for all REPL commands
|
|
with usage examples and option descriptions.
|
|
|
|
Author: Claude
|
|
Date: 2025-10-27
|
|
"""
|
|
from typing import Dict
|
|
from rich.console import Console
|
|
from rich.table import Table
|
|
from rich.panel import Panel
|
|
from rich.markdown import Markdown
|
|
from rich import box
|
|
|
|
console = Console()
|
|
|
|
|
|
class HelpFormatter:
|
|
"""Format and display help text for commands."""
|
|
|
|
@staticmethod
|
|
def show_command_help(command_name: str, help_data: Dict) -> None:
|
|
"""
|
|
Display detailed help for a specific command.
|
|
|
|
Args:
|
|
command_name: Name of the command
|
|
help_data: Dictionary with help information
|
|
{
|
|
'summary': 'Brief description',
|
|
'usage': 'command [OPTIONS]',
|
|
'options': [
|
|
{'name': '--option', 'type': 'TYPE', 'desc': 'Description'}
|
|
],
|
|
'examples': ['example 1', 'example 2']
|
|
}
|
|
"""
|
|
# Build help text
|
|
help_text = []
|
|
|
|
# Summary
|
|
help_text.append(f"**{command_name}** - {help_data.get('summary', 'No description')}")
|
|
help_text.append("")
|
|
|
|
# Usage
|
|
if 'usage' in help_data:
|
|
help_text.append("**USAGE:**")
|
|
help_text.append(f" {help_data['usage']}")
|
|
help_text.append("")
|
|
|
|
# Options
|
|
if 'options' in help_data and help_data['options']:
|
|
help_text.append("**OPTIONS:**")
|
|
|
|
# Create options table
|
|
table = Table(box=box.SIMPLE, show_header=False, padding=(0, 2))
|
|
table.add_column("Option", style="cyan", no_wrap=True)
|
|
table.add_column("Type", style="yellow")
|
|
table.add_column("Description", style="white")
|
|
|
|
for opt in help_data['options']:
|
|
table.add_row(
|
|
opt['name'],
|
|
opt.get('type', ''),
|
|
opt.get('desc', '')
|
|
)
|
|
|
|
console.print(table)
|
|
console.print()
|
|
|
|
# Examples
|
|
if 'examples' in help_data and help_data['examples']:
|
|
help_text.append("**EXAMPLES:**")
|
|
for example in help_data['examples']:
|
|
help_text.append(f" {example}")
|
|
help_text.append("")
|
|
|
|
# Display in panel
|
|
if help_text:
|
|
md = Markdown("\n".join(help_text[:3])) # Just summary and usage
|
|
panel = Panel(
|
|
md,
|
|
title=f"[bold cyan]Help: {command_name}[/bold cyan]",
|
|
border_style="cyan",
|
|
box=box.ROUNDED
|
|
)
|
|
console.print(panel)
|
|
|
|
# Print rest outside panel for better formatting
|
|
if len(help_text) > 3:
|
|
console.print()
|
|
for line in help_text[3:]:
|
|
if line.startswith('**'):
|
|
console.print(line.replace('**', ''), style="bold cyan")
|
|
else:
|
|
console.print(line)
|
|
|
|
@staticmethod
|
|
def show_command_list() -> None:
|
|
"""Display list of all available commands."""
|
|
console.print("\n[bold cyan]Available Commands:[/bold cyan]\n")
|
|
|
|
# Game Management
|
|
console.print("[bold yellow]Game Management:[/bold yellow]")
|
|
console.print(" new_game Create a new game with test lineups and start it")
|
|
console.print(" list_games List all games in state manager")
|
|
console.print(" use_game Switch to a different game")
|
|
console.print(" status Display current game state")
|
|
console.print(" box_score Display box score")
|
|
console.print()
|
|
|
|
# Gameplay
|
|
console.print("[bold yellow]Gameplay:[/bold yellow]")
|
|
console.print(" defensive Submit defensive decision")
|
|
console.print(" offensive Submit offensive decision")
|
|
console.print(" resolve Resolve the current play")
|
|
console.print(" quick_play Auto-play multiple plays")
|
|
console.print()
|
|
|
|
# Utilities
|
|
console.print("[bold yellow]Utilities:[/bold yellow]")
|
|
console.print(" config Show configuration")
|
|
console.print(" clear Clear the screen")
|
|
console.print(" help Show help for commands")
|
|
console.print(" quit/exit Exit the REPL")
|
|
console.print()
|
|
|
|
console.print("[dim]Type 'help <command>' for detailed information.[/dim]")
|
|
console.print("[dim]Use TAB for auto-completion of commands and options.[/dim]\n")
|
|
|
|
|
|
# Detailed help data for each command
|
|
HELP_DATA = {
|
|
'new_game': {
|
|
'summary': 'Create a new game with test lineups and start it immediately',
|
|
'usage': 'new_game [--league LEAGUE] [--home-team ID] [--away-team ID]',
|
|
'options': [
|
|
{
|
|
'name': '--league',
|
|
'type': 'sba|pd',
|
|
'desc': 'League type (default: sba)'
|
|
},
|
|
{
|
|
'name': '--home-team',
|
|
'type': 'INT',
|
|
'desc': 'Home team ID (default: 1)'
|
|
},
|
|
{
|
|
'name': '--away-team',
|
|
'type': 'INT',
|
|
'desc': 'Away team ID (default: 2)'
|
|
}
|
|
],
|
|
'examples': [
|
|
'new_game',
|
|
'new_game --league pd',
|
|
'new_game --league sba --home-team 5 --away-team 3'
|
|
]
|
|
},
|
|
|
|
'defensive': {
|
|
'summary': 'Submit defensive decision for the current play',
|
|
'usage': 'defensive [--alignment TYPE] [--infield DEPTH] [--outfield DEPTH] [--hold BASES]',
|
|
'options': [
|
|
{
|
|
'name': '--alignment',
|
|
'type': 'STRING',
|
|
'desc': 'Defensive alignment: normal, shifted_left, shifted_right, extreme_shift (default: normal)'
|
|
},
|
|
{
|
|
'name': '--infield',
|
|
'type': 'STRING',
|
|
'desc': 'Infield depth: in, normal, back, double_play (default: normal)'
|
|
},
|
|
{
|
|
'name': '--outfield',
|
|
'type': 'STRING',
|
|
'desc': 'Outfield depth: in, normal, back (default: normal)'
|
|
},
|
|
{
|
|
'name': '--hold',
|
|
'type': 'LIST',
|
|
'desc': 'Comma-separated bases to hold runners: 1,2,3 (default: none)'
|
|
}
|
|
],
|
|
'examples': [
|
|
'defensive',
|
|
'defensive --alignment shifted_left',
|
|
'defensive --infield double_play --hold 1,3',
|
|
'defensive --alignment extreme_shift --infield back --outfield back'
|
|
]
|
|
},
|
|
|
|
'offensive': {
|
|
'summary': 'Submit offensive decision for the current play',
|
|
'usage': 'offensive [--approach TYPE] [--steal BASES] [--hit-run] [--bunt]',
|
|
'options': [
|
|
{
|
|
'name': '--approach',
|
|
'type': 'STRING',
|
|
'desc': 'Batting approach: normal, contact, power, patient (default: normal)'
|
|
},
|
|
{
|
|
'name': '--steal',
|
|
'type': 'LIST',
|
|
'desc': 'Comma-separated bases to steal: 2,3 (default: none)'
|
|
},
|
|
{
|
|
'name': '--hit-run',
|
|
'type': 'FLAG',
|
|
'desc': 'Execute hit-and-run play (default: false)'
|
|
},
|
|
{
|
|
'name': '--bunt',
|
|
'type': 'FLAG',
|
|
'desc': 'Attempt bunt (default: false)'
|
|
}
|
|
],
|
|
'examples': [
|
|
'offensive',
|
|
'offensive --approach power',
|
|
'offensive --steal 2',
|
|
'offensive --steal 2,3 --hit-run',
|
|
'offensive --approach contact --bunt'
|
|
]
|
|
},
|
|
|
|
'resolve': {
|
|
'summary': 'Resolve the current play using submitted decisions',
|
|
'usage': 'resolve',
|
|
'options': [],
|
|
'examples': [
|
|
'resolve'
|
|
],
|
|
'notes': 'Both defensive and offensive decisions must be submitted before resolving.'
|
|
},
|
|
|
|
'quick_play': {
|
|
'summary': 'Auto-play multiple plays with default decisions',
|
|
'usage': 'quick_play [COUNT]',
|
|
'options': [
|
|
{
|
|
'name': 'COUNT',
|
|
'type': 'INT',
|
|
'desc': 'Number of plays to execute (default: 1). Positional argument.'
|
|
}
|
|
],
|
|
'examples': [
|
|
'quick_play',
|
|
'quick_play 10',
|
|
'quick_play 27 # Play roughly 3 innings',
|
|
'quick_play 100 # Play full game quickly'
|
|
]
|
|
},
|
|
|
|
'status': {
|
|
'summary': 'Display current game state',
|
|
'usage': 'status',
|
|
'options': [],
|
|
'examples': [
|
|
'status'
|
|
]
|
|
},
|
|
|
|
'box_score': {
|
|
'summary': 'Display box score for the current game',
|
|
'usage': 'box_score',
|
|
'options': [],
|
|
'examples': [
|
|
'box_score'
|
|
]
|
|
},
|
|
|
|
'list_games': {
|
|
'summary': 'List all games currently loaded in state manager',
|
|
'usage': 'list_games',
|
|
'options': [],
|
|
'examples': [
|
|
'list_games'
|
|
]
|
|
},
|
|
|
|
'use_game': {
|
|
'summary': 'Switch to a different game',
|
|
'usage': 'use_game <GAME_ID>',
|
|
'options': [
|
|
{
|
|
'name': 'GAME_ID',
|
|
'type': 'UUID',
|
|
'desc': 'UUID of the game to switch to. Positional argument.'
|
|
}
|
|
],
|
|
'examples': [
|
|
'use_game a1b2c3d4-e5f6-7890-abcd-ef1234567890',
|
|
'use_game <TAB> # Use tab completion to see available games'
|
|
]
|
|
},
|
|
|
|
'config': {
|
|
'summary': 'Show terminal client configuration',
|
|
'usage': 'config',
|
|
'options': [],
|
|
'examples': [
|
|
'config'
|
|
]
|
|
},
|
|
|
|
'clear': {
|
|
'summary': 'Clear the screen',
|
|
'usage': 'clear',
|
|
'options': [],
|
|
'examples': [
|
|
'clear'
|
|
]
|
|
}
|
|
}
|
|
|
|
|
|
def get_help_text(command: str) -> Dict:
|
|
"""
|
|
Get help data for a command.
|
|
|
|
Args:
|
|
command: Command name
|
|
|
|
Returns:
|
|
Help data dictionary or empty dict if not found
|
|
"""
|
|
return HELP_DATA.get(command, {})
|
|
|
|
|
|
def show_help(command: str = None) -> None:
|
|
"""
|
|
Show help for a command or list all commands.
|
|
|
|
Args:
|
|
command: Command name or None for command list
|
|
"""
|
|
if command:
|
|
help_data = get_help_text(command)
|
|
if help_data:
|
|
HelpFormatter.show_command_help(command, help_data)
|
|
else:
|
|
console.print(f"[yellow]No help available for '{command}'[/yellow]")
|
|
console.print(f"[dim]Type 'help' to see all available commands.[/dim]")
|
|
else:
|
|
HelpFormatter.show_command_list()
|
|
|
|
Files to Update
|
|
|
|
2. Update backend/terminal_client/repl.py
|
|
|
|
# Add import at top
|
|
from terminal_client.help_text import show_help, get_help_text, HelpFormatter
|
|
|
|
# Update the intro to be more helpful
|
|
class GameREPL(GameREPLCompletions, cmd.Cmd):
|
|
"""Interactive REPL for game engine testing."""
|
|
|
|
intro = """
|
|
╔══════════════════════════════════════════════════════════════════════════════╗
|
|
║ Paper Dynasty Game Engine - Terminal Client ║
|
|
║ Interactive Mode ║
|
|
╚══════════════════════════════════════════════════════════════════════════════╝
|
|
|
|
[cyan]Type 'help' to see all available commands.[/cyan]
|
|
[cyan]Type 'help <command>' for detailed information about a specific command.[/cyan]
|
|
[cyan]Use TAB for auto-completion of commands and options.[/cyan]
|
|
|
|
[yellow]Quick start:[/yellow]
|
|
new_game Create and start a new game
|
|
status Show current game state
|
|
defensive Submit defensive decision
|
|
offensive Submit offensive decision
|
|
resolve Resolve the play
|
|
quick_play 10 Auto-play 10 plays
|
|
|
|
Press Ctrl+D or type 'quit' to exit.
|
|
|
|
"""
|
|
prompt = '⚾ > '
|
|
|
|
# ... rest of __init__ stays the same ...
|
|
|
|
# ==================== Enhanced Help System ====================
|
|
|
|
def do_help(self, arg):
|
|
"""
|
|
Show help for commands.
|
|
|
|
Usage:
|
|
help List all commands
|
|
help <command> Show detailed help for a command
|
|
"""
|
|
if arg:
|
|
# Show detailed help for specific command
|
|
show_help(arg)
|
|
else:
|
|
# Show command list
|
|
HelpFormatter.show_command_list()
|
|
|
|
def help_new_game(self):
|
|
"""Show detailed help for new_game command."""
|
|
show_help('new_game')
|
|
|
|
def help_defensive(self):
|
|
"""Show detailed help for defensive command."""
|
|
show_help('defensive')
|
|
|
|
def help_offensive(self):
|
|
"""Show detailed help for offensive command."""
|
|
show_help('offensive')
|
|
|
|
def help_resolve(self):
|
|
"""Show detailed help for resolve command."""
|
|
show_help('resolve')
|
|
|
|
def help_quick_play(self):
|
|
"""Show detailed help for quick_play command."""
|
|
show_help('quick_play')
|
|
|
|
def help_status(self):
|
|
"""Show detailed help for status command."""
|
|
show_help('status')
|
|
|
|
def help_box_score(self):
|
|
"""Show detailed help for box_score command."""
|
|
show_help('box_score')
|
|
|
|
def help_list_games(self):
|
|
"""Show detailed help for list_games command."""
|
|
show_help('list_games')
|
|
|
|
def help_use_game(self):
|
|
"""Show detailed help for use_game command."""
|
|
show_help('use_game')
|
|
|
|
def help_config(self):
|
|
"""Show detailed help for config command."""
|
|
show_help('config')
|
|
|
|
def help_clear(self):
|
|
"""Show detailed help for clear command."""
|
|
show_help('clear')
|
|
|
|
# ==================== Keep existing command methods ====================
|
|
# All do_* methods remain unchanged
|
|
|
|
3. Create backend/terminal_client/__main__.py Update
|
|
|
|
# Add help command for standalone mode
|
|
# Add this at the bottom of main.py
|
|
|
|
@cli.command('help')
|
|
@click.argument('command', required=False)
|
|
def show_cli_help(command):
|
|
"""
|
|
Show help for terminal client commands.
|
|
|
|
Usage:
|
|
python -m terminal_client help # Show all commands
|
|
python -m terminal_client help new-game # Show help for specific command
|
|
"""
|
|
from terminal_client.help_text import show_help
|
|
|
|
# Convert hyphenated command to underscore for lookup
|
|
if command:
|
|
command = command.replace('-', '_')
|
|
|
|
show_help(command)
|
|
|
|
Testing Plan
|
|
|
|
4. Create backend/tests/unit/terminal_client/test_help_text.py
|
|
|
|
"""
|
|
Unit tests for help text system.
|
|
"""
|
|
import pytest
|
|
from io import StringIO
|
|
from unittest.mock import patch
|
|
|
|
from terminal_client.help_text import (
|
|
HelpFormatter,
|
|
get_help_text,
|
|
show_help,
|
|
HELP_DATA
|
|
)
|
|
|
|
|
|
class TestHelpData:
|
|
"""Tests for help data structure."""
|
|
|
|
def test_all_commands_have_help(self):
|
|
"""Test that all major commands have help data."""
|
|
required_commands = [
|
|
'new_game', 'defensive', 'offensive', 'resolve',
|
|
'quick_play', 'status', 'use_game', 'list_games'
|
|
]
|
|
|
|
for cmd in required_commands:
|
|
assert cmd in HELP_DATA, f"Missing help data for {cmd}"
|
|
|
|
def test_help_data_structure(self):
|
|
"""Test that help data has required fields."""
|
|
for cmd, data in HELP_DATA.items():
|
|
assert 'summary' in data, f"{cmd} missing summary"
|
|
assert 'usage' in data, f"{cmd} missing usage"
|
|
assert 'options' in data, f"{cmd} missing options"
|
|
assert 'examples' in data, f"{cmd} missing examples"
|
|
|
|
# Validate options structure
|
|
for opt in data['options']:
|
|
assert 'name' in opt, f"{cmd} option missing name"
|
|
assert 'desc' in opt, f"{cmd} option missing desc"
|
|
|
|
def test_get_help_text_valid(self):
|
|
"""Test getting help text for valid command."""
|
|
result = get_help_text('new_game')
|
|
assert result is not None
|
|
assert 'summary' in result
|
|
|
|
def test_get_help_text_invalid(self):
|
|
"""Test getting help text for invalid command."""
|
|
result = get_help_text('nonexistent_command')
|
|
assert result == {}
|
|
|
|
|
|
class TestHelpFormatter:
|
|
"""Tests for HelpFormatter."""
|
|
|
|
@patch('terminal_client.help_text.console')
|
|
def test_show_command_help(self, mock_console):
|
|
"""Test showing help for a command."""
|
|
help_data = {
|
|
'summary': 'Test command',
|
|
'usage': 'test [OPTIONS]',
|
|
'options': [
|
|
{'name': '--option', 'type': 'STRING', 'desc': 'Test option'}
|
|
],
|
|
'examples': ['test --option value']
|
|
}
|
|
|
|
HelpFormatter.show_command_help('test', help_data)
|
|
|
|
# Verify console.print was called
|
|
assert mock_console.print.called
|
|
|
|
@patch('terminal_client.help_text.console')
|
|
def test_show_command_list(self, mock_console):
|
|
"""Test showing command list."""
|
|
HelpFormatter.show_command_list()
|
|
|
|
# Verify console.print was called multiple times
|
|
assert mock_console.print.call_count > 5
|
|
|
|
|
|
class TestShowHelp:
|
|
"""Tests for show_help function."""
|
|
|
|
@patch('terminal_client.help_text.HelpFormatter.show_command_help')
|
|
def test_show_help_specific_command(self, mock_show):
|
|
"""Test showing help for specific command."""
|
|
show_help('new_game')
|
|
|
|
mock_show.assert_called_once()
|
|
args = mock_show.call_args[0]
|
|
assert args[0] == 'new_game'
|
|
assert 'summary' in args[1]
|
|
|
|
@patch('terminal_client.help_text.HelpFormatter.show_command_list')
|
|
def test_show_help_no_command(self, mock_list):
|
|
"""Test showing command list when no command specified."""
|
|
show_help()
|
|
|
|
mock_list.assert_called_once()
|
|
|
|
@patch('terminal_client.help_text.console')
|
|
def test_show_help_invalid_command(self, mock_console):
|
|
"""Test showing help for invalid command."""
|
|
show_help('invalid_command')
|
|
|
|
# Should print warning message
|
|
assert mock_console.print.called
|
|
call_args = str(mock_console.print.call_args)
|
|
assert 'No help available' in call_args
|
|
|
|
Example Output
|
|
|
|
Command List (help):
|
|
⚾ > help
|
|
|
|
Available Commands:
|
|
|
|
Game Management:
|
|
new_game Create a new game with test lineups and start it
|
|
list_games List all games in state manager
|
|
use_game Switch to a different game
|
|
status Display current game state
|
|
box_score Display box score
|
|
|
|
Gameplay:
|
|
defensive Submit defensive decision
|
|
offensive Submit offensive decision
|
|
resolve Resolve the current play
|
|
quick_play Auto-play multiple plays
|
|
|
|
Utilities:
|
|
config Show configuration
|
|
clear Clear the screen
|
|
help Show help for commands
|
|
quit/exit Exit the REPL
|
|
|
|
Type 'help <command>' for detailed information.
|
|
Use TAB for auto-completion of commands and options.
|
|
|
|
Detailed Help (help defensive):
|
|
⚾ > help defensive
|
|
|
|
╭─────────────── Help: defensive ───────────────╮
|
|
│ defensive - Submit defensive decision for │
|
|
│ the current play │
|
|
│ │
|
|
│ USAGE: │
|
|
│ defensive [--alignment TYPE] [--infield │
|
|
│ DEPTH] [--outfield DEPTH] [--hold BASES] │
|
|
╰───────────────────────────────────────────────╯
|
|
|
|
OPTIONS:
|
|
--alignment STRING Defensive alignment: normal, shifted_left,
|
|
shifted_right, extreme_shift (default: normal)
|
|
--infield STRING Infield depth: in, normal, back, double_play
|
|
(default: normal)
|
|
--outfield STRING Outfield depth: in, normal, back (default: normal)
|
|
--hold LIST Comma-separated bases to hold runners: 1,2,3
|
|
(default: none)
|
|
|
|
EXAMPLES:
|
|
defensive
|
|
defensive --alignment shifted_left
|
|
defensive --infield double_play --hold 1,3
|
|
defensive --alignment extreme_shift --infield back --outfield back
|
|
|
|
Benefits
|
|
|
|
1. Self-documenting: No need to check external docs for basic usage
|
|
2. Rich formatting: Beautiful output with colors, tables, and panels
|
|
3. Comprehensive: Every option explained with examples
|
|
4. Discoverable: Easy to explore what's available
|
|
5. Consistent: Same help format across all commands
|
|
6. Learning tool: Examples teach proper usage patterns
|