From cf7da1843c45a4c2df7a749f7886a2d2ba0ee92a Mon Sep 17 00:00:00 2001
From: Daniel Baumann ... by `visit_inline`.
+ supported_inline_tags: set[str] = set()
+
+ def __init__(self, document: nodes.document, builder: Builder) -> None:
+ super().__init__(document, builder)
+
+ self.highlighter = self.builder.highlighter
+ self.docnames = [self.builder.current_docname] # for singlehtml builder
+ self.manpages_url = self.config.manpages_url
+ self.protect_literal_text = 0
+ self.secnumber_suffix = self.config.html_secnumber_suffix
+ self.param_separator = ''
+ self.optional_param_level = 0
+ self._table_row_indices = [0]
+ self._fieldlist_row_indices = [0]
+ self.required_params_left = 0
+
+ def visit_start_of_file(self, node: Element) -> None:
+ # only occurs in the single-file builder
+ self.docnames.append(node['docname'])
+ self.body.append('' % node['docname'])
+
+ def depart_start_of_file(self, node: Element) -> None:
+ self.docnames.pop()
+
+ #############################################################
+ # Domain-specific object descriptions
+ #############################################################
+
+ # Top-level nodes for descriptions
+ ##################################
+
+ def visit_desc(self, node: Element) -> None:
+ self.body.append(self.starttag(node, 'dl'))
+
+ def depart_desc(self, node: Element) -> None:
+ self.body.append('\n\n')
+
+ def visit_desc_signature(self, node: Element) -> None:
+ # the id is set automatically
+ self.body.append(self.starttag(node, 'dt'))
+ self.protect_literal_text += 1
+
+ def depart_desc_signature(self, node: Element) -> None:
+ self.protect_literal_text -= 1
+ if not node.get('is_multiline'):
+ self.add_permalink_ref(node, _('Link to this definition'))
+ self.body.append('\n')
+
+ def visit_desc_signature_line(self, node: Element) -> None:
+ pass
+
+ def depart_desc_signature_line(self, node: Element) -> None:
+ if node.get('add_permalink'):
+ # the permalink info is on the parent desc_signature node
+ self.add_permalink_ref(node.parent, _('Link to this definition'))
+ self.body.append('
')
+
+ def visit_desc_content(self, node: Element) -> None:
+ self.body.append(self.starttag(node, 'dd', ''))
+
+ def depart_desc_content(self, node: Element) -> None:
+ self.body.append('')
+
+ def visit_desc_inline(self, node: Element) -> None:
+ self.body.append(self.starttag(node, 'span', ''))
+
+ def depart_desc_inline(self, node: Element) -> None:
+ self.body.append('')
+
+ # Nodes for high-level structure in signatures
+ ##############################################
+
+ def visit_desc_name(self, node: Element) -> None:
+ self.body.append(self.starttag(node, 'span', ''))
+
+ def depart_desc_name(self, node: Element) -> None:
+ self.body.append('')
+
+ def visit_desc_addname(self, node: Element) -> None:
+ self.body.append(self.starttag(node, 'span', ''))
+
+ def depart_desc_addname(self, node: Element) -> None:
+ self.body.append('')
+
+ def visit_desc_type(self, node: Element) -> None:
+ pass
+
+ def depart_desc_type(self, node: Element) -> None:
+ pass
+
+ def visit_desc_returns(self, node: Element) -> None:
+ self.body.append(' ')
+ self.body.append('')
+
+ def depart_desc_returns(self, node: Element) -> None:
+ self.body.append('')
+
+ def _visit_sig_parameter_list(
+ self,
+ node: Element,
+ parameter_group: type[Element],
+ sig_open_paren: str,
+ sig_close_paren: str,
+ ) -> None:
+ """Visit a signature parameters or type parameters list.
+
+ The *parameter_group* value is the type of child nodes acting as required parameters
+ or as a set of contiguous optional parameters.
+ """
+ self.body.append(f' ')
+ self.body.append(' {sig_open_paren}')
+ self.is_first_param = True
+ self.optional_param_level = 0
+ self.params_left_at_level = 0
+ self.param_group_index = 0
+ # Counts as what we call a parameter group either a required parameter, or a
+ # set of contiguous optional ones.
+ self.list_is_required_param = [isinstance(c, parameter_group) for c in node.children]
+ # How many required parameters are left.
+ self.required_params_left = sum(self.list_is_required_param)
+ self.param_separator = node.child_text_separator
+ self.multi_line_parameter_list = node.get('multi_line_parameter_list', False)
+ if self.multi_line_parameter_list:
+ self.body.append('\n\n')
+ self.body.append(self.starttag(node, 'dl'))
+ self.param_separator = self.param_separator.rstrip()
+ self.context.append(sig_close_paren)
+
+ def _depart_sig_parameter_list(self, node: Element) -> None:
+ if node.get('multi_line_parameter_list'):
+ self.body.append('\n\n')
+ sig_close_paren = self.context.pop()
+ self.body.append(f'{sig_close_paren}')
+
+ def visit_desc_parameterlist(self, node: Element) -> None:
+ self._visit_sig_parameter_list(node, addnodes.desc_parameter, '(', ')')
+
+ def depart_desc_parameterlist(self, node: Element) -> None:
+ self._depart_sig_parameter_list(node)
+
+ def visit_desc_type_parameter_list(self, node: Element) -> None:
+ self._visit_sig_parameter_list(node, addnodes.desc_type_parameter, '[', ']')
+
+ def depart_desc_type_parameter_list(self, node: Element) -> None:
+ self._depart_sig_parameter_list(node)
+
+ # If required parameters are still to come, then put the comma after
+ # the parameter. Otherwise, put the comma before. This ensures that
+ # signatures like the following render correctly (see issue #1001):
+ #
+ # foo([a, ]b, c[, d])
+ #
+ def visit_desc_parameter(self, node: Element) -> None:
+ on_separate_line = self.multi_line_parameter_list
+ if on_separate_line and not (self.is_first_param and self.optional_param_level > 0):
+ self.body.append(self.starttag(node, 'dd', ''))
+ if self.is_first_param:
+ self.is_first_param = False
+ elif not on_separate_line and not self.required_params_left:
+ self.body.append(self.param_separator)
+ if self.optional_param_level == 0:
+ self.required_params_left -= 1
+ else:
+ self.params_left_at_level -= 1
+ if not node.hasattr('noemph'):
+ self.body.append('')
+
+ def depart_desc_parameter(self, node: Element) -> None:
+ if not node.hasattr('noemph'):
+ self.body.append('')
+ is_required = self.list_is_required_param[self.param_group_index]
+ if self.multi_line_parameter_list:
+ is_last_group = self.param_group_index + 1 == len(self.list_is_required_param)
+ next_is_required = (
+ not is_last_group
+ and self.list_is_required_param[self.param_group_index + 1]
+ )
+ opt_param_left_at_level = self.params_left_at_level > 0
+ if opt_param_left_at_level or is_required and (is_last_group or next_is_required):
+ self.body.append(self.param_separator)
+ self.body.append('\n')
+
+ elif self.required_params_left:
+ self.body.append(self.param_separator)
+
+ if is_required:
+ self.param_group_index += 1
+
+ def visit_desc_type_parameter(self, node: Element) -> None:
+ self.visit_desc_parameter(node)
+
+ def depart_desc_type_parameter(self, node: Element) -> None:
+ self.depart_desc_parameter(node)
+
+ def visit_desc_optional(self, node: Element) -> None:
+ self.params_left_at_level = sum([isinstance(c, addnodes.desc_parameter)
+ for c in node.children])
+ self.optional_param_level += 1
+ self.max_optional_param_level = self.optional_param_level
+ if self.multi_line_parameter_list:
+ # If the first parameter is optional, start a new line and open the bracket.
+ if self.is_first_param:
+ self.body.append(self.starttag(node, 'dd', ''))
+ self.body.append('[')
+ # Else, if there remains at least one required parameter, append the
+ # parameter separator, open a new bracket, and end the line.
+ elif self.required_params_left:
+ self.body.append(self.param_separator)
+ self.body.append('[')
+ self.body.append('\n')
+ # Else, open a new bracket, append the parameter separator,
+ # and end the line.
+ else:
+ self.body.append('[')
+ self.body.append(self.param_separator)
+ self.body.append('\n')
+ else:
+ self.body.append('[')
+
+ def depart_desc_optional(self, node: Element) -> None:
+ self.optional_param_level -= 1
+ if self.multi_line_parameter_list:
+ # If it's the first time we go down one level, add the separator
+ # before the bracket.
+ if self.optional_param_level == self.max_optional_param_level - 1:
+ self.body.append(self.param_separator)
+ self.body.append(']')
+ # End the line if we have just closed the last bracket of this
+ # optional parameter group.
+ if self.optional_param_level == 0:
+ self.body.append('\n')
+ else:
+ self.body.append(']')
+ if self.optional_param_level == 0:
+ self.param_group_index += 1
+
+ def visit_desc_annotation(self, node: Element) -> None:
+ self.body.append(self.starttag(node, 'em', '', CLASS='property'))
+
+ def depart_desc_annotation(self, node: Element) -> None:
+ self.body.append('')
+
+ ##############################################
+
+ def visit_versionmodified(self, node: Element) -> None:
+ self.body.append(self.starttag(node, 'div', CLASS=node['type']))
+
+ def depart_versionmodified(self, node: Element) -> None:
+ self.body.append('\n')
+
+ # overwritten
+ def visit_reference(self, node: Element) -> None:
+ atts = {'class': 'reference'}
+ if node.get('internal') or 'refuri' not in node:
+ atts['class'] += ' internal'
+ else:
+ atts['class'] += ' external'
+ if 'refuri' in node:
+ atts['href'] = node['refuri'] or '#'
+ if self.settings.cloak_email_addresses and atts['href'].startswith('mailto:'):
+ atts['href'] = self.cloak_mailto(atts['href'])
+ self.in_mailto = True
+ else:
+ assert 'refid' in node, \
+ 'References must have "refuri" or "refid" attribute.'
+ atts['href'] = '#' + node['refid']
+ if not isinstance(node.parent, nodes.TextElement):
+ assert len(node) == 1 and isinstance(node[0], nodes.image) # NoQA: PT018
+ atts['class'] += ' image-reference'
+ if 'reftitle' in node:
+ atts['title'] = node['reftitle']
+ if 'target' in node:
+ atts['target'] = node['target']
+ self.body.append(self.starttag(node, 'a', '', **atts))
+
+ if node.get('secnumber'):
+ self.body.append(('%s' + self.secnumber_suffix) %
+ '.'.join(map(str, node['secnumber'])))
+
+ def visit_number_reference(self, node: Element) -> None:
+ self.visit_reference(node)
+
+ def depart_number_reference(self, node: Element) -> None:
+ self.depart_reference(node)
+
+ # overwritten -- we don't want source comments to show up in the HTML
+ def visit_comment(self, node: Element) -> None: # type: ignore[override]
+ raise nodes.SkipNode
+
+ # overwritten
+ def visit_admonition(self, node: Element, name: str = '') -> None:
+ self.body.append(self.starttag(
+ node, 'div', CLASS=('admonition ' + name)))
+ if name:
+ node.insert(0, nodes.title(name, admonitionlabels[name]))
+
+ def depart_admonition(self, node: Element | None = None) -> None:
+ self.body.append('\n')
+
+ def visit_seealso(self, node: Element) -> None:
+ self.visit_admonition(node, 'seealso')
+
+ def depart_seealso(self, node: Element) -> None:
+ self.depart_admonition(node)
+
+ def get_secnumber(self, node: Element) -> tuple[int, ...] | None:
+ if node.get('secnumber'):
+ return node['secnumber']
+
+ if isinstance(node.parent, nodes.section):
+ if self.builder.name == 'singlehtml':
+ docname = self.docnames[-1]
+ anchorname = "{}/#{}".format(docname, node.parent['ids'][0])
+ if anchorname not in self.builder.secnumbers:
+ anchorname = "%s/" % docname # try first heading which has no anchor
+ else:
+ anchorname = '#' + node.parent['ids'][0]
+ if anchorname not in self.builder.secnumbers:
+ anchorname = '' # try first heading which has no anchor
+
+ if self.builder.secnumbers.get(anchorname):
+ return self.builder.secnumbers[anchorname]
+
+ return None
+
+ def add_secnumber(self, node: Element) -> None:
+ secnumber = self.get_secnumber(node)
+ if secnumber:
+ self.body.append('%s' %
+ ('.'.join(map(str, secnumber)) + self.secnumber_suffix))
+
+ def add_fignumber(self, node: Element) -> None:
+ def append_fignumber(figtype: str, figure_id: str) -> None:
+ if self.builder.name == 'singlehtml':
+ key = f"{self.docnames[-1]}/{figtype}"
+ else:
+ key = figtype
+
+ if figure_id in self.builder.fignumbers.get(key, {}):
+ self.body.append(' ')
+
+ figtype = self.builder.env.domains['std'].get_enumerable_node_type(node)
+ if figtype:
+ if len(node['ids']) == 0:
+ msg = __('Any IDs not assigned for %s node') % node.tagname
+ logger.warning(msg, location=node)
+ else:
+ append_fignumber(figtype, node['ids'][0])
+
+ def add_permalink_ref(self, node: Element, title: str) -> None:
+ icon = self.config.html_permalinks_icon
+ if node['ids'] and self.config.html_permalinks and self.builder.add_permalinks:
+ self.body.append(
+ f'{icon}',
+ )
+
+ # overwritten
+ def visit_bullet_list(self, node: Element) -> None:
+ if len(node) == 1 and isinstance(node[0], addnodes.toctree):
+ # avoid emitting empty
+ raise nodes.SkipNode
+ super().visit_bullet_list(node)
+
+ # overwritten
+ def visit_definition(self, node: Element) -> None:
+ # don't insert here.
+ self.body.append(self.starttag(node, 'dd', ''))
+
+ # overwritten
+ def depart_definition(self, node: Element) -> None:
+ self.body.append('\n')
+
+ # overwritten
+ def visit_classifier(self, node: Element) -> None:
+ self.body.append(self.starttag(node, 'span', '', CLASS='classifier'))
+
+ # overwritten
+ def depart_classifier(self, node: Element) -> None:
+ self.body.append('')
+
+ next_node: Node = node.next_node(descend=False, siblings=True)
+ if not isinstance(next_node, nodes.classifier):
+ # close `