Source code for berhoel.sphinx_settings

"""Sphinx configuration for documentation for ``berhoel`` python packages."""

from __future__ import annotations

from abc import ABC, abstractmethod
import enum
from pathlib import Path
from typing import TYPE_CHECKING

import cmakelists_parsing.parsing as cmp
import tomli

if TYPE_CHECKING:
    from sphinx.application import Sphinx

__date__ = "2024/11/19 22:27:47 hoel"
__author__ = "Berthold Höllmann"
__copyright__ = "Copyright © 2022 by Berthold Höllmann"
__credits__ = ["Berthold Höllmann"]
__maintainer__ = "Berthold Höllmann"
__email__ = "berhoel@gmail.com"

__all__ = [
    "ProjectTypes",
    "configuration",
    "extensions",
    "templates_path",
    "language",
    "exclude_patterns",
    "html_theme",
    "html_theme_path",
    "html_static_path",
    "latex_elements",
    "latex_show_urls",
    "latex_engine",
    "intersphinx_mapping",
    "todo_include_todos",
    "napoleon_google_docstring",
    "napoleon_numpy_docstring",
    "napoleon_include_init_with_doc",
    "napoleon_include_private_with_doc",
    "napoleon_include_special_with_doc",
    "napoleon_use_admonition_for_examples",
    "napoleon_use_admonition_for_notes",
    "napoleon_use_admonition_for_references",
    "napoleon_use_ivar",
    "napoleon_use_param",
    "napoleon_use_rtype",
    "autodoc_default_options",
    "favicons",
    "get_html_theme_path",
    "setup",
    "sitemap_url_scheme",
    "sitemap_excludes",
]


[docs] class ProjectTypes(enum.Enum): """Enum to allow identifying build system.""" POETRY = enum.auto() CMAKE = enum.auto()
# -- process project configuration -------------------------------------------
[docs] class _ProjectInfo(ABC): """Base class for Project information.""" @property @abstractmethod def project(self) -> str: pass @property @abstractmethod def release(self) -> str: pass @property @abstractmethod def author(self) -> str: pass
[docs] def configuration(self) -> dict[str, str]: """Return collected configuration information. Returns: dict[str, str] mapping containing project information """ res = {"project": self.project, "release": self.release, "author": self.author} if hasattr(self, "html_baseurl"): res.update({"html_baseurl": self.html_baseurl}) return res
[docs] class PyProject_toml(_ProjectInfo): # noqa:N801 """Provide project information from ``pyproject.toml`` file."""
[docs] def __init__(self, cfg_file: Path | str | None = None): """Process pyproject.toml file for project information. Parameters: cfg_file: str|Path|None path to pyproject.toml file for current project. (default: search for appropriate file) """ if cfg_file is None: base_path = Path().absolute() py_project = base_path / "pyproject.toml" while not py_project.is_file(): if base_path == base_path.parent: msg = "pyproject.toml not found" raise SystemError(msg) base_path = base_path.parent py_project = base_path / "pyproject.toml" self._toml_inst = tomli.load((base_path / "pyproject.toml").open("rb")) else: self._toml_inst = tomli.load( (cfg_file if isinstance(cfg_file, Path) else Path(cfg_file)).open("rb"), )
@property def project(self) -> str: return self._toml_inst["tool"]["poetry"]["name"] @property def release(self) -> str: return self._toml_inst["tool"]["poetry"]["version"] @property def author(self) -> str: return ", ".join(self._toml_inst["tool"]["poetry"]["authors"]) # -- Option for sphinx_sitemap extension @property def html_baseurl(self) -> str: """Return bnase URL for generated documentation. Used for feeding sitemap extension. Returns: str base URL """ return self._toml_inst["tool"]["poetry"]["homepage"]
[docs] class CMakeLists_txt(_ProjectInfo): # noqa:N801 """Provide project information from CMakeLists.txt file."""
[docs] def __init__(self, cfg_file: Path | str | None = None): """Process CMakeLists.txt file for project information. Parameters: cfg_file: str|Path|None path to CMakeLists.txt file for current project. (default: search for appropriate file) """ if cfg_file is None: base_path = Path().absolute() cmake_project = base_path / "CMakeLists.txt" while not cmake_project.is_file() or not self._has_project(cmake_project): if base_path == base_path.parent: msg = "CMakeLists.txt not found" raise SystemError(msg) base_path = base_path.parent cmake_project = base_path / "CMakeLists.txt" self._cmake_lists = cmp.parse((base_path / "CMakeLists.txt").read_text()) else: self._cmake_lists = cmp.parse( ( cfg_file if isinstance(cfg_file, Path) else Path(cfg_file) ).read_text(), )
[docs] @staticmethod def _has_project(cfg_file: Path) -> bool: """Check if ``CMakeLists.txt`` file has ``project`` command.""" cmake_lists = cmp.parse(cfg_file.read_text()) if not isinstance(cmake_lists, list): raise TypeError for line in cmake_lists: if hasattr(line, "name") and line.name == "project": return True return False
@property def project(self) -> str: """Return project name. Returns: str project information """ if not isinstance(self._cmake_lists, list): raise TypeError for line in self._cmake_lists: if hasattr(line, "name") and line.name == "project": return line.body[0].contents msg = "No project command found in CMakeLists.txt" raise SystemError(msg) @property def release(self) -> str: """Return release information. Returns: str version string Raises: SystemError in case the version information is not avaliable """ if not isinstance(self._cmake_lists, list): raise TypeError for line in self._cmake_lists: if hasattr(line, "name") and line.name == "project": if not isinstance(line.body, list): raise TypeError found = False for entry in line.body[1:]: if found: return entry.contents if entry.contents == "VERSION": found = True msg = "No version number found in CMakeLists.txt project command" raise SystemError(msg) msg = "No project command found in CMakeLists.txt" raise SystemError(msg) @property def author(self) -> str: """Return information on author. Returns: str author information """ return "Berthold Höllmann <berthold@xn--hllmanns-n4a.de>"
[docs] def configuration( project_type: ProjectTypes = ProjectTypes.POETRY, cfg_file: Path | str | None = None, ) -> _ProjectInfo: """Get configuration class appropriate to build system for current project. Parameters: project_type: project_types Type of build system configuration file (poetry or CMake) cfg_file: str | Path | None Path to build system configuration file. Will be search if not given. (default None) """ if project_type == ProjectTypes.POETRY: return PyProject_toml(cfg_file) if project_type == ProjectTypes.CMAKE: return CMakeLists_txt(cfg_file) msg = "Unknown project type" raise SystemError(msg)
# -- Path setup --------------------------------------------------------------
[docs] def get_html_theme_path() -> list[Path]: """Return path to HTML theme. Returns: list[Path] path to HTML theme """ return [Path(__file__).parent]
# -- General configuration --------------------------------------------------- # 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.autodoc", "sphinx.ext.viewcode", "sphinx.ext.todo", "sphinx.ext.autodoc", "sphinx.ext.intersphinx", "sphinx.ext.coverage", "sphinx.ext.mathjax", "sphinx.ext.autosummary", "sphinx.ext.napoleon", "sphinx.ext.viewcode", "sphinx_favicon", "sphinx_sitemap", ] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = "en" # 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 = [ "_build", "Thumbs.db", ".DS_Store", "**/flycheck_*.py", "tests", ] # -- Options for HTML output ------------------------------------------------- # Activate the theme. html_theme = "berhoel_sphinx_theme" html_theme_path = [Path(__file__).parent] # 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"] # -- Options for LaTeX/PDF output ------------------------------------------------- latex_elements = { "fontenc": "\\usepackage{fontspec}", "fontpkg": """\ \\setmainfont{DejaVu Serif} \\setsansfont{DejaVu Sans} \\setmonofont{DejaVu Sans Mono}""", "geometry": "\\usepackage[vmargin=2.5cm, hmargin=3cm]{geometry}", "preamble": """\ \\usepackage[titles]{tocloft} \\cftsetpnumwidth {1.25cm}\\cftsetrmarg{1.5cm} \\setlength{\\cftchapnumwidth}{0.75cm} \\setlength{\\cftsecindent}{\\cftchapnumwidth} \\setlength{\\cftsecnumwidth}{1.25cm}""", "fncychap": "\\usepackage[Bjornstrup]{fncychap}", "printindex": "\\footnotesize\\raggedright\\printindex", } latex_show_urls = "footnote" latex_engine = "xelatex" # -- Extension configuration ------------------------------------------------- # -- Options for intersphinx extension --------------------------------------- # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = {"python": ("https://docs.python.org/3", None)} # -- Options for todo extension ---------------------------------------------- # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = True # Napoleon settings napoleon_google_docstring = True napoleon_numpy_docstring = True napoleon_include_init_with_doc = False napoleon_include_private_with_doc = False napoleon_include_special_with_doc = True napoleon_use_admonition_for_examples = False napoleon_use_admonition_for_notes = False napoleon_use_admonition_for_references = False napoleon_use_ivar = False napoleon_use_param = True napoleon_use_rtype = True # -- Options for autodoc extension ---------------------------------------------- autodoc_default_options = { "private-members": True, "member-order": "bysource", "ignore-module-all": True, "special-members": True, "undoc-members": True, "exclude-members": "__weakref__,__dict__,__module__", } # -- Options for favicon extension ---------------------------------------------- favicons = [ { "rel": "icon", "static-file": "bhLogo.svg", # => use `_static/icon.svg` "type": "image/ico", }, ]
[docs] def setup(app: Sphinx) -> None: """Prepare settings for Sphinx. Parameters: app: Sphinx app zu configure. """ # app.add_css_file("bhoel-sphinx.css") # also can be a full URL app.add_css_file("custom.css") # also can be a full URL app.add_css_file("https://www.höllmanns.de/fonts/jetbrains-mono.css") app.add_css_file("https://www.höllmanns.de/fonts/orelega-one.css") app.add_css_file("https://www.höllmanns.de/fonts/roboto.css")
# -- Options for sphinx_sitemap extension ---------------------------------------------- sitemap_url_scheme = "{link}" sitemap_excludes = [ "search.html", "genindex.html", ]