diff options
Diffstat (limited to 'docs')
-rw-r--r-- | docs/_static/changelog.css | 11 | ||||
-rw-r--r-- | docs/_static/changelog.js | 37 | ||||
-rw-r--r-- | docs/changelog.rst | 14 | ||||
-rw-r--r-- | docs/conf.py | 125 | ||||
-rw-r--r-- | docs/index.rst | 6 | ||||
-rw-r--r-- | docs/utils.py | 117 |
6 files changed, 135 insertions, 175 deletions
diff --git a/docs/_static/changelog.css b/docs/_static/changelog.css deleted file mode 100644 index 343792a1..00000000 --- a/docs/_static/changelog.css +++ /dev/null @@ -1,11 +0,0 @@ -[data-theme='dark'] #changelog .dark, -[data-theme='light'] #changelog .light, -[data-theme='auto'] #changelog .light { - display: inline; -} - -[data-theme='dark'] #changelog .light, -[data-theme='light'] #changelog .dark, -[data-theme='auto'] #changelog .dark { - display: none; -} diff --git a/docs/_static/changelog.js b/docs/_static/changelog.js deleted file mode 100644 index f72d025c..00000000 --- a/docs/_static/changelog.js +++ /dev/null @@ -1,37 +0,0 @@ -/** Update the changelog colors in dark mode */ - -const changelog = document.getElementById("changelog"); - -function updateEntryColor(entry) { - const line = entry.lastChild; - const lightColorSpan = line.childNodes.item(1); - const darkColorSpan = lightColorSpan.cloneNode(true); - - line.insertBefore(darkColorSpan, lightColorSpan); - - lightColorSpan.classList.add("light"); - darkColorSpan.classList.add("dark"); - - let color; - switch (darkColorSpan.textContent) { - case "Feature": - color = "#5BF38E"; - break; - case "Support": - color = "#55A5E7"; - break; - case "Bug": - color = "#E14F4F"; - break; - default: - color = null; - } - - darkColorSpan.style["color"] = color; -} - -if (changelog !== null) { - for (let collection of changelog.getElementsByClassName("simple")) { - Array.from(collection.children).forEach(updateEntryColor); - } -} diff --git a/docs/changelog.rst b/docs/changelog.rst deleted file mode 100644 index 743fcc20..00000000 --- a/docs/changelog.rst +++ /dev/null @@ -1,14 +0,0 @@ -.. See docs for details on formatting your entries - https://releases.readthedocs.io/en/latest/concepts.html - - -Changelog -========= - -- :release:`1.2.0 <9th January 2022>` -- :feature:`12` Code block detection regex -- :release:`1.1.0 <2nd December 2021>` -- :support:`2` Autogenerated docs. -- :feature:`2` Regex utility. -- :release:`1.0.0 <17th November 2021>` -- :support:`1` Core package, poetry, and linting CI. diff --git a/docs/conf.py b/docs/conf.py index 4ab831d3..476a4d36 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,17 +1,20 @@ # Configuration file for the Sphinx documentation builder. # https://www.sphinx-doc.org/en/master/usage/configuration.html -import ast -import importlib -import inspect +import functools +import os.path import sys -import typing from pathlib import Path import git import tomli from sphinx.application import Sphinx +# Handle the path not being set correctly in actions. +sys.path.insert(0, os.path.abspath('..')) + +from docs import utils # noqa: E402 + # -- Project information ----------------------------------------------------- project = "Bot Core" @@ -38,11 +41,11 @@ add_module_names = False # ones. extensions = [ "sphinx.ext.extlinks", + "sphinx.ext.intersphinx", "sphinx.ext.autodoc", "sphinx.ext.todo", "sphinx.ext.napoleon", "sphinx_autodoc_typehints", - "releases", "sphinx.ext.linkcode", "sphinx.ext.githubpages", ] @@ -80,52 +83,10 @@ html_logo = "https://raw.githubusercontent.com/python-discord/branding/main/logo html_favicon = html_logo html_css_files = [ - "changelog.css", "logo.css", ] -html_js_files = [ - "changelog.js", -] - - -# -- Autodoc cleanup --------------------------------------------------------- -# Clean up the output generated by autodoc to produce a nicer documentation tree -# This is kept in a function to avoid polluting the namespace -def __cleanup() -> None: - for file in (PROJECT_ROOT / "docs" / "output").iterdir(): - if file.name == "modules.rst": - # We only have one module, so this is redundant - # Remove it and flatten out the tree - file.unlink() - - elif file.name == "botcore.rst": - # We want to bring the submodule name to the top, and remove anything that's not a submodule - result = "" - for line in file.read_text(encoding="utf-8").splitlines(keepends=True): - if ".." not in line and result == "": - # We have not reached the first submodule, this is all filler - continue - elif "Module contents" in line: - # We have parsed all the submodules, so let's skip the redudant module name - break - result += line - - result = "Botcore\n=======\n\n" + result - file.write_text(result, encoding="utf-8") - - else: - # Clean up the submodule name so it's just the name without the top level module name - # example: `botcore.regex module` -> `regex` - lines = file.read_text(encoding="utf-8").splitlines() - lines[0] = lines[0].replace("botcore.", "").replace("module", "").strip() - - # Take the opportunity to configure autodoc - lines = "\n".join(lines).replace("undoc-members", "special-members") - file.write_text(lines, encoding="utf-8") - - -__cleanup() +utils.cleanup() def skip(*args) -> bool: @@ -159,70 +120,18 @@ napoleon_numpy_docstring = False napoleon_attr_annotations = True -# -- Options for releases extension ------------------------------------------ -releases_github_path = REPO_LINK.removeprefix("https://github.com/") - - # -- Options for extlinks extension ------------------------------------------ extlinks = { "repo-file": (f"{REPO_LINK}/blob/main/%s", "repo-file %s") } +# -- Options for intersphinx extension --------------------------------------- +intersphinx_mapping = { + "python": ("https://docs.python.org/3", None), + "discord": ("https://discordpy.readthedocs.io/en/master/", None), +} + + # -- Options for the linkcode extension -------------------------------------- -def linkcode_resolve(domain: str, info: dict[str, str]) -> typing.Optional[str]: - """ - Function called by linkcode to get the URL for a given resource. - - See for more details: - https://www.sphinx-doc.org/en/master/usage/extensions/linkcode.html#confval-linkcode_resolve - """ - if domain != "py": - raise Exception("Unknown domain passed to linkcode function.") - - symbol_name = info["fullname"] - - module = importlib.import_module(info["module"]) - - symbol = [module] - for name in symbol_name.split("."): - symbol.append(getattr(symbol[-1], name)) - symbol_name = name - - try: - lines, start = inspect.getsourcelines(symbol[-1]) - end = start + len(lines) - except TypeError: - # Find variables by parsing the ast - source = ast.parse(inspect.getsource(symbol[-2])) - while isinstance(source.body[0], ast.ClassDef): - source = source.body[0] - - for ast_obj in source.body: - if isinstance(ast_obj, ast.Assign): - names = [] - for target in ast_obj.targets: - if isinstance(target, ast.Tuple): - names.extend([name.id for name in target.elts]) - else: - names.append(target.id) - - if symbol_name in names: - start, end = ast_obj.lineno, ast_obj.end_lineno - break - else: - raise Exception(f"Could not find symbol `{symbol_name}` in {module.__name__}.") - - _, offset = inspect.getsourcelines(symbol[-2]) - if offset != 0: - offset -= 1 - start += offset - end += offset - - file = Path(inspect.getfile(module)).relative_to(PROJECT_ROOT).as_posix() - - url = f"{SOURCE_FILE_LINK}/{file}#L{start}" - if end != start: - url += f"-L{end}" - - return url +linkcode_resolve = functools.partial(utils.linkcode_resolve, SOURCE_FILE_LINK) diff --git a/docs/index.rst b/docs/index.rst index e7c25ef1..81975f35 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -13,11 +13,6 @@ Reference output/botcore -.. toctree:: - :caption: Other: - - changelog - Extras ================== @@ -25,3 +20,4 @@ Extras * :ref:`genindex` * :ref:`search` * :repo-file:`Information <docs/README.md>` +* :repo-file:`Changelog <CHANGELOG.md>` diff --git a/docs/utils.py b/docs/utils.py new file mode 100644 index 00000000..76b3e098 --- /dev/null +++ b/docs/utils.py @@ -0,0 +1,117 @@ +"""Utilities used in generating docs.""" + +import ast +import importlib +import inspect +import typing +from pathlib import Path + +PROJECT_ROOT = Path(__file__).parent.parent + + +def linkcode_resolve(source_url: str, domain: str, info: dict[str, str]) -> typing.Optional[str]: + """ + Function called by linkcode to get the URL for a given resource. + + See for more details: + https://www.sphinx-doc.org/en/master/usage/extensions/linkcode.html#confval-linkcode_resolve + """ + if domain != "py": + raise Exception("Unknown domain passed to linkcode function.") + + symbol_name = info["fullname"] + + module = importlib.import_module(info["module"]) + + symbol = [module] + for name in symbol_name.split("."): + symbol.append(getattr(symbol[-1], name)) + symbol_name = name + + try: + lines, start = inspect.getsourcelines(symbol[-1]) + end = start + len(lines) + except TypeError: + # Find variables by parsing the ast + source = ast.parse(inspect.getsource(symbol[-2])) + while isinstance(source.body[0], ast.ClassDef): + source = source.body[0] + + for ast_obj in source.body: + if isinstance(ast_obj, ast.Assign): + names = [] + for target in ast_obj.targets: + if isinstance(target, ast.Tuple): + names.extend([name.id for name in target.elts]) + else: + names.append(target.id) + + if symbol_name in names: + start, end = ast_obj.lineno, ast_obj.end_lineno + break + else: + raise Exception(f"Could not find symbol `{symbol_name}` in {module.__name__}.") + + _, offset = inspect.getsourcelines(symbol[-2]) + if offset != 0: + offset -= 1 + start += offset + end += offset + + file = Path(inspect.getfile(module)).relative_to(PROJECT_ROOT).as_posix() + + url = f"{source_url}/{file}#L{start}" + if end != start: + url += f"-L{end}" + + return url + + +def cleanup() -> None: + """Remove unneeded autogenerated doc files, and clean up others.""" + included = __get_included() + + for file in (PROJECT_ROOT / "docs" / "output").iterdir(): + if file.name in ("botcore.rst", "botcore.exts.rst", "botcore.utils.rst") and file.name in included: + content = file.read_text(encoding="utf-8").splitlines(keepends=True) + + # Rename the extension to be less wordy + # Example: botcore.exts -> Botcore Exts + title = content[0].split()[0].strip().replace("botcore.", "").replace(".", " ").title() + title = f"{title}\n{'=' * len(title)}\n\n" + content = title, *content[3:] + + file.write_text("".join(content), encoding="utf-8") + + elif file.name in included: + # Clean up the submodule name so it's just the name without the top level module name + # example: `botcore.regex module` -> `regex` + lines = file.read_text(encoding="utf-8").splitlines(keepends=True) + lines[0] = lines[0].replace("module", "").strip().split(".")[-1] + "\n" + file.write_text("".join(lines)) + + else: + # These are files that have not been explicitly included in the docs via __all__ + print("Deleted file", file.name) + file.unlink() + continue + + # Take the opportunity to configure autodoc + content = file.read_text(encoding="utf-8").replace("undoc-members", "special-members") + file.write_text(content, encoding="utf-8") + + +def __get_included() -> set[str]: + """Get a list of files that should be included in the final build.""" + + def get_all_from_module(module_name: str) -> set[str]: + module = importlib.import_module(module_name) + _modules = {module.__name__ + ".rst"} + + if hasattr(module, "__all__"): + for sub_module in module.__all__: + _modules.update(get_all_from_module(sub_module)) + + return _modules + + return get_all_from_module("botcore") |