239 lines
8.8 KiB
Python
239 lines
8.8 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Generate comprehensive CLI documentation from Click commands
|
|
"""
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
# Add project root to Python path
|
|
project_root = Path(__file__).parent.parent
|
|
sys.path.insert(0, str(project_root))
|
|
|
|
# Import after path setup
|
|
from src.cli.commands import app
|
|
import click
|
|
|
|
|
|
def format_option(option):
|
|
"""Format a Click option for documentation"""
|
|
names = []
|
|
if option.opts:
|
|
names.extend(option.opts)
|
|
if option.secondary_opts:
|
|
names.extend(option.secondary_opts)
|
|
|
|
name_str = ", ".join(f"`{n}`" for n in names)
|
|
|
|
# Get option type info
|
|
type_info = ""
|
|
if hasattr(option, 'type') and option.type:
|
|
if isinstance(option.type, click.Choice):
|
|
choices = ", ".join(f"`{c}`" for c in option.type.choices)
|
|
type_info = f"Choice: {choices}"
|
|
elif isinstance(option.type, click.Path):
|
|
type_info = "Path"
|
|
if hasattr(option.type, 'exists') and option.type.exists:
|
|
type_info += " (must exist)"
|
|
elif hasattr(option.type, '__name__'):
|
|
type_info = option.type.__name__
|
|
else:
|
|
type_info = str(option.type)
|
|
|
|
# Get default value
|
|
default_info = ""
|
|
if option.is_flag:
|
|
default_info = "Flag (boolean)"
|
|
elif option.default is not None and not callable(option.default):
|
|
# Filter out Click's Sentinel.UNSET
|
|
default_val = str(option.default)
|
|
if 'Sentinel' not in default_val and 'UNSET' not in default_val:
|
|
default_info = f"Default: `{option.default}`"
|
|
|
|
# Required indicator
|
|
required = option.required
|
|
|
|
return {
|
|
'name': name_str,
|
|
'help': option.help or "",
|
|
'type': type_info,
|
|
'default': default_info,
|
|
'required': required
|
|
}
|
|
|
|
|
|
def format_command(cmd):
|
|
"""Format a Click command for documentation"""
|
|
if not isinstance(cmd, click.Command):
|
|
return None
|
|
|
|
doc = {
|
|
'name': cmd.name,
|
|
'help': cmd.get_short_help_str() or cmd.help or "",
|
|
'description': cmd.help or "",
|
|
'options': []
|
|
}
|
|
|
|
# Get all options
|
|
for param in cmd.params:
|
|
if isinstance(param, click.Option):
|
|
doc['options'].append(format_option(param))
|
|
|
|
return doc
|
|
|
|
|
|
def generate_docs():
|
|
"""Generate comprehensive CLI documentation"""
|
|
|
|
commands = []
|
|
for name, cmd in app.commands.items():
|
|
cmd_doc = format_command(cmd)
|
|
if cmd_doc:
|
|
commands.append(cmd_doc)
|
|
|
|
# Sort commands alphabetically
|
|
commands.sort(key=lambda x: x['name'])
|
|
|
|
# Group commands by category
|
|
categories = {
|
|
'System': ['config', 'health', 'models'],
|
|
'User Management': ['add-user', 'delete-user', 'list-users'],
|
|
'Site Management': ['provision-site', 'attach-domain', 'list-sites', 'get-site', 'remove-site', 'sync-sites'],
|
|
'Project Management': ['ingest-cora', 'ingest-simple', 'list-projects'],
|
|
'Content Generation': ['generate-batch'],
|
|
'Deployment': ['deploy-batch', 'verify-deployment'],
|
|
'Link Export': ['get-links']
|
|
}
|
|
|
|
# Build markdown
|
|
md_lines = []
|
|
md_lines.append("# CLI Command Reference")
|
|
md_lines.append("")
|
|
md_lines.append("Comprehensive documentation for all CLI commands.")
|
|
md_lines.append("")
|
|
md_lines.append("## Table of Contents")
|
|
md_lines.append("")
|
|
|
|
for category in categories.keys():
|
|
md_lines.append(f"- [{category}](#{category.lower().replace(' ', '-')})")
|
|
|
|
md_lines.append("")
|
|
md_lines.append("---")
|
|
md_lines.append("")
|
|
|
|
# Generate documentation for each category
|
|
for category, command_names in categories.items():
|
|
md_lines.append(f"## {category}")
|
|
md_lines.append("")
|
|
|
|
for cmd_doc in commands:
|
|
if cmd_doc['name'] in command_names:
|
|
md_lines.append(f"### `{cmd_doc['name']}`")
|
|
md_lines.append("")
|
|
|
|
if cmd_doc['description']:
|
|
md_lines.append(cmd_doc['description'])
|
|
md_lines.append("")
|
|
|
|
if cmd_doc['options']:
|
|
md_lines.append("**Options:**")
|
|
md_lines.append("")
|
|
|
|
for opt in cmd_doc['options']:
|
|
parts = [opt['name']]
|
|
if opt['required']:
|
|
parts.append("**(required)**")
|
|
md_lines.append(f"- {' '.join(parts)}")
|
|
|
|
details = []
|
|
if opt['type']:
|
|
details.append(f"Type: {opt['type']}")
|
|
if opt['default'] and 'Sentinel' not in opt['default']:
|
|
details.append(opt['default'])
|
|
if opt['help']:
|
|
details.append(opt['help'])
|
|
|
|
if details:
|
|
md_lines.append(f" - {' | '.join(details)}")
|
|
md_lines.append("")
|
|
else:
|
|
md_lines.append("No options required.")
|
|
md_lines.append("")
|
|
|
|
md_lines.append("**Example:**")
|
|
md_lines.append("")
|
|
md_lines.append("```bash")
|
|
example_cmd = f"uv run python main.py {cmd_doc['name']}"
|
|
|
|
# Build example with required options and common optional ones
|
|
example_parts = []
|
|
for opt in cmd_doc['options']:
|
|
opt_name = opt['name'].replace('`', '').split(',')[0].strip()
|
|
|
|
if opt['required']:
|
|
# Add required options with example values
|
|
if '--username' in opt_name or '--admin-user' in opt_name:
|
|
example_parts.append("--username admin")
|
|
elif '--password' in opt_name or '--admin-password' in opt_name:
|
|
example_parts.append("--password yourpass")
|
|
elif '--file' in opt_name or '-f' in opt_name:
|
|
example_parts.append("--file path/to/file.xlsx")
|
|
elif '--job-file' in opt_name or '-j' in opt_name:
|
|
example_parts.append("--job-file jobs/example.json")
|
|
elif '--project-id' in opt_name or '-p' in opt_name:
|
|
example_parts.append("--project-id 1")
|
|
elif '--batch-id' in opt_name or '-b' in opt_name:
|
|
example_parts.append("--batch-id 1")
|
|
elif '--domain' in opt_name:
|
|
example_parts.append("--domain www.example.com")
|
|
elif '--name' in opt_name:
|
|
example_parts.append("--name \"My Project\"")
|
|
elif '--tier' in opt_name or '-t' in opt_name:
|
|
example_parts.append("--tier 1")
|
|
elif '--storage-name' in opt_name:
|
|
example_parts.append("--storage-name my-storage-zone")
|
|
elif '--region' in opt_name:
|
|
example_parts.append("--region DE")
|
|
else:
|
|
example_parts.append(f"{opt_name} <value>")
|
|
elif not opt['required'] and '--debug' in opt_name:
|
|
# Include common flags in examples
|
|
example_parts.append("--debug")
|
|
|
|
if example_parts:
|
|
example_cmd += " " + " ".join(example_parts)
|
|
|
|
md_lines.append(example_cmd)
|
|
md_lines.append("```")
|
|
md_lines.append("")
|
|
md_lines.append("---")
|
|
md_lines.append("")
|
|
|
|
# Add any commands not in categories
|
|
uncategorized = [c for c in commands if not any(c['name'] in names for names in categories.values())]
|
|
if uncategorized:
|
|
md_lines.append("## Other Commands")
|
|
md_lines.append("")
|
|
for cmd_doc in uncategorized:
|
|
md_lines.append(f"### `{cmd_doc['name']}`")
|
|
md_lines.append("")
|
|
if cmd_doc['description']:
|
|
md_lines.append(cmd_doc['description'])
|
|
md_lines.append("")
|
|
|
|
return "\n".join(md_lines)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
docs = generate_docs()
|
|
|
|
output_file = Path(__file__).parent.parent / "docs" / "CLI_COMMAND_REFERENCE.md"
|
|
output_file.parent.mkdir(exist_ok=True)
|
|
|
|
with open(output_file, 'w', encoding='utf-8') as f:
|
|
f.write(docs)
|
|
|
|
print(f"CLI documentation generated: {output_file}")
|
|
print(f"Total commands documented: {len(app.commands)}")
|
|
|