edq.clilib.pdoc

  1import os
  2import typing
  3
  4import bs4
  5
  6import edq.clilib.model
  7import edq.util.dirent
  8
  9CSS_CLASS_MODULE_DOCS: str = 'edq-cli-module-docs'
 10CSS_CLASS_PACKAGE_DOCS: str = 'edq-cli-package-docs'
 11CSS_CLASS_PACKAGE_DIRENT: str = 'edq-cli-package-dirent'
 12
 13def update_pdoc(package_dir: str, base_qualified_name: str, docs_base_dir: str) -> None:
 14    """
 15    Update a built pdoc HTML documentation dir with information about CLI utils from the given package dir.
 16    This will add information (like usage) to the HTML documentation.
 17
 18    The base docs dir should be such that we can use a module's qualified name to locate the documentation file,
 19    e.g., `edq.cli.version` -> `<base docs dir>/edq/cli/version.html`.
 20    """
 21
 22    package = edq.clilib.model.CLIPackage.from_path(package_dir, base_qualified_name)
 23    if (package is None):
 24        raise ValueError(f"Target dir is not a CLI package: '{package_dir}'.")
 25
 26    _update_package(package, docs_base_dir)
 27
 28def _update_package(package: edq.clilib.model.CLIPackage, docs_base_dir: str) -> None:
 29    """ Recursively update the documentation for a package. """
 30
 31    _update_package_docs(package, docs_base_dir)
 32
 33    for entry in package.dirents:
 34        if (isinstance(entry, edq.clilib.model.CLIModule)):
 35            _update_module_docs(entry, docs_base_dir)
 36        elif (isinstance(entry, edq.clilib.model.CLIPackage)):
 37            _update_package(entry, docs_base_dir)
 38        else:
 39            raise ValueError(f"Unknown CLI type: '{type(entry)}'.")
 40
 41def _update_package_docs(package: edq.clilib.model.CLIPackage, docs_base_dir: str) -> None:
 42    """ Update the documentation for a package. """
 43
 44    path = _get_docs_path(package, docs_base_dir)
 45
 46    # The base rel name needs to point to the parent.
 47    base_rel_name = '.'.join(package.qualified_name.split('.')[0:-1])
 48
 49    lines: typing.List[str] = []
 50    _list_package(package, docs_base_dir, base_rel_name, lines)
 51
 52    if (len(lines) == 0):
 53        return
 54
 55    content = '<p>This package contains the following CLI tools:</p><hr />'
 56    content += "\n<hr />\n".join(lines)
 57
 58    _insert_html(path, CSS_CLASS_PACKAGE_DOCS, content)
 59
 60def _list_package(
 61        package: edq.clilib.model.CLIPackage,
 62        docs_base_dir: str,
 63        base_rel_name: str,
 64        lines: typing.List[str],
 65        ) -> None:
 66    """ List the contents of a package for a docs page. """
 67
 68    for entry in package.dirents:
 69        rel_name = entry.qualified_name.removeprefix(base_rel_name + '.')
 70
 71        parts = rel_name.split('.')
 72        parts[-1] += '.html'
 73        rel_href = '/'.join(parts)
 74
 75        if (isinstance(entry, edq.clilib.model.CLIModule)):
 76            html = f"""
 77                <div class='{CSS_CLASS_PACKAGE_DIRENT}'>
 78                    <a href='{rel_href}'>{entry.qualified_name}</a>
 79                    <p>
 80                        {entry.get_description()}
 81                    </p><pre><code>{entry.get_usage_text()}</code></pre>
 82                </div>
 83            """
 84            lines.append(html)
 85        elif (isinstance(entry, edq.clilib.model.CLIPackage)):
 86            html = f"""
 87                <div class='{CSS_CLASS_PACKAGE_DIRENT}'>
 88                    <a href='{rel_href}'>{entry.qualified_name}.*</a>
 89                    <p>
 90                        {entry.get_description()}
 91                    </p>
 92                </div>
 93            """
 94            lines.append(html)
 95
 96            _list_package(entry, docs_base_dir, base_rel_name, lines)
 97        else:
 98            raise ValueError(f"Unknown CLI type: '{type(entry)}'.")
 99
100def _update_module_docs(module: edq.clilib.model.CLIModule, docs_base_dir: str) -> None:
101    """ Update the documentation for a module. """
102
103    path = _get_docs_path(module, docs_base_dir)
104    content = f"<div class='.{CSS_CLASS_MODULE_DOCS}'><pre><code>{module.get_help_text()}</code></pre></div>"
105    _insert_html(path, CSS_CLASS_MODULE_DOCS, content)
106
107def _insert_html(path: str, css_class: str, content: str) -> None:
108    """ Insert HTML into a doc file. """
109
110    text = edq.util.dirent.read_file(path)
111    document = bs4.BeautifulSoup(text, 'html.parser')
112
113    # Check for previous content.
114    tags = document.select(f"div.{css_class}")
115    for tag in tags:
116        tag.decompose()
117
118    # Add in new content.
119    parents = document.select('.module-info .docstring')
120    if (len(parents) != 1):
121        raise ValueError(f"Could not find exactly one HTML docstring (found {len(parents)}): '{path}'.")
122
123    new_tag = bs4.BeautifulSoup(content, 'html.parser')
124
125    parents[0].append(new_tag)
126
127    edq.util.dirent.write_file(path, str(document))
128
129def _get_docs_path(dirent: edq.clilib.model.CLIDirent, docs_base_dir: str) -> str:
130    """ Create the relative path for a CLI dirent's HTML documentation from its qualified name. """
131
132    parts = dirent.qualified_name.split('.')
133    parts[-1] += '.html'
134    return os.path.join(docs_base_dir, *parts)
CSS_CLASS_MODULE_DOCS: str = 'edq-cli-module-docs'
CSS_CLASS_PACKAGE_DOCS: str = 'edq-cli-package-docs'
CSS_CLASS_PACKAGE_DIRENT: str = 'edq-cli-package-dirent'
def update_pdoc(package_dir: str, base_qualified_name: str, docs_base_dir: str) -> None:
14def update_pdoc(package_dir: str, base_qualified_name: str, docs_base_dir: str) -> None:
15    """
16    Update a built pdoc HTML documentation dir with information about CLI utils from the given package dir.
17    This will add information (like usage) to the HTML documentation.
18
19    The base docs dir should be such that we can use a module's qualified name to locate the documentation file,
20    e.g., `edq.cli.version` -> `<base docs dir>/edq/cli/version.html`.
21    """
22
23    package = edq.clilib.model.CLIPackage.from_path(package_dir, base_qualified_name)
24    if (package is None):
25        raise ValueError(f"Target dir is not a CLI package: '{package_dir}'.")
26
27    _update_package(package, docs_base_dir)

Update a built pdoc HTML documentation dir with information about CLI utils from the given package dir. This will add information (like usage) to the HTML documentation.

The base docs dir should be such that we can use a module's qualified name to locate the documentation file, e.g., edq.cli.version -> <base docs dir>/edq/cli/version.html.