Example #1
0
def register_sections_as_label(app: Sphinx, document: Node) -> None:
    domain = cast(StandardDomain, app.env.get_domain('std'))
    for node in document.findall(nodes.section):
        if (app.config.autosectionlabel_maxdepth and
                get_node_depth(node) >= app.config.autosectionlabel_maxdepth):
            continue
        labelid = node['ids'][0]
        docname = app.env.docname
        title = cast(nodes.title, node[0])
        ref_name = getattr(title, 'rawsource', title.astext())
        if app.config.autosectionlabel_prefix_document:
            name = nodes.fully_normalize_name(docname + ':' + ref_name)
        else:
            name = nodes.fully_normalize_name(ref_name)
        sectname = clean_astext(title)

        if name in domain.labels:
            logger.warning(__('duplicate label %s, other instance in %s'),
                           name,
                           app.env.doc2path(domain.labels[name][0]),
                           location=node,
                           type='autosectionlabel',
                           subtype=docname)

        domain.anonlabels[name] = docname, labelid
        domain.labels[name] = docname, labelid, sectname
Example #2
0
 def post_process_images(self, doctree: Node) -> None:
     """Pick the best candidate for all image URIs."""
     images = ImageAdapter(self.env)
     for node in doctree.findall(nodes.image):
         if '?' in node['candidates']:
             # don't rewrite nonlocal image URIs
             continue
         if '*' not in node['candidates']:
             for imgtype in self.supported_image_types:
                 candidate = node['candidates'].get(imgtype, None)
                 if candidate:
                     break
             else:
                 mimetypes = sorted(node['candidates'])
                 image_uri = images.get_original_image_uri(node['uri'])
                 if mimetypes:
                     logger.warning(__('a suitable image for %s builder not found: '
                                       '%s (%s)'),
                                    self.name, mimetypes, image_uri, location=node)
                 else:
                     logger.warning(__('a suitable image for %s builder not found: %s'),
                                    self.name, image_uri, location=node)
                 continue
             node['uri'] = candidate
         else:
             candidate = node['uri']
         if candidate not in self.env.images:
             # non-existing URI; let it alone
             continue
         self.images[candidate] = self.env.images[candidate][1]
Example #3
0
 def write_doc(self, docname: str, doctree: Node) -> None:
     # work around multiple string % tuple issues in docutils;
     # replace tuples in attribute values with lists
     doctree = doctree.deepcopy()
     for domain in self.env.domains.values():
         xmlns = "xmlns:" + domain.name
         doctree[xmlns] = "https://www.sphinx-doc.org/"  # type: ignore
     for node in doctree.findall(nodes.Element):
         for att, value in node.attributes.items():
             if isinstance(value, tuple):
                 node.attributes[att] = list(value)
             value = node.attributes[att]
             if isinstance(value, list):
                 for i, val in enumerate(value):
                     if isinstance(val, tuple):
                         value[i] = list(val)
     destination = StringOutput(encoding='utf-8')
     self.writer.write(doctree, destination)
     outfilename = path.join(self.outdir,
                             os_path(docname) + self.out_suffix)
     ensuredir(path.dirname(outfilename))
     try:
         with open(outfilename, 'w', encoding='utf-8') as f:
             f.write(self.writer.output)
     except OSError as err:
         logger.warning(__("error writing file %s: %s"), outfilename, err)
Example #4
0
def add_uids(doctree: Node, condition: Any) -> Iterator[Node]:
    """Add a unique id to every node in the `doctree` which matches the
    condition and yield the nodes.

    :param doctree:
        A :class:`docutils.nodes.document` instance.

    :param condition:
        A callable which returns either ``True`` or ``False`` for a given node.
    """
    for node in doctree.findall(condition):
        node.uid = uuid4().hex
        yield node
Example #5
0
 def process_doc(self, env: BuildEnvironment, docname: str, document: Node) -> None:
     """Process a document after it is read by the environment."""
     entries = self.entries.setdefault(env.docname, [])
     for node in list(document.findall(addnodes.index)):
         try:
             for entry in node['entries']:
                 split_index_msg(entry[0], entry[1])
         except ValueError as exc:
             logger.warning(str(exc), location=node)
             node.parent.remove(node)
         else:
             for entry in node['entries']:
                 entries.append(entry)
Example #6
0
 def fix_refuris(self, tree: Node) -> None:
     # fix refuris with double anchor
     fname = self.config.root_doc + self.out_suffix
     for refnode in tree.findall(nodes.reference):
         if 'refuri' not in refnode:
             continue
         refuri = refnode['refuri']
         hashindex = refuri.find('#')
         if hashindex < 0:
             continue
         hashindex = refuri.find('#', hashindex + 1)
         if hashindex >= 0:
             refnode['refuri'] = fname + refuri[hashindex:]
Example #7
0
def doctree_read(app: Sphinx, doctree: Node) -> None:
    env = app.builder.env

    resolve_target = getattr(env.config, 'linkcode_resolve', None)
    if not callable(env.config.linkcode_resolve):
        raise LinkcodeError(
            "Function `linkcode_resolve` is not given in conf.py")

    domain_keys = {
        'py': ['module', 'fullname'],
        'c': ['names'],
        'cpp': ['names'],
        'js': ['object', 'fullname'],
    }

    for objnode in list(doctree.findall(addnodes.desc)):
        domain = objnode.get('domain')
        uris: Set[str] = set()
        for signode in objnode:
            if not isinstance(signode, addnodes.desc_signature):
                continue

            # Convert signode to a specified format
            info = {}
            for key in domain_keys.get(domain, []):
                value = signode.get(key)
                if not value:
                    value = ''
                info[key] = value
            if not info:
                continue

            # Call user code to resolve the link
            uri = resolve_target(domain, info)
            if not uri:
                # no source
                continue

            if uri in uris or not uri:
                # only one link per name, please
                continue
            uris.add(uri)

            inline = nodes.inline('', _('[source]'), classes=['viewcode-link'])
            onlynode = addnodes.only(expr='html')
            onlynode += nodes.reference('',
                                        '',
                                        inline,
                                        internal=False,
                                        refuri=uri)
            signode += onlynode
Example #8
0
def process_only_nodes(document: Node, tags: "Tags") -> None:
    """Filter ``only`` nodes which do not match *tags*."""
    for node in document.findall(addnodes.only):
        try:
            ret = tags.eval_condition(node['expr'])
        except Exception as err:
            logger.warning(__('exception while evaluating only directive expression: %s'), err,
                           location=node)
            node.replace_self(node.children or nodes.comment())
        else:
            if ret:
                node.replace_self(node.children or nodes.comment())
            else:
                # A comment on the comment() nodes being inserted: replacing by [] would
                # result in a "Losing ids" exception if there is a target node before
                # the only node, so we make sure docutils can transfer the id to
                # something, even if it's just a comment and will lose the id anyway...
                node.replace_self(nodes.comment())
Example #9
0
def merge_doctrees(old: Node, new: Node, condition: Any) -> Iterator[Node]:
    """Merge the `old` doctree with the `new` one while looking at nodes
    matching the `condition`.

    Each node which replaces another one or has been added to the `new` doctree
    will be yielded.

    :param condition:
        A callable which returns either ``True`` or ``False`` for a given node.
    """
    old_iter = old.findall(condition)
    new_iter = new.findall(condition)
    old_nodes = []
    new_nodes = []
    ratios = {}
    seen = set()
    # compare the nodes each doctree in order
    for old_node, new_node in zip_longest(old_iter, new_iter):
        if old_node is None:
            new_nodes.append(new_node)
            continue
        if not getattr(old_node, 'uid', None):
            # maybe config.gettext_uuid has been changed.
            old_node.uid = uuid4().hex
        if new_node is None:
            old_nodes.append(old_node)
            continue
        ratio = get_ratio(old_node.rawsource, new_node.rawsource)
        if ratio == 0:
            new_node.uid = old_node.uid
            seen.add(new_node)
        else:
            ratios[old_node, new_node] = ratio
            old_nodes.append(old_node)
            new_nodes.append(new_node)
    # calculate the ratios for each unequal pair of nodes, should we stumble
    # on a pair which is equal we set the uid and add it to the seen ones
    for old_node, new_node in product(old_nodes, new_nodes):
        if new_node in seen or (old_node, new_node) in ratios:
            continue
        ratio = get_ratio(old_node.rawsource, new_node.rawsource)
        if ratio == 0:
            new_node.uid = old_node.uid
            seen.add(new_node)
        else:
            ratios[old_node, new_node] = ratio
    # choose the old node with the best ratio for each new node and set the uid
    # as long as the ratio is under a certain value, in which case we consider
    # them not changed but different
    ratios = sorted(ratios.items(), key=itemgetter(1))  # type: ignore
    for (old_node, new_node), ratio in ratios:
        if new_node in seen:
            continue
        else:
            seen.add(new_node)
        if ratio < VERSIONING_RATIO:
            new_node.uid = old_node.uid
        else:
            new_node.uid = uuid4().hex
            yield new_node
    # create new uuids for any new node we left out earlier, this happens
    # if one or more nodes are simply added.
    for new_node in set(new_nodes) - seen:
        new_node.uid = uuid4().hex
        yield new_node
Example #10
0
    def test_doc(self, docname: str, doctree: Node) -> None:
        groups: Dict[str, TestGroup] = {}
        add_to_all_groups = []
        self.setup_runner = SphinxDocTestRunner(verbose=False,
                                                optionflags=self.opt)
        self.test_runner = SphinxDocTestRunner(verbose=False,
                                               optionflags=self.opt)
        self.cleanup_runner = SphinxDocTestRunner(verbose=False,
                                                  optionflags=self.opt)

        self.test_runner._fakeout = self.setup_runner._fakeout  # type: ignore
        self.cleanup_runner._fakeout = self.setup_runner._fakeout  # type: ignore

        if self.config.doctest_test_doctest_blocks:

            def condition(node: Node) -> bool:
                return (isinstance(node, (nodes.literal_block, nodes.comment)) and
                        'testnodetype' in node) or \
                    isinstance(node, nodes.doctest_block)
        else:

            def condition(node: Node) -> bool:
                return isinstance(node, (nodes.literal_block, nodes.comment)) \
                    and 'testnodetype' in node

        for node in doctree.findall(condition):  # type: Element
            if self.skipped(node):
                continue

            source = node['test'] if 'test' in node else node.astext()
            filename = self.get_filename_for_node(node, docname)
            line_number = self.get_line_number(node)
            if not source:
                logger.warning(__('no code/output in %s block at %s:%s'),
                               node.get('testnodetype', 'doctest'), filename,
                               line_number)
            code = TestCode(source,
                            type=node.get('testnodetype', 'doctest'),
                            filename=filename,
                            lineno=line_number,
                            options=node.get('options'))
            node_groups = node.get('groups', ['default'])
            if '*' in node_groups:
                add_to_all_groups.append(code)
                continue
            for groupname in node_groups:
                if groupname not in groups:
                    groups[groupname] = TestGroup(groupname)
                groups[groupname].add_code(code)
        for code in add_to_all_groups:
            for group in groups.values():
                group.add_code(code)
        if self.config.doctest_global_setup:
            code = TestCode(self.config.doctest_global_setup,
                            'testsetup',
                            filename=None,
                            lineno=0)
            for group in groups.values():
                group.add_code(code, prepend=True)
        if self.config.doctest_global_cleanup:
            code = TestCode(self.config.doctest_global_cleanup,
                            'testcleanup',
                            filename=None,
                            lineno=0)
            for group in groups.values():
                group.add_code(code)
        if not groups:
            return

        self._out('\nDocument: %s\n----------%s\n' %
                  (docname, '-' * len(docname)))
        for group in groups.values():
            self.test_group(group)
        # Separately count results from setup code
        res_f, res_t = self.setup_runner.summarize(self._out, verbose=False)
        self.setup_failures += res_f
        self.setup_tries += res_t
        if self.test_runner.tries:
            res_f, res_t = self.test_runner.summarize(self._out, verbose=True)
            self.total_failures += res_f
            self.total_tries += res_t
        if self.cleanup_runner.tries:
            res_f, res_t = self.cleanup_runner.summarize(self._out,
                                                         verbose=True)
            self.cleanup_failures += res_f
            self.cleanup_tries += res_t
Example #11
0
def doctree_read(app: Sphinx, doctree: Node) -> None:
    env = app.builder.env
    if not hasattr(env, '_viewcode_modules'):
        env._viewcode_modules = {}  # type: ignore

    def has_tag(modname: str, fullname: str, docname: str,
                refname: str) -> bool:
        entry = env._viewcode_modules.get(modname, None)  # type: ignore
        if entry is False:
            return False

        code_tags = app.emit_firstresult('viewcode-find-source', modname)
        if code_tags is None:
            try:
                analyzer = ModuleAnalyzer.for_module(modname)
                analyzer.find_tags()
            except Exception:
                env._viewcode_modules[modname] = False  # type: ignore
                return False

            code = analyzer.code
            tags = analyzer.tags
        else:
            code, tags = code_tags

        if entry is None or entry[0] != code:
            entry = code, tags, {}, refname
            env._viewcode_modules[modname] = entry  # type: ignore
        _, tags, used, _ = entry
        if fullname in tags:
            used[fullname] = docname
            return True

        return False

    for objnode in list(doctree.findall(addnodes.desc)):
        if objnode.get('domain') != 'py':
            continue
        names: Set[str] = set()
        for signode in objnode:
            if not isinstance(signode, addnodes.desc_signature):
                continue
            modname = signode.get('module')
            fullname = signode.get('fullname')
            refname = modname
            if env.config.viewcode_follow_imported_members:
                new_modname = app.emit_firstresult(
                    'viewcode-follow-imported',
                    modname,
                    fullname,
                )
                if not new_modname:
                    new_modname = _get_full_modname(app, modname, fullname)
                modname = new_modname
            if not modname:
                continue
            fullname = signode.get('fullname')
            if not has_tag(modname, fullname, env.docname, refname):
                continue
            if fullname in names:
                # only one link per name, please
                continue
            names.add(fullname)
            pagename = posixpath.join(OUTPUT_DIRNAME,
                                      modname.replace('.', '/'))
            signode += viewcode_anchor(reftarget=pagename,
                                       refid=fullname,
                                       refdoc=env.docname)