aboutsummaryrefslogtreecommitdiffstats
path: root/docs/conf.py
diff options
context:
space:
mode:
Diffstat (limited to 'docs/conf.py')
-rw-r--r--docs/conf.py228
1 files changed, 228 insertions, 0 deletions
diff --git a/docs/conf.py b/docs/conf.py
new file mode 100644
index 00000000..4ab831d3
--- /dev/null
+++ b/docs/conf.py
@@ -0,0 +1,228 @@
+# Configuration file for the Sphinx documentation builder.
+# https://www.sphinx-doc.org/en/master/usage/configuration.html
+
+import ast
+import importlib
+import inspect
+import sys
+import typing
+from pathlib import Path
+
+import git
+import tomli
+from sphinx.application import Sphinx
+
+# -- Project information -----------------------------------------------------
+
+project = "Bot Core"
+copyright = "2021, Python Discord"
+author = "Python Discord"
+
+PROJECT_ROOT = Path(__file__).parent.parent
+REPO_LINK = "https://github.com/python-discord/bot-core"
+SOURCE_FILE_LINK = f"{REPO_LINK}/blob/{git.Repo(PROJECT_ROOT).commit().hexsha}"
+
+sys.path.insert(0, str(PROJECT_ROOT.absolute()))
+
+# The full version, including alpha/beta/rc tags
+release = version = tomli.loads(
+ (PROJECT_ROOT / "pyproject.toml").read_text(encoding="utf-8")
+)["tool"]["poetry"]["version"]
+
+# -- General configuration ---------------------------------------------------
+
+add_module_names = False
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named "sphinx.ext.*") or your custom
+# ones.
+extensions = [
+ "sphinx.ext.extlinks",
+ "sphinx.ext.autodoc",
+ "sphinx.ext.todo",
+ "sphinx.ext.napoleon",
+ "sphinx_autodoc_typehints",
+ "releases",
+ "sphinx.ext.linkcode",
+ "sphinx.ext.githubpages",
+]
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ["_templates"]
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+# This pattern also affects html_static_path and html_extra_path.
+exclude_patterns = []
+
+
+# -- Options for HTML output -------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+#
+html_theme = "furo"
+
+html_theme_options = {
+ "light_css_variables": {
+ "color-api-name": "var(--color-link)",
+ },
+}
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ["_static"]
+html_title = f"{project} v{version}"
+html_short_title = project
+
+html_logo = "https://raw.githubusercontent.com/python-discord/branding/main/logos/logo_full/logo_full.min.svg"
+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()
+
+
+def skip(*args) -> bool:
+ """Things that should be skipped by the autodoc generation."""
+ name = args[2]
+ would_skip = args[4]
+
+ if name in (
+ "__weakref__",
+ ):
+ return True
+ return would_skip
+
+
+def setup(app: Sphinx) -> None:
+ """Add extra hook-based autodoc configuration."""
+ app.connect("autodoc-skip-member", skip)
+
+
+# -- Extension configuration -------------------------------------------------
+
+# -- Options for todo extension ----------------------------------------------
+
+# If true, `todo` and `todoList` produce output, else they produce nothing.
+todo_include_todos = True
+
+
+# -- Options for napoleon extension ------------------------------------------
+napoleon_google_docstring = True
+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 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