10This module is a Sphinx Extension for the Belle~II Software:
12* add a domain "b2" for modules and variables.
13 Modules can be documented using the ``.. b2:module::` directive and variables
14 using the ``.. b2:variable::` directive. They can be cross referenced
with
15 :b2:mod:
and :b2:var: respectively
16* add an index
for basf2 modules
17* add a directive to automatically document basf2 modules similar to autodoc
21 :modules: EventInfoSetter, EventInfoPrinter
23 :regex-filter: Event.*
25* add directive to automatically document basf2 variables
38from docutils import nodes
39from sphinx.util.nodes import nested_parse_with_titles
40from docutils.parsers.rst import Directive, directives
41from docutils.statemachine import StringList
42from basf2domain import Basf2Domain
43from basf2 import list_available_modules, register_module
44from sphinx.domains.std import StandardDomain
47def parse_with_titles(state, content):
48 """Shortcut function to parse a reStructuredText fragment into docutils nodes"""
49 node = nodes.container()
51 node.document = state.document
52 if isinstance(content, str):
53 content = content.splitlines()
54 if not isinstance(content, StringList):
55 content = StringList(content)
56 nested_parse_with_titles(state, content, node)
61 """Directive to Render Docstring as Docutils nodes.
62 This is useful
as standard reStructuredText does
not parse Google
63 Docstrings but we support it
in docstrings
for python functions, modules
64 and variables. So to show example docstrings
in the documentation we don
't
65 want to write the example in Google Docstring
and keep that synchronous
66 with a reStructuredText version
71 "lines": directives.unchanged
76 """Just pass on the content to the autodoc-process-docstring event and
77 then parse the resulting reStructuredText."""
78 env = self.state.document.settings.env
79 content = list(self.content)
81 start_index, end_index = (int(e)
for e
in self.options.get(
"lines",
None).split(
","))
82 content = content[start_index:end_index]
87 content = textwrap.dedent(
"\n".join(content)).splitlines()
89 env.app.emit(
'autodoc-process-docstring',
"docstring",
None,
None,
None, content)
90 return parse_with_titles(self.state, content)
94class ModuleListDirective(Directive):
97 "library": directives.unchanged,
98 "modules": directives.unchanged,
99 "package": directives.unchanged,
100 "no-parameters": directives.flag,
101 "noindex": directives.flag,
102 "regex-filter": directives.unchanged,
103 "io-plots": directives.flag,
107 def show_module(self, module, library):
108 description = module.description().splitlines()
109 for i, line
in enumerate(description):
110 description[i] = line.replace(
'|release|', self.state.document.settings.env.app.config.release)
114 env = self.state.document.settings.env
115 env.app.emit(
'autodoc-process-docstring',
"b2:module", module.name(), module,
None, description)
116 description += [
"",
"",
117 f
":Package: {module.package()}",
118 f
":Library: {os.path.basename(library)}",
121 if "no-parameters" not in self.options:
124 for p
in module.available_params():
125 dest = required_params
if p.forceInSteering
else optional_params
126 default =
"" if p.forceInSteering
else f
", default={p.default!r}"
127 param_desc = p.description.splitlines()
130 env.app.emit(
'autodoc-process-docstring',
'b2:module:param', module.name() +
'.' + p.name, p,
None, param_desc)
131 param_desc = textwrap.indent(
"\n".join(param_desc), 8 *
" ").splitlines()
132 dest += [f
" * **{p.name}** *({p.type}{default})*"]
135 if (required_params):
136 description += [
":Required Parameters:",
" "] + required_params
137 if (optional_params):
138 description += [
":Parameters:",
" "] + optional_params
140 if "io-plots" in self.options:
141 image = f
"build/ioplots/{module.name()}.png"
142 if os.path.exists(image):
143 description += [
":IO diagram:",
" ", f
" .. image:: /{image}"]
145 content = [f
".. b2:module:: {module.name()}"] + self.noindex + [
" "]
146 content += [
" " + e
for e
in description]
147 return parse_with_titles(self.state, content)
150 all_modules = list_available_modules().items()
153 if "modules" in self.options:
154 modules = [e.strip()
for e
in self.options[
"modules"].split(
",")]
155 all_modules = [e
for e
in all_modules
if e[0]
in modules]
158 if "regex-filter" in self.options:
159 re_filter = re.compile(self.options[
"regex-filter"])
160 all_modules = [e
for e
in all_modules
if re_filter.match(e[0])
is not None]
164 if "library" in self.options:
165 lib = self.options[
"library"].strip()
166 all_modules = [e
for e
in all_modules
if os.path.basenam(e[1]) == lib]
169 self.noindex = [
" :noindex:"]
if "noindex" in self.options
else []
175 for name, library
in sorted(all_modules):
176 module = register_module(name)
178 if "package" in self.options
and module.package() != self.options[
"package"]:
182 all_nodes += self.show_module(module, library)
188class VariableListDirective(Directive):
191 "group": directives.unchanged,
192 "variables": directives.unchanged,
193 "regex-filter": directives.unchanged,
194 "description-regex-filter": directives.unchanged,
195 "noindex": directives.flag,
200 from ROOT
import Belle2
202 self.noindex = [
" :noindex:"]
if "noindex" in self.options
else []
206 desc_regex_filter =
None
207 if "variables" in self.options:
208 explicit_list = [e.strip()
for e
in self.options[
"variables"].split(
",")]
209 if "regex-filter" in self.options:
210 regex_filter = re.compile(self.options[
"regex-filter"])
211 if "description-regex-filter" in self.options:
212 desc_regex_filter = re.compile(self.options[
"description-regex-filter"])
214 for var
in manager.getVariables():
215 if "group" in self.options
and self.options[
"group"] != var.group:
217 if explicit_list
and var.name
not in explicit_list:
219 if regex_filter
and not regex_filter.match(var.name):
221 if desc_regex_filter
and not desc_regex_filter.match(var.description):
223 all_variables.append(var)
226 env = self.state.document.settings.env
227 for var
in sorted(all_variables, key=
lambda x: x.name):
232 if ":noindex:" in var.description:
233 index = [
" :noindex:"]
234 var.description = var.description.replace(
":noindex:",
"")
236 docstring = var.description.splitlines()
240 env.app.emit(
'autodoc-process-docstring',
"b2:variable", var.name, var,
None, docstring)
242 description = [f
".. b2:variable:: {var.name}"] + index + [
""]
243 description += [
" " + e
for e
in docstring]
244 if "group" not in self.options:
245 description += [
"", f
" :Group: {var.group}"]
247 all_nodes += parse_with_titles(self.state, description)
252def html_page_context(app, pagename, templatename, context, doctree):
253 """Provide Link to GitLab Repository, see https://mg.pov.lt/blog/sphinx-edit-on-github.html
255 this goes in conjunction
with
256 site_scons/sphinx/_sphinxtemplates/sourcelink.html
and adds a link to our
257 git repository instead to the local source link
260 if templatename !=
'page.html' or not doctree:
263 path = os.path.relpath(doctree.get(
'source'), app.builder.srcdir)
264 repository = app.config.basf2_repository
268 commit = app.config.basf2_commitid
269 context[
"source_url"] = f
"{repository}/-/blob/main/{path}"
271 context[
"source_url"] = f
"{repository}/-/blob/{commit}/{path}"
274def gitlab_issue_role(role, rawtext, text, lineno, inliner, options=None, content=None):
279 issue_url = inliner.document.settings.env.app.config.basf2_issues
281 return [nodes.literal(rawtext, text=text, language=
None)], []
283 url = f
"{issue_url}/{text}"
284 return [nodes.reference(rawtext, text=text, refuri=url)], []
287def doxygen_role(role, rawtext, text, lineno, inliner, options=None, content=None):
292 release_version = inliner.document.settings.env.app.config.release
293 match = re.match(
r'(.+?)<(.+?)>', text)
295 display_text, url_text = match.groups()
299 if "html#" in url_text:
300 url = f
"https://software.belle2.org/{release_version}/doxygen/{url_text}"
302 url = f
"https://software.belle2.org/{release_version}/doxygen/{url_text}.html"
304 return [nodes.reference(rawsource=rawtext, text=display_text, refuri=url)], []
307def sphinx_role(role, rawtext, text, lineno, inliner, options=None, content=None):
312 release_version = inliner.document.settings.env.app.config.release
313 match = re.match(
r'(.+?)<(.+?)>', text)
315 display_text, url_text = match.groups()
319 if "html#" in url_text:
320 url = f
"https://software.belle2.org/{release_version}/sphinx/{url_text}"
322 url = f
"https://software.belle2.org/{release_version}/sphinx/{url_text}.html"
324 return [nodes.reference(rawsource=rawtext, text=display_text, refuri=url)], []
329 basf2.logging.log_level = basf2.LogLevel.WARNING
331 app.add_config_value(
"basf2_repository",
"",
True)
332 app.add_config_value(
"basf2_commitid",
"",
True)
333 app.add_config_value(
"basf2_issues",
"",
True)
334 app.add_domain(Basf2Domain)
335 app.add_directive(
"b2-modules", ModuleListDirective)
336 app.add_directive(
"b2-variables", VariableListDirective)
337 app.add_directive(
"docstring", RenderDocstring)
338 app.add_role(
"issue", gitlab_issue_role)
339 app.add_role(
"doxygen", doxygen_role)
340 app.add_role(
"sphinx", sphinx_role)
341 app.connect(
'html-page-context', html_page_context)
344 StandardDomain.initial_data[
"labels"][
"b2-modindex"] = (
"b2-modindex",
"",
"basf2 Module Index")
345 StandardDomain.initial_data[
"labels"][
"b2-varindex"] = (
"b2-varindex",
"",
"basf2 Variable Index")
346 StandardDomain.initial_data[
"anonlabels"][
"b2-modindex"] = (
"b2-modindex",
"")
347 StandardDomain.initial_data[
"anonlabels"][
"b2-varindex"] = (
"b2-varindex",
"")
348 return {
'version': 0.2}
static Manager & Instance()
get singleton instance.