"""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",
]