def run(self): """ Restructured text extension for including inline JSAV content on module pages """ self.options['exer_name'] = self.arguments[0] self.options['type'] = self.arguments[1] self.options['odsa_path'] = os.path.relpath(conf.odsa_path,conf.ebook_path) # Set defaults for any values that aren't configured if 'required' not in self.options: self.options['required'] = False if 'points' not in self.options: self.options['points'] = 0 if 'threshold' not in self.options: self.options['threshold'] = 1.0 if 'long_name' not in self.options: self.options['long_name'] = self.options['exer_name'] if 'align' not in self.options: self.options['align'] = 'center' if 'output' in self.options and self.options['output'] == "show": self.options['output_code'] = '<p class="jsavoutput jsavline"></p>' else: self.options['output_code'] = '' if self.options['type'] == "dgm": avdgm_node = av_dgm() anchor_node = av_anchor() avdgm_node['exer_name'] = self.options['exer_name'] anchor_node['ids'].append(self.options['exer_name']) avdgm_node += anchor_node if self.content: node = nodes.Element() # anonymous container for parsing self.state.nested_parse(self.content, self.content_offset, node) first_node = node[0] if isinstance(first_node, nodes.paragraph): caption = nodes.caption(first_node.rawsource, '', *first_node.children) caption['align']= self.options['align'] avdgm_node += caption return [avdgm_node] elif self.options['type'] == "ss" and self.content: avss_node = av_ss() avss_node['res'] = SLIDESHOW % self.options node = nodes.Element() # anonymous container for parsing self.state.nested_parse(self.content, self.content_offset, node) first_node = node[0] if isinstance(first_node, nodes.paragraph): caption = nodes.caption(first_node.rawsource, '', *first_node.children) caption['align']= self.options['align'] avss_node += caption return [avss_node] else: res = SLIDESHOW % self.options return [nodes.raw('', res, format='html')]
def run(self): env = self.state.document.settings.env repo = Repo(env.srcdir) commits = repo.iter_commits() l = nodes.bullet_list() for commit in list(commits)[:10]: date_str = datetime.fromtimestamp(commit.authored_date) if '\n' in commit.message: message, detailed_message = commit.message.split('\n', 1) else: message = commit.message detailed_message = None item = nodes.list_item() item += [ nodes.strong(text=message), nodes.inline(text=" by "), nodes.emphasis(text=str(commit.author)), nodes.inline(text=" at "), nodes.emphasis(text=str(date_str)) ] if detailed_message: item.append(nodes.caption(text=detailed_message.strip())) l.append(item) return [l]
def run(self): label = self.options.get('label', None) spec = self.options.get('spec', None) caption = self.options.get('caption', None) alt = self.options.get('alt', None) nofig = 'nofig' in self.options loc = self.options.get('loc', None) figtable_node = figtable('', ids=[label] if label is not None else []) figtable_node['nofig'] = nofig if spec is not None: table_spec_node = addnodes.tabular_col_spec() table_spec_node['spec'] = spec figtable_node.append(table_spec_node) node = nodes.Element() self.state.nested_parse(self.content, self.content_offset, node) tablenode = node[0] if alt is not None: tablenode['alt'] = alt figtable_node.append(tablenode) if caption is not None: caption_node = nodes.caption('', '', nodes.Text(caption)) figtable_node.append(caption_node) if label is not None: targetnode = nodes.target('', '', ids=[label]) figtable_node.append(targetnode) if loc is not None: figtable_node['loc'] = '[' + loc + ']' return [figtable_node]
def run(self): figclasses = self.options.pop('figclass', None) (image_node,) = Image.run(self) if isinstance(image_node, nodes.system_message): return [image_node] figure_node = nodes.figure('', image_node) if figclasses: figure_node['classes'] += figclasses figure_node['classes'] += ['m-figure'] if self.content: node = nodes.Element() # anonymous container for parsing self.state.nested_parse(self.content, self.content_offset, node) first_node = node[0] if isinstance(first_node, nodes.paragraph): caption = nodes.caption(first_node.rawsource, '', *first_node.children) caption.source = first_node.source caption.line = first_node.line figure_node += caption elif not (isinstance(first_node, nodes.comment) and len(first_node) == 0): error = self.state_machine.reporter.error( 'Figure caption must be a paragraph or empty comment.', nodes.literal_block(self.block_text, self.block_text), line=self.lineno) return [figure_node, error] if len(node) > 1: figure_node += nodes.legend('', *node[1:]) return [figure_node]
def run(self): results = super(BlockdiagDirective, self).run() node = results[0] if not isinstance(node, self.node_class): return results try: diagram = self.node2diagram(node) except Exception as e: raise self.warning(e.message) if 'desctable' in node['options']: del node['options']['desctable'] results += self.description_tables(diagram) results[0] = self.node2image(node, diagram) if 'caption' in node: fig = nodes.figure() fig += results[0] fig += nodes.caption(text=node['caption']) results[0] = fig return results
def run(self): required_arguments = 0 optional_arguments = 2 align = self.options.pop('align', None) capalign = self.options.pop('capalign', None) odsatable_node = odsatable() odsatable_node['align'] = align odsatable_node['capalign'] = capalign if align: odsatable_node['align'] = align if self.content: node = nodes.Element() # anonymous container for parsing self.state.nested_parse(self.content, self.content_offset, node) first_node = node[0] if isinstance(first_node, nodes.paragraph): caption = nodes.caption(first_node.rawsource, '', *first_node.children) caption['align']= capalign odsatable_node += caption elif not (isinstance(first_node, nodes.comment) and len(first_node) == 0): error = self.state_machine.reporter.error( 'Table caption must be a paragraph or empty comment.', nodes.literal_block(self.block_text, self.block_text), line=self.lineno) return [odsatable_node, error] if len(node) > 1: odsatable_node += nodes.legend('', *node[1:]) return [odsatable_node]
def run(self): label = self.options.get('label', None) width = self.options.get('width', None) alt = self.options.get('alt', None) node = subfigend('', ids=[label] if label is not None else []) if width is not None: node['width'] = width if alt is not None: node['alt'] = alt if self.content: anon = nodes.Element() self.state.nested_parse(self.content, self.content_offset, anon) first_node = anon[0] if isinstance(first_node, nodes.paragraph): caption = nodes.caption(first_node.rawsource, '', *first_node.children) node += caption if label is not None: targetnode = nodes.target('', '', ids=[label]) node.append(targetnode) return [node]
def run(self): env = self.state.document.settings.env config = env.config repodir = env.srcdir + '/' + config["git_repository_root"] doc_path = env.srcdir + '/' + env.docname + config["source_suffix"] if self.options.get('dir', False) == None: doc_path = '/'.join(doc_path.split('/')[:-1]) repo = Repo(repodir) commits = repo.iter_commits(paths=doc_path) l = nodes.bullet_list() revisions_to_display = self.options.get('revisions', 10) for commit in list(commits)[:revisions_to_display]: date_str = datetime.fromtimestamp(commit.authored_date) if '\n' in commit.message: message, detailed_message = commit.message.split('\n', 1) else: message = commit.message detailed_message = None item = nodes.list_item() item += [ nodes.strong(text=message), nodes.inline(text=" by "), nodes.emphasis(text=str(commit.author)), nodes.inline(text=" at "), nodes.emphasis(text=str(date_str)) ] if detailed_message: item.append(nodes.caption(text=detailed_message.strip())) l.append(item) return [l]
def run(self): set_classes(self.options) text = '\n'.join(self.content) figure_node = nodes.figure(text, **self.options) figure_node['classes'] += self.style_classes self.state.nested_parse(self.content, self.content_offset, figure_node) # Insert the title node, if any, right after the code / math / graph. # There could be things like the class directive before, so be sure to # find the right node. if len(self.arguments) == 1: title_text = self.arguments[0] title_nodes, _ = self.state.inline_text(title_text, self.lineno) title_node = nodes.caption('', '', *title_nodes) for i, child in enumerate(figure_node): if isinstance(child, nodes.raw) or isinstance(child, nodes.literal_block): figure_node.insert(i + 1, title_node) break else: assert False # pragma: no cover return [figure_node]
def run(self): figwidth = self.options.pop('figwidth', None) figclasses = self.options.pop('figclass', None) align = self.options.pop('align', None) (media_node,) = Media.run(self) if isinstance(media_node, nodes.system_message): return [media_node] figure_node = nodes.figure('', media_node) if figwidth == 'image': if PIL and self.state.document.settings.file_insertion_enabled: # PIL doesn't like Unicode paths: try: i = PIL.open(str(media_node['uri'])) except (IOError, UnicodeError): pass else: self.state.document.settings.record_dependencies.add( media_node['uri']) figure_node['width'] = i.size[0] elif figwidth is not None: figure_node['width'] = figwidth if figclasses: figure_node['classes'] += figclasses if align: figure_node['align'] = align if self.content: node = nodes.Element() # anonymous container for parsing self.state.nested_parse(self.content, self.content_offset, node) first_node = node[0] if isinstance(first_node, nodes.paragraph): caption = nodes.caption(first_node.rawsource, '', *first_node.children) figure_node += caption elif not (isinstance(first_node, nodes.comment) and len(first_node) == 0): error = self.state_machine.reporter.error( 'Figure caption must be a paragraph or empty comment.', nodes.literal_block(self.block_text, self.block_text), line=self.lineno) return [figure_node, error] if len(node) > 1: figure_node += nodes.legend('', *node[1:]) node = figure_node node['label'] = self.options.get('label', None) if not node['label']: node['label'] = self.options.get('uri') node['number'] = None ret = [node] if node['label']: key = node['label'] tnode = nodes.target('', '', ids=['figure-' + node['label']]) self.state.document.note_explicit_target(tnode) ret.insert(0, tnode) return ret
def run(self): figure = nodes.figure() # Figure out what the show for this demo py_path = Path(self.arguments[0]) zip_path = py_path.parent / (py_path.stem + '_assets.zip') png_path = py_path.parent / (py_path.stem + '.png') # Error out if the given python script doesn't exist. if py_path.suffix != '.py': raise self.error(f"'{py_path}' must be a python script.") if not py_path.exists(): raise self.error(f"'{py_path}' doesn't exist.") if self.content: # Make sure the content is present in the given file. Complain if # there are differences. from textwrap import dedent with open(py_path) as py_file: py_code = [l.strip() for l in py_file.readlines()] for py_line in self.content: if py_line.strip() not in py_code: raise self.error(f"""\ Error in \"demo\" directive: The following line isn't present in '{py_path}': {py_line}""") # Add a node for the code snippet from sphinx.directives.code import CodeBlock figure += self.make_snippet(self.content, png_path.exists()) # Add a node for the screenshot if png_path.exists(): figure += self.make_screenshot(png_path) # Add a node for the download links. caption = nodes.caption() caption += self.make_download_link(py_path) if zip_path.exists(): caption += linebreak() caption += self.make_download_link(zip_path) figure += caption return [figure]
def run(self): figwidth = self.options.pop('figwidth', None) position = self.options.pop('position', None) figclasses = self.options.pop('figclass', None) align = self.options.pop('align', None) (image_node,) = Image.run(self) if isinstance(image_node, nodes.system_message): return [image_node] figure_node = nodes.figure('', image_node) if figwidth == 'image': if PIL and self.state.document.settings.file_insertion_enabled: # PIL doesn't like Unicode paths: try: i = PIL.open(str(image_node['uri'])) except (IOError, UnicodeError): pass else: self.state.document.settings.record_dependencies.add( image_node['uri']) figure_node['width'] = i.size[0] elif figwidth is not None: figure_node['width'] = figwidth if figclasses: figure_node['classes'] += figclasses if align: figure_node['align'] = align if position: figure_node['position'] = position if self.content: node = nodes.Element() # anonymous container for parsing self.state.nested_parse(self.content, self.content_offset, node) first_node = node[0] cap = first_node.astext().strip() if re.match('[Tt]he.*',cap): print >>sys.stderr, "WARNING: Style: Caption '%s' begins with 'The'" % cap if isinstance(first_node, nodes.paragraph): caption = nodes.caption(first_node.rawsource, '', *first_node.children) figure_node += caption elif not (isinstance(first_node, nodes.comment) and len(first_node) == 0): error = self.state_machine.reporter.error( 'Figure caption must be a paragraph or empty comment.', nodes.literal_block(self.block_text, self.block_text), line=self.lineno) return [figure_node, error] if len(node) > 1: figure_node += nodes.legend('', *node[1:]) else: self.state.document.reporter.error('Figure without caption\n', line = self.lineno) return [figure_node]
def container_wrapper(directive, literal_node, caption): container_node = nodes.container("", literal_block=True, classes=["literal-block-wrapper"]) parsed = nodes.Element() directive.state.nested_parse(ViewList([caption], source=""), directive.content_offset, parsed) if isinstance(parsed[0], nodes.system_message): raise ValueError(parsed[0]) caption_node = nodes.caption(parsed[0].rawsource, "", *parsed[0].children) caption_node.source = parsed[0].source caption_node.line = parsed[0].line container_node += caption_node container_node += literal_node return container_node
def container_wrapper(directive, literal_node, caption): container_node = nodes.container('', literal_block=True) parsed = nodes.Element() directive.state.nested_parse(ViewList([caption], source=''), directive.content_offset, parsed) caption_node = nodes.caption(parsed[0].rawsource, '', *parsed[0].children) caption_node.source = parsed[0].source caption_node.line = parsed[0].line container_node += caption_node container_node += literal_node return container_node
def figure_wrapper(directive, node, caption): # type: (Directive, graphviz, str) -> nodes.figure figure_node = nodes.figure('', node) if 'align' in node: figure_node['align'] = node.attributes.pop('align') inodes, messages = directive.state.inline_text(caption, directive.lineno) caption_node = nodes.caption(caption, '', *inodes) caption_node.extend(messages) set_source_info(directive, caption_node) figure_node += caption_node return figure_node
def figure_wrapper(directive, node, caption): figure_node = nodes.figure('', node) parsed = nodes.Element() directive.state.nested_parse(ViewList([caption], source=''), directive.content_offset, parsed) caption_node = nodes.caption(parsed[0].rawsource, '', *parsed[0].children) caption_node.source = parsed[0].source caption_node.line = parsed[0].line figure_node += caption_node return figure_node
def run(self): figwidth = self.options.pop('figwidth', None) figclasses = self.options.pop('figclass', None) if self.options.get('caption'): align = self.options.pop('align', None) else: align = None results = super(BlockdiagDirective, self).run() node = results[0] if not isinstance(node, self.node_class): return results try: diagram = self.node2diagram(node) except Exception as e: raise self.warning(str(e)) if 'desctable' in node['options']: results += self.description_tables(diagram) results[0] = self.node2image(node, diagram) self.add_name(results[0]) if node.get('caption'): elem = nodes.Element() self.state.nested_parse(ViewList([node['caption']], source=''), self.content_offset, elem) caption_node = nodes.caption(elem[0].rawsource, '', *elem[0].children) caption_node.source = elem[0].source caption_node.line = elem[0].line fig = nodes.figure() fig += results[0] fig += caption_node if figwidth == 'image': width = self.get_actual_width(node, diagram) fig['width'] = str(width) + 'px' elif figwidth is not None: fig['width'] = figwidth if figclasses: fig['classes'] += figclasses if align: fig['align'] = align results[0] = fig return results
def run(self): figwidth = self.options.pop('figwidth', None) figclasses = self.options.pop('figclass', None) align = self.options.pop('align', None) (image_node,) = Image.run(self) if isinstance(image_node, nodes.system_message): return [image_node] figure_node = nodes.figure('', image_node) if figwidth == 'image': if PIL and self.state.document.settings.file_insertion_enabled: imagepath = urllib.url2pathname(image_node['uri']) try: if isinstance(imagepath, str): imagepath_str = imagepath else: imagepath_str = imagepath.encode(sys.getfilesystemencoding()) img = PIL.Image.open(imagepath_str) except (IOError, UnicodeEncodeError): pass # TODO: warn? else: self.state.document.settings.record_dependencies.add( imagepath.replace('\\', '/')) figure_node['width'] = '%dpx' % img.size[0] del img elif figwidth is not None: figure_node['width'] = figwidth if figclasses: figure_node['classes'] += figclasses if align: figure_node['align'] = align if self.content: node = nodes.Element() # anonymous container for parsing self.state.nested_parse(self.content, self.content_offset, node) first_node = node[0] if isinstance(first_node, nodes.paragraph): caption = nodes.caption(first_node.rawsource, '', *first_node.children) caption.source = first_node.source caption.line = first_node.line figure_node += caption elif not (isinstance(first_node, nodes.comment) and len(first_node) == 0): error = self.state_machine.reporter.error( 'Figure caption must be a paragraph or empty comment.', nodes.literal_block(self.block_text, self.block_text), line=self.lineno) return [figure_node, error] if len(node) > 1: figure_node += nodes.legend('', *node[1:]) return [figure_node]
def run(self): figwidth = self.options.get('figwidth') if figwidth: del self.options['figwidth'] figclasses = self.options.get('figclass') if figclasses: del self.options['figclass'] align = self.options.get('align') if align: del self.options['align'] (image_node,) = Image.run(self) if isinstance(image_node, nodes.system_message): return [image_node] figure_node = nodes.figure('', image_node) if figwidth == 'image': if PIL and self.state.document.settings.file_insertion_enabled: # PIL doesn't like Unicode paths: try: i = PIL.open(str(image_node['uri'])) except (IOError, UnicodeError): pass else: self.state.document.settings.record_dependencies.add( image_node['uri']) figure_node['width'] = i.size[0] elif figwidth is not None: figure_node['width'] = figwidth if figclasses: figure_node['classes'] += figclasses if align: figure_node['align'] = align if self.content: node = nodes.Element() # anonymous container for parsing self.state.nested_parse(self.content, self.content_offset, node) first_node = node[0] if isinstance(first_node, nodes.paragraph): caption = nodes.caption(first_node.rawsource, '', *first_node.children) figure_node += caption elif not (isinstance(first_node, nodes.comment) and len(first_node) == 0): error = self.state_machine.reporter.error( 'Figure caption must be a paragraph or empty comment.', nodes.literal_block(self.block_text, self.block_text), line=self.lineno) return [figure_node, error] if len(node) > 1: figure_node += nodes.legend('', *node[1:]) return [figure_node]
def run(self): node = plantuml(self.block_text, **self.options) node['uml'] = '\n'.join(self.content) # if a caption is defined, insert a 'figure' with this node and the caption if 'caption' in self.options: import docutils.statemachine cnode = nodes.Element() # anonymous container for parsing sl = docutils.statemachine.StringList([self.options['caption']],source='') self.state.nested_parse(sl,self.content_offset, cnode) caption = nodes.caption(self.options['caption'], '', *cnode) fig = nodes.figure('',node) fig += caption node = fig return [node]
def figure_wrapper(directive, node, caption): # type: (Directive, nodes.Node, unicode) -> nodes.figure figure_node = nodes.figure('', node) if 'align' in node: figure_node['align'] = node.attributes.pop('align') parsed = nodes.Element() directive.state.nested_parse(ViewList([caption], source=''), directive.content_offset, parsed) caption_node = nodes.caption(parsed[0].rawsource, '', *parsed[0].children) caption_node.source = parsed[0].source caption_node.line = parsed[0].line figure_node += caption_node return figure_node
def sourcecode( name, arguments, options, content, lineno, content_offset, block_text, state, state_machine, ): filename = options.get('filename', None) if filename is None: code = u'\n'.join(content) else: source = state_machine.input_lines.source( lineno - state_machine.input_offset - 1) source_dir = os.path.dirname(os.path.abspath(source)) filename = os.path.normpath(os.path.join(source_dir, filename)) filename = utils.relative_path(None, filename) state.document.settings.record_dependencies.add(filename) op = state.document.settings.roast_operation inp = op.open_input(filename) code = inp.read().decode('utf-8') inp.close() if arguments: (syntax,) = arguments else: syntax = 'text' lexer = lexers.get_lexer_by_name(syntax) formatter = formatters.HtmlFormatter() html = highlight( code=code, lexer=lexer, formatter=formatter, ) title_text = options.get('title') if title_text: text_nodes, messages = state.inline_text(title_text, lineno) title = nodes.caption('', '# ', *text_nodes) else: messages = [] title = None fig = nodes.figure('') fig['classes'].append('py-listing') if title is not None: fig += title fig += nodes.raw('', html, format='html') return [fig] + messages
def run(self): """ Restructured text extension for including inline JSAV content on module pages """ self.options["exer_name"] = self.arguments[0] self.options["type"] = self.arguments[1] self.options["odsa_path"] = os.path.relpath(conf.odsa_path, conf.ebook_path) if "required" not in self.options: self.options["required"] = False if "points" not in self.options: self.options["points"] = 0 if "threshold" not in self.options: self.options["threshold"] = 1.0 if "long_name" not in self.options: self.options["long_name"] = self.options["exer_name"] if "align" not in self.options: self.options["align"] = "center" if "output" in self.options and self.options["output"] == "show": self.options["output_code"] = '<p class="jsavoutput jsavline"></p>' else: self.options["output_code"] = "" if self.options["type"] == "dgm": avdgm_node = av_dgm() anchor_node = av_anchor() avdgm_node["exer_name"] = self.options["exer_name"] anchor_node["ids"].append(self.options["exer_name"]) avdgm_node += anchor_node if self.content: node = nodes.Element() # anonymous container for parsing self.state.nested_parse(self.content, self.content_offset, node) first_node = node[0] if isinstance(first_node, nodes.paragraph): caption = nodes.caption(first_node.rawsource, "", *first_node.children) caption["align"] = self.options["align"] avdgm_node += caption return [avdgm_node] else: res = SLIDESHOW % self.options return [nodes.raw("", res, format="html")]
def container_wrapper(directive, literal_node, caption): # type: (Directive, nodes.Node, unicode) -> nodes.container container_node = nodes.container('', literal_block=True, classes=['literal-block-wrapper']) parsed = nodes.Element() directive.state.nested_parse(ViewList([caption], source=''), directive.content_offset, parsed) if isinstance(parsed[0], nodes.system_message): msg = __('Invalid caption: %s' % parsed[0].astext()) raise ValueError(msg) caption_node = nodes.caption(parsed[0].rawsource, '', *parsed[0].children) caption_node.source = literal_node.source caption_node.line = literal_node.line container_node += caption_node container_node += literal_node return container_node
def run(self): name, = self.content source_name, scenario = name.rsplit("-", 1) source = source_name + ".sam" image_reference = directives.uri("_images/" + name + ".png") self.options["uri"] = image_reference source_link = nodes.reference(self.content, source, refuri="examples/" + source) # text = nodes.text('', 'Output from ' + source) image_node = nodes.image("_images/" + name + ".png", **self.options) figure_node = nodes.figure("", image_node) msg = "Output from " if scenario != "baseline": msg += "'" + scenario + "' in " caption = nodes.caption("hi", msg, source_link) figure_node += caption return [figure_node]
def figure(name, arguments, options, content, lineno, content_offset, block_text, state, state_machine): figwidth = options.setdefault("figwidth") figclasses = options.setdefault("figclass") align = options.setdefault("align") del options["figwidth"] del options["figclass"] del options["align"] (image_node,) = image(name, arguments, options, content, lineno, content_offset, block_text, state, state_machine) if isinstance(image_node, nodes.system_message): return [image_node] figure_node = nodes.figure("", image_node) if figwidth == "image": if Image and state.document.settings.file_insertion_enabled: # PIL doesn't like Unicode paths: try: i = Image.open(str(image_node["uri"])) except (IOError, UnicodeError): pass else: state.document.settings.record_dependencies.add(image_node["uri"]) figure_node["width"] = i.size[0] elif figwidth is not None: figure_node["width"] = figwidth if figclasses: figure_node["classes"] += figclasses if align: figure_node["align"] = align if content: node = nodes.Element() # anonymous container for parsing state.nested_parse(content, content_offset, node) first_node = node[0] if isinstance(first_node, nodes.paragraph): caption = nodes.caption(first_node.rawsource, "", *first_node.children) figure_node += caption elif not (isinstance(first_node, nodes.comment) and len(first_node) == 0): error = state_machine.reporter.error( "Figure caption must be a paragraph or empty comment.", nodes.literal_block(block_text, block_text), line=lineno, ) return [figure_node, error] if len(node) > 1: figure_node += nodes.legend("", *node[1:]) return [figure_node]
def process_toctree_list(navtree, bullet_list, caption, app, collapse, maxdepth, shift_toc, root_links, level=1): for list_item in bullet_list.children: if len(list_item) == 1: # Just a link, no sublist continue if len(list_item) != 2: raise NavtreeError("Expected 'list_item' with exactly 2 child nodes, got '%s' with %d: %s" % (list_item.__class__.__name__, len(list_item), list_item.children)) title_node = list_item[0][0] # list_item > paragraph > reference list_node = list_item[1] # list_item > bullet_list if not root_links: # Use the `Text` node instead of the enclosing `reference` object. title_node = title_node[0] if caption: prune_depth = maxdepth.get(title_node.astext(), maxdepth.get(caption.astext(), maxdepth['default'])) else: prune_depth = maxdepth.get(title_node.astext(), maxdepth['default']) app.env._toctree_prune(node = list_item, depth = level if shift_toc else level + 1, maxdepth = prune_depth, collapse = collapse) if shift_toc and level == 1: caption = nodes.caption(title_node.astext(), '', title_node) if root_links: caption['classes'].append('link') navtree += caption navtree += list_node if level < 2: process_toctree_list(navtree, list_node, None, app, collapse, maxdepth, shift_toc, root_links, level=level+1) if not shift_toc and level == 1: if caption: navtree += caption navtree += bullet_list
def run(self): figwidth = self.options.pop('figwidth', None) figclasses = self.options.pop('figclass', None) if self.options.get('caption'): align = self.options.pop('align', None) else: align = None results = super(BlockdiagDirective, self).run() node = results[0] if not isinstance(node, self.node_class): return results try: diagram = self.node2diagram(node) except Exception as e: raise self.warning(str(e)) if 'desctable' in node['options']: results += self.description_tables(diagram) results[0] = self.node2image(node, diagram) self.add_name(results[0]) if node.get('caption'): fig = nodes.figure() fig += results[0] fig += nodes.caption(text=node['caption']) if figwidth == 'image': width = self.get_actual_width(node, diagram) fig['width'] = str(width) + 'px' elif figwidth is not None: fig['width'] = figwidth if figclasses: fig['classes'] += figclasses if align: fig['align'] = align results[0] = fig return results
def test_process_doc_handle_figure_caption(): env = mock.Mock(domaindata={}) figure_node = nodes.figure( '', nodes.caption('caption text', 'caption text'), ) document = mock.Mock( nametypes={'testname': True}, nameids={'testname': 'testid'}, ids={'testid': figure_node}, ) domain = StandardDomain(env) if 'testname' in domain.data['labels']: del domain.data['labels']['testname'] domain.process_doc(env, 'testdoc', document) assert 'testname' in domain.data['labels'] assert domain.data['labels']['testname'] == ( 'testdoc', 'testid', 'caption text')
def run(self): fig = general_figure() self.state.nested_parse(self.content, self.content_offset, fig) if len(fig) < 1 or not isinstance(fig[0],nodes.paragraph): print >>sys.stderr, "ERROR: first paragraph of generalfigure should be present for the caption" return [] first_para = fig[0] cap = nodes.caption() for c in first_para.children: cap.append(c.deepcopy()) txt = cap.astext().strip() if re.match('[Tt]he.*',txt): print >>sys.stderr, "WARNING: Style: Caption '%s' begins with 'The'" % txt fig.remove(first_para) fig.append(cap) return [fig]
def test_process_doc_handle_figure_caption(): env = mock.Mock(domaindata={}) figure_node = nodes.figure( '', nodes.caption('caption text', 'caption text'), ) document = mock.Mock( nametypes={'testname': True}, nameids={'testname': 'testid'}, ids={'testid': figure_node}, citation_refs={}, ) document.traverse.return_value = [] domain = StandardDomain(env) if 'testname' in domain.data['labels']: del domain.data['labels']['testname'] domain.process_doc(env, 'testdoc', document) assert 'testname' in domain.data['labels'] assert domain.data['labels']['testname'] == ( 'testdoc', 'testid', 'caption text')
def test_process_doc_handle_image_parent_figure_caption(): env = mock.Mock(domaindata={}) img_node = nodes.image('', alt='image alt') figure_node = nodes.figure( '', nodes.caption('caption text', 'caption text'), img_node, ) document = mock.Mock( nametypes={'testname': True}, nameids={'testname': 'testid'}, ids={'testid': img_node}, ) domain = StandardDomain(env) if 'testname' in domain.data['labels']: del domain.data['labels']['testname'] domain.process_doc(env, 'testdoc', document) assert 'testname' in domain.data['labels'] assert domain.data['labels']['testname'] == ('testdoc', 'testid', 'caption text')
def container_wrapper(directive, literal_node, caption): # type: (SphinxDirective, nodes.Node, str) -> nodes.container container_node = nodes.container("", literal_block=True, classes=["literal-block-wrapper"]) parsed = nodes.Element() directive.state.nested_parse(StringList([caption], source=""), directive.content_offset, parsed) if isinstance(parsed[0], nodes.system_message): msg = "Invalid caption: %s" % parsed[0].astext() raise ValueError(msg) elif isinstance(parsed[0], nodes.Element): caption_node = nodes.caption(parsed[0].rawsource, "", *parsed[0].children) caption_node.source = literal_node.source caption_node.line = literal_node.line container_node += caption_node container_node += literal_node return container_node else: raise RuntimeError # never reached
def container_wrapper(directive: SphinxDirective, literal_node: Node, caption: str) -> nodes.container: # NOQA container_node = nodes.container('', literal_block=True, classes=['literal-block-wrapper']) parsed = nodes.Element() directive.state.nested_parse(StringList([caption], source=''), directive.content_offset, parsed) if isinstance(parsed[0], nodes.system_message): msg = __('Invalid caption: %s' % parsed[0].astext()) raise ValueError(msg) elif isinstance(parsed[0], nodes.Element): caption_node = nodes.caption(parsed[0].rawsource, '', *parsed[0].children) caption_node.source = literal_node.source caption_node.line = literal_node.line container_node += caption_node container_node += literal_node return container_node else: raise RuntimeError # never reached
def _make_figure_node(self, *children): # Build figure node. if isinstance(children[0], nodes.system_message): return children[:1] figure_node = nodes.figure('', *children) # Pop options. figwidth = self.options.pop('figwidth', None) figclasses = self.options.pop('figclass', None) align = self.options.pop('align', None) # Figure property "figwidth". if figwidth: figure_node['width'] = figwidth # Figure property "figclass". if figclasses: figure_node['classes'] += figclasses # Figure property "align". if align: figure_node['align'] = align # Figure property "caption". Only valid when texpath is used. if self.content: node = nodes.Element() # Anonymous container for parsing. self.state.nested_parse(self.content, self.content_offset, node) first_node = node[0] if isinstance(first_node, nodes.paragraph): caption = nodes.caption(first_node.rawsource, '', *first_node.children) caption.source = first_node.source caption.line = first_node.line figure_node += caption elif not (isinstance(first_node, nodes.comment) and len(first_node) == 0): error = self.state_machine.reporter.error( 'Figure caption must be a paragraph or empty comment.', nodes.literal_block(self.block_text, self.block_text), line=self.lineno) return [figure_node, error] if len(node) > 1: figure_node += nodes.legend('', *node[1:]) return [figure_node]
def run(self): warning = self.state.document.reporter.warning env = self.state.document.settings.env if self.arguments and self.content: return [warning('uml directive cannot have both content and ' 'a filename argument', line=self.lineno)] if self.arguments: fn = search_image_for_language(self.arguments[0], env) relfn, absfn = env.relfn2path(fn) env.note_dependency(relfn) try: umlcode = _read_utf8(absfn) except (IOError, UnicodeDecodeError) as err: return [warning('PlantUML file "%s" cannot be read: %s' % (fn, err), line=self.lineno)] else: relfn = env.doc2path(env.docname, base=None) umlcode = '\n'.join(self.content) node = plantuml(self.block_text, **self.options) node['uml'] = umlcode node['incdir'] = os.path.dirname(relfn) node['filename'] = os.path.split(relfn)[1] # XXX maybe this should be moved to _visit_plantuml functions. it # seems wrong to insert "figure" node by "plantuml" directive. if 'caption' in self.options or 'align' in self.options: node = nodes.figure('', node) if 'align' in self.options: node['align'] = self.options['align'] if 'caption' in self.options: import docutils.statemachine cnode = nodes.Element() # anonymous container for parsing sl = docutils.statemachine.StringList([self.options['caption']], source='') self.state.nested_parse(sl, self.content_offset, cnode) caption = nodes.caption(self.options['caption'], '', *cnode) node += caption return [node]
def run(self): label = self.options.get('label', None) spec = self.options.get('spec', None) caption = self.options.get('caption', None) alt = self.options.get('alt', None) nofig = 'nofig' in self.options figtable_node = figtable('', ids=[label] if label is not None else []) figtable_node['nofig'] = nofig if spec is not None: table_spec_node = addnodes.tabular_col_spec() table_spec_node['spec'] = spec figtable_node.append(table_spec_node) node = nodes.Element() self.state.nested_parse(self.content, self.content_offset, node) tablenode = node[0] if alt is not None: tablenode['alt'] = alt figtable_node.append(tablenode) if caption is not None: # Original SphinxTr source: # caption_node = nodes.caption('', '', nodes.Text(caption)) # figtable_node.append(caption_node) # Modified: Support for parsing content of captions caption_node = nodes.Element() self.state.nested_parse(docutils.statemachine.StringList([caption]), self.content_offset, caption_node) if isinstance(caption_node[0], nodes.paragraph): caption = nodes.caption(caption_node[0].rawsource, '', *caption_node[0].children) figtable_node += caption if label is not None: targetnode = nodes.target('', '', ids=[label]) figtable_node.append(targetnode) return [figtable_node]
def run(self): env = self.state.document.settings.env definitions = env.numbered_blocks_definitions (figure, ) = Figure.run(self) if isinstance(figure, nodes.system_message): return [figure] id = "%s-%d" % (self.name, env.new_serialno(self.name)) figure['ids'] = [id] figure['classes'].append('numbered-block') figure['type'] = self.name numbered = definitions[ self.name]['numbered'] and 'nonumber' not in self.options figure['numbered'] = numbered # Move name from image to figure and register as target image = figure.children[figure.first_child_matching_class(nodes.image)] if len(image['names']) > 0: name = image['names'][0] figure['names'] = [name] if name in self.state.document.nameids: del (self.state.document.nameids[name]) self.state.document.note_explicit_target(figure) # Prepare caption caption_pos = figure.first_child_matching_class(nodes.caption) if numbered: if not caption_pos: figure.append(nodes.caption('', '')) else: caption = figure.children[caption_pos] caption.insert( 0, nodes.inline('', definitions[self.name]['title-separator'], classes=['separator'])) return [figure]
def container_wrapper(directive: SphinxDirective, literal_node: Node, caption: str) -> nodes.container: """We need the container to have class highlight.""" container_node = nodes.container("", literal_block=True, language=literal_node["language"], classes=["highlight"]) parsed = nodes.Element() directive.state.nested_parse(StringList([caption], source=""), directive.content_offset, parsed) if isinstance(parsed[0], nodes.system_message): msg = __("Invalid caption: %s" % parsed[0].astext()) raise ValueError(msg) elif isinstance(parsed[0], nodes.Element): caption_node = nodes.caption(parsed[0].rawsource, "", *parsed[0].children) caption_node.source = literal_node.source caption_node.line = literal_node.line container_node += caption_node container_node += literal_node return container_node else: raise RuntimeError # never reached
def generate_collapsible_classlist(app, fromdocname, classes, container, caption, module_index): entries = defaultdict(list) prefix = ".".join(classes[0][0].split(".")[:module_index]) + "." for e in classes: module = e[0].split(".")[module_index] entries[module].append(e) #print("t", fromdocname) toc = nodes.bullet_list() toc += nodes.caption(caption, '', *[nodes.Text(caption)]) for module, class_list in entries.items(): #print("t2", "src." + prefix + module) ref = nodes.reference('', '') ref['refuri'] = app.builder.get_relative_uri(fromdocname, prefix + module) ref.append(nodes.Text(module.capitalize())) module_item = nodes.list_item('', addnodes.compact_paragraph('', '', ref), classes=["toctree-l1"]) if fromdocname.startswith(prefix + module): module_item["classes"].append('current') toc += module_item subtree = nodes.bullet_list() module_item += subtree for e in class_list: ref = nodes.reference('', '') ref['refdocname'] = e[3] ref['refuri'] = app.builder.get_relative_uri(fromdocname, e[3]) ref['refuri'] += '#' + e[4] ref.append(nodes.Text(e[0].split(".")[-1])) class_item = nodes.list_item('', addnodes.compact_paragraph('', '', ref), classes=["toctree-l2"]) if fromdocname.startswith(e[3]): class_item['classes'].append('current') subtree += class_item container += toc
def run(self): if (self.options.get('type') == 'table'): content_node = table_container('') elif (self.options.get('type') == 'figure'): content_node = figure_container('') elif (self.options.get('type') == 'code'): content_node = code_container('') else: content_node = float_container('') self.state.nested_parse(self.content, self.content_offset, content_node) set_source_info(self, content_node) self.add_name(content_node) caption = self.options.get('caption') if caption: parsed = nodes.Element() self.state.nested_parse(ViewList([caption], source=''), self.content_offset, parsed) caption_node = nodes.caption(parsed[0].rawsource, '', *parsed[0].children) caption_node.source = parsed[0].source caption_node.line = parsed[0].line if ('caption-top' in self.options): content_node.insert(0, caption_node) else: content_node += caption_node return [content_node]
def run(self): """ Executes python code for an RST document, taking input from content or from a filename :return: """ language = self.options.get('language', 'python') output_language = self.options.get('output_language', 'none') output = [] shown_code = '' executed_code = '' hide = False skip = False for line in self.content: line_switch = line.replace(' ', '').lower() if line_switch == '#hide': hide = not hide continue if line_switch == '#skip': skip = not skip continue if not hide: shown_code += line + '\n' if not skip: executed_code += line + '\n' shown_code = shown_code.strip() executed_code = executed_code.strip() # Show the example code if 'hide_code' not in self.options: input_code = nodes.literal_block(shown_code, shown_code) input_code['language'] = language input_code['linenos'] = 'linenos' in self.options if 'header_code' in self.options: output.append(nodes.caption(text=self.options['header_code'])) output.append(input_code) # Show the code results if 'header_output' in self.options: output.append(nodes.caption(text=self.options['header_output'])) try: code_results = execute_code(executed_code, ignore_stderr='ignore_stderr' in self.options) except CodeException as e: # Newline so we don't have the build message mixed up with logs print('\n') code_lines = executed_code.splitlines() # If we don't get the line we print everything if e.line is None: e.line = len(code_lines) for i in range(max(0, e.line - 8), e.line - 1): log.error(f' {code_lines[i]}') log.error(f' {code_lines[e.line - 1]} <--') log.error('') for line in e.err.splitlines(): log.error(line) raise ExtensionError('Could not execute code!') from None if 'ignore_stderr' not in self.options: for out in code_results.split('\n'): if 'Error in ' in out: log.error(f'Possible Error in codeblock: {out}') code_results = nodes.literal_block(code_results, code_results) code_results['linenos'] = 'linenos' in self.options code_results['language'] = output_language if 'hide_output' not in self.options: output.append(code_results) return output
def process_needgantt(app, doctree, fromdocname): # Replace all needgantt nodes with a list of the collected needs. env = app.builder.env link_types = env.config.needs_extra_links allowed_link_types_options = [ link.upper() for link in env.config.needs_flow_link_types ] # NEEDGANTT for node in doctree.traverse(Needgantt): if not app.config.needs_include_needs: # Ok, this is really dirty. # If we replace a node, docutils checks, if it will not lose any attributes. # But this is here the case, because we are using the attribute "ids" of a node. # However, I do not understand, why losing an attribute is such a big deal, so we delete everything # before docutils claims about it. for att in ('ids', 'names', 'classes', 'dupnames'): node[att] = [] node.replace_self([]) continue id = node.attributes["ids"][0] current_needgantt = env.need_all_needgantts[id] all_needs_dict = env.needs_all_needs content = [] try: if "sphinxcontrib.plantuml" not in app.config.extensions: raise ImportError from sphinxcontrib.plantuml import plantuml except ImportError: no_plantuml(node) continue plantuml_block_text = ".. plantuml::\n" \ "\n" \ " @startuml" \ " @enduml" puml_node = plantuml(plantuml_block_text, **dict()) puml_node["uml"] = "@startuml\n" puml_connections = "" # Adding config config = current_needgantt['config'] puml_node["uml"] += add_config(config) all_needs = list(all_needs_dict.values()) found_needs = procces_filters(all_needs, current_needgantt) # Scale/timeline handling if current_needgantt['timeline'] is not None and current_needgantt[ 'timeline'] != '': puml_node["uml"] += 'printscale {}\n'.format( current_needgantt["timeline"]) # Project start date handling start_date_string = current_needgantt['start_date'] start_date_plantuml = None if start_date_string is not None and start_date_string != '': try: start_date = datetime.strptime(start_date_string, '%Y-%m-%d') # start_date = datetime.fromisoformat(start_date_string) # > py3.7 only except Exception: raise NeedGanttException( 'start_date "{}"for needgantt is invalid. ' 'File: {}:current_needgantt["lineno"]'.format( start_date_string, current_needgantt["docname"])) month = MONTH_NAMES[int(start_date.strftime("%-m"))] start_date_plantuml = start_date.strftime( "%dth of {} %Y".format(month)) if start_date_plantuml is not None: puml_node["uml"] += 'Project starts the {}\n'.format( start_date_plantuml) # Element handling puml_node["uml"] += '\n\' Elements definition \n\n' el_link_string = '' el_completion_string = '' el_color_string = '' for need in found_needs: complete = None if current_needgantt[ 'milestone_filter'] is None or current_needgantt[ 'milestone_filter'] == '': is_milestone = False else: is_milestone = filter_single_need( need, current_needgantt['milestone_filter']) if current_needgantt['milestone_filter'] is None or current_needgantt['milestone_filter'] == '' or \ not is_milestone: # Normal gantt element handling duration_option = current_needgantt['duration_option'] duration = need[duration_option] complete_option = current_needgantt['completion_option'] complete = need[complete_option] if duration is None or duration == '' or not duration.isdigit( ): logger.warning( 'Duration not set or invalid for needgantt chart. ' 'Need: {}. Duration: {}'.format(need["id"], duration)) duration = 1 gantt_element = '[{}] as [{}] lasts {} days\n'.format( need["title"], need["id"], duration) else: gantt_element = '[{}] as [{}] lasts 0 days\n'.format( need["title"], need["id"]) el_link_string += '[{}] links to [[{}]]\n'.format( need["title"], calculate_link(app, need)) if complete is not None and complete != '': complete = complete.replace('%', '') el_completion_string += '[{}] is {}% completed\n'.format( need["title"], complete) el_color_string += '[{}] is colored in {}\n'.format( need["title"], need["type_color"]) puml_node["uml"] += gantt_element puml_node["uml"] += '\n\' Element links definition \n\n' puml_node[ "uml"] += '\n\' Deactivated, as currently supported by plantuml beta only' # ToDo: Activate if linking is working with default plantuml # puml_node["uml"] += el_link_string + '\n' puml_node["uml"] += '\n\' Element completion definition \n\n' puml_node["uml"] += el_completion_string + '\n' puml_node["uml"] += '\n\' Element color definition \n\n' if current_needgantt['no_color']: puml_node["uml"] += '\' Color support deactivated via flag' else: puml_node["uml"] += el_color_string + '\n' # Constrain handling puml_node["uml"] += '\n\' Constraints definition \n\n' puml_node["uml"] += '\n\' Constraints definition \n\n' for need in found_needs: if current_needgantt[ 'milestone_filter'] is None or current_needgantt[ 'milestone_filter'] == '': is_milestone = False else: is_milestone = filter_single_need( need, current_needgantt['milestone_filter']) constrain_types = [ 'starts_with_links', 'starts_after_links', 'ends_with_links' ] for con_type in constrain_types: if is_milestone: keyword = 'happens' elif con_type in ['starts_with_links', 'starts_after_links']: keyword = 'starts' else: keyword = 'ends' if con_type in ['starts_after_links', 'ends_with_links']: start_end_sync = 'end' else: start_end_sync = 'start' for link_type in current_needgantt[con_type]: start_with_links = need[link_type] for start_with_link in start_with_links: start_need = all_needs_dict[start_with_link] gantt_constraint = '[{}] {} at [{}]\'s ' \ '{}\n'.format(need["id"], keyword, start_need["id"], start_end_sync) puml_node["uml"] += gantt_constraint # Create a legend if current_needgantt["show_legend"]: puml_node["uml"] += '\n\n\' Legend definition \n\n' puml_node["uml"] += "legend\n" puml_node["uml"] += "|= Color |= Type |\n" for need in app.config.needs_types: puml_node[ "uml"] += "|<back:{color}> {color} </back>| {name} |\n".format( color=need["color"], name=need["title"]) puml_node["uml"] += "endlegend\n" puml_node["uml"] += "\n@enduml" puml_node["incdir"] = os.path.dirname(current_needgantt["docname"]) puml_node["filename"] = os.path.split( current_needgantt["docname"])[1] # Needed for plantuml >= 0.9 scale = int(current_needgantt['scale']) # if scale != 100: puml_node['scale'] = scale puml_node = nodes.figure('', puml_node) if current_needgantt['align'] is not None and len( current_needgantt['align']) != '': puml_node['align'] = current_needgantt['align'] else: puml_node['align'] = 'center' if current_needgantt['caption'] is not None and len( current_needgantt['caption']) != '': # Make the caption to a link to the original file. try: if "SVG" in app.config.plantuml_output_format.upper(): file_ext = 'svg' else: file_ext = 'png' except Exception: file_ext = 'png' gen_flow_link = generate_name(app, puml_node.children[0], file_ext) current_file_parts = fromdocname.split('/') subfolder_amount = len(current_file_parts) - 1 img_locaton = '../' * subfolder_amount + '_images/' + gen_flow_link[ 0].split('/')[-1] flow_ref = nodes.reference('t', current_needgantt['caption'], refuri=img_locaton) puml_node += nodes.caption('', '', flow_ref) content.append(puml_node) if len(content) == 0: nothing_found = "No needs passed the filters" para = nodes.paragraph() nothing_found_node = nodes.Text(nothing_found, nothing_found) para += nothing_found_node content.append(para) if current_needgantt["show_filters"]: content.append(get_filter_para(current_needgantt)) if current_needgantt['debug']: content += get_debug_containter(puml_node) puml_node['class'] = ['needgantt'] node.replace_self(content)
def run(self): config = self.state.document.settings.env.config # Read enabled builders; Defaults to None if self.arguments: chosen_builders = choose_builders(self.arguments) else: chosen_builders = [] # Enable 'http' language for http part self.arguments = ['http'] # Optionally, read directive content from 'request' option cwd = os.path.dirname(self.state.document.current_source) if 'request' in self.options: request = utils.resolve_path(self.options['request'], cwd) with open(request) as fp: self.content = StringList( list(map(str.rstrip, fp.readlines())), request) # Wrap and render main directive as 'http-example-http' klass = 'http-example-http' container = nodes.container('', classes=[klass]) container.append(nodes.caption('', 'http')) container.extend(super(HTTPExample, self).run()) # Init result node list result = [container] # Append builder responses for name in chosen_builders: raw = ('\r\n'.join(self.content)).encode('utf-8') request = parsers.parse_request(raw, config.httpexample_scheme) builder_, language = AVAILABLE_BUILDERS[name] command = builder_(request) content = StringList([command], self.content.source(0)) options = self.options.copy() options.pop('name', None) options.pop('caption', None) block = CodeBlock('code-block', [language], options, content, self.lineno, self.content_offset, self.block_text, self.state, self.state_machine) # Wrap and render main directive as 'http-example-{name}' klass = 'http-example-{}'.format(name) container = nodes.container('', classes=[klass]) container.append(nodes.caption('', name)) container.extend(block.run()) # Append to result nodes result.append(container) # Append optional response if 'response' in self.options: response = utils.resolve_path(self.options['response'], cwd) with open(response) as fp: content = StringList(list(map(str.rstrip, fp.readlines())), response) options = self.options.copy() options.pop('name', None) options.pop('caption', None) block = CodeBlock('code-block', ['http'], options, content, self.lineno, self.content_offset, self.block_text, self.state, self.state_machine) # Wrap and render main directive as 'http-example-response' klass = 'http-example-response' container = nodes.container('', classes=[klass]) container.append(nodes.caption('', 'response')) container.extend(block.run()) # Append to result nodes result.append(container) # Final wrap container_node = nodes.container('', classes=['http-example']) container_node.extend(result) return [container_node]
def process_needsequence(app, doctree, fromdocname): # Replace all needsequence nodes with a list of the collected needs. env = app.builder.env link_types = env.config.needs_extra_links # allowed_link_types_options = [link.upper() for link in env.config.needs_flow_link_types] # NEEDSEQUENCE for node in doctree.traverse(Needsequence): if not app.config.needs_include_needs: # Ok, this is really dirty. # If we replace a node, docutils checks, if it will not lose any attributes. # But this is here the case, because we are using the attribute "ids" of a node. # However, I do not understand, why losing an attribute is such a big deal, so we delete everything # before docutils claims about it. for att in ("ids", "names", "classes", "dupnames"): node[att] = [] node.replace_self([]) continue id = node.attributes["ids"][0] current_needsequence = env.need_all_needsequences[id] all_needs_dict = env.needs_all_needs option_link_types = [ link.upper() for link in current_needsequence["link_types"] ] for lt in option_link_types: if lt not in [link["option"].upper() for link in link_types]: logger.warning( "Unknown link type {link_type} in needsequence {flow}. Allowed values:" " {link_types}".format( link_type=lt, flow=current_needsequence["target_node"], link_types=",".join(link_types))) content = [] try: if "sphinxcontrib.plantuml" not in app.config.extensions: raise ImportError from sphinxcontrib.plantuml import plantuml except ImportError: no_plantuml(node) continue plantuml_block_text = ".. plantuml::\n" "\n" " @startuml" " @enduml" puml_node = plantuml(plantuml_block_text) puml_node["uml"] = "@startuml\n" # Adding config config = current_needsequence["config"] puml_node["uml"] += add_config(config) # all_needs = list(all_needs_dict.values()) start_needs_id = [ x.strip() for x in re.split(";|,", current_needsequence["start"]) ] if len(start_needs_id) == 0: raise NeedsequenceDirective("No start-id set for needsequence" " File {}" ":{}".format( {current_needsequence["docname"]}, current_needsequence["lineno"])) puml_node["uml"] += "\n' Nodes definition \n\n" # Add start participants p_string = "" c_string = "" for need_id in start_needs_id: try: need = all_needs_dict[need_id.strip()] except KeyError: raise NeedSequenceException( "Given {} in needsequence unknown." " File {}" ":{}".format(need_id, current_needsequence["docname"], current_needsequence["lineno"])) # Add children of participants _msg_receiver_needs, p_string_new, c_string_new = get_message_needs( app, need, current_needsequence["link_types"], all_needs_dict, filter=current_needsequence["filter"]) p_string += p_string_new c_string += c_string_new puml_node["uml"] += p_string puml_node["uml"] += "\n' Connection definition \n\n" puml_node["uml"] += c_string # Create a legend if current_needsequence["show_legend"]: puml_node["uml"] += create_legend(app.config.needs_types) puml_node["uml"] += "\n@enduml" puml_node["incdir"] = os.path.dirname(current_needsequence["docname"]) puml_node["filename"] = os.path.split( current_needsequence["docname"])[1] # Needed for plantuml >= 0.9 scale = int(current_needsequence["scale"]) # if scale != 100: puml_node["scale"] = scale puml_node = nodes.figure("", puml_node) if current_needsequence["align"]: puml_node["align"] = current_needsequence["align"] else: puml_node["align"] = "center" if current_needsequence["caption"]: # Make the caption to a link to the original file. try: if "SVG" in app.config.plantuml_output_format.upper(): file_ext = "svg" else: file_ext = "png" except Exception: file_ext = "png" gen_flow_link = generate_name(app, puml_node.children[0], file_ext) current_file_parts = fromdocname.split("/") subfolder_amount = len(current_file_parts) - 1 img_locaton = "../" * subfolder_amount + "_images/" + gen_flow_link[ 0].split("/")[-1] flow_ref = nodes.reference("t", current_needsequence["caption"], refuri=img_locaton) puml_node += nodes.caption("", "", flow_ref) # Add lineno to node puml_node.line = current_needsequence["lineno"] content.append(puml_node) if len(content) == 0: nothing_found = "No needs passed the filters" para = nodes.paragraph() nothing_found_node = nodes.Text(nothing_found, nothing_found) para += nothing_found_node content.append(para) if current_needsequence["show_filters"]: content.append(get_filter_para(current_needsequence)) if current_needsequence["debug"]: content += get_debug_container(puml_node) node.replace_self(content)
def run(self): figure_node = my_figure() figure_node += nodes.image(uri=self.arguments[0]) figure_node += nodes.caption(text=''.join(self.content)) return [figure_node]
def run(self): config = self.state.document.settings.env.config # Read enabled builders; Defaults to None if self.arguments: chosen_builders = choose_builders(self.arguments) else: chosen_builders = [] # Enable 'http' language for http part self.arguments = ['http'] # split the request and optional response in the content. # The separator is two empty lines followed by a line starting with # 'HTTP/' request_content = StringList() response_content = None emptylines_count = 0 in_response = False for i, line in enumerate(self.content): source = self.content.source(i) if in_response: response_content.append(line, source) else: if emptylines_count >= 2 and line.startswith('HTTP/'): in_response = True response_content = StringList() response_content.append(line, source) elif line == '': emptylines_count += 1 else: request_content.extend( StringList([''] * emptylines_count, source)) emptylines_count = 0 request_content.append(line, source) # Load optional external request cwd = os.path.dirname(self.state.document.current_source) if 'request' in self.options: request = utils.resolve_path(self.options['request'], cwd) with open(request) as fp: request_content = StringList( list(map(str.rstrip, fp.readlines())), request) # Load optional external response if 'response' in self.options: response = utils.resolve_path(self.options['response'], cwd) with open(response) as fp: response_content = StringList( list(map(str.rstrip, fp.readlines())), response) # reset the content to just the request self.content = request_content # Wrap and render main directive as 'http-example-http' klass = 'http-example-http' container = nodes.container('', classes=[klass]) container.append(nodes.caption('', 'http')) container.extend(super(HTTPExample, self).run()) # Init result node list result = [container] # Append builder responses for name in chosen_builders: raw = ('\r\n'.join(request_content)).encode('utf-8') request = parsers.parse_request(raw, config.httpexmpl_scheme) builder_, language = AVAILABLE_BUILDERS[name] command = builder_(request) content = StringList([command], request_content.source(0)) options = self.options.copy() options.pop('name', None) options.pop('caption', None) block = CodeBlock( 'code-block', [language], options, content, self.lineno, self.content_offset, self.block_text, self.state, self.state_machine ) # Wrap and render main directive as 'http-example-{name}' klass = 'http-example-{}'.format(name) container = nodes.container('', classes=[klass]) container.append(nodes.caption('', name)) container.extend(block.run()) # Append to result nodes result.append(container) # Append optional response if response_content: options = self.options.copy() options.pop('name', None) options.pop('caption', None) block = CodeBlock( 'code-block', ['http'], options, response_content, self.lineno, self.content_offset, self.block_text, self.state, self.state_machine ) # Wrap and render main directive as 'http-example-response' klass = 'http-example-response' container = nodes.container('', classes=[klass]) container.append(nodes.caption('', 'response')) container.extend(block.run()) # Append to result nodes result.append(container) # Final wrap container_node = nodes.container('', classes=['http-example']) container_node.extend(result) return [container_node]
def process_needgantt(app, doctree, fromdocname): # Replace all needgantt nodes with a list of the collected needs. env = app.builder.env # link_types = env.config.needs_extra_links # allowed_link_types_options = [link.upper() for link in env.config.needs_flow_link_types] # NEEDGANTT for node in doctree.traverse(Needgantt): if not app.config.needs_include_needs: # Ok, this is really dirty. # If we replace a node, docutils checks, if it will not lose any attributes. # But this is here the case, because we are using the attribute "ids" of a node. # However, I do not understand, why losing an attribute is such a big deal, so we delete everything # before docutils claims about it. for att in ("ids", "names", "classes", "dupnames"): node[att] = [] node.replace_self([]) continue id = node.attributes["ids"][0] current_needgantt = env.need_all_needgantts[id] all_needs_dict = env.needs_all_needs content = [] try: if "sphinxcontrib.plantuml" not in app.config.extensions: raise ImportError from sphinxcontrib.plantuml import plantuml except ImportError: no_plantuml(node) continue plantuml_block_text = ".. plantuml::\n" "\n" " @startuml" " @enduml" puml_node = plantuml(plantuml_block_text) puml_node["uml"] = "@startuml\n" # Adding config config = current_needgantt["config"] puml_node["uml"] += add_config(config) all_needs = list(all_needs_dict.values()) found_needs = process_filters(app, all_needs, current_needgantt) # Scale/timeline handling if current_needgantt["timeline"]: puml_node["uml"] += "printscale {}\n".format( current_needgantt["timeline"]) # Project start date handling start_date_string = current_needgantt["start_date"] start_date_plantuml = None if start_date_string: try: start_date = datetime.strptime(start_date_string, "%Y-%m-%d") # start_date = datetime.fromisoformat(start_date_string) # > py3.7 only except Exception: raise NeedGanttException( 'start_date "{}"for needgantt is invalid. ' 'File: {}:current_needgantt["lineno"]'.format( start_date_string, current_needgantt["docname"])) month = MONTH_NAMES[int(start_date.strftime("%m"))] start_date_plantuml = start_date.strftime(f"%dth of {month} %Y") if start_date_plantuml: puml_node["uml"] += f"Project starts the {start_date_plantuml}\n" # Element handling puml_node["uml"] += "\n' Elements definition \n\n" el_link_string = "" el_completion_string = "" el_color_string = "" for need in found_needs: complete = None if current_needgantt["milestone_filter"]: is_milestone = filter_single_need( app, need, current_needgantt["milestone_filter"]) else: is_milestone = False if current_needgantt["milestone_filter"] and is_milestone: gantt_element = "[{}] as [{}] lasts 0 days\n".format( need["title"], need["id"]) else: # Normal gantt element handling duration_option = current_needgantt["duration_option"] duration = need[duration_option] complete_option = current_needgantt["completion_option"] complete = need[complete_option] if not (duration and duration.isdigit()): logger.warning( "Duration not set or invalid for needgantt chart. " "Need: {}. Duration: {}".format(need["id"], duration)) duration = 1 gantt_element = "[{}] as [{}] lasts {} days\n".format( need["title"], need["id"], duration) el_link_string += "[{}] links to [[{}]]\n".format( need["title"], calculate_link(app, need, fromdocname)) if complete: complete = complete.replace("%", "") el_completion_string += "[{}] is {}% completed\n".format( need["title"], complete) el_color_string += "[{}] is colored in {}\n".format( need["title"], need["type_color"]) puml_node["uml"] += gantt_element puml_node["uml"] += "\n' Element links definition \n\n" puml_node[ "uml"] += "\n' Deactivated, as currently supported by plantuml beta only" # ToDo: Activate if linking is working with default plantuml # puml_node["uml"] += el_link_string + '\n' puml_node["uml"] += "\n' Element completion definition \n\n" puml_node["uml"] += el_completion_string + "\n" puml_node["uml"] += "\n' Element color definition \n\n" if current_needgantt["no_color"]: puml_node["uml"] += "' Color support deactivated via flag" else: puml_node["uml"] += el_color_string + "\n" # Constrain handling puml_node["uml"] += "\n' Constraints definition \n\n" puml_node["uml"] += "\n' Constraints definition \n\n" for need in found_needs: if current_needgantt["milestone_filter"]: is_milestone = filter_single_need( app, need, current_needgantt["milestone_filter"]) else: is_milestone = False constrain_types = [ "starts_with_links", "starts_after_links", "ends_with_links" ] for con_type in constrain_types: if is_milestone: keyword = "happens" elif con_type in ["starts_with_links", "starts_after_links"]: keyword = "starts" else: keyword = "ends" if con_type in ["starts_after_links", "ends_with_links"]: start_end_sync = "end" else: start_end_sync = "start" for link_type in current_needgantt[con_type]: start_with_links = need[link_type] for start_with_link in start_with_links: start_need = all_needs_dict[start_with_link] gantt_constraint = "[{}] {} at [{}]'s " "{}\n".format( need["id"], keyword, start_need["id"], start_end_sync) puml_node["uml"] += gantt_constraint # Create a legend if current_needgantt["show_legend"]: puml_node["uml"] += create_legend(app.config.needs_types) puml_node["uml"] += "\n@enduml" puml_node["incdir"] = os.path.dirname(current_needgantt["docname"]) puml_node["filename"] = os.path.split( current_needgantt["docname"])[1] # Needed for plantuml >= 0.9 scale = int(current_needgantt["scale"]) # if scale != 100: puml_node["scale"] = scale puml_node = nodes.figure("", puml_node) puml_node["align"] = current_needgantt["align"] or "center" if current_needgantt["caption"]: # Make the caption to a link to the original file. try: if "SVG" in app.config.plantuml_output_format.upper(): file_ext = "svg" else: file_ext = "png" except Exception: file_ext = "png" gen_flow_link = generate_name(app, puml_node.children[0], file_ext) current_file_parts = fromdocname.split("/") subfolder_amount = len(current_file_parts) - 1 img_location = "../" * subfolder_amount + "_images/" + gen_flow_link[ 0].split("/")[-1] flow_ref = nodes.reference("t", current_needgantt["caption"], refuri=img_location) puml_node += nodes.caption("", "", flow_ref) # Add lineno to node puml_node.line = current_needgantt["lineno"] content.append(puml_node) if len(content) == 0: nothing_found = "No needs passed the filters" para = nodes.paragraph() nothing_found_node = nodes.Text(nothing_found, nothing_found) para += nothing_found_node content.append(para) if current_needgantt["show_filters"]: content.append(get_filter_para(current_needgantt)) if current_needgantt["debug"]: content += get_debug_container(puml_node) puml_node["class"] = ["needgantt"] node.replace_self(content)
def latex(self, widget: Widget, code_block_info : CodeBlockInfo): """Performs Latex parsing on nodes Used to create the PDF builds of the site. Args: widget (Widget): The current working widget Returns: List[nodes]: Returns a list of Latex nodes """ nodes_latex = [] for f in widget.files: # Based on sphinx/directives/code.py container_node = nodes.container( '', literal_block=True, classes=['literal-block-wrapper']) literal = nodes.literal_block('', f.content, format='latex') literal['language'] = self.arguments[0].split(' ')[0] literal['linenos'] = True literal['source'] = f.basename caption = nodes.caption('', f.basename) caption.source = literal.source caption.line = literal.line container_node += caption container_node += literal nodes_latex.append(container_node) def get_info_preamble(info_type : str) -> str: known_info_type : Dict[str, str] = { 'build' : '\\textbf{Build output}', 'run' : '\\textbf{Runtime output}', 'compile' : '\\textbf{Compilation output}', 'prove' : '\\textbf{Prover output}' } if info_type in known_info_type: return known_info_type[info_type] else: return "Let's " + info_type + " the example:" block_info : Dict[str, str] = code_block_info.get_info() for info_type in sorted(block_info): if block_info[info_type] == "": # Do not show empty boxes continue preamble_node = nodes.container( '', literal_block=False, classes=[]) preamble_raw = nodes.raw('', get_info_preamble(info_type), format='latex') preamble_node += preamble_raw container_node = nodes.container( '', literal_block=True, classes=['literal-block-wrapper']) literal = nodes.literal_block('', block_info[info_type], format='latex') literal['language'] = 'none' literal['source'] = info_type caption = nodes.caption('', info_type) caption.source = literal.source caption.line = literal.line # container_node += caption container_node += literal nodes_latex.append(preamble_node) nodes_latex.append(container_node) return nodes_latex
def run(self): """ Parse a plotly chart directive """ self.assert_has_content() env = self.state.document.settings.env # Ensure the current chart ID is initialised in the environment if "next_chart_id" not in env.temp_data: env.temp_data["next_chart_id"] = 0 # Get the ID of this chart id = env.temp_data["next_chart_id"] # Handle the src and destination URI of the *.json asset uri = directives.uri(self.arguments[0].strip()) src_uri = os.path.join(env.app.builder.srcdir, uri) # Where to get the asset during build build_uri = os.path.join(env.app.builder.outdir, "_charts", uri) # Where to put the asset during build page_uri = os.path.join( env.app.builder.outdir, env.docname) # The location of the current page relative_uri = relative_path( page_uri, build_uri) # Ultimate path of the asset relative to current page # Create the main node container and store the URI of the file which will be collected later node = nodes.container(id="ffs") node["classes"] = ["sphinx-charts"] # Increment the ID counter ready for the next chart env.temp_data["next_chart_id"] += 1 # Only if its a supported builder do we proceed (otherwise return an empty node) if env.app.builder.name in get_compatible_builders(env.app): # Make the directories and copy file (if file has changed) ensuredir(os.path.dirname(build_uri)) copyfile(src_uri, build_uri) download_name = self.options.pop("download_name", DEFAULT_DOWNLOAD_NAME) chart_node = nodes.container() chart_node["classes"] = [ "sphinx-charts-chart", f"sphinx-charts-chart-id-{id}", f"sphinx-charts-chart-uri-{relative_uri}", f"sphinx-charts-chart-dn-{download_name}", ] chart_node.replace_attr("ids", [f"sphinx-charts-chart-id-{id}"]) # This is a botch. Purely here to force inclusion of mathjax on pages with charts but without latex in the # document, because it doesn't find the $$ in the chart data (which isn't part of the document tree) math_node = nodes.paragraph("$$", "") math_node["classes"] = ["sphinx-charts-hidden"] placeholder_node = nodes.container() placeholder_node["classes"] = [ "sphinx-charts-placeholder", f"sphinx-charts-placeholder-{id}" ] placeholder_node += nodes.caption("", "Loading...") node += chart_node node += math_node node += placeholder_node # Add optional chart caption and legend (as per figure directive) if self.content: caption_node = nodes.Element( ) # Anonymous container for parsing self.state.nested_parse(self.content, self.content_offset, caption_node) first_node = caption_node[0] if isinstance(first_node, nodes.paragraph): caption = nodes.caption(first_node.rawsource, "", *first_node.children) caption.source = first_node.source caption.line = first_node.line node += caption elif not (isinstance(first_node, nodes.comment) and len(first_node) == 0): error = self.state_machine.reporter.error( "Chart caption must be a paragraph or empty comment.", nodes.literal_block(self.block_text, self.block_text), line=self.lineno, ) return [node, error] if len(caption_node) > 1: node += nodes.legend("", *caption_node[1:]) return [node]
def run(self): """ Executes python code for an RST document, taking input from content or from a filename :return: """ language = self.options.get('language', 'python') output_language = self.options.get('output_language', 'none') output = [] shown_code = '' executed_code = '' hide = False skip = False for line in self.content: line_switch = line.replace(' ', '').lower() if line_switch == '#hide': hide = not hide continue if line_switch == '#skip': skip = not skip continue if not hide: shown_code += line + '\n' if not skip: executed_code += line + '\n' shown_code = shown_code.strip() # Show the example code if 'hide_code' not in self.options: input_code = nodes.literal_block(shown_code, shown_code) input_code['language'] = language input_code['linenos'] = 'linenos' in self.options if 'header_code' in self.options: output.append(nodes.caption(text=self.options['header_code'])) output.append(input_code) # Show the code results if 'header_output' in self.options: output.append(nodes.caption(text=self.options['header_output'])) code_results = execute_code(executed_code, ignore_stderr='ignore_stderr' in self.options) if 'ignore_stderr' not in self.options: for out in code_results.split('\n'): if 'Error in ' in out: log.error(f'Possible Error in codeblock: {out}') code_results = nodes.literal_block(code_results, code_results) code_results['linenos'] = 'linenos' in self.options code_results['language'] = output_language if 'hide_output' not in self.options: output.append(code_results) return output
def run(self) -> List[nodes.Node]: figwidth = self.options.pop("width", None) figclasses = self.options.pop("class", None) align = self.options.pop("align", None) # parser = default_parser(self.env.myst_config) node = nodes.Element() # TODO test that we are using myst parser # ensure html image enabled enable_html_img = self.state._renderer.config.get("enable_html_img", False) try: self.state._renderer.config["enable_html_img"] = True self.state.nested_parse(self.content, self.content_offset, node) finally: self.state._renderer.config["enable_html_img"] = enable_html_img if not len(node.children) == 2: return [ self.figure_error( "content should be one image, " "followed by a single paragraph caption" ) ] image_node, caption_para = node.children if isinstance(image_node, nodes.paragraph): image_node = image_node[0] if not isinstance(image_node, nodes.image): return [ self.figure_error( "content should be one image (not found), " "followed by single paragraph caption" ) ] if not isinstance(caption_para, nodes.paragraph): return [ self.figure_error( "content should be one image, " "followed by single paragraph caption (not found)" ) ] caption_node = nodes.caption(caption_para.rawsource, "", *caption_para.children) caption_node.source = caption_para.source caption_node.line = caption_para.line figure_node = nodes.figure("", image_node, caption_node) self.set_source_info(figure_node) if figwidth is not None: figure_node["width"] = figwidth if figclasses: figure_node["classes"] += figclasses if align: figure_node["align"] = align if self.arguments: self.options["name"] = self.arguments[0] self.add_name(figure_node) return [figure_node]
def process_needflow(app, doctree, fromdocname): # Replace all needflow nodes with a list of the collected needs. # Augment each need with a backlink to the original location. env = app.builder.env link_types = env.config.needs_extra_links allowed_link_types_options = [link.upper() for link in env.config.needs_flow_link_types] # NEEDFLOW for node in doctree.traverse(Needflow): if not app.config.needs_include_needs: # Ok, this is really dirty. # If we replace a node, docutils checks, if it will not lose any attributes. # But this is here the case, because we are using the attribute "ids" of a node. # However, I do not understand, why losing an attribute is such a big deal, so we delete everything # before docutils claims about it. for att in ("ids", "names", "classes", "dupnames"): node[att] = [] node.replace_self([]) continue id = node.attributes["ids"][0] current_needflow = env.need_all_needflows[id] all_needs = env.needs_all_needs option_link_types = [link.upper() for link in current_needflow["link_types"]] for lt in option_link_types: if lt not in [link["option"].upper() for link in link_types]: logger.warning( "Unknown link type {link_type} in needflow {flow}. Allowed values: {link_types}".format( link_type=lt, flow=current_needflow["target_node"], link_types=",".join(link_types) ) ) content = [] try: if "sphinxcontrib.plantuml" not in app.config.extensions: raise ImportError from sphinxcontrib.plantuml import plantuml except ImportError: content = nodes.error() para = nodes.paragraph() text = nodes.Text("PlantUML is not available!", "PlantUML is not available!") para += text content.append(para) node.replace_self(content) continue plantuml_block_text = ".. plantuml::\n" "\n" " @startuml" " @enduml" puml_node = plantuml(plantuml_block_text) puml_node["uml"] = "@startuml\n" puml_connections = "" # Adding config config = current_needflow["config"] if config and len(config) >= 3: # Remove all empty lines config = "\n".join([line.strip() for line in config.split("\n") if line.strip()]) puml_node["uml"] += "\n' Config\n\n" puml_node["uml"] += config puml_node["uml"] += "\n\n" all_needs = list(all_needs.values()) found_needs = process_filters(app, all_needs, current_needflow) processed_need_part_ids = [] puml_node["uml"] += "\n' Nodes definition \n\n" for need_info in found_needs: # Check if need_part was already handled during handling of parent need. # If this is the case, it is already part of puml-code and we do not need to create a node. if not (need_info["is_part"] and need_info["id_complete"] in processed_need_part_ids): # Check if we need to embed need_parts into parent need, because they are also part of search result. node_part_code = "" valid_need_parts = [x for x in found_needs if x["is_part"] and x["id_parent"] == need_info["id"]] for need_part in valid_need_parts: part_link = calculate_link(app, need_part, fromdocname) diagram_template = Template(env.config.needs_diagram_template) part_text = diagram_template.render(**need_part) part_colors = [] if need_part["type_color"]: # We set # later, as the user may not have given a color and the node must get highlighted part_colors.append(need_part["type_color"].replace("#", "")) if current_needflow["highlight"] and filter_single_need( app, need_part, current_needflow["highlight"], all_needs ): part_colors.append("line:FF0000") node_part_code += '{style} "{node_text}" as {id} [[{link}]] #{color}\n'.format( id=make_entity_name(need_part["id_complete"]), node_text=part_text, link=part_link, color=";".join(part_colors), style="rectangle", ) processed_need_part_ids.append(need_part["id_complete"]) link = calculate_link(app, need_info, fromdocname) diagram_template = Template(env.config.needs_diagram_template) node_text = diagram_template.render(**need_info) if need_info["is_part"]: need_id = need_info["id_complete"] else: need_id = need_info["id"] colors = [] if need_info["type_color"]: # We set # later, as the user may not have given a color and the node must get highlighted colors.append(need_info["type_color"].replace("#", "")) if current_needflow["highlight"] and filter_single_need( app, need_info, current_needflow["highlight"], all_needs ): colors.append("line:FF0000") # Only add subelements and their {...} container, if we really need them. # Otherwise plantuml may not set style correctly, if {..} is empty if node_part_code: node_part_code = f"{{\n {node_part_code} }}" style = need_info["type_style"] node_code = '{style} "{node_text}" as {id} [[{link}]] #{color} {need_parts}\n'.format( id=make_entity_name(need_id), node_text=node_text, link=link, color=";".join(colors), style=style, need_parts=node_part_code, ) puml_node["uml"] += node_code for link_type in link_types: # Skip link-type handling, if it is not part of a specified list of allowed link_types or # if not part of the overall configuration of needs_flow_link_types if (current_needflow["link_types"] and link_type["option"].upper() not in option_link_types) or ( not current_needflow["link_types"] and link_type["option"].upper() not in allowed_link_types_options ): continue for link in need_info[link_type["option"]]: # If source or target of link is a need_part, a specific style is needed if "." in link or "." in need_info["id_complete"]: final_link = link if current_needflow["show_link_names"] or env.config.needs_flow_show_links: desc = link_type["outgoing"] + "\\n" comment = f": {desc}" else: comment = "" if "style_part" in link_type and link_type["style_part"]: link_style = "[{style}]".format(style=link_type["style_part"]) else: link_style = "[dotted]" else: final_link = link if current_needflow["show_link_names"] or env.config.needs_flow_show_links: comment = ": {desc}".format(desc=link_type["outgoing"]) else: comment = "" if "style" in link_type and link_type["style"]: link_style = "[{style}]".format(style=link_type["style"]) else: link_style = "" # Do not create an links, if the link target is not part of the search result. if final_link not in [x["id"] for x in found_needs if x["is_need"]] and final_link not in [ x["id_complete"] for x in found_needs if x["is_part"] ]: continue if "style_start" in link_type and link_type["style_start"]: style_start = link_type["style_start"] else: style_start = "-" if "style_end" in link_type and link_type["style_end"]: style_end = link_type["style_end"] else: style_end = "->" puml_connections += "{id} {style_start}{link_style}{style_end} {link}{comment}\n".format( id=make_entity_name(need_info["id_complete"]), link=make_entity_name(final_link), comment=comment, link_style=link_style, style_start=style_start, style_end=style_end, ) puml_node["uml"] += "\n' Connection definition \n\n" puml_node["uml"] += puml_connections # Create a legend if current_needflow["show_legend"]: puml_node["uml"] += create_legend(app.config.needs_types) puml_node["uml"] += "\n@enduml" puml_node["incdir"] = os.path.dirname(current_needflow["docname"]) puml_node["filename"] = os.path.split(current_needflow["docname"])[1] # Needed for plantuml >= 0.9 scale = int(current_needflow["scale"]) # if scale != 100: puml_node["scale"] = scale puml_node = nodes.figure("", puml_node) if current_needflow["align"]: puml_node["align"] = current_needflow["align"] else: puml_node["align"] = "center" if current_needflow["caption"]: # Make the caption to a link to the original file. try: if "SVG" in app.config.plantuml_output_format.upper(): file_ext = "svg" else: file_ext = "png" except Exception: file_ext = "png" gen_flow_link = generate_name(app, puml_node.children[0], file_ext) current_file_parts = fromdocname.split("/") subfolder_amount = len(current_file_parts) - 1 img_locaton = "../" * subfolder_amount + "_images/" + gen_flow_link[0].split("/")[-1] flow_ref = nodes.reference("t", current_needflow["caption"], refuri=img_locaton) puml_node += nodes.caption("", "", flow_ref) # Add lineno to node puml_node.line = current_needflow["lineno"] content.append(puml_node) if len(content) == 0: nothing_found = "No needs passed the filters" para = nodes.paragraph() nothing_found_node = nodes.Text(nothing_found, nothing_found) para += nothing_found_node content.append(para) if current_needflow["show_filters"]: para = nodes.paragraph() filter_text = "Used filter:" filter_text += ( " status(%s)" % " OR ".join(current_needflow["status"]) if len(current_needflow["status"]) > 0 else "" ) if len(current_needflow["status"]) > 0 and len(current_needflow["tags"]) > 0: filter_text += " AND " filter_text += ( " tags(%s)" % " OR ".join(current_needflow["tags"]) if len(current_needflow["tags"]) > 0 else "" ) if (len(current_needflow["status"]) > 0 or len(current_needflow["tags"]) > 0) and len( current_needflow["types"] ) > 0: filter_text += " AND " filter_text += ( " types(%s)" % " OR ".join(current_needflow["types"]) if len(current_needflow["types"]) > 0 else "" ) filter_node = nodes.emphasis(filter_text, filter_text) para += filter_node content.append(para) if current_needflow["debug"]: debug_container = nodes.container() if isinstance(puml_node, nodes.figure): data = puml_node.children[0]["uml"] else: data = puml_node["uml"] data = "\n".join([html.escape(line) for line in data.split("\n")]) debug_para = nodes.raw("", f"<pre>{data}</pre>", format="html") debug_container += debug_para content += debug_container node.replace_self(content)
def run(self): grid_node = nodes.container() grid_node['classes'] += ['m-imagegrid', 'm-container-inflate'] rows = [[]] total_widths = [0] for uri in self.content: # New line, calculating width from 0 again if not uri: rows.append([]) total_widths.append(0) continue # Open the files and calculate the overall width # Support both {filename} (3.7.1) and {static} (3.8) placeholders file = os.path.join(os.getcwd(), settings['PATH']) absuri = uri.format(filename=file, static=file) im = PIL.Image.open(absuri) # Get EXIF info, if it's there if hasattr(im, '_getexif') and im._getexif() is not None: exif = { PIL.ExifTags.TAGS[k]: v for k, v in im._getexif().items() if k in PIL.ExifTags.TAGS and len(str(v)) < 256 } # Not all info might be present caption = [] if 'FNumber' in exif: caption += [ "F{}".format( float( float(exif['FNumber'][0]) / float(exif['FNumber'][1]))) ] if 'ExposureTime' in exif: numerator, denominator = exif['ExposureTime'] if int(numerator) > int(denominator): caption += [ "{} s".format( float(numerator) / float(denominator)) ] else: caption += ["{}/{} s".format(numerator, denominator)] if 'ISOSpeedRatings' in exif: caption += ["ISO {}".format(exif['ISOSpeedRatings'])] caption = ', '.join(caption) # It's not (e.g. a PNG file), empty caption else: caption = "" rel_width = float(im.width) / im.height total_widths[-1] += rel_width rows[-1].append((uri, rel_width, caption)) for i, row in enumerate(rows): row_node = nodes.container() for uri, rel_width, caption in row: image_reference = rst.directives.uri(uri) image_node = nodes.image('', uri=image_reference) # <figurecaption> in case there's a caption if caption: text_nodes, _ = self.state.inline_text( caption, self.lineno) text_node = nodes.paragraph('', '', *text_nodes) overlay_node = nodes.caption() overlay_node.append(text_node) # Otherwise an empty <div> else: overlay_node = nodes.container() link_node = nodes.reference('', refuri=image_reference) link_node.append(image_node) link_node.append(overlay_node) wrapper_node = nodes.figure( width="{:.3f}%".format(rel_width * 100.0 / total_widths[i])) wrapper_node.append(link_node) row_node.append(wrapper_node) grid_node.append(row_node) return [grid_node]
def _render_image(output: NotebookNode, index: int): # Sphinx treats absolute paths as being rooted at the source # directory, so make a relative path, which Sphinx treats # as being relative to the current working directory. filename = os.path.basename(output.metadata["filenames"][mime_type]) # checks if file dir path is inside a subdir of dir filedir = os.path.dirname(output.metadata["filenames"][mime_type]) subpaths = filedir.split(self.sphinx_dir) final_dir = self.sphinx_dir if subpaths and len(subpaths) > 1: subpath = subpaths[1] final_dir += subpath uri = os.path.join(final_dir, filename) # TODO I'm not quite sure why, but as soon as you give it a width, # it becomes clickable?! (i.e. will open the image in the browser) image_node = nodes.image(uri=uri) myst_meta_img = self.node.metadata.get( self.env.config.nb_render_key, {} ).get("image", {}) for key, spec in [ ("classes", directives.class_option), ("alt", directives.unchanged), ("height", directives.length_or_unitless), ("width", directives.length_or_percentage_or_unitless), ("scale", directives.percentage), ("align", align), ]: if key in myst_meta_img: value = myst_meta_img[key] try: image_node[key] = spec(value) except (ValueError, TypeError) as error: error_msg = ( "Invalid image attribute: " "(key: '{}'; value: {})\n{}".format(key, value, error) ) return [self.make_error(error_msg)] myst_meta_fig = self.node.metadata.get( self.env.config.nb_render_key, {} ).get("figure", {}) if "caption" not in myst_meta_fig: return [image_node] figure_node = nodes.figure("", image_node) caption = nodes.caption(myst_meta_fig["caption"], "") figure_node += caption # TODO only contents of one paragraph? (and second should be a legend) self.parse_markdown(myst_meta_fig["caption"], caption) if "name" in myst_meta_fig: name = myst_meta_fig["name"] self.add_source_and_line(figure_node) self.add_name(figure_node, name) # The target should have already been processed by now, with # sphinx.transforms.references.SphinxDomains, which calls # sphinx.domains.std.StandardDomain.process_doc, # so we have to replicate that here std = self.env.get_domain("std") nametypes = self.document.nametypes.items() self.document.nametypes = {name: True} try: std.process_doc(self.env, self.env.docname, self.document) finally: self.document.nametypes = nametypes return [figure_node]
def render_container_myst_open(self, token): """Render a container (`:::{name}` blocks), based on its name.""" # match first line regex match = REGEX_ADMONTION.match(token.info.strip()) # default behaviour if not match: return self.default_container( token, "admonition argument did not match required regex") name = match.groupdict()["name"] if name in STD_ADMONITIONS: admonition = self.get_admonition(token, **match.groupdict()) self.add_line_and_source_path(admonition, token) with self.current_node_context(admonition, append=True): self.render_children(token) return if name == "figure" and self.config.get("enable_figures", False): # must be of length 2 if not len(token.children) == 2: return self.figure_error(token) # 1st must be paragraph_open with 1 child type inline and 1 child type image html_image = None if token.children[0].type == "html_block": html_image = HTMLImgParser().parse(token.children[0].content, self.document, token.children[0].map[0]) if html_image is None: return self.figure_error(token) elif not (token.children[0].children and len(token.children[0].children) == 1 and len(token.children[0].children[0].children) == 1 and token.children[0].children[0].children[0].type == "image"): return self.figure_error(token) # 2nd must be a paragraph if not token.children[1].type == "paragraph_open": return self.figure_error(token) figure_node = nodes.figure( "", classes=match.groupdict()["classes"][1:].split(",")) if match.groupdict()["title"].strip(): name = nodes.fully_normalize_name( match.groupdict()["title"].strip()) figure_node["names"].append(name) self.document.note_explicit_target(figure_node, figure_node) with self.current_node_context(figure_node, append=True): if html_image is None: self.render_image( token.children[0].children[0].children[0]) else: self.current_node.append(html_image) caption = nodes.caption("", "") with self.current_node_context(caption, append=True): self.render_children(token.children[1]) return return self.default_container( token, f"admonition name not recognised: {name}")
def run(self): """ Restructured text extension for including inline JSAV content on module pages """ self.options['exer_name'] = self.arguments[0] self.options['type'] = self.arguments[1] self.options['odsa_path'] = os.path.relpath(conf.odsa_path, conf.ebook_path) # Set defaults for any values that aren't configured if 'required' not in self.options: self.options['required'] = False if 'points' not in self.options: self.options['points'] = 0 if 'threshold' not in self.options: self.options['threshold'] = 1.0 if 'long_name' not in self.options: self.options['long_name'] = self.options['exer_name'] if 'align' not in self.options: self.options['align'] = 'center' if 'output' in self.options and self.options['output'] == "show": self.options['output_code'] = '<p class="jsavoutput jsavline"></p>' else: self.options['output_code'] = '' if self.options['type'] == "dgm": avdgm_node = av_dgm() anchor_node = av_anchor() avdgm_node['exer_name'] = self.options['exer_name'] anchor_node['ids'].append(self.options['exer_name']) avdgm_node += anchor_node if self.content: node = nodes.Element() # anonymous container for parsing self.state.nested_parse(self.content, self.content_offset, node) first_node = node[0] if isinstance(first_node, nodes.paragraph): caption = nodes.caption(first_node.rawsource, '', *first_node.children) caption['align'] = self.options['align'] avdgm_node += caption return [avdgm_node] elif self.options['type'] == "ss" and self.content: avss_node = av_ss() avss_node['res'] = SLIDESHOW % self.options node = nodes.Element() # anonymous container for parsing self.state.nested_parse(self.content, self.content_offset, node) first_node = node[0] if isinstance(first_node, nodes.paragraph): caption = nodes.caption(first_node.rawsource, '', *first_node.children) caption['align'] = self.options['align'] avss_node += caption return [avss_node] else: res = SLIDESHOW % self.options return [nodes.raw('', res, format='html')]
def process_needsequence(app, doctree, fromdocname): # Replace all needsequence nodes with a list of the collected needs. env = app.builder.env link_types = env.config.needs_extra_links allowed_link_types_options = [ link.upper() for link in env.config.needs_flow_link_types ] # NEEDSEQUENCE for node in doctree.traverse(Needsequence): if not app.config.needs_include_needs: # Ok, this is really dirty. # If we replace a node, docutils checks, if it will not lose any attributes. # But this is here the case, because we are using the attribute "ids" of a node. # However, I do not understand, why losing an attribute is such a big deal, so we delete everything # before docutils claims about it. for att in ('ids', 'names', 'classes', 'dupnames'): node[att] = [] node.replace_self([]) continue id = node.attributes["ids"][0] current_needsequence = env.need_all_needsequences[id] all_needs_dict = env.needs_all_needs option_link_types = [ link.upper() for link in current_needsequence['link_types'] ] for lt in option_link_types: if lt not in [link['option'].upper() for link in link_types]: logger.warning( 'Unknown link type {link_type} in needsequence {flow}. Allowed values:' ' {link_types}'.format( link_type=lt, flow=current_needsequence['target_node'], link_types=",".join(link_types))) content = [] try: if "sphinxcontrib.plantuml" not in app.config.extensions: raise ImportError from sphinxcontrib.plantuml import plantuml except ImportError: no_plantuml(node) continue plantuml_block_text = ".. plantuml::\n" \ "\n" \ " @startuml" \ " @enduml" puml_node = plantuml(plantuml_block_text, **dict()) puml_node["uml"] = "@startuml\n" # Adding config config = current_needsequence['config'] puml_node["uml"] += add_config(config) all_needs = list(all_needs_dict.values()) start_needs_id = [ x.strip() for x in re.split(";|,", current_needsequence['start']) ] if len(start_needs_id) == 0: raise NeedsequenceDirective('No start-id set for needsequence' ' File ' ':{}'.format( {current_needsequence["docname"]}, current_needsequence["lineno"])) puml_node["uml"] += '\n\' Nodes definition \n\n' # Add start participants p_string = '' c_string = '' for need_id in start_needs_id: try: need = all_needs_dict[need_id.strip()] except KeyError: raise NeedSequenceException( 'Given {} in needsequence unknown.' ' File {}' ':{}'.format(need_id, current_needsequence["docname"], current_needsequence["lineno"])) # Add children of participants msg_receiver_needs, p_string_new, c_string_new = get_message_needs( need, current_needsequence['link_types'], all_needs_dict, filter=current_needsequence['filter']) p_string += p_string_new c_string += c_string_new puml_node["uml"] += p_string puml_node["uml"] += '\n\' Connection definition \n\n' puml_node["uml"] += c_string # Create a legend if current_needsequence["show_legend"]: puml_node["uml"] += '\n\n\' Legend definition \n\n' puml_node["uml"] += "legend\n" puml_node["uml"] += "|= Color |= Type |\n" for need in app.config.needs_types: puml_node[ "uml"] += "|<back:{color}> {color} </back>| {name} |\n".format( color=need["color"], name=need["title"]) puml_node["uml"] += "endlegend\n" puml_node["uml"] += "\n@enduml" puml_node["incdir"] = os.path.dirname(current_needsequence["docname"]) puml_node["filename"] = os.path.split( current_needsequence["docname"])[1] # Needed for plantuml >= 0.9 scale = int(current_needsequence['scale']) # if scale != 100: puml_node['scale'] = scale puml_node = nodes.figure('', puml_node) if current_needsequence['align'] is not None and len( current_needsequence['align']) != '': puml_node['align'] = current_needsequence['align'] else: puml_node['align'] = 'center' if current_needsequence['caption'] is not None and len( current_needsequence['caption']) != '': # Make the caption to a link to the original file. try: if "SVG" in app.config.plantuml_output_format.upper(): file_ext = 'svg' else: file_ext = 'png' except Exception: file_ext = 'png' gen_flow_link = generate_name(app, puml_node.children[0], file_ext) current_file_parts = fromdocname.split('/') subfolder_amount = len(current_file_parts) - 1 img_locaton = '../' * subfolder_amount + '_images/' + gen_flow_link[ 0].split('/')[-1] flow_ref = nodes.reference('t', current_needsequence['caption'], refuri=img_locaton) puml_node += nodes.caption('', '', flow_ref) content.append(puml_node) if len(content) == 0: nothing_found = "No needs passed the filters" para = nodes.paragraph() nothing_found_node = nodes.Text(nothing_found, nothing_found) para += nothing_found_node content.append(para) if current_needsequence["show_filters"]: content.append(get_filter_para(current_needsequence)) if current_needsequence['debug']: content += get_debug_containter(puml_node) node.replace_self(content)
def resolve_toctree(self, docname, builder, toctree, prune=True, maxdepth=0, titles_only=False, collapse=False, includehidden=False): # type: (unicode, Builder, addnodes.toctree, bool, int, bool, bool, bool) -> nodes.Node """Resolve a *toctree* node into individual bullet lists with titles as items, returning None (if no containing titles are found) or a new node. If *prune* is True, the tree is pruned to *maxdepth*, or if that is 0, to the value of the *maxdepth* option on the *toctree* node. If *titles_only* is True, only toplevel document titles will be in the resulting tree. If *collapse* is True, all branches not containing docname will be collapsed. """ if toctree.get('hidden', False) and not includehidden: return None # For reading the following two helper function, it is useful to keep # in mind the node structure of a toctree (using HTML-like node names # for brevity): # # <ul> # <li> # <p><a></p> # <p><a></p> # ... # <ul> # ... # </ul> # </li> # </ul> # # The transformation is made in two passes in order to avoid # interactions between marking and pruning the tree (see bug #1046). toctree_ancestors = self.get_toctree_ancestors(docname) def _toctree_add_classes(node, depth): """Add 'toctree-l%d' and 'current' classes to the toctree.""" for subnode in node.children: if isinstance(subnode, (addnodes.compact_paragraph, nodes.list_item)): # for <p> and <li>, indicate the depth level and recurse subnode['classes'].append('toctree-l%d' % (depth - 1)) _toctree_add_classes(subnode, depth) elif isinstance(subnode, nodes.bullet_list): # for <ul>, just recurse _toctree_add_classes(subnode, depth + 1) elif isinstance(subnode, nodes.reference): # for <a>, identify which entries point to the current # document and therefore may not be collapsed if subnode['refuri'] == docname: if not subnode['anchorname']: # give the whole branch a 'current' class # (useful for styling it differently) branchnode = subnode while branchnode: branchnode['classes'].append('current') branchnode = branchnode.parent # mark the list_item as "on current page" if subnode.parent.parent.get('iscurrent'): # but only if it's not already done return while subnode: subnode['iscurrent'] = True subnode = subnode.parent def _entries_from_toctree(toctreenode, parents, separate=False, subtree=False): """Return TOC entries for a toctree node.""" refs = [(e[0], e[1]) for e in toctreenode['entries']] entries = [] for (title, ref) in refs: try: refdoc = None if url_re.match(ref): if title is None: title = ref reference = nodes.reference('', '', internal=False, refuri=ref, anchorname='', *[nodes.Text(title)]) para = addnodes.compact_paragraph('', '', reference) item = nodes.list_item('', para) toc = nodes.bullet_list('', item) elif ref == 'self': # 'self' refers to the document from which this # toctree originates ref = toctreenode['parent'] if not title: title = clean_astext(self.env.titles[ref]) reference = nodes.reference('', '', internal=True, refuri=ref, anchorname='', *[nodes.Text(title)]) para = addnodes.compact_paragraph('', '', reference) item = nodes.list_item('', para) # don't show subitems toc = nodes.bullet_list('', item) else: if ref in parents: logger.warning( 'circular toctree references ' 'detected, ignoring: %s <- %s', ref, ' <- '.join(parents), location=ref) continue refdoc = ref toc = self.tocs[ref].deepcopy() maxdepth = self.env.metadata[ref].get('tocdepth', 0) if ref not in toctree_ancestors or (prune and maxdepth > 0): self._toctree_prune(toc, 2, maxdepth, collapse) process_only_nodes(toc, builder.tags) if title and toc.children and len(toc.children) == 1: child = toc.children[0] for refnode in child.traverse(nodes.reference): if refnode['refuri'] == ref and \ not refnode['anchorname']: refnode.children = [nodes.Text(title)] if not toc.children: # empty toc means: no titles will show up in the toctree logger.warning( 'toctree contains reference to document %r that ' 'doesn\'t have a title: no link will be generated', ref, location=toctreenode) except KeyError: # this is raised if the included file does not exist logger.warning( 'toctree contains reference to nonexisting document %r', ref, location=toctreenode) else: # if titles_only is given, only keep the main title and # sub-toctrees if titles_only: # delete everything but the toplevel title(s) # and toctrees for toplevel in toc: # nodes with length 1 don't have any children anyway if len(toplevel) > 1: subtrees = toplevel.traverse(addnodes.toctree) if subtrees: toplevel[1][:] = subtrees else: toplevel.pop(1) # resolve all sub-toctrees for subtocnode in toc.traverse(addnodes.toctree): if not (subtocnode.get('hidden', False) and not includehidden): i = subtocnode.parent.index(subtocnode) + 1 for item in _entries_from_toctree( subtocnode, [refdoc] + parents, subtree=True): subtocnode.parent.insert(i, item) i += 1 subtocnode.parent.remove(subtocnode) if separate: entries.append(toc) else: entries.extend(toc.children) if not subtree and not separate: ret = nodes.bullet_list() ret += entries return [ret] return entries maxdepth = maxdepth or toctree.get('maxdepth', -1) if not titles_only and toctree.get('titlesonly', False): titles_only = True if not includehidden and toctree.get('includehidden', False): includehidden = True # NOTE: previously, this was separate=True, but that leads to artificial # separation when two or more toctree entries form a logical unit, so # separating mode is no longer used -- it's kept here for history's sake tocentries = _entries_from_toctree(toctree, [], separate=False) if not tocentries: return None newnode = addnodes.compact_paragraph('', '') caption = toctree.attributes.get('caption') if caption: caption_node = nodes.caption(caption, '', *[nodes.Text(caption)]) caption_node.line = toctree.line caption_node.source = toctree.source caption_node.rawsource = toctree['rawcaption'] if hasattr(toctree, 'uid'): # move uid to caption_node to translate it caption_node.uid = toctree.uid del toctree.uid newnode += caption_node newnode.extend(tocentries) newnode['toctree'] = True # prune the tree to maxdepth, also set toc depth and current classes _toctree_add_classes(newnode, 1) self._toctree_prune(newnode, 1, prune and maxdepth or 0, collapse) if len(newnode[-1]) == 0: # No titles found return None # set the target paths in the toctrees (they are not known at TOC # generation time) for refnode in newnode.traverse(nodes.reference): if not url_re.match(refnode['refuri']): refnode['refuri'] = builder.get_relative_uri( docname, refnode['refuri']) + refnode['anchorname'] return newnode
def run(self) -> List[nodes.Node]: """Con :class:`.TestDirective` it's some subclass, and append custom options in return node.""" options = self.options ## Empty directive would add an empty literal line. # (common, just to print a graphvar global from a previous doctest) # if not "\n".join(self.content).strip(): options["hide"] = True self.name = self._con_name try: original_nodes = super().run() finally: self.name = self._real_name location = self.state_machine.get_source_and_line(self.lineno) img_format = self._decide_img_format(options) log.debug("decided `graph-format`: %r", img_format, location=location) if not img_format: # Bail out, probably unknown builder. return original_nodes node = graphtik_node(graphvar=options.get("graphvar"), img_format=img_format) node.source, node.line = location ## Decide a unique filename (and id). # name = options.get("name") or "" if name: name = nodes.fully_normalize_name(name) targetname = ( f"graphtik-{self.env.docname}-{name}-{self.env.new_serialno('graphtik')}" ) node["filename"] = targetname node += original_nodes figure = nodes.figure() figure.source, figure.line = location align = options.get("align") if align: align = f"align-{align}" figure["classes"].append(align) figure["classes"].extend(options.get("figclass", "").split()) node += figure # TODO: emulate sphinx-processing for image width & height attrs. img_attrs = {k: v for k, v in options.items() if k in _img_options} image = dynaimage(**img_attrs) image.source, image.line = location image["classes"].extend(options.get("class", "").split()) # TODO: TCs for zooamble-SVGs options & configs. if "svg" in img_format: zoomable = options.get("zoomable") if zoomable is None: zoomable = self.config.graphtik_zoomable if zoomable: image["classes"].append("graphtik-zoomable-svg") ## Assign a special *dataset* html-attribute # with the content of a respective option. # zoomable_options = options.get("zoomable-opts") if zoomable_options: image["data-svg-zoom-opts"] = zoomable_options figure += image # A caption is needed if :name: is given, to create a permalink on it # (see sphinx/writers/html:HTMLTranslator.depart_caption()), # so get it here, not to overwrite it with :name: (if given below).` caption = options.get("caption") ## Prepare target, # if "name" in options: ## adapted from: self.add_name() # name = options.pop("name") if not caption: caption = name figure["names"].append(targetname) ## adapted from: sphinx.domains.std.Target directive. # std = cast(StandardDomain, self.env.get_domain("std")) ## Suppress warning on duplicates (replaced with INFO). # Duplicates occur either by include directives # or because :noindex: in autoclass is ignored here. # objtype = "graphtik" if (objtype, name) in std.objects: docname = std.objects[objtype, name][0] log.info( __("Skipping duplicate %s description of %s, other instance in %s"), objtype, name, docname, location=location, ) else: std.note_object(objtype, name, targetname, figure) ## See sphinx.ext.graphviz:figure_wrapper(), # and <sphinx.git>/tests/roots/test-add_enumerable_node/enumerable_node.py:MyFigure # if caption: inodes, messages = self.state.inline_text(caption, self.lineno) caption_node = nodes.caption(caption, "", *inodes) self.set_source_info(caption_node) caption_node += messages figure += caption_node return [node]