CCF/doc/conf.py

494 строки
15 KiB
Python

# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the Apache 2.0 License.
# -*- coding: utf-8 -*-
#
# Configuration file for the Sphinx documentation builder.
#
# This file does only contain a selection of the most common options. For a
# full list see the documentation:
# http://www.sphinx-doc.org/en/master/config
# -- Path setup --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
import os
import sys
import subprocess
import pathlib
import re
from docutils import nodes
# To import generate_config_rst
sys.path.insert(0, os.path.abspath("."))
import generate_config_rst
# -- Project information -----------------------------------------------------
project = "CCF"
copyright = "2024, Microsoft"
author = "Microsoft"
# The short X.Y version
version = ""
# The full version, including alpha/beta/rc tags
release = ""
# -- General configuration ---------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#
# needs_sphinx = '1.0'
# 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.todo",
"sphinx.ext.mathjax",
"sphinx.ext.ifconfig",
"sphinx.ext.viewcode",
"breathe",
"sphinxcontrib.mermaid",
"sphinx.ext.autosectionlabel",
"sphinx.ext.githubpages",
"sphinx_multiversion",
"sphinx_copybutton",
"sphinx.ext.autodoc",
"sphinxcontrib.openapi",
"sphinx_panels",
"sphinx.ext.extlinks",
"sphinx_inline_tabs",
]
autosectionlabel_prefix_document = True
# Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"]
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
# source_suffix = ['.rst', '.md']
source_suffix = ".rst"
# The master toctree document.
master_doc = "index"
# 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 = []
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = "default"
pygments_dark_style = "zenburn"
# Ignore main index file that has clickable images, JS/Doxygen output,
# github anchors https://github.com/sphinx-doc/sphinx/issues/9016), and nghttp2
# (which is now HTTP2-only)
linkcheck_exclude_documents = [r"^index$"]
linkcheck_ignore = [
r"https://github.com/.*#",
r"../js/ccf-app.*",
r"../doxygen/index.html",
r"https://nghttp2.org/.*",
r"https://www.w3.org/Protocols/rfc2616/.*",
]
# -- 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"
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#
html_theme_options = {
"light_logo": "ccf.svg",
"dark_logo": "ccf.svg",
}
# 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"]
# Custom sidebar templates, must be a dictionary that maps document names
# to template names.
#
# The default sidebars (for documents that don't match any pattern) are
# defined by theme itself. Builtin themes are using these templates by
# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
# 'searchbox.html']``.
#
html_sidebars = {}
html_css_files = [
"css/custom.css",
]
html_js_files = ["https://kit.fontawesome.com/c75a35380d.js"]
html_extra_path = []
# -- Options for HTMLHelp output ---------------------------------------------
# Output file base name for HTML help builder.
htmlhelp_basename = "CCFdoc"
# -- Options for LaTeX output ------------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#
# 'preamble': '',
# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, "CCF.tex", "CCF Documentation", "Microsoft", "manual")
]
# -- Options for manual page output ------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [(master_doc, "ccf", "CCF Documentation", [author], 1)]
# -- Options for Texinfo output ----------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(
master_doc,
"CCF",
"CCF Documentation",
author,
"CCF",
"One line description of project.",
"Miscellaneous",
)
]
# -- Extension configuration -------------------------------------------------
# -- Options for todo extension ----------------------------------------------
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = True
# -- Breathe configuration
# Setup the breathe extension
breathe_projects = {"CCF": "../doxygen/xml"}
breathe_default_project = "CCF"
# Set up multiversion extension
smv_tag_whitelist = None
smv_branch_whitelist = r"^(main)|(release\/([4-9]|\d\d\d*)\.x)$"
smv_remote_whitelist = None
smv_outputdir_format = "{ref.name}"
assert re.match(smv_branch_whitelist, "main")
assert not re.match(smv_branch_whitelist, "release/1.x")
assert not re.match(smv_branch_whitelist, "release/2.x")
assert re.match(smv_branch_whitelist, "release/100.x")
assert not re.match(smv_branch_whitelist, "release/1.x_feature")
# Intercept command line arguments passed by sphinx-multiversion to retrieve doc version.
# This is a little hacky with sphinx-multiversion 0.2.4 and the `SPHINX_MULTIVERSION_NAME`
# envvar should be used for further versions (release pending).
docs_version = None
for arg in sys.argv:
if "smv_current_version=" in arg:
docs_version = arg.split("=")[1]
break
# If that didn't work, try extracting it from env vars inserted by Azure pipelines. This
# is used by single-build jobs and Link checks targeting release branches.
if docs_version is None:
if os.environ.get("BUILD_REASON") == "PullRequest":
docs_version = os.environ["SYSTEM_PULLREQUEST_TARGETBRANCH"]
# If we still have no docs_version, assume we're on main
if docs_version is None:
docs_version = "main"
# :ccf_repo: directive can be used to create a versioned link to GitHub repo
extlinks = {
"ccf_repo": (
f"https://github.com/microsoft/CCF/tree/{docs_version}/%s",
"%s",
)
}
# Theme options
html_logo = "_static/ccf.svg"
html_favicon = "_static/favicon.ico"
html_context = {
"github_user": "Microsoft",
"github_repo": "CCF",
"github_version": "main",
"doc_path": "doc/",
}
# Configuration for sphinxcontrib-httpdomain, without which the following error happens
# ERROR: HTTP status code must be an integer (e.g. `200`) or start with an integer (e.g. `200 OK`);
# <#text: 'default'> is invalid
http_strict_mode = False
# Python autodoc options
autodoc_default_options = {
"member-order": "bysource",
}
# sphinxcontrib.spelling options
spelling_show_suggestions = True
spelling_lang = "en_UK"
tokenizer_lang = "en_UK"
spelling_word_list_filename = ["spelling_wordlist.txt"]
# sphinxcontrib-mermaid options
mermaid_init_js = """mermaid.initialize({startOnLoad:true});
// Remove height from all mermaid diagrams
window.addEventListener(
'load',
function() {
let nodes = document.querySelectorAll('.mermaid');
for (let i = 0; i < nodes.length; i++) {
const element = nodes[i];
const svg = element.firstChild;
svg.removeAttribute('height');
}
},
false
);"""
def typedoc_role(
name: str, rawtext: str, text: str, lineno, inliner, options={}, content=[]
):
"""
Supported syntaxes:
:typedoc:package:`ccf-app`
:typedoc:module:`ccf-app/global`
:typedoc:function:`ccf-app/crypto#wrapKey`
:typedoc:interface:`ccf-app/endpoints/Body`
:typedoc:class:`ccf-app/kv/TypedKvMap`
:typedoc:classmethod:`ccf-app/kv/TypedKvMap#delete`
:typedoc:interfacemethod:`ccf-app/endpoints/Body#json`
:typedoc:interface:`Body <ccf-app/endpoints/Body>`
"""
# check for custom label
if "<" in text:
label, text = text.split(" <")
text = text[:-1]
else:
label = text
# extract hash if any, has to be appended after .html later on
text_without_hash, *hash_name = text.split("#")
url_hash = f"#{hash_name[0].lower()}" if hash_name else ""
# translate role kind into typedoc subfolder
# and add '()' for functions/methods
kind_name = name.replace("typedoc:", "")
is_kind_package = False
if kind_name == "package":
is_kind_package = True
elif kind_name in ["module", "interface"]:
kind_name += "s"
elif kind_name == "class":
kind_name += "es"
elif kind_name == "function":
kind_name = "modules"
label += "()"
elif kind_name == "classmethod":
kind_name = "classes"
label += "()"
elif kind_name == "interfacemethod":
kind_name = "interfaces"
label += "()"
else:
raise ValueError(f"unknown typedoc kind: {kind_name}")
# build typedoc url relative to doc root
pkg_name, *element_path = text_without_hash.split("/")
typedoc_path = f"js/{pkg_name}"
if not is_kind_package:
element_path = ".".join(element_path)
typedoc_path += f"/{kind_name}/{element_path}.html{url_hash}"
# construct final url relative to current page
source = inliner.document.attributes["source"]
rel_source = source.split("/doc/", 1)[1]
levels = rel_source.count("/")
refuri = "../" * levels + typedoc_path
# build docutils node
text_node = nodes.literal(label, label, classes=["xref"])
ref_node = nodes.reference("", "", refuri=refuri)
ref_node += text_node
return [ref_node], []
def config_property_role(
name: str, rawtext: str, text: str, lineno, inliner, options={}, content=[]
):
try:
prefix, val = text.rsplit(".", 1)
prefix = prefix + "."
except ValueError:
prefix, val = None, text
children = [
nodes.strong(
val,
val,
ids=[text],
),
nodes.reference("#", "#", refid=text, classes=["headerlink"]),
]
if prefix:
children.insert(
0,
nodes.emphasis(
prefix,
prefix,
),
)
line = nodes.line(
"",
"",
*children,
classes=["configproperty"],
)
return [line], []
def config_inited(app, config):
# anything that needs to access app.config goes here
doc_dir = pathlib.Path(app.srcdir)
outdir = pathlib.Path(app.outdir)
js_pkg_dir = doc_dir / ".." / "js" / "ccf-app"
js_docs_dir = outdir / "js" / "ccf-app"
if js_pkg_dir.exists():
# make versions.json from sphinx-multiversion available
if app.config.smv_metadata_path:
os.environ["SMV_METADATA_PATH"] = app.config.smv_metadata_path
os.environ["SMV_CURRENT_VERSION"] = app.config.smv_current_version
subprocess.run(
["sed", "-i", "s/\^4.2.3/4.2.4/g", "package.json"],
cwd=js_pkg_dir,
check=True,
)
subprocess.run(
["sed", "-i", 's/"\^14\.14\.35"/"14\.17\.27"/g', "package.json"],
cwd=js_pkg_dir,
check=True,
)
subprocess.run(
["npm", "install", "--save-exact", "colors@1.4.0"],
cwd=js_pkg_dir,
check=True,
)
subprocess.run(
["npm", "install", "--no-package-lock", "--no-audit", "--no-fund"],
cwd=js_pkg_dir,
check=True,
)
subprocess.run(
["npm", "run", "docs", "--", "--out", str(js_docs_dir)],
cwd=js_pkg_dir,
check=True,
)
# allow to link to typedoc pages
for kind in [
"package",
"module",
"interface",
"class",
"function",
"interfacemethod",
"classmethod",
]:
app.add_role(f"typedoc:{kind}", typedoc_role)
def setup(app):
if not os.environ.get("SKIP_JS"):
app.connect("config-inited", config_inited)
app.add_role("configproperty", config_property_role)
doc_dir = pathlib.Path(app.srcdir) # CCF/doc/
root_dir = pathlib.Path(os.path.abspath(doc_dir / "..")) # CCF/
out_dir = pathlib.Path(app.outdir) # CCF/build/html
# import ccf python package to generate docs for this version
python_path = os.path.abspath(doc_dir / "../python")
sys.path.insert(0, python_path)
# doxygen
breathe_projects["CCF"] = str(doc_dir / breathe_projects["CCF"])
if not os.environ.get("SKIP_DOXYGEN"):
subprocess.run(["doxygen"], cwd=root_dir, check=True)
doxygen_html_src = root_dir / "doxygen/html"
if doxygen_html_src.exists() and doxygen_html_src.is_dir():
doxygen_html_dest = str(out_dir / "doxygen")
subprocess.run(
["cp", "-r", str(doxygen_html_src), doxygen_html_dest], check=True
)
# configuration generator
input_file_path = doc_dir / "host_config_schema/cchost_config.json"
output_file_path = doc_dir / "operations/generated_config.rst"
if os.path.exists(input_file_path):
generate_config_rst.generate_configuration_docs(
input_file_path, output_file_path
)