#!/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 = "" END_MARKER = "" 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 += "\n" content += "" for opts, help in options: content += textwrap.dedent(f""" """) content += "\n" content += "
{escape(opts)} {escape(help)}
\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]+)`", "\\1", text) return text if __name__ == "__main__": main()