#!/usr/bin/env python
#
# docs.py
"""
Configuration for documentation with
`Sphinx <https://www.sphinx-doc.org/en/master/>`_ and
`ReadTheDocs <https://readthedocs.org/>`_.
""" # noqa: D400
#
# Copyright © 2020 Dominic Davis-Foster <dominic@davis-foster.co.uk>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301, USA.
#
# stdlib
import logging
import os.path
import pathlib
import shutil
import warnings
from contextlib import suppress
from typing import Any, Dict, List, Mapping, MutableMapping, Tuple, Union
# 3rd party
import dict2css
import dom_toml
import ruamel.yaml as yaml
from domdf_python_tools.paths import PathPlus
from domdf_python_tools.stringlist import DelimitedList, StringList
from domdf_python_tools.typing import PathLike
from domdf_python_tools.utils import enquote_value
from shippinglabel import normalize
from shippinglabel.requirements import (
ComparableRequirement,
RequirementsManager,
combine_requirements,
read_requirements
)
# this package
import repo_helper
from repo_helper.blocks import (
ShieldsBlock,
create_docs_install_block,
create_docs_links_block,
installation_regex,
links_regex,
shields_regex,
short_desc_regex
)
from repo_helper.configupdater2 import ConfigUpdater
from repo_helper.files import management
from repo_helper.templates import Environment, init_repo_template_dir, template_dir
from repo_helper.utils import license_lookup, pformat_tabs, reformat_file, resource
__all__ = [
"ensure_doc_requirements",
"make_rtfd",
"make_docutils_conf",
"make_conf",
"make_alabaster_theming",
"make_readthedocs_theming",
"copy_docs_styling",
"rewrite_docs_index",
"make_404_page",
"make_docs_source_rst",
"make_docs_license_rst",
]
# Disable logging from cssutils
logging.getLogger("CSSUTILS").addHandler(logging.NullHandler())
logging.getLogger("CSSUTILS").propagate = False
logging.getLogger("CSSUTILS").addFilter(lambda record: False)
class DocRequirementsManager(RequirementsManager):
target_requirements = {
ComparableRequirement("sphinxemoji>=0.1.6"),
ComparableRequirement("sphinx-notfound-page>=0.5"),
ComparableRequirement("sphinx-copybutton>=0.2.12"),
ComparableRequirement("sphinx-pyproject>=0.1.0"),
ComparableRequirement("sphinxcontrib-applehelp==1.0.4"),
ComparableRequirement("sphinxcontrib-devhelp==1.0.2"),
ComparableRequirement("sphinxcontrib-htmlhelp==2.0.1"),
ComparableRequirement("sphinxcontrib-jsmath==1.0.1"),
ComparableRequirement("sphinxcontrib-qthelp==1.0.3"),
ComparableRequirement("sphinxcontrib-serializinghtml==1.1.5"),
}
def __init__(self, repo_path: PathLike, templates: Environment):
self.filename = os.path.join(templates.globals["docs_dir"], "requirements.txt")
self._globals = templates.globals
super().__init__(repo_path)
# Mapping of pypi_name to version specifier
theme_versions = {
"alabaster": ">=0.7.12",
"sphinx-rtd-theme": "<0.5",
"domdf-sphinx-theme": ">=0.3.0",
"repo-helper-sphinx_theme": ">=0.0.2",
"furo": "==2021.06.18b36",
}
# Mapping of pypi_name to version specifier
my_sphinx_extensions = {
"extras-require": ">=0.5.0",
"seed-intersphinx-mapping": ">=1.2.2",
"default-values": ">=0.6.0",
"toctree-plus": ">=0.6.1",
"sphinx-toolbox": ">=3.5.0",
"sphinx-debuginfo": ">=0.2.2",
"sphinx-licenseinfo": ">=0.3.1",
"html-section": ">=0.3.0",
}
def compile_target_requirements(self) -> None:
theme_name = normalize(self._globals["sphinx_html_theme"])
pypi_name = normalize(self._globals["pypi_name"])
for name, specifier in self.theme_versions.items():
if normalize(name) == theme_name:
self.target_requirements.add(ComparableRequirement(f"{name}{specifier}"))
break
else:
self.target_requirements.add(ComparableRequirement(theme_name))
for name, specifier in self.my_sphinx_extensions.items():
if name != pypi_name:
self.target_requirements.add(ComparableRequirement(f"{name}{specifier}"))
def merge_requirements(self) -> List[str]:
current_requirements_, comments, invalid_lines = read_requirements(self.req_file, include_invalid=True)
current_requirements = list(current_requirements_)
current_requirements.append(ComparableRequirement("sphinx>=3.0.3"))
for line in invalid_lines:
if line.startswith("git+"):
if line == "git+https://github.com/sphinx-toolbox/html-section.git":
continue
comments.append(line)
else:
warnings.warn(f"Ignored invalid requirement {line!r}")
other_themes = list(self.theme_versions.keys())
theme_name = normalize(self._globals["sphinx_html_theme"])
if theme_name in other_themes:
other_themes.remove(theme_name)
for req in current_requirements:
req.name = normalize(req.name)
# if req.name not in self.get_target_requirement_names() and req.name not in self.theme_versions.keys():
if req.name not in other_themes:
if req.name in {"sphinx-autodoc-typehints", "autodocsumm"}:
continue
else:
self.target_requirements.add(req)
self.target_requirements = set(combine_requirements(self.target_requirements))
self.target_requirements = {
req
for req in self.target_requirements
if req.name not in {"sphinx-tabs", "sphinx-prompt"}
}
return comments
[docs]@management.register("doc_requirements", ["enable_docs"])
def ensure_doc_requirements(repo_path: pathlib.Path, templates: Environment) -> List[str]:
"""
Ensure ``<docs_dir>/requirements.txt`` contains the required entries.
:param repo_path: Path to the repository root.
:param templates:
"""
req_file: PathPlus = DocRequirementsManager(repo_path, templates).run()
return [req_file.relative_to(repo_path).as_posix()]
[docs]@management.register("rtfd", ["enable_docs"])
def make_rtfd(repo_path: pathlib.Path, templates: Environment) -> List[str]:
"""
Add configuration for ``ReadTheDocs``.
https://readthedocs.org/
See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
:param repo_path: Path to the repository root.
:param templates:
"""
file = PathPlus(repo_path / ".readthedocs.yml")
docs_dir = PathPlus(repo_path / templates.globals["docs_dir"])
install_requirements = [
"requirements.txt",
f"{templates.globals['docs_dir']}/requirements.txt",
*templates.globals["additional_requirements_files"],
]
install_config: List[Dict] = [{"requirements": r} for r in install_requirements]
if (docs_dir / "rtd-extra-deps.txt").is_file():
install_config.append({"requirements": f"{templates.globals['docs_dir']}/rtd-extra-deps.txt"})
if templates.globals["tox_testenv_extras"]:
post_create_command = f"pip install .[{templates.globals['tox_testenv_extras']}]"
else:
post_create_command = "pip install ."
pinned_sphinxcontrib_deps = [
"sphinxcontrib-applehelp==1.0.4",
"sphinxcontrib-devhelp==1.0.2",
"sphinxcontrib-htmlhelp==2.0.1",
"sphinxcontrib-jsmath==1.0.1",
"sphinxcontrib-qthelp==1.0.3",
"sphinxcontrib-serializinghtml==1.1.5",
]
post_install_command = "pip install " + ' '.join(pinned_sphinxcontrib_deps)
# Formats: Optionally build your docs in additional formats such as PDF and ePub
config = {
"version": 2,
"sphinx": {
"builder": "html",
"configuration": f"{templates.globals['docs_dir']}/conf.py",
},
"formats": ["pdf", "htmlzip"],
"python": {"install": install_config},
"build": {
"os": "ubuntu-20.04",
"tools": {"python": "3.9"},
"jobs": {
"post_create_environment": [post_create_command],
"post_install": [post_install_command],
}
}
}
# TODO: support user customisation of search rankings
# https://docs.readthedocs.io/en/stable/config-file/v2.html#search-ranking
dumper = yaml.YAML()
dumper.indent(mapping=2, sequence=3, offset=1)
yaml_buf = yaml.StringIO()
dumper.dump(config, yaml_buf)
file.write_lines([
f"# {templates.globals['managed_message']}",
"# Read the Docs configuration file",
"---",
yaml_buf.getvalue()
])
return [file.relative_to(repo_path).as_posix()]
[docs]@management.register("docutils_conf", ["enable_docs"])
def make_docutils_conf(repo_path: pathlib.Path, templates: Environment) -> List[str]:
"""
Add configuration for ``Docutils``.
:param repo_path: Path to the repository root.
:param templates:
"""
file = PathPlus(repo_path / templates.globals["docs_dir"] / "docutils.conf")
file.parent.maybe_make(parents=True)
if not file.is_file():
file.write_text('\n'.join([
"[restructuredtext parser]",
"tab_width = 4",
'',
'',
]))
conf = ConfigUpdater()
conf.read(str(file))
required_sections = ["restructuredtext parser"]
for section in required_sections:
if section not in conf.sections():
conf.add_section(section)
conf["restructuredtext parser"]["tab_width"] = 4
file.write_clean(str(conf))
return [file.relative_to(repo_path).as_posix()]
[docs]@management.register("conf", ["enable_docs"])
def make_conf(repo_path: pathlib.Path, templates: Environment) -> List[str]:
"""
Add ``conf.py`` configuration file for ``Sphinx``.
https://www.sphinx-doc.org/en/master/index.html
:param repo_path: Path to the repository root.
:param templates:
"""
conf = templates.get_template("conf._py")
file = PathPlus(repo_path / templates.globals["docs_dir"] / "conf.py")
file.parent.maybe_make(parents=True)
username = templates.globals["username"]
repo_name = templates.globals["repo_name"]
if templates.globals["sphinx_html_theme"] in {"sphinx-rtd-theme", "domdf-sphinx-theme"}:
style = {
"display_github": True, # Integrate GitHub
"github_user": username, # Username
"github_repo": repo_name, # Repo name
"github_version": "master", # Version
"conf_py_path": f"/{templates.globals['docs_dir']}/", # Path in the checkout to the docs root
}
for key, val in style.items():
if key not in templates.globals["html_context"]:
templates.globals["html_context"][key] = val
options = {
# 'logo': 'logo.png',
"logo_only": False, # True will show just the logo
}
for key, val in options.items():
if key not in templates.globals["html_theme_options"]:
templates.globals["html_theme_options"][key] = val
elif templates.globals["sphinx_html_theme"] in {"alabaster", "repo-helper-sphinx-theme"}:
# See https://github.com/bitprophet/alabaster/blob/master/alabaster/theme.conf
# and https://alabaster.readthedocs.io/en/latest/customization.html
style = {
# 'logo': 'logo.png',
"page_width": "1200px",
"logo_name": "true",
"github_user": username, # Username
"github_repo": repo_name, # Repo name
"description": templates.globals["short_desc"],
"github_banner": "true",
"github_type": "star",
"badge_branch": "master",
"fixed_sidebar": "true",
}
for key, val in style.items():
if key not in templates.globals["html_theme_options"]:
templates.globals["html_theme_options"][key] = val
elif templates.globals["sphinx_html_theme"] in {"furo"}:
# See https://github.com/bitprophet/alabaster/blob/master/alabaster/theme.conf
# and https://alabaster.readthedocs.io/en/latest/customization.html
style = {
"toc-title-font-size": "12pt",
"toc-font-size": "12pt",
"admonition-font-size": "12pt",
}
for key in ["light_css_variables", "dark_css_variables"]:
if key not in templates.globals["html_theme_options"]:
templates.globals["html_theme_options"][key] = style
else:
templates.globals["html_theme_options"][key] = {
**style,
**templates.globals["html_theme_options"][key],
}
file.write_clean(conf.render(pformat=pformat_tabs, enquote_value=enquote_value))
with resource(repo_helper.files, "isort.cfg") as isort_config:
yapf_style = PathPlus(isort_config).parent.parent / "templates" / "style.yapf"
reformat_file(file, yapf_style=str(yapf_style), isort_config_file=str(isort_config))
return [file.relative_to(repo_path).as_posix()]
def get_alabaster_theming() -> Dict[str, MutableMapping]:
"""
Returns the custom stylesheet for the alabaster Sphinx theme, as a dictionary.
"""
# Common options
solid_border = {"border-style": "solid"}
docs_bottom_margin = {"margin-bottom": (dict2css.px(17), dict2css.IMPORTANT)}
BorderMappingType = MutableMapping[str, Union[str, Tuple[str, str]]]
object_border: BorderMappingType = {
"padding": "3px 3px 3px 5px",
**docs_bottom_margin,
**solid_border,
}
class_border: BorderMappingType = {
**object_border,
"margin-top": ("7px", dict2css.IMPORTANT),
"border-color": "rgba(240, 128, 128, 0.5)",
}
function_border: BorderMappingType = {
**object_border,
"margin-top": ("7px", dict2css.IMPORTANT),
"border-color": "lightskyblue",
}
attribute_border: BorderMappingType = {**object_border, "border-color": "rgba(119, 136, 153, 0.5)"}
method_border: BorderMappingType = {**object_border, "border-color": "rgba(32, 178, 170, 0.5)"}
table_vertical_margins = {
"margin-bottom": (dict2css.px(20), "important"),
"margin-top": (dict2css.px(-15), dict2css.IMPORTANT),
}
style: Dict[str, MutableMapping] = {
"li p:last-child": {"margin-bottom": dict2css.px(12)},
# "html": {"scroll-behavior": "smooth"}, # Smooth scrolling between sections
"dl.class": class_border,
"dl.function": function_border,
"dl.function dt": {"margin-bottom": (dict2css.px(10), dict2css.IMPORTANT)},
"dl.attribute": attribute_border,
"dl.method": method_border,
"div.sphinxsidebar": {"font-size": dict2css.px(14), "line-height": "1.5"},
"div.sphinxsidebar h3": {"font-weight": "bold"},
"div.sphinxsidebar p.caption": {"font-size": dict2css.px(20)},
"div.sphinxsidebar div.sphinxsidebarwrapper": {"padding-right": (dict2css.px(20), dict2css.IMPORTANT)},
"table.longtable": table_vertical_margins,
}
# The following styling from Tox's documentation
# https://github.com/tox-dev/tox/blob/master/docs/_static/custom.css
# MIT Licensed
# Page width
style["div.document"] = {"width": "100%", "max-width": dict2css.px(1400)}
style["div.body"] = {"max-width": dict2css.px(1100)}
# No end-of-line hyphenation
style["div.body p, ol > li, div.body td"] = {"hyphens": None}
style["img, div.figure"] = {"margin": ('0', dict2css.IMPORTANT)}
style["ul > li"] = {"text-align": "justify"}
style["ul > li > p"] = {"margin-bottom": '0'}
style["ol > li > p"] = {"margin-bottom": '0'}
style["div.body code.descclassname"] = {"display": None}
style[".wy-table-responsive table td"] = {"white-space": ("normal", dict2css.IMPORTANT)}
style[".wy-table-responsive"] = {"overflow": ("visible", dict2css.IMPORTANT)}
style["div.toctree-wrapper.compound > ul > li"] = {
"margin": '0',
"padding": '0',
}
# TODO
# code.docutils.literal {{
# background-color: #ECF0F3;
# padding: 0 1px;
# }}
style["@media screen and (min-width: 870px)"] = {"div.sphinxsidebar": {"width": "250px"}}
return style
[docs]def make_alabaster_theming() -> str:
"""
Make the custom stylesheet for the alabaster Sphinx theme.
:return: The custom stylesheet.
"""
return dict2css.dumps(get_alabaster_theming(), trailing_semicolon=True)
def get_readthedocs_theming() -> Dict[str, Mapping]:
"""
Returns the custom stylesheet for the ReadTheDocs Sphinx theme as a dictionary.
"""
style: Dict[str, Mapping] = {
".wy-nav-content": {"max-width": (dict2css.px(1200), dict2css.IMPORTANT)},
"li p:last-child": {"margin-bottom": (dict2css.px(12), dict2css.IMPORTANT)},
# "html": {"scroll-behavior": "smooth"},
}
return style
[docs]def make_readthedocs_theming() -> str:
"""
Make the custom stylesheet for the ReadTheDocs Sphinx theme.
:return: The custom stylesheet.
"""
return dict2css.dumps(get_readthedocs_theming(), trailing_semicolon=True)
def get_furo_theming() -> Dict[str, Mapping]:
"""
Returns the custom stylesheet for the `Furo <https://github.com/pradyunsg/furo>`_ Sphinx theme.
:return: The custom stylesheet, as a dictionary.
"""
style: Dict[str, Mapping] = {
"div.highlight": {"-moz-tab-size": 4, "tab-size": 4},
".field-list dt, dl.simple dt": {"margin-top": ".5rem"},
"div.versionchanged ul, div.versionremoved ul": {"margin-left": "20px", "margin-top": 0},
}
return style
def make_furo_theming() -> str:
"""
Make the custom stylesheet for the `Furo <https://github.com/pradyunsg/furo>`_ Sphinx theme.
:return: The custom stylesheet.
"""
return dict2css.dumps(get_furo_theming(), trailing_semicolon=True)
[docs]def copy_docs_styling(repo_path: pathlib.Path, templates: Environment) -> List[str]:
"""
Copy custom styling for documentation to the desired repository.
:param repo_path: Path to the repository root.
:param templates:
"""
docs_dir = PathPlus(repo_path / templates.globals["docs_dir"])
style_css = docs_dir / "_static" / "style.css"
layout_html = docs_dir / "_templates" / "layout.html"
base_html = docs_dir / "_templates" / "base.html"
furo_navigation = docs_dir / "_templates" / "sidebar" / "navigation.html"
for directory in {style_css.parent, layout_html.parent}:
directory.maybe_make(parents=True)
sphinx_html_theme = templates.globals["sphinx_html_theme"]
style_dict: Dict
if sphinx_html_theme == "sphinx-rtd-theme":
style_dict = get_readthedocs_theming()
elif sphinx_html_theme == "alabaster":
style_dict = get_alabaster_theming()
elif sphinx_html_theme == "furo":
style_dict = get_furo_theming()
else:
style_dict = {}
style_dict[".longtable.autosummary"] = {"width": "100%"}
style_css.write_lines([
f"/* {templates.globals['managed_message']} */",
'',
dict2css.dumps(style_dict, trailing_semicolon=True),
])
with suppress(FileNotFoundError):
shutil.rmtree(furo_navigation.parent)
if templates.globals["sphinx_html_theme"] == "furo":
base_html.write_lines(_template_with_custom_css(templates.globals["managed_message"], "base.html"))
layout_html.unlink(missing_ok=True)
else:
layout_html.write_lines(_template_with_custom_css(templates.globals["managed_message"], "layout.html"))
base_html.unlink(missing_ok=True)
return [
style_css.relative_to(repo_path).as_posix(),
layout_html.relative_to(repo_path).as_posix(),
base_html.relative_to(repo_path).as_posix(),
furo_navigation.relative_to(repo_path).as_posix(),
]
[docs]@management.register("index.rst", ["enable_docs"])
def rewrite_docs_index(repo_path: pathlib.Path, templates: Environment) -> List[str]:
"""
Update blocks in the documentation ``index.rst`` file.
:param repo_path: Path to the repository root.
:param templates:
"""
index_rst_file = PathPlus(repo_path / templates.globals["docs_dir"] / "index.rst")
index_rst_file.parent.maybe_make()
if templates.globals["on_conda_forge"]:
conda_channels = ["conda-forge"]
primary_conda_channel = "conda-forge"
else:
conda_channels = templates.globals["conda_channels"]
primary_conda_channel = templates.globals["primary_conda_channel"]
# Set up the blocks
sb = ShieldsBlock(
username=templates.globals["username"],
repo_name=templates.globals["repo_name"],
version=templates.globals["version"],
conda=templates.globals["enable_conda"],
tests=templates.globals["enable_tests"] and not templates.globals["stubs_package"],
docs=templates.globals["enable_docs"],
pypi_name=templates.globals["pypi_name"],
docker_shields=templates.globals["docker_shields"],
docker_name=templates.globals["docker_name"],
platforms=templates.globals["platforms"],
on_pypi=templates.globals["on_pypi"],
primary_conda_channel=primary_conda_channel,
)
sb.set_docs_mode()
make_out = sb.make()
shield_block_list = StringList([*make_out[0:2], ".. only:: html"])
with shield_block_list.with_indent_size(1):
shield_block_list.extend(make_out[1:-1])
shield_block_list.append(make_out[-1])
shields_block = str(shield_block_list)
if templates.globals["license"] == "GNU General Public License v2 (GPLv2)":
source = f"https://img.shields.io/github/license/{templates.globals['username']}/{templates.globals['repo_name']}"
shields_block.replace(source, "https://img.shields.io/badge/license-GPLv2-orange")
# .. image:: https://img.shields.io/badge/License-LGPL%20v3-blue.svg
install_block = create_docs_install_block(
templates.globals["repo_name"],
templates.globals["username"],
templates.globals["enable_conda"],
templates.globals["on_pypi"],
templates.globals["pypi_name"],
conda_channels,
) + '\n'
links_block = create_docs_links_block(
templates.globals["username"],
templates.globals["repo_name"],
)
# Do the replacement
index_rst = index_rst_file.read_text(encoding="UTF-8")
index_rst = shields_regex.sub(shields_block, index_rst)
index_rst = installation_regex.sub(install_block, index_rst)
index_rst = links_regex.sub(links_block, index_rst)
index_rst = short_desc_regex.sub(
".. start short_desc\n\n.. documentation-summary::\n\t:meta:\n\n.. end short_desc",
index_rst,
)
if ".. sidebar-links" not in index_rst and not templates.globals["preserve_custom_theme"]:
index_rst = index_rst.replace(
".. start links",
'\n'.join([
".. sidebar-links::",
"\t:caption: Links",
"\t:github:",
(f" :pypi: {templates.globals['pypi_name']}" if templates.globals["on_pypi"] else ''),
'',
'',
".. start links",
])
)
index_rst_file.write_clean(index_rst)
return [index_rst_file.relative_to(repo_path).as_posix()]
[docs]@management.register("404", ["enable_docs"])
def make_404_page(repo_path: pathlib.Path, templates: Environment) -> List[str]:
"""
:param repo_path: Path to the repository root.
:param templates:
"""
docs_dir = PathPlus(repo_path / templates.globals["docs_dir"])
_404_rst = docs_dir / "404.rst"
not_found_png = docs_dir / "not-found.png"
if not _404_rst.exists():
_404_template = templates.get_template("404.rst")
_404_rst.write_clean(_404_template.render())
if not not_found_png.exists():
shutil.copy2(template_dir / "not-found.png", not_found_png)
return [
_404_rst.relative_to(repo_path).as_posix(),
not_found_png.relative_to(repo_path).as_posix(),
]
[docs]@management.register("Source_rst", ["enable_docs"])
def make_docs_source_rst(repo_path: pathlib.Path, templates: Environment) -> List[str]:
"""
Create the "Source" page in the documentation, and add the associated image.
:param repo_path: Path to the repository root.
:param templates:
"""
docs_dir = PathPlus(repo_path / templates.globals["docs_dir"])
docs_source_rst = docs_dir / "Source.rst"
git_download_png = docs_dir / "git_download.png"
docs_building_rst = docs_dir / "Building.rst"
source_template = templates.get_template(docs_source_rst.name)
docs_source_rst.write_clean(source_template.render())
if docs_building_rst.is_file():
docs_building_rst.unlink()
if not git_download_png.exists():
shutil.copy2(init_repo_template_dir / git_download_png.name, git_download_png)
return [
docs_source_rst.relative_to(repo_path).as_posix(),
docs_building_rst.relative_to(repo_path).as_posix(),
git_download_png.relative_to(repo_path).as_posix(),
]
[docs]@management.register("license_rst", ["enable_docs"])
def make_docs_license_rst(repo_path: pathlib.Path, templates: Environment) -> List[str]:
"""
Create the "License" page in the documentation.
:param repo_path: Path to the repository root.
:param templates:
"""
docs_dir = PathPlus(repo_path / templates.globals["docs_dir"])
docs_license_rst = docs_dir / "license.rst"
license_key = templates.globals["license"]
license_key = {v: k for k, v in license_lookup.items()}.get(license_key, license_key)
source_template = templates.get_template(docs_license_rst.name)
docs_license_rst.write_clean(source_template.render(license_key=license_key))
return [docs_license_rst.relative_to(repo_path).as_posix()]
def make_sphinx_config_dict(templates: Environment) -> Dict[str, Any]:
"""
Returns a dictionary of configuration values for use in ``conf.py``.
:param templates:
"""
data = {}
data["github_username"] = templates.globals["username"]
data["github_repository"] = templates.globals["repo_name"]
data["author"] = templates.globals["rtfd_author"]
data["project"] = templates.globals["modname"].replace('_', '-')
data["copyright"] = "{copyright_years} {author}".format_map(templates.globals)
data["language"] = "en"
data["package_root"] = templates.globals["import_name"].replace('.', '/')
data["extensions"] = [
"sphinx_toolbox",
"sphinx_toolbox.more_autodoc",
"sphinx_toolbox.more_autosummary",
"sphinx_toolbox.documentation_summary",
"sphinx_toolbox.tweaks.param_dash",
"sphinxcontrib.toctree_plus",
"sphinx_toolbox.tweaks.latex_layout",
"sphinx_toolbox.tweaks.latex_toc",
"sphinx.ext.intersphinx",
"sphinx.ext.mathjax",
"sphinxcontrib.extras_require",
"sphinx.ext.todo", # "sphinxemoji.sphinxemoji",
"notfound.extension",
"sphinx_copybutton",
"sphinxcontrib.default_values",
"sphinx_debuginfo",
"sphinx_licenseinfo",
"seed_intersphinx_mapping",
"html_section",
*templates.globals["extra_sphinx_extensions"],
]
# "sphinx.ext.autosectionlabel",
# "sphinx_gitstamp",
# data["sphinxemoji_style"] = "twemoji"
data["gitstamp_fmt"] = "%d %b %Y"
data["templates_path"] = ["_templates"]
data["html_static_path"] = ["_static"]
data["source_suffix"] = ".rst"
data["master_doc"] = "index"
data["suppress_warnings"] = ["image.nonlocal_uri"]
data["pygments_style"] = "default"
data["html_theme"] = templates.globals["sphinx_html_theme"].replace('-', '_')
data["html_theme_path"] = ["../.."]
data["html_show_sourcelink"] = True # True will show link to source
data["toctree_plus_types"] = sorted({
"class",
"function",
"data",
"enum",
"flag",
"confval",
"directive",
"role",
"confval",
"protocol",
"typeddict",
"namedtuple",
"exception",
})
data["add_module_names"] = False
data["hide_none_rtype"] = True
data["all_typevars"] = True
data["overloads_location"] = "bottom"
data["html_codeblock_linenos_style"] = "table"
# Exclude "standard" methods.
data["autodoc_exclude_members"] = [
"__dict__",
"__class__",
"__dir__",
"__weakref__",
"__module__",
"__annotations__",
"__orig_bases__",
"__parameters__",
"__subclasshook__",
"__init_subclass__",
"__attrs_attrs__",
"__init__",
"__new__",
"__getnewargs__",
"__abstractmethods__",
"__hash__",
]
return data
class PythonFormatTomlEncoder(dom_toml.TomlEncoder):
def format_inline_array(self, obj: Union[Tuple, List], nest_level: int) -> str:
values = DelimitedList(str(self.format_literal(u)) for u in obj)
single_line = f"[{values:, }]"
if len(single_line) <= self.max_width:
return single_line
retval = StringList(['['])
with retval.with_indent(" ", 1):
for u in obj:
retval.append(f"{str(self.format_literal(u))},")
retval.append(']')
return str(retval)
def format_literal(self, obj: object, *, nest_level: int = 0) -> str:
if isinstance(obj, bool):
return str(obj)
return super().format_literal(obj, nest_level=nest_level)
def _template_with_custom_css(managed_message: str, filename: str) -> List[str]:
return [
f"<!--- {managed_message} --->",
f'{{% extends "!{filename}" %}}',
"{% block extrahead %}",
'\t<link href="{{ pathto("_static/style.css", True) }}" rel="stylesheet" type="text/css">',
"{% endblock %}",
'',
]