def write_doc(self, docname: str, doctree: nodes.Node) -> None: logger.debug(f"[nbtutorial]: Called on {docname}") # Determine if the document represents a tutorial. nodes = list(doctree.traverse(condition=nbtutorial)) if len(nodes) == 0: return # Find any solutions that it may constain. solutions = list(doctree.traverse(condition=nbsolution)) self._process_solutions(docname, solutions) destination = StringOutput(encoding="utf-8") self.docwriter.write(doctree, destination) base, fname = os.path.split(docname) basedir = os.path.join(self.outdir, base) if not os.path.exists(basedir): os.makedirs(basedir) Path(basedir, "__init__.py").touch() outfile = os.path.join(basedir, fname + ".ipynb") with open(outfile, "w") as f: f.write(self.docwriter.output)
def write_doc(self, docname: str, doctree: Node) -> None: logger.info('') n = 0 # reference nodes for refnode in doctree.traverse(nodes.reference): if 'refuri' not in refnode: continue uri = refnode['refuri'] lineno = get_node_line(refnode) self.wqueue.put((uri, docname, lineno), False) n += 1 # image nodes for imgnode in doctree.traverse(nodes.image): uri = imgnode['candidates'].get('?') if uri and '://' in uri: lineno = get_node_line(imgnode) self.wqueue.put((uri, docname, lineno), False) n += 1 done = 0 while done < n: self.process_result(self.rqueue.get()) done += 1 if self.broken: self.app.statuscode = 1
def register_sections_as_label(app: Sphinx, document: Node) -> None: domain = cast(StandardDomain, app.env.get_domain('std')) for node in document.traverse(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
def select_doc_nodes(self, doctree: nodes.Node) -> List[nodes.Node]: """Select the nodes for which entries in the TOC are desired This is a separate method so that it might be overriden by subclasses wanting to add other types of nodes to the TOC. """ return doctree.traverse(addnodes.desc)
def doctree_read(app: Sphinx, doctree: Node) -> None: """ Go through the entire document looking for C signature nodes to create cross references to the actual source listings. Args: app (Sphinx): The sphinx app currently doing the processing. doctree (Node): The root node of the document to walk through. This will be modified in place, by modifiying signature nodes of C constructs. """ c_nodes = (n for n in doctree.traverse(addnodes.desc) if n.get("domain") == "c") for node in c_nodes: signature_nodes = (n for n in node if isinstance(n, addnodes.desc_signature)) # I really dislike negative flags. Anyway it's ok to link to source # listing from anywhere, but it's undesirable to link from the source # listing back to any documentation location. So we leverage the # :noindex: option and assume that only the entries which allowed the # indices are the consolidated locations of documentation. use_back_refs = not node.get("noindex", False) for signature in signature_nodes: fullname = signature.get("fullname") if use_back_refs: _add_pending_back_reference(app, signature, fullname) _add_pending_source_cross_reference(app, signature, fullname)
def doctree_read(app: Sphinx, document: Node) -> None: domain = cast(StandardDomain, app.env.get_domain("std")) # Traverse extension nodes for node in document.traverse(): docname, labelid, sectname = "", "", "" if is_extension_node(node): name = node.get("names", [])[0] labelid = document.nameids[name] docname = app.env.docname # If solution node if is_linked_node(node): sectname = "Solution to " else: # If other node, simply add :math: to title # to allow for easy parsing in ref_node for item in node[0]: if isinstance(item, nodes.math): sectname += f":math:`{item.astext()}` " continue sectname += f"{item.astext()} " # Lastly, remove parans from title _r, _l = sectname.rfind(")"), sectname.find("(") + 1 sectname = sectname[_l:_r].strip() # Now update domain.anonlabels and domain.labels # to include the newly updated sectname domain.anonlabels[name] = docname, labelid domain.labels[name] = docname, labelid, sectname
def register_sections_as_label(app: Sphinx, document: Node) -> None: docname = app.env.docname print(docname) for pattern in app.config.autosectionlabel_skip_docs: if fnmatch(docname, pattern): return None domain = cast(StandardDomain, app.env.get_domain("std")) for node in document.traverse(nodes.section): if (app.config.autosectionlabel_maxdepth and get_node_depth(node) >= app.config.autosectionlabel_maxdepth): continue labelid = node["ids"][0] 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
def post_process_images(self, doctree: Node) -> None: """Pick the best candidate for all image URIs.""" images = ImageAdapter(self.env) for node in doctree.traverse(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]
def get_rst_title(rst_doc: Node) -> Optional[Any]: """ Given some RST, extract what docutils thinks is the title """ for title in rst_doc.traverse(nodes.title): return title.astext() return None
def register_tabs_as_label(app: Sphinx, document: nodes.Node) -> None: docname = app.env.docname labels = app.env.domaindata['std']['labels'] anonlabels = app.env.domaindata['std']['anonlabels'] for node in document.traverse(nxt_tab_head): if node.label_id in labels: logger.warning(__('duplicate label %s, other instance in %s'), node.label_id, app.env.doc2path(labels[node.label_id][0]), location=node) anonlabels[node.label_id] = docname, node.label_id labels[node.label_id] = docname, node.label_id, node.astext()
def post_process_images(self, doctree: Node) -> None: for node in doctree.traverse(RevealjsNode): elm = getattr(node, 'revealit_el', None) if elm: for img in elm.images.values(): if img not in self.env.images: # non-existing URI; let it alone continue self.images[img] = self.env.images[img][1] super().post_process_images(doctree)
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 document.traverse(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)
def _run_doctests_on_graphtik_document(app: Sphinx, doctree: nodes.Node): """Callback of `doctree-resolved`` event. """ try: docname = app.env.docname from ._graphtikbuilder import get_graphtik_builder if _should_work(app) and any(doctree.traverse(graphtik_node)): log.info(__("Graphtik-ing document %r..."), docname) graphtik_builder = get_graphtik_builder(app) graphtik_builder.test_doc(docname, doctree) except Exception as ex: log.error("General failure of Graphtik-sphinx extension: %s", ex, exc_info=True) raise
def fix_refuris(self, tree: Node) -> None: # fix refuris with double anchor fname = self.config.master_doc + self.out_suffix for refnode in tree.traverse(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:]
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.traverse(condition): node.uid = uuid4().hex yield node
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.traverse(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
def doctree_read(app: Sphinx, document: Node) -> None: """ Read the doctree and apply updates to sphinx-exercise nodes """ domain = cast(StandardDomain, app.env.get_domain("std")) # Traverse sphinx-exercise nodes for node in document.traverse(): if is_extension_node(node): name = node.get("names", [])[0] label = document.nameids[name] docname = app.env.docname section_name = node.attributes.get("title") domain.anonlabels[name] = docname, label domain.labels[name] = docname, label, section_name
def process_only_nodes(document: Node, tags: "Tags") -> None: """Filter ``only`` nodes which does not match *tags*.""" for node in document.traverse(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())
def test_doc(self, docname: str, doctree: Node) -> None: # Get all applicable nodes doc_nodes = list(doctree.traverse(self.condition)) if not doc_nodes: return print() print(f"{self.name} testing: {docname}") print() self.total_tries += 1 try: with tempfile.TemporaryDirectory( ) as tempdir, contextlib.ExitStack() as stack: ctx = {"cwd": tempdir} for node in doc_nodes: # type: Element filename = self.get_filename_for_node(node, docname) line_number = self.get_line_number(node) if "consoletest-literalinclude" in node: print() print("Copying", node["source"], node["filepath"]) print() shutil.copyfile( node["source"], os.path.join(ctx["cwd"], *node["filepath"]), ) elif "consoletest_commands" in node: for command in node["consoletest_commands"]: print() print("Running", command) print() stack.enter_context(command) command.run(ctx) print() print("No more tempdir") print() except: self.total_failures += 1
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 node in doctree.traverse(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)
def process_attributetable(app: Sphinx, doctree: nodes.Node, fromdocname: str) -> None: env = app.builder.env lookup = build_lookup_table(env) for node in doctree.traverse(attributetableplaceholder): modulename, classname, fullname = node['python-module'], node[ 'python-class'], node['python-full-name'] groups = get_class_results(lookup, modulename, classname, fullname) table = attributetable('') for label, subitems in groups.items(): if not subitems: continue table.append( class_results_to_node(label, sorted(subitems, key=lambda c: c.label))) table['python-class'] = fullname if not table: node.replace_self([]) else: node.replace_self([table])
def post_process_images(self, doctree: Node) -> None: """Pick the best candidate for an image and link down-scaled images to their high res version. """ Builder.post_process_images(self, doctree) if self.config.html_scaled_image_link and self.html_scaled_image_link: for node in doctree.traverse(nodes.image): scale_keys = ('scale', 'width', 'height') if not any((key in node) for key in scale_keys) or \ isinstance(node.parent, nodes.reference): # docutils does unfortunately not preserve the # ``target`` attribute on images, so we need to check # the parent node here. continue uri = node['uri'] reference = nodes.reference('', '', internal=True) if uri in self.images: reference['refuri'] = posixpath.join(self.imgpath, self.images[uri]) else: reference['refuri'] = uri node.replace_self(reference) reference.append(node)
def test_doc(self, docname: str, doctree: nodes.Node) -> None: """ HACK: Method overridden to annotate all TestCode instances with their nodes, so as to store back on them the value of `:graphvar:` in the doctest-runner globals, after they have been executed. """ groups: Dict[str, extdoctest.TestGroup] = {} add_to_all_groups = [] TestRunner = extdoctest.SphinxDocTestRunner self.setup_runner = TestRunner(verbose=False, optionflags=self.opt) self.test_runner = TestRunner(verbose=False, optionflags=self.opt) self.cleanup_runner = TestRunner(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: nodes.Node) -> bool: return (isinstance(node, (nodes.literal_block, nodes.comment)) and "testnodetype" in node) or isinstance( node, nodes.doctest_block) else: def condition(node: nodes.Node) -> bool: return (isinstance(node, (nodes.literal_block, nodes.comment)) and "testnodetype" in node) for node in doctree.traverse(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 and not self.run_empty_code: log.warning( __("no code/output in %s block"), node.get("testnodetype", "doctest"), location=(filename, line_number), ) code = extdoctest.TestCode( source, type=node.get("testnodetype", "doctest"), filename=filename, lineno=line_number, options=node.get("options"), ) # HACK: annotate the TestCode with the node # to store back plottable from doctest-runner globals. code.node = node 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] = extdoctest.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 = extdoctest.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 = extdoctest.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
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 doctree.traverse(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)
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.traverse(condition) new_iter = new.traverse(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
def doctree_read(app: Sphinx, doctree: Node) -> None: env = app.builder.env if not hasattr(env, '_viewcode_modules'): env._viewcode_modules = {} # type: ignore if app.builder.name == "singlehtml": return if app.builder.name.startswith("epub") and not env.config.viewcode_enable_epub: return 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 doctree.traverse(addnodes.desc): if objnode.get('domain') != 'py': continue names = set() # type: Set[str] 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 = '_modules/' + modname.replace('.', '/') inline = nodes.inline('', _('[source]'), classes=['viewcode-link']) onlynode = addnodes.only(expr='html') onlynode += addnodes.pending_xref('', inline, reftype='viewcode', refdomain='std', refexplicit=False, reftarget=pagename, refid=fullname, refdoc=env.docname) signode += onlynode
def test_doc(self, docname: str, doctree: Node) -> None: groups = {} # type: 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.traverse(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