Skip to content

Commit 73ef33f

Browse files
committed
feat(docs): Improve command documentation generation script
- Refactor script into modular functions with clear responsibilities - Add comprehensive docstrings and type hints - Fix option name display to show both long and short forms - Handle hidden options correctly - Improve code organization and error handling
1 parent 9553894 commit 73ef33f

File tree

4 files changed

+276
-194
lines changed

4 files changed

+276
-194
lines changed

.github/workflows/generate_docs.py

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
"""Script to generate markdown documentation for the Helm Values Manager CLI commands.
2+
3+
This script uses the Typer library's introspection capabilities to automatically
4+
generate comprehensive documentation for all CLI commands, including their
5+
arguments, options, and help text.
6+
"""
7+
8+
import inspect
9+
from typing import List, Optional, Tuple, Union
10+
11+
import typer
12+
13+
from helm_values_manager.cli import app
14+
15+
16+
def get_command_parameters(
17+
command: typer.models.CommandInfo,
18+
) -> List[Tuple[str, Union[typer.models.OptionInfo, typer.models.ArgumentInfo]]]:
19+
"""Extract parameters from a command's signature.
20+
21+
Args:
22+
command: The Typer command to extract parameters from
23+
24+
Returns:
25+
List of tuples containing parameter names and their Typer info objects
26+
"""
27+
params = []
28+
sig = inspect.signature(command.callback)
29+
for param_name, param in sig.parameters.items():
30+
if param_name != "ctx": # Skip context parameter
31+
if isinstance(param.default, (typer.models.OptionInfo, typer.models.ArgumentInfo)):
32+
params.append((param_name, param.default))
33+
return params
34+
35+
36+
def format_parameter_options(param_name: str, param: typer.models.OptionInfo) -> List[str]:
37+
"""Format the option names (long and short forms) for a parameter.
38+
39+
Args:
40+
param_name: The parameter's variable name
41+
param: The Typer option info object
42+
43+
Returns:
44+
List of formatted option strings
45+
"""
46+
options = []
47+
48+
# Get the option names from the Typer.Option constructor
49+
if isinstance(param, typer.models.OptionInfo):
50+
# Get the first two arguments which are the long and short forms
51+
args = [arg for arg in param.param_decls if isinstance(arg, str)]
52+
if args:
53+
# Sort to ensure long form comes first
54+
options.extend(sorted(args, key=lambda x: not x.startswith("--")))
55+
56+
return options
57+
58+
59+
def format_default_value(param: Union[typer.models.OptionInfo, typer.models.ArgumentInfo]) -> Optional[str]:
60+
"""Format the default value for a parameter.
61+
62+
Args:
63+
param: The Typer parameter info object
64+
65+
Returns:
66+
Formatted default value string or None if no default
67+
"""
68+
if not hasattr(param, "default") or param.default is ...:
69+
return None
70+
71+
if isinstance(param.default, str) and not param.default:
72+
return "(default: empty string)"
73+
return f"(default: {param.default})"
74+
75+
76+
def generate_command_list(commands: List[typer.models.CommandInfo]) -> List[str]:
77+
"""Generate the list of available commands.
78+
79+
Args:
80+
commands: List of Typer command info objects
81+
82+
Returns:
83+
List of formatted command strings
84+
"""
85+
docs = []
86+
for command in commands:
87+
name = command.name or command.callback.__name__
88+
help_text = command.help or command.callback.__doc__ or "No description available."
89+
help_text = help_text.split("\n")[0] # Get first line only
90+
docs.append(f"- [`{name}`](#{name}) - {help_text}\n")
91+
return docs
92+
93+
94+
def generate_command_details(command: typer.models.CommandInfo) -> List[str]:
95+
"""Generate detailed documentation for a single command.
96+
97+
Args:
98+
command: The Typer command info object
99+
100+
Returns:
101+
List of formatted documentation strings
102+
"""
103+
docs = []
104+
name = command.name or command.callback.__name__
105+
help_text = command.help or command.callback.__doc__ or "No description available."
106+
107+
docs.extend(
108+
[
109+
f"\n### `{name}`\n\n{help_text}\n",
110+
"\n**Usage:**\n```bash\nhelm values-manager",
111+
]
112+
)
113+
114+
if name != "main":
115+
docs.append(f" {name}")
116+
117+
# Get command parameters
118+
params = get_command_parameters(command)
119+
if params:
120+
docs.append(" [options]")
121+
docs.append("\n```\n")
122+
123+
# Document parameters
124+
args = []
125+
opts = []
126+
for param_name, param in params:
127+
if isinstance(param, typer.models.OptionInfo):
128+
if not hasattr(param, "hidden") or not param.hidden: # Skip hidden options
129+
opts.append((param_name, param))
130+
elif isinstance(param, typer.models.ArgumentInfo):
131+
args.append((param_name, param))
132+
133+
if args:
134+
docs.append("\n**Arguments:**\n")
135+
for param_name, param in args:
136+
docs.append(f"- `{param_name}`: {param.help}")
137+
default_value = format_default_value(param)
138+
if default_value:
139+
docs.append(f" {default_value}")
140+
docs.append("\n")
141+
142+
if opts:
143+
docs.append("\n**Options:**\n")
144+
for param_name, param in opts:
145+
option_names = format_parameter_options(param_name, param)
146+
if not option_names: # Skip if no option names found
147+
continue
148+
docs.append(f"- `{', '.join(option_names)}`: {param.help}")
149+
default_value = format_default_value(param)
150+
if default_value:
151+
docs.append(f" {default_value}")
152+
docs.append("\n")
153+
154+
return docs
155+
156+
157+
def generate_markdown_docs() -> str:
158+
"""Generate complete markdown documentation for all commands.
159+
160+
Returns:
161+
Complete markdown documentation as a string
162+
"""
163+
header = "# Command Reference"
164+
description = (
165+
"This document provides detailed information about all available commands in the Helm Values Manager plugin."
166+
)
167+
docs = [f"{header}\n\n{description}\n\n## Available Commands\n"]
168+
169+
# Add command list
170+
docs.extend(generate_command_list(app.registered_commands))
171+
172+
# Add command details
173+
docs.append("\n## Command Details\n")
174+
for command in app.registered_commands:
175+
docs.extend(generate_command_details(command))
176+
177+
# Add help section
178+
docs.append(
179+
"""
180+
## Using Help
181+
182+
Each command supports the `--help` flag for detailed information:
183+
184+
```bash
185+
helm values-manager --help
186+
helm values-manager <command> --help
187+
```"""
188+
)
189+
190+
return "".join(docs)
191+
192+
193+
if __name__ == "__main__":
194+
docs = generate_markdown_docs()
195+
with open("docs/Commands/README.md", "w") as f:
196+
f.write(docs)

.github/workflows/update-docs.yml

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
name: Update Command Documentation
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
paths:
7+
- 'helm_values_manager/cli.py'
8+
- 'helm_values_manager/commands/**'
9+
pull_request:
10+
paths:
11+
- 'helm_values_manager/cli.py'
12+
- 'helm_values_manager/commands/**'
13+
workflow_dispatch: # Allow manual triggers
14+
15+
jobs:
16+
update-docs:
17+
runs-on: ubuntu-latest
18+
permissions:
19+
contents: write
20+
pull-requests: write
21+
22+
steps:
23+
- uses: actions/checkout@v4
24+
25+
- name: Set up Python
26+
uses: actions/setup-python@v4
27+
with:
28+
python-version: '3.12'
29+
30+
- name: Install dependencies
31+
run: |
32+
python -m pip install --upgrade pip
33+
pip install -e ".[dev]"
34+
35+
- name: Generate command documentation
36+
run: python .github/workflows/generate_docs.py
37+
38+
- name: Check for changes
39+
id: check_changes
40+
run: |
41+
git diff --quiet docs/Commands/README.md || echo "changes=true" >> $GITHUB_OUTPUT
42+
43+
- name: Get branch name
44+
id: branch_name
45+
run: |
46+
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
47+
echo "branch=${{ github.head_ref }}" >> $GITHUB_OUTPUT
48+
else
49+
echo "branch=${{ github.ref_name }}" >> $GITHUB_OUTPUT
50+
fi
51+
52+
# Commit directly to branch if not main
53+
- name: Commit changes to branch
54+
if: |
55+
steps.check_changes.outputs.changes == 'true' &&
56+
steps.branch_name.outputs.branch != 'main'
57+
run: |
58+
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
59+
git config --local user.name "github-actions[bot]"
60+
git add docs/Commands/README.md
61+
git commit -m "docs: Update command documentation [skip ci]"
62+
git push
63+
64+
# Create PR only for main branch
65+
- name: Create Pull Request
66+
if: |
67+
steps.check_changes.outputs.changes == 'true' &&
68+
steps.branch_name.outputs.branch == 'main'
69+
uses: peter-evans/create-pull-request@v5
70+
with:
71+
commit-message: 'docs: Update command documentation'
72+
title: 'docs: Update command documentation'
73+
body: |
74+
This PR updates the command documentation to reflect the latest changes in the CLI.
75+
76+
Changes were automatically generated based on the current CLI implementation.
77+
branch: update-command-docs
78+
delete-branch: true
79+
labels: documentation
80+
base: main

0 commit comments

Comments
 (0)