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.