crackerjack 0.33.0__py3-none-any.whl → 0.33.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of crackerjack might be problematic. Click here for more details.
- crackerjack/__main__.py +1350 -34
- crackerjack/adapters/__init__.py +17 -0
- crackerjack/adapters/lsp_client.py +358 -0
- crackerjack/adapters/rust_tool_adapter.py +194 -0
- crackerjack/adapters/rust_tool_manager.py +193 -0
- crackerjack/adapters/skylos_adapter.py +231 -0
- crackerjack/adapters/zuban_adapter.py +560 -0
- crackerjack/agents/base.py +7 -3
- crackerjack/agents/coordinator.py +271 -33
- crackerjack/agents/documentation_agent.py +9 -15
- crackerjack/agents/dry_agent.py +3 -15
- crackerjack/agents/formatting_agent.py +1 -1
- crackerjack/agents/import_optimization_agent.py +36 -180
- crackerjack/agents/performance_agent.py +17 -98
- crackerjack/agents/performance_helpers.py +7 -31
- crackerjack/agents/proactive_agent.py +1 -3
- crackerjack/agents/refactoring_agent.py +16 -85
- crackerjack/agents/refactoring_helpers.py +7 -42
- crackerjack/agents/security_agent.py +9 -48
- crackerjack/agents/test_creation_agent.py +356 -513
- crackerjack/agents/test_specialist_agent.py +0 -4
- crackerjack/api.py +6 -25
- crackerjack/cli/cache_handlers.py +204 -0
- crackerjack/cli/cache_handlers_enhanced.py +683 -0
- crackerjack/cli/facade.py +100 -0
- crackerjack/cli/handlers.py +224 -9
- crackerjack/cli/interactive.py +6 -4
- crackerjack/cli/options.py +642 -55
- crackerjack/cli/utils.py +2 -1
- crackerjack/code_cleaner.py +58 -117
- crackerjack/config/global_lock_config.py +8 -48
- crackerjack/config/hooks.py +53 -62
- crackerjack/core/async_workflow_orchestrator.py +24 -34
- crackerjack/core/autofix_coordinator.py +3 -17
- crackerjack/core/enhanced_container.py +4 -13
- crackerjack/core/file_lifecycle.py +12 -89
- crackerjack/core/performance.py +2 -2
- crackerjack/core/performance_monitor.py +15 -55
- crackerjack/core/phase_coordinator.py +104 -204
- crackerjack/core/resource_manager.py +14 -90
- crackerjack/core/service_watchdog.py +62 -95
- crackerjack/core/session_coordinator.py +149 -0
- crackerjack/core/timeout_manager.py +14 -72
- crackerjack/core/websocket_lifecycle.py +13 -78
- crackerjack/core/workflow_orchestrator.py +171 -174
- crackerjack/docs/INDEX.md +11 -0
- crackerjack/docs/generated/api/API_REFERENCE.md +10895 -0
- crackerjack/docs/generated/api/CLI_REFERENCE.md +109 -0
- crackerjack/docs/generated/api/CROSS_REFERENCES.md +1755 -0
- crackerjack/docs/generated/api/PROTOCOLS.md +3 -0
- crackerjack/docs/generated/api/SERVICES.md +1252 -0
- crackerjack/documentation/__init__.py +31 -0
- crackerjack/documentation/ai_templates.py +756 -0
- crackerjack/documentation/dual_output_generator.py +765 -0
- crackerjack/documentation/mkdocs_integration.py +518 -0
- crackerjack/documentation/reference_generator.py +977 -0
- crackerjack/dynamic_config.py +55 -50
- crackerjack/executors/async_hook_executor.py +10 -15
- crackerjack/executors/cached_hook_executor.py +117 -43
- crackerjack/executors/hook_executor.py +8 -34
- crackerjack/executors/hook_lock_manager.py +26 -183
- crackerjack/executors/individual_hook_executor.py +13 -11
- crackerjack/executors/lsp_aware_hook_executor.py +270 -0
- crackerjack/executors/tool_proxy.py +417 -0
- crackerjack/hooks/lsp_hook.py +79 -0
- crackerjack/intelligence/adaptive_learning.py +25 -10
- crackerjack/intelligence/agent_orchestrator.py +2 -5
- crackerjack/intelligence/agent_registry.py +34 -24
- crackerjack/intelligence/agent_selector.py +5 -7
- crackerjack/interactive.py +17 -6
- crackerjack/managers/async_hook_manager.py +0 -1
- crackerjack/managers/hook_manager.py +79 -1
- crackerjack/managers/publish_manager.py +44 -8
- crackerjack/managers/test_command_builder.py +1 -15
- crackerjack/managers/test_executor.py +1 -3
- crackerjack/managers/test_manager.py +98 -7
- crackerjack/managers/test_manager_backup.py +10 -9
- crackerjack/mcp/cache.py +2 -2
- crackerjack/mcp/client_runner.py +1 -1
- crackerjack/mcp/context.py +191 -68
- crackerjack/mcp/dashboard.py +7 -5
- crackerjack/mcp/enhanced_progress_monitor.py +31 -28
- crackerjack/mcp/file_monitor.py +30 -23
- crackerjack/mcp/progress_components.py +31 -21
- crackerjack/mcp/progress_monitor.py +50 -53
- crackerjack/mcp/rate_limiter.py +6 -6
- crackerjack/mcp/server_core.py +17 -16
- crackerjack/mcp/service_watchdog.py +2 -1
- crackerjack/mcp/state.py +4 -7
- crackerjack/mcp/task_manager.py +11 -9
- crackerjack/mcp/tools/core_tools.py +173 -32
- crackerjack/mcp/tools/error_analyzer.py +3 -2
- crackerjack/mcp/tools/execution_tools.py +8 -10
- crackerjack/mcp/tools/execution_tools_backup.py +42 -30
- crackerjack/mcp/tools/intelligence_tool_registry.py +7 -5
- crackerjack/mcp/tools/intelligence_tools.py +5 -2
- crackerjack/mcp/tools/monitoring_tools.py +33 -70
- crackerjack/mcp/tools/proactive_tools.py +24 -11
- crackerjack/mcp/tools/progress_tools.py +5 -8
- crackerjack/mcp/tools/utility_tools.py +20 -14
- crackerjack/mcp/tools/workflow_executor.py +62 -40
- crackerjack/mcp/websocket/app.py +8 -0
- crackerjack/mcp/websocket/endpoints.py +352 -357
- crackerjack/mcp/websocket/jobs.py +40 -57
- crackerjack/mcp/websocket/monitoring_endpoints.py +2935 -0
- crackerjack/mcp/websocket/server.py +7 -25
- crackerjack/mcp/websocket/websocket_handler.py +6 -17
- crackerjack/mixins/__init__.py +0 -2
- crackerjack/mixins/error_handling.py +1 -70
- crackerjack/models/config.py +12 -1
- crackerjack/models/config_adapter.py +49 -1
- crackerjack/models/protocols.py +122 -122
- crackerjack/models/resource_protocols.py +55 -210
- crackerjack/monitoring/ai_agent_watchdog.py +13 -13
- crackerjack/monitoring/metrics_collector.py +426 -0
- crackerjack/monitoring/regression_prevention.py +8 -8
- crackerjack/monitoring/websocket_server.py +643 -0
- crackerjack/orchestration/advanced_orchestrator.py +11 -6
- crackerjack/orchestration/coverage_improvement.py +3 -3
- crackerjack/orchestration/execution_strategies.py +26 -6
- crackerjack/orchestration/test_progress_streamer.py +8 -5
- crackerjack/plugins/base.py +2 -2
- crackerjack/plugins/hooks.py +7 -0
- crackerjack/plugins/managers.py +11 -8
- crackerjack/security/__init__.py +0 -1
- crackerjack/security/audit.py +6 -35
- crackerjack/services/anomaly_detector.py +392 -0
- crackerjack/services/api_extractor.py +615 -0
- crackerjack/services/backup_service.py +2 -2
- crackerjack/services/bounded_status_operations.py +15 -152
- crackerjack/services/cache.py +127 -1
- crackerjack/services/changelog_automation.py +395 -0
- crackerjack/services/config.py +15 -9
- crackerjack/services/config_merge.py +19 -80
- crackerjack/services/config_template.py +506 -0
- crackerjack/services/contextual_ai_assistant.py +48 -22
- crackerjack/services/coverage_badge_service.py +171 -0
- crackerjack/services/coverage_ratchet.py +27 -25
- crackerjack/services/debug.py +3 -3
- crackerjack/services/dependency_analyzer.py +460 -0
- crackerjack/services/dependency_monitor.py +14 -11
- crackerjack/services/documentation_generator.py +491 -0
- crackerjack/services/documentation_service.py +675 -0
- crackerjack/services/enhanced_filesystem.py +6 -5
- crackerjack/services/enterprise_optimizer.py +865 -0
- crackerjack/services/error_pattern_analyzer.py +676 -0
- crackerjack/services/file_hasher.py +1 -1
- crackerjack/services/git.py +8 -25
- crackerjack/services/health_metrics.py +10 -8
- crackerjack/services/heatmap_generator.py +735 -0
- crackerjack/services/initialization.py +11 -30
- crackerjack/services/input_validator.py +5 -97
- crackerjack/services/intelligent_commit.py +327 -0
- crackerjack/services/log_manager.py +15 -12
- crackerjack/services/logging.py +4 -3
- crackerjack/services/lsp_client.py +628 -0
- crackerjack/services/memory_optimizer.py +19 -87
- crackerjack/services/metrics.py +42 -33
- crackerjack/services/parallel_executor.py +9 -67
- crackerjack/services/pattern_cache.py +1 -1
- crackerjack/services/pattern_detector.py +6 -6
- crackerjack/services/performance_benchmarks.py +18 -59
- crackerjack/services/performance_cache.py +20 -81
- crackerjack/services/performance_monitor.py +27 -95
- crackerjack/services/predictive_analytics.py +510 -0
- crackerjack/services/quality_baseline.py +234 -0
- crackerjack/services/quality_baseline_enhanced.py +646 -0
- crackerjack/services/quality_intelligence.py +785 -0
- crackerjack/services/regex_patterns.py +618 -524
- crackerjack/services/regex_utils.py +43 -123
- crackerjack/services/secure_path_utils.py +5 -164
- crackerjack/services/secure_status_formatter.py +30 -141
- crackerjack/services/secure_subprocess.py +11 -92
- crackerjack/services/security.py +9 -41
- crackerjack/services/security_logger.py +12 -24
- crackerjack/services/server_manager.py +124 -16
- crackerjack/services/status_authentication.py +16 -159
- crackerjack/services/status_security_manager.py +4 -131
- crackerjack/services/thread_safe_status_collector.py +19 -125
- crackerjack/services/unified_config.py +21 -13
- crackerjack/services/validation_rate_limiter.py +5 -54
- crackerjack/services/version_analyzer.py +459 -0
- crackerjack/services/version_checker.py +1 -1
- crackerjack/services/websocket_resource_limiter.py +10 -144
- crackerjack/services/zuban_lsp_service.py +390 -0
- crackerjack/slash_commands/__init__.py +2 -7
- crackerjack/slash_commands/run.md +2 -2
- crackerjack/tools/validate_input_validator_patterns.py +14 -40
- crackerjack/tools/validate_regex_patterns.py +19 -48
- {crackerjack-0.33.0.dist-info → crackerjack-0.33.2.dist-info}/METADATA +196 -25
- crackerjack-0.33.2.dist-info/RECORD +229 -0
- crackerjack/CLAUDE.md +0 -207
- crackerjack/RULES.md +0 -380
- crackerjack/py313.py +0 -234
- crackerjack-0.33.0.dist-info/RECORD +0 -187
- {crackerjack-0.33.0.dist-info → crackerjack-0.33.2.dist-info}/WHEEL +0 -0
- {crackerjack-0.33.0.dist-info → crackerjack-0.33.2.dist-info}/entry_points.txt +0 -0
- {crackerjack-0.33.0.dist-info → crackerjack-0.33.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,977 @@
|
|
|
1
|
+
"""Reference generator for comprehensive command documentation.
|
|
2
|
+
|
|
3
|
+
This module provides automatic generation of command reference documentation
|
|
4
|
+
from CLI definitions, including usage examples, parameter descriptions, and
|
|
5
|
+
workflow integration guides.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import ast
|
|
9
|
+
import typing as t
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
from enum import Enum
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
from ..models.protocols import (
|
|
16
|
+
ConfigManagerProtocol,
|
|
17
|
+
LoggerProtocol,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ReferenceFormat(Enum):
|
|
22
|
+
"""Formats for reference documentation."""
|
|
23
|
+
|
|
24
|
+
MARKDOWN = "markdown"
|
|
25
|
+
HTML = "html"
|
|
26
|
+
JSON = "json"
|
|
27
|
+
YAML = "yaml"
|
|
28
|
+
RST = "rst"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class ParameterInfo:
|
|
33
|
+
"""Information about a command parameter."""
|
|
34
|
+
|
|
35
|
+
name: str
|
|
36
|
+
type_hint: str
|
|
37
|
+
default_value: t.Any
|
|
38
|
+
description: str
|
|
39
|
+
required: bool = False
|
|
40
|
+
choices: list[str] | None = None
|
|
41
|
+
example: str | None = None
|
|
42
|
+
deprecated: bool = False
|
|
43
|
+
added_in_version: str | None = None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass
|
|
47
|
+
class CommandInfo:
|
|
48
|
+
"""Information about a CLI command."""
|
|
49
|
+
|
|
50
|
+
name: str
|
|
51
|
+
description: str
|
|
52
|
+
category: str
|
|
53
|
+
parameters: list[ParameterInfo] = field(default_factory=list)
|
|
54
|
+
examples: list[dict[str, str]] = field(default_factory=list)
|
|
55
|
+
related_commands: list[str] = field(default_factory=list)
|
|
56
|
+
aliases: list[str] = field(default_factory=list)
|
|
57
|
+
deprecated: bool = False
|
|
58
|
+
added_in_version: str | None = None
|
|
59
|
+
|
|
60
|
+
# Workflow integration
|
|
61
|
+
common_workflows: list[str] = field(default_factory=list)
|
|
62
|
+
prerequisites: list[str] = field(default_factory=list)
|
|
63
|
+
side_effects: list[str] = field(default_factory=list)
|
|
64
|
+
|
|
65
|
+
# AI optimization
|
|
66
|
+
ai_context: dict[str, t.Any] = field(default_factory=dict[str, t.Any])
|
|
67
|
+
success_patterns: list[str] = field(default_factory=list)
|
|
68
|
+
failure_patterns: list[str] = field(default_factory=list)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@dataclass
|
|
72
|
+
class CommandReference:
|
|
73
|
+
"""Complete command reference documentation."""
|
|
74
|
+
|
|
75
|
+
commands: dict[str, CommandInfo]
|
|
76
|
+
categories: dict[str, list[str]]
|
|
77
|
+
workflows: dict[str, list[str]]
|
|
78
|
+
generated_at: datetime = field(default_factory=datetime.now)
|
|
79
|
+
version: str = "unknown"
|
|
80
|
+
|
|
81
|
+
def get_commands_by_category(self, category: str) -> list[CommandInfo]:
|
|
82
|
+
"""Get all commands in a specific category.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
category: Category name
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
List of commands in category
|
|
89
|
+
"""
|
|
90
|
+
return [cmd for cmd in self.commands.values() if cmd.category == category]
|
|
91
|
+
|
|
92
|
+
def get_command_by_name(self, name: str) -> CommandInfo | None:
|
|
93
|
+
"""Get command by name or alias.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
name: Command name or alias
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
Command info if found, None otherwise
|
|
100
|
+
"""
|
|
101
|
+
# Direct name match
|
|
102
|
+
if name in self.commands:
|
|
103
|
+
return self.commands[name]
|
|
104
|
+
|
|
105
|
+
# Alias match
|
|
106
|
+
for cmd in self.commands.values():
|
|
107
|
+
if name in cmd.aliases:
|
|
108
|
+
return cmd
|
|
109
|
+
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class ReferenceGenerator:
|
|
114
|
+
"""Generator for comprehensive command reference documentation."""
|
|
115
|
+
|
|
116
|
+
def __init__(
|
|
117
|
+
self,
|
|
118
|
+
config_manager: ConfigManagerProtocol,
|
|
119
|
+
logger: LoggerProtocol,
|
|
120
|
+
):
|
|
121
|
+
self.config_manager = config_manager
|
|
122
|
+
self.logger = logger
|
|
123
|
+
|
|
124
|
+
async def generate_reference(
|
|
125
|
+
self,
|
|
126
|
+
cli_module_path: str,
|
|
127
|
+
output_format: ReferenceFormat = ReferenceFormat.MARKDOWN,
|
|
128
|
+
include_examples: bool = True,
|
|
129
|
+
include_workflows: bool = True,
|
|
130
|
+
) -> CommandReference:
|
|
131
|
+
"""Generate command reference from CLI module.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
cli_module_path: Path to CLI module to analyze
|
|
135
|
+
output_format: Output format for documentation
|
|
136
|
+
include_examples: Whether to include usage examples
|
|
137
|
+
include_workflows: Whether to include workflow information
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
Generated command reference
|
|
141
|
+
"""
|
|
142
|
+
self.logger.info(f"Generating command reference from: {cli_module_path}")
|
|
143
|
+
|
|
144
|
+
# Analyze CLI module
|
|
145
|
+
commands = await self._analyze_cli_module(cli_module_path)
|
|
146
|
+
|
|
147
|
+
# Enhance with examples if requested
|
|
148
|
+
if include_examples:
|
|
149
|
+
commands = await self._enhance_with_examples(commands)
|
|
150
|
+
|
|
151
|
+
# Enhance with workflows if requested
|
|
152
|
+
if include_workflows:
|
|
153
|
+
commands = await self._enhance_with_workflows(commands)
|
|
154
|
+
|
|
155
|
+
# Categorize commands
|
|
156
|
+
categories = self._categorize_commands(commands)
|
|
157
|
+
|
|
158
|
+
# Generate workflows
|
|
159
|
+
workflows = self._generate_workflows(commands) if include_workflows else {}
|
|
160
|
+
|
|
161
|
+
reference = CommandReference(
|
|
162
|
+
commands=commands,
|
|
163
|
+
categories=categories,
|
|
164
|
+
workflows=workflows,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
self.logger.info(f"Generated reference for {len(commands)} commands")
|
|
168
|
+
return reference
|
|
169
|
+
|
|
170
|
+
async def render_reference(
|
|
171
|
+
self,
|
|
172
|
+
reference: CommandReference,
|
|
173
|
+
output_format: ReferenceFormat,
|
|
174
|
+
template_name: str | None = None,
|
|
175
|
+
) -> str:
|
|
176
|
+
"""Render command reference to specified format.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
reference: Command reference to render
|
|
180
|
+
output_format: Output format
|
|
181
|
+
template_name: Optional template name to use
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
Rendered reference documentation
|
|
185
|
+
"""
|
|
186
|
+
if output_format == ReferenceFormat.MARKDOWN:
|
|
187
|
+
return self._render_markdown(reference)
|
|
188
|
+
elif output_format == ReferenceFormat.HTML:
|
|
189
|
+
return self._render_html(reference)
|
|
190
|
+
elif output_format == ReferenceFormat.JSON:
|
|
191
|
+
return self._render_json(reference)
|
|
192
|
+
elif output_format == ReferenceFormat.YAML:
|
|
193
|
+
return self._render_yaml(reference)
|
|
194
|
+
elif output_format == ReferenceFormat.RST:
|
|
195
|
+
return self._render_rst(reference)
|
|
196
|
+
else:
|
|
197
|
+
raise ValueError(f"Unsupported format: {output_format}")
|
|
198
|
+
|
|
199
|
+
async def _analyze_cli_module(self, module_path: str) -> dict[str, CommandInfo]:
|
|
200
|
+
"""Analyze CLI module to extract command information.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
module_path: Path to CLI module
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
Dictionary of command name to CommandInfo
|
|
207
|
+
"""
|
|
208
|
+
commands = {}
|
|
209
|
+
|
|
210
|
+
try:
|
|
211
|
+
# Read and parse the module
|
|
212
|
+
module_file = Path(module_path)
|
|
213
|
+
if not module_file.exists():
|
|
214
|
+
raise FileNotFoundError(f"CLI module not found: {module_path}")
|
|
215
|
+
|
|
216
|
+
source_code = module_file.read_text()
|
|
217
|
+
|
|
218
|
+
# Parse AST
|
|
219
|
+
tree = ast.parse(source_code)
|
|
220
|
+
|
|
221
|
+
# Extract command information
|
|
222
|
+
commands = self._extract_commands_from_ast(tree)
|
|
223
|
+
|
|
224
|
+
except Exception as e:
|
|
225
|
+
self.logger.error(f"Failed to analyze CLI module: {e}")
|
|
226
|
+
|
|
227
|
+
return commands
|
|
228
|
+
|
|
229
|
+
def _extract_commands_from_ast(self, tree: ast.AST) -> dict[str, CommandInfo]:
|
|
230
|
+
"""Extract command information from AST.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
tree: Parsed AST
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
Dictionary of commands
|
|
237
|
+
"""
|
|
238
|
+
commands: dict[str, CommandInfo] = {}
|
|
239
|
+
visitor = self._create_command_visitor(commands)
|
|
240
|
+
visitor.visit(tree)
|
|
241
|
+
return commands
|
|
242
|
+
|
|
243
|
+
def _create_command_visitor(
|
|
244
|
+
self, commands: dict[str, CommandInfo]
|
|
245
|
+
) -> ast.NodeVisitor:
|
|
246
|
+
"""Create AST visitor for command extraction.
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
commands: Dictionary to populate with commands
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
Configured AST visitor
|
|
253
|
+
"""
|
|
254
|
+
|
|
255
|
+
class CommandVisitor(ast.NodeVisitor):
|
|
256
|
+
def __init__(self, generator: t.Any) -> None:
|
|
257
|
+
self.generator = generator
|
|
258
|
+
|
|
259
|
+
def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
|
|
260
|
+
self.generator._process_function_node(node, commands)
|
|
261
|
+
self.generic_visit(node)
|
|
262
|
+
|
|
263
|
+
return CommandVisitor(self)
|
|
264
|
+
|
|
265
|
+
def _process_function_node(
|
|
266
|
+
self, node: ast.FunctionDef, commands: dict[str, CommandInfo]
|
|
267
|
+
) -> None:
|
|
268
|
+
"""Process function node for command extraction.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
node: Function definition node
|
|
272
|
+
commands: Commands dictionary to update
|
|
273
|
+
"""
|
|
274
|
+
for decorator in node.decorator_list:
|
|
275
|
+
if self._is_command_decorator(decorator):
|
|
276
|
+
command_info = self._extract_command_from_function(node)
|
|
277
|
+
if command_info:
|
|
278
|
+
commands[command_info.name] = command_info
|
|
279
|
+
|
|
280
|
+
def _is_command_decorator(self, decorator: ast.AST) -> bool:
|
|
281
|
+
"""Check if decorator indicates a CLI command.
|
|
282
|
+
|
|
283
|
+
Args:
|
|
284
|
+
decorator: AST decorator node
|
|
285
|
+
|
|
286
|
+
Returns:
|
|
287
|
+
True if command decorator
|
|
288
|
+
"""
|
|
289
|
+
if isinstance(decorator, ast.Name):
|
|
290
|
+
return decorator.id in ("command", "click_command")
|
|
291
|
+
elif isinstance(decorator, ast.Attribute):
|
|
292
|
+
return decorator.attr in ("command", "callback")
|
|
293
|
+
return False
|
|
294
|
+
|
|
295
|
+
def _extract_command_from_function(
|
|
296
|
+
self, node: ast.FunctionDef
|
|
297
|
+
) -> CommandInfo | None:
|
|
298
|
+
"""Extract command info from function definition.
|
|
299
|
+
|
|
300
|
+
Args:
|
|
301
|
+
node: Function definition node
|
|
302
|
+
|
|
303
|
+
Returns:
|
|
304
|
+
Command info or None if extraction fails
|
|
305
|
+
"""
|
|
306
|
+
try:
|
|
307
|
+
command_name = node.name.replace("_", "-")
|
|
308
|
+
description = self._extract_docstring(node)
|
|
309
|
+
parameters = self._extract_function_parameters(node)
|
|
310
|
+
|
|
311
|
+
return CommandInfo(
|
|
312
|
+
name=command_name,
|
|
313
|
+
description=description or f"Execute {command_name}",
|
|
314
|
+
category="general",
|
|
315
|
+
parameters=parameters,
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
except Exception as e:
|
|
319
|
+
self.logger.warning(f"Failed to extract command {node.name}: {e}")
|
|
320
|
+
return None
|
|
321
|
+
|
|
322
|
+
def _extract_function_parameters(
|
|
323
|
+
self, node: ast.FunctionDef
|
|
324
|
+
) -> list[ParameterInfo]:
|
|
325
|
+
"""Extract parameter information from function.
|
|
326
|
+
|
|
327
|
+
Args:
|
|
328
|
+
node: Function definition node
|
|
329
|
+
|
|
330
|
+
Returns:
|
|
331
|
+
List of parameter information
|
|
332
|
+
"""
|
|
333
|
+
parameters = []
|
|
334
|
+
for arg in node.args.args:
|
|
335
|
+
if arg.arg != "self":
|
|
336
|
+
param_info = self._extract_parameter_info(arg, node)
|
|
337
|
+
parameters.append(param_info)
|
|
338
|
+
return parameters
|
|
339
|
+
|
|
340
|
+
def _extract_docstring(self, node: ast.FunctionDef) -> str | None:
|
|
341
|
+
"""Extract docstring from function.
|
|
342
|
+
|
|
343
|
+
Args:
|
|
344
|
+
node: Function definition node
|
|
345
|
+
|
|
346
|
+
Returns:
|
|
347
|
+
Docstring or None
|
|
348
|
+
"""
|
|
349
|
+
if (
|
|
350
|
+
node.body
|
|
351
|
+
and isinstance(node.body[0], ast.Expr)
|
|
352
|
+
and isinstance(node.body[0].value, ast.Constant)
|
|
353
|
+
and isinstance(node.body[0].value.value, str)
|
|
354
|
+
):
|
|
355
|
+
return node.body[0].value.value.strip()
|
|
356
|
+
return None
|
|
357
|
+
|
|
358
|
+
def _extract_parameter_info(
|
|
359
|
+
self, arg: ast.arg, func_node: ast.FunctionDef
|
|
360
|
+
) -> ParameterInfo:
|
|
361
|
+
"""Extract parameter information.
|
|
362
|
+
|
|
363
|
+
Args:
|
|
364
|
+
arg: Function argument node
|
|
365
|
+
func_node: Parent function node
|
|
366
|
+
|
|
367
|
+
Returns:
|
|
368
|
+
Parameter information
|
|
369
|
+
"""
|
|
370
|
+
param_name = arg.arg.replace("_", "-")
|
|
371
|
+
type_hint = ast.unparse(arg.annotation) if arg.annotation else "Any"
|
|
372
|
+
default_value, required = self._extract_default_value(arg, func_node)
|
|
373
|
+
|
|
374
|
+
return ParameterInfo(
|
|
375
|
+
name=param_name,
|
|
376
|
+
type_hint=type_hint,
|
|
377
|
+
default_value=default_value,
|
|
378
|
+
description=f"Parameter: {param_name}",
|
|
379
|
+
required=required,
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
def _extract_default_value(
|
|
383
|
+
self, arg: ast.arg, func_node: ast.FunctionDef
|
|
384
|
+
) -> tuple[t.Any, bool]:
|
|
385
|
+
"""Extract default value and required status for parameter.
|
|
386
|
+
|
|
387
|
+
Args:
|
|
388
|
+
arg: Function argument node
|
|
389
|
+
func_node: Parent function node
|
|
390
|
+
|
|
391
|
+
Returns:
|
|
392
|
+
Tuple of (default_value, required)
|
|
393
|
+
"""
|
|
394
|
+
defaults_count = len(func_node.args.defaults)
|
|
395
|
+
args_count = len(func_node.args.args)
|
|
396
|
+
defaults_start = args_count - defaults_count
|
|
397
|
+
|
|
398
|
+
arg_index = func_node.args.args.index(arg)
|
|
399
|
+
if arg_index >= defaults_start:
|
|
400
|
+
default_index = arg_index - defaults_start
|
|
401
|
+
default_node = func_node.args.defaults[default_index]
|
|
402
|
+
if isinstance(default_node, ast.Constant):
|
|
403
|
+
return default_node.value, False
|
|
404
|
+
|
|
405
|
+
return None, True
|
|
406
|
+
|
|
407
|
+
async def _enhance_with_examples(
|
|
408
|
+
self, commands: dict[str, CommandInfo]
|
|
409
|
+
) -> dict[str, CommandInfo]:
|
|
410
|
+
"""Enhance commands with usage examples.
|
|
411
|
+
|
|
412
|
+
Args:
|
|
413
|
+
commands: Commands to enhance
|
|
414
|
+
|
|
415
|
+
Returns:
|
|
416
|
+
Enhanced commands with examples
|
|
417
|
+
"""
|
|
418
|
+
for command in commands.values():
|
|
419
|
+
# Generate basic examples
|
|
420
|
+
basic_example = f"python -m crackerjack --{command.name}"
|
|
421
|
+
|
|
422
|
+
# Add parameter examples
|
|
423
|
+
param_examples = []
|
|
424
|
+
for param in command.parameters:
|
|
425
|
+
if not param.required and param.default_value is not None:
|
|
426
|
+
if isinstance(param.default_value, bool):
|
|
427
|
+
param_examples.append(f"--{param.name}")
|
|
428
|
+
else:
|
|
429
|
+
param_examples.append(f"--{param.name} {param.default_value}")
|
|
430
|
+
|
|
431
|
+
if param_examples:
|
|
432
|
+
enhanced_example = f"{basic_example} {' '.join(param_examples)}"
|
|
433
|
+
command.examples.append(
|
|
434
|
+
{
|
|
435
|
+
"description": f"Using {command.name} with parameters",
|
|
436
|
+
"command": enhanced_example,
|
|
437
|
+
}
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
command.examples.append(
|
|
441
|
+
{
|
|
442
|
+
"description": f"Basic {command.name} usage",
|
|
443
|
+
"command": basic_example,
|
|
444
|
+
}
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
return commands
|
|
448
|
+
|
|
449
|
+
async def _enhance_with_workflows(
|
|
450
|
+
self, commands: dict[str, CommandInfo]
|
|
451
|
+
) -> dict[str, CommandInfo]:
|
|
452
|
+
"""Enhance commands with workflow information.
|
|
453
|
+
|
|
454
|
+
Args:
|
|
455
|
+
commands: Commands to enhance
|
|
456
|
+
|
|
457
|
+
Returns:
|
|
458
|
+
Enhanced commands with workflow info
|
|
459
|
+
"""
|
|
460
|
+
# Define common workflow patterns
|
|
461
|
+
workflow_patterns = {
|
|
462
|
+
"development": ["test", "format", "lint", "type-check"],
|
|
463
|
+
"release": ["version", "build", "publish", "tag"],
|
|
464
|
+
"maintenance": ["clean", "update", "optimize", "backup"],
|
|
465
|
+
"monitoring": ["status", "health", "metrics", "logs"],
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
for command in commands.values():
|
|
469
|
+
# Assign workflows based on command name patterns
|
|
470
|
+
for workflow, patterns in workflow_patterns.items():
|
|
471
|
+
if any(pattern in command.name for pattern in patterns):
|
|
472
|
+
command.common_workflows.append(workflow)
|
|
473
|
+
|
|
474
|
+
# Add AI context based on command purpose
|
|
475
|
+
if "test" in command.name:
|
|
476
|
+
command.ai_context.update(
|
|
477
|
+
{
|
|
478
|
+
"purpose": "quality_assurance",
|
|
479
|
+
"automation_level": "high",
|
|
480
|
+
"ai_agent_compatible": True,
|
|
481
|
+
}
|
|
482
|
+
)
|
|
483
|
+
command.success_patterns.append("All tests passed")
|
|
484
|
+
command.failure_patterns.append("Test failures detected")
|
|
485
|
+
|
|
486
|
+
elif "format" in command.name or "lint" in command.name:
|
|
487
|
+
command.ai_context.update(
|
|
488
|
+
{
|
|
489
|
+
"purpose": "code_quality",
|
|
490
|
+
"automation_level": "high",
|
|
491
|
+
"ai_agent_compatible": True,
|
|
492
|
+
}
|
|
493
|
+
)
|
|
494
|
+
command.success_patterns.append("No formatting issues")
|
|
495
|
+
command.failure_patterns.append("Style violations found")
|
|
496
|
+
|
|
497
|
+
return commands
|
|
498
|
+
|
|
499
|
+
def _categorize_commands(
|
|
500
|
+
self, commands: dict[str, CommandInfo]
|
|
501
|
+
) -> dict[str, list[str]]:
|
|
502
|
+
"""Categorize commands by purpose.
|
|
503
|
+
|
|
504
|
+
Args:
|
|
505
|
+
commands: Commands to categorize
|
|
506
|
+
|
|
507
|
+
Returns:
|
|
508
|
+
Dictionary of category to command names
|
|
509
|
+
"""
|
|
510
|
+
categories: dict[str, list[str]] = {}
|
|
511
|
+
|
|
512
|
+
category_patterns = {
|
|
513
|
+
"development": ["test", "format", "lint", "check", "run"],
|
|
514
|
+
"server": ["server", "start", "stop", "restart", "monitor"],
|
|
515
|
+
"release": ["version", "bump", "publish", "build", "tag"],
|
|
516
|
+
"configuration": ["config", "init", "setup", "install"],
|
|
517
|
+
"utilities": ["clean", "help", "info", "status"],
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
for command in commands.values():
|
|
521
|
+
assigned = False
|
|
522
|
+
|
|
523
|
+
# Assign based on patterns
|
|
524
|
+
for category, patterns in category_patterns.items():
|
|
525
|
+
if any(pattern in command.name for pattern in patterns):
|
|
526
|
+
command.category = category
|
|
527
|
+
if category not in categories:
|
|
528
|
+
categories[category] = []
|
|
529
|
+
categories[category].append(command.name)
|
|
530
|
+
assigned = True
|
|
531
|
+
break
|
|
532
|
+
|
|
533
|
+
# Default category
|
|
534
|
+
if not assigned:
|
|
535
|
+
command.category = "general"
|
|
536
|
+
if "general" not in categories:
|
|
537
|
+
categories["general"] = []
|
|
538
|
+
categories["general"].append(command.name)
|
|
539
|
+
|
|
540
|
+
return categories
|
|
541
|
+
|
|
542
|
+
def _generate_workflows(
|
|
543
|
+
self, commands: dict[str, CommandInfo]
|
|
544
|
+
) -> dict[str, list[str]]:
|
|
545
|
+
"""Generate workflow sequences from commands.
|
|
546
|
+
|
|
547
|
+
Args:
|
|
548
|
+
commands: Available commands
|
|
549
|
+
|
|
550
|
+
Returns:
|
|
551
|
+
Dictionary of workflow name to command sequence
|
|
552
|
+
"""
|
|
553
|
+
workflows = {
|
|
554
|
+
"development_cycle": [
|
|
555
|
+
"format",
|
|
556
|
+
"lint",
|
|
557
|
+
"test",
|
|
558
|
+
"type-check",
|
|
559
|
+
],
|
|
560
|
+
"release_cycle": [
|
|
561
|
+
"test",
|
|
562
|
+
"lint",
|
|
563
|
+
"version-bump",
|
|
564
|
+
"build",
|
|
565
|
+
"publish",
|
|
566
|
+
],
|
|
567
|
+
"maintenance_cycle": [
|
|
568
|
+
"clean",
|
|
569
|
+
"update-dependencies",
|
|
570
|
+
"test",
|
|
571
|
+
"optimize",
|
|
572
|
+
],
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
# Filter workflows to only include available commands
|
|
576
|
+
available_workflows = {}
|
|
577
|
+
for workflow_name, command_sequence in workflows.items():
|
|
578
|
+
available_sequence = [
|
|
579
|
+
cmd
|
|
580
|
+
for cmd in command_sequence
|
|
581
|
+
if any(cmd in available_cmd.name for available_cmd in commands.values())
|
|
582
|
+
]
|
|
583
|
+
if available_sequence:
|
|
584
|
+
available_workflows[workflow_name] = available_sequence
|
|
585
|
+
|
|
586
|
+
return available_workflows
|
|
587
|
+
|
|
588
|
+
def _render_markdown(self, reference: CommandReference) -> str:
|
|
589
|
+
"""Render reference as Markdown.
|
|
590
|
+
|
|
591
|
+
Args:
|
|
592
|
+
reference: Command reference
|
|
593
|
+
|
|
594
|
+
Returns:
|
|
595
|
+
Markdown formatted reference
|
|
596
|
+
"""
|
|
597
|
+
lines = [
|
|
598
|
+
"# Command Reference",
|
|
599
|
+
"",
|
|
600
|
+
f"Generated: {reference.generated_at.strftime('%Y-%m-%d %H:%M:%S')}",
|
|
601
|
+
f"Version: {reference.version}",
|
|
602
|
+
"",
|
|
603
|
+
"## Categories",
|
|
604
|
+
"",
|
|
605
|
+
]
|
|
606
|
+
|
|
607
|
+
# Add table of contents
|
|
608
|
+
lines.extend(self._render_markdown_toc(reference.categories))
|
|
609
|
+
lines.append("")
|
|
610
|
+
|
|
611
|
+
# Add commands by category
|
|
612
|
+
lines.extend(self._render_markdown_categories(reference))
|
|
613
|
+
|
|
614
|
+
# Add workflows if present
|
|
615
|
+
if reference.workflows:
|
|
616
|
+
lines.extend(self._render_markdown_workflows(reference.workflows))
|
|
617
|
+
|
|
618
|
+
return "\n".join(lines)
|
|
619
|
+
|
|
620
|
+
def _render_markdown_toc(self, categories: dict[str, list[str]]) -> list[str]:
|
|
621
|
+
"""Render table of contents for markdown.
|
|
622
|
+
|
|
623
|
+
Args:
|
|
624
|
+
categories: Command categories
|
|
625
|
+
|
|
626
|
+
Returns:
|
|
627
|
+
List of TOC lines
|
|
628
|
+
"""
|
|
629
|
+
return [
|
|
630
|
+
f"- [{category.title()}](#{category.replace('_', '-')})"
|
|
631
|
+
for category in categories
|
|
632
|
+
]
|
|
633
|
+
|
|
634
|
+
def _render_markdown_categories(self, reference: CommandReference) -> list[str]:
|
|
635
|
+
"""Render command categories for markdown.
|
|
636
|
+
|
|
637
|
+
Args:
|
|
638
|
+
reference: Command reference
|
|
639
|
+
|
|
640
|
+
Returns:
|
|
641
|
+
List of category section lines
|
|
642
|
+
"""
|
|
643
|
+
category_lines = []
|
|
644
|
+
for category, command_names in reference.categories.items():
|
|
645
|
+
category_lines.extend(
|
|
646
|
+
[
|
|
647
|
+
f"## {category.title()}",
|
|
648
|
+
"",
|
|
649
|
+
]
|
|
650
|
+
)
|
|
651
|
+
|
|
652
|
+
for command_name in command_names:
|
|
653
|
+
command = reference.commands[command_name]
|
|
654
|
+
category_lines.extend(self._render_command_markdown(command))
|
|
655
|
+
|
|
656
|
+
return category_lines
|
|
657
|
+
|
|
658
|
+
def _render_markdown_workflows(self, workflows: dict[str, list[str]]) -> list[str]:
|
|
659
|
+
"""Render workflows section for markdown.
|
|
660
|
+
|
|
661
|
+
Args:
|
|
662
|
+
workflows: Workflow definitions
|
|
663
|
+
|
|
664
|
+
Returns:
|
|
665
|
+
List of workflow section lines
|
|
666
|
+
"""
|
|
667
|
+
workflow_lines = [
|
|
668
|
+
"## Workflows",
|
|
669
|
+
"",
|
|
670
|
+
]
|
|
671
|
+
|
|
672
|
+
for workflow_name, command_sequence in workflows.items():
|
|
673
|
+
workflow_lines.extend(
|
|
674
|
+
[
|
|
675
|
+
f"### {workflow_name.replace('_', ' ').title()}",
|
|
676
|
+
"",
|
|
677
|
+
]
|
|
678
|
+
)
|
|
679
|
+
|
|
680
|
+
for i, cmd in enumerate(command_sequence, 1):
|
|
681
|
+
workflow_lines.append(f"{i}. `{cmd}`")
|
|
682
|
+
|
|
683
|
+
workflow_lines.append("")
|
|
684
|
+
|
|
685
|
+
return workflow_lines
|
|
686
|
+
|
|
687
|
+
def _render_command_markdown(self, command: CommandInfo) -> list[str]:
|
|
688
|
+
"""Render single command as Markdown.
|
|
689
|
+
|
|
690
|
+
Args:
|
|
691
|
+
command: Command to render
|
|
692
|
+
|
|
693
|
+
Returns:
|
|
694
|
+
List of markdown lines
|
|
695
|
+
"""
|
|
696
|
+
lines = [
|
|
697
|
+
f"### `{command.name}`",
|
|
698
|
+
"",
|
|
699
|
+
command.description,
|
|
700
|
+
"",
|
|
701
|
+
]
|
|
702
|
+
|
|
703
|
+
# Add parameters section
|
|
704
|
+
if command.parameters:
|
|
705
|
+
lines.extend(self._render_command_parameters_markdown(command.parameters))
|
|
706
|
+
|
|
707
|
+
# Add examples section
|
|
708
|
+
if command.examples:
|
|
709
|
+
lines.extend(self._render_command_examples_markdown(command.examples))
|
|
710
|
+
|
|
711
|
+
# Add related commands section
|
|
712
|
+
if command.related_commands:
|
|
713
|
+
lines.extend(
|
|
714
|
+
self._render_command_related_markdown(command.related_commands)
|
|
715
|
+
)
|
|
716
|
+
|
|
717
|
+
return lines
|
|
718
|
+
|
|
719
|
+
def _render_command_parameters_markdown(
|
|
720
|
+
self, parameters: list[ParameterInfo]
|
|
721
|
+
) -> list[str]:
|
|
722
|
+
"""Render command parameters for markdown.
|
|
723
|
+
|
|
724
|
+
Args:
|
|
725
|
+
parameters: List of parameters to render
|
|
726
|
+
|
|
727
|
+
Returns:
|
|
728
|
+
List of parameter section lines
|
|
729
|
+
"""
|
|
730
|
+
param_lines = [
|
|
731
|
+
"**Parameters:**",
|
|
732
|
+
"",
|
|
733
|
+
]
|
|
734
|
+
|
|
735
|
+
for param in parameters:
|
|
736
|
+
required_str = " (required)" if param.required else ""
|
|
737
|
+
default_str = (
|
|
738
|
+
f" (default: {param.default_value})"
|
|
739
|
+
if param.default_value is not None
|
|
740
|
+
else ""
|
|
741
|
+
)
|
|
742
|
+
param_lines.append(
|
|
743
|
+
f"- `--{param.name}` ({param.type_hint}){required_str}{default_str}: {param.description}"
|
|
744
|
+
)
|
|
745
|
+
|
|
746
|
+
param_lines.append("")
|
|
747
|
+
return param_lines
|
|
748
|
+
|
|
749
|
+
def _render_command_examples_markdown(
|
|
750
|
+
self, examples: list[dict[str, str]]
|
|
751
|
+
) -> list[str]:
|
|
752
|
+
"""Render command examples for markdown.
|
|
753
|
+
|
|
754
|
+
Args:
|
|
755
|
+
examples: List of examples to render
|
|
756
|
+
|
|
757
|
+
Returns:
|
|
758
|
+
List of examples section lines
|
|
759
|
+
"""
|
|
760
|
+
example_lines = [
|
|
761
|
+
"**Examples:**",
|
|
762
|
+
"",
|
|
763
|
+
]
|
|
764
|
+
|
|
765
|
+
for example in examples:
|
|
766
|
+
example_lines.extend(
|
|
767
|
+
[
|
|
768
|
+
f"*{example['description']}:*",
|
|
769
|
+
"```bash",
|
|
770
|
+
example["command"],
|
|
771
|
+
"```",
|
|
772
|
+
"",
|
|
773
|
+
]
|
|
774
|
+
)
|
|
775
|
+
|
|
776
|
+
return example_lines
|
|
777
|
+
|
|
778
|
+
def _render_command_related_markdown(
|
|
779
|
+
self, related_commands: list[str]
|
|
780
|
+
) -> list[str]:
|
|
781
|
+
"""Render related commands for markdown.
|
|
782
|
+
|
|
783
|
+
Args:
|
|
784
|
+
related_commands: List of related command names
|
|
785
|
+
|
|
786
|
+
Returns:
|
|
787
|
+
List of related commands section lines
|
|
788
|
+
"""
|
|
789
|
+
return [
|
|
790
|
+
"**Related commands:** "
|
|
791
|
+
+ ", ".join(f"`{cmd}`" for cmd in related_commands),
|
|
792
|
+
"",
|
|
793
|
+
]
|
|
794
|
+
|
|
795
|
+
def _render_html(self, reference: CommandReference) -> str:
|
|
796
|
+
"""Render reference as HTML."""
|
|
797
|
+
html = self._render_html_header(
|
|
798
|
+
reference.generated_at.strftime("%Y-%m-%d %H:%M:%S")
|
|
799
|
+
)
|
|
800
|
+
html += self._render_html_commands(reference)
|
|
801
|
+
html += "</body></html>"
|
|
802
|
+
return html
|
|
803
|
+
|
|
804
|
+
def _render_html_header(self, generated_at: str) -> str:
|
|
805
|
+
"""Render HTML header with styles and metadata."""
|
|
806
|
+
return f"""<!DOCTYPE html>
|
|
807
|
+
<html>
|
|
808
|
+
<head>
|
|
809
|
+
<title>Command Reference</title>
|
|
810
|
+
<style>
|
|
811
|
+
body {{ font-family: Arial, sans-serif; margin: 40px; }}
|
|
812
|
+
.command {{ margin-bottom: 2em; }}
|
|
813
|
+
.parameter {{ margin-left: 1em; }}
|
|
814
|
+
code {{ background-color: #f5f5f5; padding: 2px 4px; }}
|
|
815
|
+
pre {{ background-color: #f5f5f5; padding: 10px; }}
|
|
816
|
+
</style>
|
|
817
|
+
</head>
|
|
818
|
+
<body>
|
|
819
|
+
<h1>Command Reference</h1>
|
|
820
|
+
<p>Generated: {generated_at}</p>
|
|
821
|
+
"""
|
|
822
|
+
|
|
823
|
+
def _render_html_commands(self, reference: CommandReference) -> str:
|
|
824
|
+
"""Render HTML commands by category."""
|
|
825
|
+
html = ""
|
|
826
|
+
for category, command_names in reference.categories.items():
|
|
827
|
+
html += f"<h2>{category.title()}</h2>"
|
|
828
|
+
html += self._render_html_category_commands(
|
|
829
|
+
reference.commands, command_names
|
|
830
|
+
)
|
|
831
|
+
return html
|
|
832
|
+
|
|
833
|
+
def _render_html_category_commands(
|
|
834
|
+
self, commands: dict[str, CommandInfo], command_names: list[str]
|
|
835
|
+
) -> str:
|
|
836
|
+
"""Render HTML for commands in a category."""
|
|
837
|
+
html = ""
|
|
838
|
+
for command_name in command_names:
|
|
839
|
+
command = commands[command_name]
|
|
840
|
+
html += '<div class="command">'
|
|
841
|
+
html += f"<h3><code>{command.name}</code></h3>"
|
|
842
|
+
html += f"<p>{command.description}</p>"
|
|
843
|
+
html += self._render_html_command_parameters(command.parameters)
|
|
844
|
+
html += "</div>"
|
|
845
|
+
return html
|
|
846
|
+
|
|
847
|
+
def _render_html_command_parameters(self, parameters: list[ParameterInfo]) -> str:
|
|
848
|
+
"""Render HTML for command parameters."""
|
|
849
|
+
if not parameters:
|
|
850
|
+
return ""
|
|
851
|
+
|
|
852
|
+
html = "<h4>Parameters:</h4><ul>"
|
|
853
|
+
for param in parameters:
|
|
854
|
+
html += f'<li class="parameter"><code>--{param.name}</code> ({param.type_hint}): {param.description}</li>'
|
|
855
|
+
html += "</ul>"
|
|
856
|
+
return html
|
|
857
|
+
|
|
858
|
+
def _render_json(self, reference: CommandReference) -> str:
|
|
859
|
+
"""Render reference as JSON."""
|
|
860
|
+
import json
|
|
861
|
+
|
|
862
|
+
data: dict[str, t.Any] = {
|
|
863
|
+
"generated_at": reference.generated_at.isoformat(),
|
|
864
|
+
"version": reference.version,
|
|
865
|
+
"categories": reference.categories,
|
|
866
|
+
"workflows": reference.workflows,
|
|
867
|
+
"commands": {},
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
for name, command in reference.commands.items():
|
|
871
|
+
data["commands"][name] = {
|
|
872
|
+
"name": command.name,
|
|
873
|
+
"description": command.description,
|
|
874
|
+
"category": command.category,
|
|
875
|
+
"parameters": [
|
|
876
|
+
{
|
|
877
|
+
"name": param.name,
|
|
878
|
+
"type": param.type_hint,
|
|
879
|
+
"default": param.default_value,
|
|
880
|
+
"description": param.description,
|
|
881
|
+
"required": param.required,
|
|
882
|
+
}
|
|
883
|
+
for param in command.parameters
|
|
884
|
+
],
|
|
885
|
+
"examples": command.examples,
|
|
886
|
+
"related_commands": command.related_commands,
|
|
887
|
+
"aliases": command.aliases,
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
return json.dumps(data, indent=2, default=str)
|
|
891
|
+
|
|
892
|
+
def _render_yaml(self, reference: CommandReference) -> str:
|
|
893
|
+
"""Render reference as YAML."""
|
|
894
|
+
import yaml
|
|
895
|
+
|
|
896
|
+
# Convert to JSON-serializable format first
|
|
897
|
+
json_data = self._render_json(reference)
|
|
898
|
+
import json
|
|
899
|
+
|
|
900
|
+
data = json.loads(json_data)
|
|
901
|
+
|
|
902
|
+
return yaml.dump(data, default_flow_style=False, sort_keys=False)
|
|
903
|
+
|
|
904
|
+
def _render_rst(self, reference: CommandReference) -> str:
|
|
905
|
+
"""Render reference as reStructuredText."""
|
|
906
|
+
lines = [
|
|
907
|
+
"Command Reference",
|
|
908
|
+
"=================",
|
|
909
|
+
"",
|
|
910
|
+
f"Generated: {reference.generated_at.strftime('%Y-%m-%d %H:%M:%S')}",
|
|
911
|
+
f"Version: {reference.version}",
|
|
912
|
+
"",
|
|
913
|
+
]
|
|
914
|
+
|
|
915
|
+
lines.extend(self._render_rst_categories(reference))
|
|
916
|
+
return "\n".join(lines)
|
|
917
|
+
|
|
918
|
+
def _render_rst_categories(self, reference: CommandReference) -> list[str]:
|
|
919
|
+
"""Render RST categories and commands."""
|
|
920
|
+
rst_lines = []
|
|
921
|
+
|
|
922
|
+
for category, command_names in reference.categories.items():
|
|
923
|
+
rst_lines.extend(
|
|
924
|
+
[
|
|
925
|
+
category.title(),
|
|
926
|
+
"-" * len(category),
|
|
927
|
+
"",
|
|
928
|
+
]
|
|
929
|
+
)
|
|
930
|
+
|
|
931
|
+
rst_lines.extend(
|
|
932
|
+
self._render_rst_category_commands(reference.commands, command_names)
|
|
933
|
+
)
|
|
934
|
+
|
|
935
|
+
return rst_lines
|
|
936
|
+
|
|
937
|
+
def _render_rst_category_commands(
|
|
938
|
+
self, commands: dict[str, CommandInfo], command_names: list[str]
|
|
939
|
+
) -> list[str]:
|
|
940
|
+
"""Render RST commands for a category."""
|
|
941
|
+
command_lines = []
|
|
942
|
+
|
|
943
|
+
for command_name in command_names:
|
|
944
|
+
command = commands[command_name]
|
|
945
|
+
command_lines.extend(
|
|
946
|
+
[
|
|
947
|
+
f"``{command.name}``",
|
|
948
|
+
"^" * (len(command.name) + 4),
|
|
949
|
+
"",
|
|
950
|
+
command.description,
|
|
951
|
+
"",
|
|
952
|
+
]
|
|
953
|
+
)
|
|
954
|
+
|
|
955
|
+
if command.parameters:
|
|
956
|
+
command_lines.extend(
|
|
957
|
+
self._render_rst_command_parameters(command.parameters)
|
|
958
|
+
)
|
|
959
|
+
|
|
960
|
+
return command_lines
|
|
961
|
+
|
|
962
|
+
def _render_rst_command_parameters(
|
|
963
|
+
self, parameters: list[ParameterInfo]
|
|
964
|
+
) -> list[str]:
|
|
965
|
+
"""Render RST command parameters."""
|
|
966
|
+
param_lines = [
|
|
967
|
+
"Parameters:",
|
|
968
|
+
"",
|
|
969
|
+
]
|
|
970
|
+
|
|
971
|
+
for param in parameters:
|
|
972
|
+
param_lines.append(
|
|
973
|
+
f"* ``--{param.name}`` ({param.type_hint}): {param.description}"
|
|
974
|
+
)
|
|
975
|
+
|
|
976
|
+
param_lines.append("")
|
|
977
|
+
return param_lines
|