twitch-dl/scripts/generate_docs
2024-03-29 08:43:30 +01:00

149 lines
3.6 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Auto-generates documentation from command defs in console.py.
"""
import click
import html
import os
import re
import shutil
import textwrap
from click import Command
from twitchdl.cli import cli
START_MARKER = "<!-- ------------------- generated docs start ------------------- -->"
END_MARKER = "<!-- ------------------- generated docs end ------------------- -->"
def main():
update_changelog()
parent_ctx = click.Context(cli, info_name="twitch-dl")
for name, command in cli.commands.items():
ctx = click.Context(cli, info_name=name, parent=parent_ctx)
update_docs(command, ctx)
def update_changelog():
print("Updating: docs/changelog.md")
root = os.path.realpath(os.path.dirname(os.path.dirname(__file__)))
source = os.path.join(root, "CHANGELOG.md")
target = os.path.join(root, "docs/changelog.md")
shutil.copy(source, target)
def update_docs(command: Command, ctx: click.Context):
path = os.path.join("docs", "commands", f"{command.name}.md")
content = render_command(command, ctx)
if not os.path.exists(path):
print(f"Creating: {path}")
write(path, content)
else:
print(f"Updating: {path}")
[_, handwritten] = read(path).split(END_MARKER)
content = f"{content.strip()}\n\n{END_MARKER}\n\n{handwritten.strip()}"
write(path, content)
def render_command(command: Command, ctx: click.Context):
content = START_MARKER
content += f"\n# twitch-dl {command.name}\n\n"
if command.help:
content += command.help + "\n\n"
content += render_usage(ctx, command)
content += render_options(ctx, command)
return content
def render_usage(ctx: click.Context, command: Command):
content = "### USAGE\n\n"
content += "```\n"
content += command.get_usage(ctx).replace("Usage: ", "")
content += "\n```\n\n"
return content
def render_options(ctx, command: Command):
options = list(get_options(command))
if not options:
return ""
content = "### OPTIONS\n\n"
content += "<table>\n"
content += "<tbody>"
for opts, help in options:
content += textwrap.dedent(f"""
<tr>
<td class="code">{escape(opts)}</td>
<td>{escape(help)}</td>
</tr>
""")
content += "</tbody>\n"
content += "</table>\n\n"
return content
def get_options(command: Command):
for option in command.params:
if isinstance(option, click.Option):
opts = ", ".join(option.opts)
opts += option_type(option)
help = option.help or ""
help = re.sub(r"\s+", " ", help)
help += choices(option)
if option.default:
help += f" [default: `{option.default}`]"
yield opts, help
def option_type(option: click.Option):
match option.type:
case click.types.StringParamType():
return " TEXT"
case click.types.Choice():
return " TEXT"
case click.types.IntParamType():
return " INTEGER"
case _:
return ""
def choices(option: click.Option):
if isinstance(option.type, click.Choice):
choices = ", ".join(f"`{c}`" for c in option.type.choices)
return f" Possible values: {choices}."
return ""
def read(path):
with open(path, "r") as f:
return f.read()
def write(path, content):
with open(path, "w") as f:
return f.write(content)
def escape(text: str):
text = html.escape(text)
text = re.sub(r"`([\S]+)`", "<code>\\1</code>", text)
return text
if __name__ == "__main__":
main()