-
Notifications
You must be signed in to change notification settings - Fork 77
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
New script to generate the command docs #4664
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
import inspect | ||
import sys | ||
from pathlib import Path | ||
from typing import List | ||
|
||
from typer.main import get_command | ||
|
||
from demisto_sdk.__main__ import app | ||
|
||
|
||
def extract_changed_commands(modified_files: List[str]) -> List[str]: | ||
"""Extract the command names from the list of modified files.""" | ||
changed_commands = [] | ||
for file in modified_files: | ||
# Check if the modified file ends with '_setup.py' | ||
if file.endswith("_setup.py"): | ||
command_name = Path(file).stem.replace("_setup", "") | ||
changed_commands.append(command_name) | ||
return changed_commands | ||
|
||
|
||
def get_sdk_command(command_name: str): | ||
click_app = get_command(app) | ||
command = click_app.commands.get(command_name) # type: ignore[attr-defined] | ||
|
||
if command is None: | ||
return f"No README found for command: {command_name}" | ||
return command | ||
|
||
|
||
def get_command_description(command_name: str) -> str: | ||
"""Retrieve the description (docstring) for the command.""" | ||
command = get_sdk_command(command_name) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What if the response is |
||
if isinstance(command, str): | ||
return command | ||
|
||
command_func = command.callback | ||
return inspect.getdoc(command_func) or "No description provided" | ||
|
||
|
||
def get_command_options(command_name: str) -> str: | ||
"""Generate the options section for the command.""" | ||
command = get_sdk_command(command_name) | ||
if isinstance(command, str): | ||
return command | ||
options_text = "### Options\n\n" | ||
for param in command.params: | ||
param_name = ( | ||
f"--{param.name.replace('_', '-')}" | ||
if param.param_type_name == "option" | ||
else param.name | ||
) | ||
options_text += ( | ||
f"- **{param_name}**: {param.help or 'No description provided'}\n" | ||
) | ||
if param.default is not None: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why |
||
options_text += f" - Default: `{param.default}`\n" | ||
options_text += "\n" | ||
return options_text | ||
|
||
|
||
def update_readme(command_name: str, description: str, options: str): | ||
"""Update the README.md file for the command with the given description and options.""" | ||
command_doc_path = Path("demisto_sdk/commands") / command_name / "README.md" | ||
|
||
if not command_doc_path.exists(): | ||
print(f"README.md not found for command: {command_name}") # noqa: T201 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not generate a new one? |
||
return | ||
|
||
# Read the existing README.md | ||
with command_doc_path.open("r") as f: | ||
readme_content = f.read() | ||
|
||
# Update the Description section | ||
description_start = readme_content.find("## Description") | ||
if description_start != -1: | ||
description_end = readme_content.find( | ||
"##", description_start + len("## Description") | ||
) | ||
if description_end == -1: | ||
description_end = len(readme_content) | ||
updated_readme = ( | ||
readme_content[:description_start] | ||
+ f"## Description\n{description}\n\n" | ||
+ readme_content[description_end:] | ||
) | ||
else: | ||
updated_readme = f"{readme_content}\n## Description\n{description}\n\n" | ||
|
||
# Update the Options section | ||
options_start = updated_readme.find("### Options") | ||
if options_start != -1: | ||
options_end = updated_readme.find("##", options_start + len("### Options")) | ||
if options_end == -1: | ||
options_end = len(updated_readme) | ||
updated_readme = ( | ||
updated_readme[:options_start] + options + updated_readme[options_end:] | ||
) | ||
else: | ||
updated_readme += "\n" + options | ||
|
||
# Write the updated content back into the README.md | ||
with command_doc_path.open("w") as f: | ||
f.write(updated_readme) | ||
|
||
print(f"Description and options section updated for command: {command_name}") # noqa: T201 | ||
|
||
|
||
def generate_docs_for_command(command_name: str): | ||
"""Generate documentation for a specific command.""" | ||
description = get_command_description(command_name) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are we ok with the description being |
||
options = get_command_options(command_name) | ||
update_readme(command_name, description, options) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it enough to write the file? what about committing etc? |
||
|
||
|
||
def main(): | ||
if len(sys.argv) < 2: | ||
print("Usage: python generate_docs.py <modified_file1> <modified_file2> ...") # noqa: T201 | ||
sys.exit(1) | ||
|
||
# Receive the list of modified files from command-line arguments | ||
modified_files = sys.argv[1:] | ||
changed_commands = extract_changed_commands(modified_files) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm confused, you called this script with changed commands (not changed files) - |
||
|
||
# Generate documentation for each modified command | ||
for command_name in changed_commands: | ||
generate_docs_for_command(command_name) | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,62 @@ | ||||||
import os | ||||||
import re | ||||||
import subprocess | ||||||
import sys | ||||||
from pathlib import Path | ||||||
|
||||||
EXCLUDED_BRANCHES_REGEX = r"^(master|[0-9]+\.[0-9]+\.[0-9]+)$" | ||||||
|
||||||
|
||||||
def get_current_branch(): | ||||||
"""Returns the current Git branch name.""" | ||||||
result = subprocess.run(["git", "rev-parse", "--abbrev-ref", "HEAD"], capture_output=True, text=True) | ||||||
return result.stdout.strip() | ||||||
|
||||||
|
||||||
def get_modified_files(): | ||||||
"""Returns a list of files modified in the current commit.""" | ||||||
result = subprocess.run(["git", "diff", "--cached", "--name-only"], capture_output=True, text=True) | ||||||
return result.stdout.splitlines() | ||||||
|
||||||
|
||||||
def extract_changed_commands(modified_files): | ||||||
"""Extract command names from modified _setup.py files.""" | ||||||
changed_commands = [] | ||||||
for file in modified_files: | ||||||
if file.endswith("_setup.py"): | ||||||
command_name = Path(file).stem.replace("_setup", "") | ||||||
changed_commands.append(command_name) | ||||||
return changed_commands | ||||||
Comment on lines
+10
to
+29
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add return argument and return value docstring as well as a return type: ( There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. True for all functions |
||||||
|
||||||
|
||||||
def main(): | ||||||
# Check if the branch should be excluded | ||||||
current_branch = get_current_branch() | ||||||
if re.match(EXCLUDED_BRANCHES_REGEX, current_branch): | ||||||
print(f"Pre-commit hook skipped on branch '{current_branch}'") | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
sys.exit(0) | ||||||
|
||||||
# Get the list of modified files | ||||||
modified_files = get_modified_files() | ||||||
|
||||||
# Filter for _setup.py files to determine which commands changed | ||||||
changed_commands = extract_changed_commands(modified_files) | ||||||
if not changed_commands: | ||||||
print("No modified _setup.py files found. Skipping documentation generation.") | ||||||
sys.exit(0) | ||||||
Comment on lines
+44
to
+46
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How does it make sense tho? Per the hook config ( |
||||||
|
||||||
# Run the documentation generation script with all changed commands | ||||||
print(f"Generating documentation for modified commands: {changed_commands}") | ||||||
subprocess.run([sys.executable, "generate_docs.py", *changed_commands]) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are we using a subprocess here? why not just import and run the relevant python module? |
||||||
|
||||||
# Stage the newly generated or updated README files for each command | ||||||
for command_name in changed_commands: | ||||||
readme_file = Path("demisto-sdk/commands") / command_name / "README.md" | ||||||
if readme_file.exists(): | ||||||
subprocess.run(["git", "add", str(readme_file)]) | ||||||
|
||||||
print("Pre-commit hook completed successfully.") | ||||||
|
||||||
|
||||||
if __name__ == "__main__": | ||||||
main() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't this duplicate?