def render_blocks(key, value, format, meta): lang = meta.get("language") if lang: lang = lang["c"][0]["c"] else: lang = meta.get("lang") if lang: lang = lang["c"] if key == "CodeBlock": [[ident, classes, keyvals], code] = value if u"blocks" in classes or u"scratch" in classes: image = block_to_image(code, lang, os.getcwd()) alt = Str(code) return Para([Image([alt], [os.path.basename(image), ""])])
def replace_fig_references(key, value, fmt, meta): global num_refs # Global references counter global references # sys.stderr.write('Key: %s -> %s\n' % (key, Str(value))) if key in ['Para', 'Plain'] and fmt == "docx": for i, x in enumerate(value): if re.match(r'.*reference-type.*', str(x).replace('\n', ' ')): for r in references.keys(): # sys.stderr.write('LOOKING FOR: %s -> %s\n' % (r, Str(x))) pattern_ref = re.compile('.*?\'%s\'.*?' % r) if re.match(pattern_ref, str(x).replace('\n', ' ')): # sys.stderr.write('FOUND: %s -> %s\nMoving to find ref str\n' % (r, Str(x))) find_ref_str(x, pattern_ref, references[r]) value[i] = x return Para(value)
def note_tip(key, value, format, _): if key == 'CodeBlock': [[ident, classes, keyvals], code] = value if "note" in classes or "tip" in classes: blockType = "" if "note" in classes: blockType = "note" elif "tip" in classes: blockType = "tip" if format == "html" or format == "markdown": return RawBlock("html", "<p class=\"" + blockType + "\"><b>" + blockType.title() + "</b>: " + code + "</p>\n") else: return Para([Str(blockType.upper() + ":" + code)])
def process_display_math(key, val, fmt, meta): """ Block-level math is inside paragraphs for some reason, so split the paragraph and pull out into a div. """ if key != 'Para' or not val: return paras = [] last_math_index = 0 for i, child in enumerate(val): if child['t'] == 'Math' and child['c'][0]['t'] == 'DisplayMath': # This filter doesn't seem to work if it follows this filter in # walk, so let's just call it from here equation_id, new_children = insert_equation_labels(child['c']) paras.append(Para(val[last_math_index:i])) paras.append( Div([equation_id, ['engrafo-equation'], []], [Para(new_children)])) last_math_index = i + 1 if last_math_index == 0: return paras.append(Para(val[last_math_index:])) return paras
def replace_attrimages(key, value, fmt, meta): """Replaces attributed images while storing reference labels.""" if is_attrimage(key, value): # Parse the image attrs, caption, target, label = parse_attrimage(value) # Bail out if the label does not conform if not label or not LABEL_PATTERN.match(label): return None # Save the reference references[label] = len(references) + 1 # Adjust caption depending on the output format if fmt == 'latex': caption = list(caption) + [RawInline('tex', r'\label{%s}' % label)] else: caption = ast('Figure %d. ' % references[label]) + list(caption) # Required for pandoc to process the image target[1] = "fig:" # Return the replacement if len(value[0]['c']) == 2: # Old pandoc < 1.16 img = Image(caption, target) else: # New pandoc >= 1.16 assert len(value[0]['c']) == 3 img = AttrImage(attrs, caption, target) if fmt in ('html', 'html5'): anchor = RawInline('html', '<a name="%s"></a>' % label) return [Plain([anchor]), Para([img])] else: return Para([img])
def process( value, format ): [[ident, classes, keyvals], code] = value if "graphviz" in classes: caption, typef, keyvals = get_caption(keyvals) filetype = get_extension(format, "png") dest = get_filename4code("graphviz", code, filetype) if not os.path.isfile(dest): import pygraphviz g = pygraphviz.AGraph(string=code) g.layout() g.draw(dest) sys.stderr.write('Created image ' + dest + '\n') return Para([Image([ident, [], keyvals], caption, [dest, typef])]) elif "standalone" in classes: caption, typef, keyvals = get_caption(keyvals) filetype = get_extension(format, "png", html="png", latex="pdf") dest = get_filename4code("standalone", code, filetype) if not os.path.isfile(dest): gen_standalone(code, dest) else: print1('[INFO] Image file %s already generated' % dest ) return Para([Image([ident, [], keyvals], caption, [dest, typef])])
def blockdiag(key, value, fmt, meta): if key == 'CodeBlock': [[ident, classes, keyvals], code] = value found, cmd = isDiag(classes) if found == True: caption, typef, keyvals = get_caption(keyvals) path = os.path.dirname(os.path.abspath(__file__)) filename = sha1(code) src = imagedir + '/' + filename + '.png' if not os.path.isfile(src): try: os.mkdir(imagedir) except OSError: pass tmp = save(code) p = subprocess.Popen([cmd, "-a", tmp, "-o", src], shell=False, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) out = p.stdout.read().decode("utf-8") err = p.stderr.read().decode("utf-8") p.stderr.close() if (len(err) > 0): return Para([Str(out + " " + err)]) os.remove(tmp) try: image = Image([ident, [], keyvals], caption, [src, typef]) return Para([image]) except: try: image = Image(caption, [src, typef]) return Para([image]) except: pass
def plantuml(key, value, format_, meta): if key == 'Header': if 'tikz' in (str(meta)): os.environ["PLANTUML_LATEX_EXPORT"] = 'latex' if key == 'CodeBlock': if os.getenv("DEBUG", "f").lower() in ("1", "true"): print("plantuml", key, value, format_, meta) [[ident, classes, keyvals], code] = value if "plantuml" in classes: caption, typef, keyvals = get_caption(keyvals) if "PLANTUML_LATEX_EXPORT" in os.environ: latex_img_format = "latex" else: latex_img_format = "eps" filename = get_filename4code("plantuml", code) filetype = get_extension(format_, "png", html="svg", latex=latex_img_format, beamer=latex_img_format) src = filename + '.uml' dest = filename + '.' + filetype if not os.path.isfile(dest): txt = code.encode(sys.getfilesystemencoding()) if not txt.startswith(b"@start"): txt = b"@startuml\n" + txt + b"\n@enduml\n" with open(src, "wb") as f: f.write(txt) subprocess.check_call(PLANTUML_BIN.split() + ["-t" + filetype, src]) sys.stderr.write('Created image ' + dest + '\n') if (filetype == "latex") and (latex_img_format == 'latex'): latex = open(dest).read() return RawBlock( 'latex', latex.split("\\begin{document}")[-1].split( "\\end{document}")[0]) else: return Para( [Image([ident, [], keyvals], caption, [dest, typef])])
def graphviz(key, value, format, _): if key == 'CodeBlock': [[ident, classes, keyvals], code] = value if "graphviz" in classes: caption, typef, keyvals = get_caption(keyvals) prog, keyvals = get_value(keyvals, u"prog", u"dot") filetype = get_extension(format, "svg", html="svg", latex="pdf") dest = get_filename4code("graphviz", code, filetype) if not os.path.isfile(dest): g = pygraphviz.AGraph(string=code) g.layout() g.draw(dest, prog=prog) sys.stderr.write('Created image ' + dest + '\n') return Para([Image([ident, [], keyvals], caption, [dest, typef])])
def process_figs(key, value, fmt, meta): # pylint: disable=global-statement global num_refs # Global references counter global references if fmt == "docx" and key == "Para": for i, x in enumerate(value): if re.match(r'.*label.*', str(x).replace('\n', ' ')): # sys.stderr.write('KEY: %s, VALUE[%d]: %s, X: %s\n' % (key, i,value[i], x)) m = re.match(r".*label.*\'(.*?R.*?Com.*?)\'\]", str(x).replace('\n', ' ')) if m: # label = m.group(1) # sys.stderr.write('Label[%d] = %s\n' % (i,label)) replace_fig_label(x) value[i] = x return Para(value)
def environment(key, value, format, meta): # Is it a div and the right format? if key == 'Div' and format in ['latex', 'beamer']: # Get the attributes [[id, classes, properties], content] = value currentClasses = set(classes) for environment, definedClasses in getDefined(meta).items(): # Is the classes correct? if currentClasses >= definedClasses: if id != '': label = ' \\label{' + id + '}' else: label = '' currentProperties = dict(properties) if 'title' in currentProperties: title = '[' + currentProperties['title'] + ']' else: title = '' # fix an empty block not rendering any output if len(content) == 0: content = [Para([])] newconts = [] pos = 0 last = len(content) for node in content: replacement = node['c'] pos += 1 if pos == 1: replacement = [ RawInline( 'tex', '\\begin{' + environment + '}\n' + title + label) ] + replacement if pos == last: replacement = replacement + [ RawInline('tex', '\n\\end{' + environment + '}') ] newconts.append({'t': node['t'], 'c': replacement}) value[1] = newconts break
def tikz(key, value, format, meta): if key == 'RawBlock': [fmt, code] = value if fmt == "latex" and re.match("\\\\begin{tikzpicture}", code): outfile = imagedir + '/' + sha1(code) if format == "latex": filetype = "pdf" else: filetype = "svg" src = outfile + '.' + filetype if not os.path.isfile(src): try: os.mkdir(imagedir) except OSError: pass tikz2svg(code, filetype, outfile) return Para([Image([], ['/' + src, ""])])
def tex2png(key, value, format, _): if key == 'CodeBlock': [[ident, classes, keyvals], code] = value if "tex2png" in classes: caption, typef, keyvals = get_caption(keyvals) filetype = get_extension(format, "png", html="png", latex="pdf") dest = get_filename4code("tex2png", code, filetype) abs_dest = os.path.abspath( os.path.expanduser(os.path.expandvars(dest))) if not os.path.isfile(dest): # sys.stderr.write('DEBUG code="' + code + '"\n') p = subprocess.run(["tex2png.sh", abs_dest], text=True, input=code) sys.stderr.write('Created image ' + dest + '\n') return Para([Image([ident, [], keyvals], caption, [dest, typef])])
def processMathDoubleBackslash(key, value, _format, _meta): if key == 'Para': if len(value) == 1 and value[0]['t'] == 'Math': [fmt, code] = value[0]['c'] if '\\\\' in code: if fmt['t'] == 'DisplayMath': res = code.split('\\\\') for item in res: if item.count('\\begin') != item.count('\\end'): return return list(map(lambda item : Para([Math(fmt, item)]), res)) elif key == 'Math': [fmt, code] = value if '\\\\' in code: if fmt['t'] == 'InlineMath': res = list(filter(None, re.split(r'( *\\\\ *)', code))) mathlist = list(map(lambda item : LineBreak() if re.match(r'( *\\\\ *)', item) else Math(fmt, item), res)) return mathlist
def gabc(key, value, fmt, meta): # pylint:disable=I0011,W0613 """Handle gabc file inclusion and gabc code block.""" if key == 'Code': [[ident, classes, kvs], contents] = value # pylint:disable=I0011,W0612 kvs = {key: value for key, value in kvs} if "gabc" in classes: if fmt == "latex": if ident == "": label = "" else: label = '\\label{' + ident + '}' return latex("\n\\smallskip\n{%\n" + latexsnippet('\\gregorioscore{' + contents + '}', kvs) + "%\n}" + label) else: infile = contents + ('.gabc' if '.gabc' not in contents else '') with open(infile, 'r') as doc: code = doc.read().split('%%\n')[1] return [ Image([], [ png(contents, latexsnippet('\\gregorioscore', kvs)), "" ]) ] elif key == 'CodeBlock': [[ident, classes, kvs], contents] = value kvs = {key: value for key, value in kvs} if "gabc" in classes: if fmt == "latex": if ident == "": label = "" else: label = '\\label{' + ident + '}' return [ latexblock("\n\\smallskip\n{%\n" + latexsnippet('\\gabcsnippet{' + contents + '}', kvs) + "%\n}" + label) ] else: return Para([ Image([], [ png(contents, latexsnippet('\\gabcsnippet', kvs)), "" ]) ])
def plantuml(key, value, format_, _): if key == 'CodeBlock': [[ident, classes, keyvals], code] = value # print(value) # print("caga") sys.stderr.write(str(ident)) if "plantuml" in classes: caption, typef, keyvals = get_caption(keyvals) imageFolder = Klasor("den/plantuml") filename = get_filename4code( 'DenemeProje2/webNdocs/static/images/Converted_Pdf', code) filetype = get_extension(format_, "png", html="svg", latex="png") src = filename + '.uml' dest = filename + '.' + filetype # print(src) # Generate image only once if not os.path.isfile(dest): txt = code.encode(sys.getfilesystemencoding()) if not txt.startswith(b"@start"): txt = b"@startuml\n" + txt + b"\n@enduml\n" with open(src, "wb") as f: f.write(txt) subprocess.check_call(PLANTUML_BIN.split() + ["-t" + filetype, src]) sys.stderr.write('Created image ' + dest + '\n') # Update symlink each run for ind, keyval in enumerate(keyvals): if keyval[0] == 'plantuml-filename': link = keyval[1] keyvals.pop(ind) if os.path.islink(link): os.remove(link) os.symlink(dest, link) dest = link break return Para([Image([ident, [], keyvals], caption, [dest, typef])])
def mermaid(key, value, format_, _): if key == 'CodeBlock': [[ident, classes, keyvals], code] = value if "mermaid" in classes: caption, typef, keyvals = get_caption(keyvals) filename = get_filename4code("mermaid", code) filetype = get_extension(format_, "png", html="svg", latex="pdf") src = filename + '.mmd' dest = filename + '.' + filetype if not os.path.isfile(dest): txt = code.encode(sys.getfilesystemencoding()) with open(src, "wb") as f: f.write(txt) # Default command to execute cmd = [MERMAID_BIN, "-i", src, "-o", dest] cmd.extend(["-c", "filters/mermaid.json"]) cmd.extend(["-C", "filters/mermaid.css"]) cmd.extend(["-b", "transparent"]) if PUPPETEER_CFG is not None: cmd.extend(["-p", PUPPETEER_CFG]) if os.path.isfile('.puppeteer.json'): cmd.extend(["-p", ".puppeteer.json"]) subprocess.check_call(cmd) sys.stderr.write('Created image ' + dest + '\n') if filetype == 'pdf': # Default command to execute cmd = ['pdfcrop', dest, dest] subprocess.check_call(cmd) sys.stderr.write('Cropped image ' + dest + '\n') time.sleep(.1) return Para([Image([ident, [], keyvals], caption, [dest, typef])])
def plantuml(key, value, format_, meta): if key == 'CodeBlock': [[ident, classes, keyvals], code] = value if "plantuml" in classes: caption, typef, keyvals = get_caption(keyvals) filename = get_filename4code("plantuml", code) if meta.get('plantuml-format'): pformat = meta.get('plantuml-format', None) filetype = get_extension(format_, pformat['c'][0]['c']) else: filetype = get_extension(format_, "png", html="svg", latex="png") src = filename + '.uml' dest = filename + '.' + filetype # Generate image only once if not os.path.isfile(dest): txt = code.encode(sys.getfilesystemencoding()) if not txt.startswith(b"@start"): txt = b"@startuml\n" + txt + b"\n@enduml\n" with open(src, "wb") as f: f.write(txt) subprocess.check_call(PLANTUML_BIN.split() + ["-t" + filetype, src]) sys.stderr.write('Created image ' + dest + '\n') # Update symlink each run for ind, keyval in enumerate(keyvals): if keyval[0] == 'plantuml-filename': link = keyval[1] keyvals.pop(ind) rel_mkdir_symlink(dest, link) dest = link break return Para([Image([ident, [], keyvals], caption, [dest, typef])])
def make_figures(key, val, fmt, meta): """ Turn <p><img alt="some caption"><img></p> into <div class="engrafo-figure"><img><img> <span class="engrafo-figcaption">some caption</span></div> """ if key != 'Para' or not val: return children = [c for c in val if c['t'] == 'Image'] if not children: return # Pick first child's caption to be the caption. This is because pandoc # gives each image in a figure the same caption. alt = children[0]['c'][1] # Pandoc sets alt text to "image" if there is none if alt and alt != [{u'c': u'image', u't': u'Str'}]: children.append(Span(['', ['engrafo-figcaption'], []], alt)) # Pandoc requires that a Div has a Para in it, so insert a single Para to # wrap all the children return Div(['', ['engrafo-figure'], []], [Para(children)])
def process_codeblock( self, value: Tuple[Tuple[str, List[str], Dict[str, str]], str], output_format: str, meta: Dict[str, str]) -> Any: # pylint: disable=unused-argument """Fenced blocks are code blocks.""" [[ident, classes, keyvals], code] = value for diag_class in self.FILTER_CLASSES: if diag_class in classes: caption, typef, keyvals = get_caption(keyvals) if output_format in PANDOC_HTML_FORMATS: _, relpath = self.generate_file( diag_class, code, "svg") image_ref = self.temp_link + relpath else: image_ref, _ = self.generate_file( diag_class, code, "png") return Para( [Image([ident, [], keyvals], caption, [image_ref, typef])]) return None
def tikz(key, value, format, meta): if key == 'CodeBlock': [[ident, classes, keyvals], code] = value caption = "caption" if 'tikz' in classes: outfile = imagedir + '/' + sha1(code) if format == "latex": filetype = "pdf" else: filetype = "svg" src = outfile + '.' + filetype if not os.path.isfile(src): try: os.mkdir(imagedir) sys.stderr.write('Created directory ' + imagedir + '\n') except OSError: pass tikz2image(code, filetype, outfile) sys.stderr.write('Created image ' + src + '\n') return Para([Image(['', [], []], [], [src, ""])])
def plantuml(key, value, format, meta): if key == 'CodeBlock': [[ident, classes, keyvals], code] = value if "plantuml" in classes: title, typef, keyvals = get_title(keyvals) filename = "plantuml-" + get_md5(code) if 'umlformat' in meta: filetype = meta['umlformat']['c'] else: filetype = 'svg' #if filetype != "eps" and filetype != "svg": # sys.stderr.write("Unsupported plantuml format: " + filetype + ". Defaulting to svg.") # filetype = "svg" src = filename + '.uml' dest = filename + '.' + filetype if not os.path.isfile(dest): #txt = code.encode(sys.getfilesystemencoding()) txt = code if not txt.startswith("@start"): txt = "@startuml\n" + txt + "\n@enduml\n" with open(src, "w") as f: f.write(txt) p = Popen(["plantuml", "-t" + filetype, src], stderr=PIPE, stdout=PIPE) (stdout, stderr) = p.communicate() if stderr.decode() != "": sys.stderr.write("WARNING: failed to run plantuml: " + stderr.decode() + "\n") else: sys.stderr.write("Created image %s\n" % dest) #call(["plantuml", "-t"+filetype, src]) #sys.stderr.write('Created image ' + dest + '\n') return Para([Image([ident, [], keyvals], title, [dest, typef])])
def replace_tikz_images(key, val, fmt, meta): ''' Replace tikzpictures with SVGs and change the div format generated by our pandoc fork to match that of images. ''' if (key == 'Div' and 'tikzpicture' in val[0][1] and val[1] and val[1][0] and isinstance(val[1][0], dict) and val[1][0]['t'] == 'RawBlock'): code = val[1][0]['c'][1] if re.match(r'\\begin\{tikzpicture\}', code): prefix = 'tikz-%s' % str(uuid.uuid4()) svg_filename = '%s.svg' % prefix tikz2image(code, prefix) if len(val[1]) > 1: caption = val[1][1]['c'] else: caption = [] return Para([Image(['', [], []], caption, [svg_filename, ''])])
def abc(key, value, format, meta): if key == 'CodeBlock': [[ident, classes, keyvals], code] = value if "abc" in classes: outfile = imagedir + '/' + sha1(code) if format == "html": filetype = "png" elif format == "latex": filetype = "pdf" else: filetype = "png" src = outfile + '.' + filetype if not os.path.isfile(src): try: os.mkdir(imagedir) sys.stderr.write('Created directory ' + imagedir + '\n') except OSError: pass abc2eps(code.encode("utf-8"), filetype, outfile) sys.stderr.write('Created image ' + src + '\n') return Para([Image([], [src, ""])])
def packetdiag(key, value, format_, _): if key != 'CodeBlock': return [[ident, classes, keyvals], code] = value target = set(CLASS_DICT.keys()) & set(classes) if len(target) == 0: return target = list(target) code_class = target[0] caption, typef, keyvals = get_caption(keyvals) filename = get_filename4code(code_class, code) src = filename + '.diag' dest = filename + '.svg' if not os.path.isfile(dest): txt = code.encode(sys.getfilesystemencoding()) with open(src, "wb") as f: f.write(txt) bin = CLASS_DICT[code_class] subprocess.check_call([bin, "-T", "svg", src, "-o", dest]) sys.stderr.write('Created image ' + dest + '\n') return Para([Image([ident, [], keyvals], caption, [dest, typef])])
def tikz(key, value, format, meta): if key == 'RawBlock': [fmt, code] = value if fmt == "latex" and re.match("\\\\begin{tikzpicture}", code): outfile = imagedir + '/' + sha1(code) if format == "html": filetype = "png" elif format == "latex": filetype = "pdf" else: filetype = "png" src = outfile + '.' + filetype if not os.path.isfile(src): try: os.mkdir(imagedir) sys.stderr.write('Created directory ' + imagedir + '\n') except OSError: pass tikz2image(code, filetype, outfile) sys.stderr.write('Created image ' + src + '\n') return Para([Image([], [src, ""])])
def plantuml(key, value, format, _): if key == 'CodeBlock': [[ident, classes, keyvals], code] = value if "plantuml" in classes or "puml" in classes: caption, typef, keyvals = get_caption(keyvals) filename = get_filename4code("plantuml", code) #filetype = get_extension(format, "png", html5="svg", html="svg", latex="eps") filetype = get_extension(format, "png", html5="svg", html="svg", latex="png") src = filename + '.uml' dest = filename + '.' + filetype if not os.path.isfile(dest): # エンコード関連をコメントアウト # txt = code.encode(sys.getfilesystemencoding()) # txt = str(code) txt = code if not txt.startswith("@start"): txt = "@startuml\n" + txt + "\n@enduml\n" with open(src, "w") as f: f.write(txt) # フィルターと同じディレクトリにplantuml.jarをおいておく plantuml_jar = os.path.join(os.path.dirname(__file__), "plantuml.jar") # subprosess.callから変更 subprocess.run( ["java", "-jar", plantuml_jar, "-t" + filetype, src]) sys.stderr.write('Created image ' + dest + '\n') return Para([Image([ident, [], keyvals], caption, [dest, typef])])
def environment(key, value, format, meta): # Is it a div and the right format? if key == 'Div' and format in ['latex', 'beamer']: # Get the attributes [[id, classes, properties], content] = value currentClasses = set(classes) for environment, definedClasses in getDefined(meta).items(): # Is the classes correct? if currentClasses >= definedClasses: if id != '': label = '\\label{' + id + '}' else: label = '' currentProperties = dict(properties) if 'title' in currentProperties: title = '[' + currentProperties['title'] + ']' else: title = '' # fix an empty block not rendering any output if len(content) == 0: content = [Para([])] begin = [ RawInline( 'tex', '\\begin{' + environment + '}' + title + '\n' + label) ] content.insert(0, {'t': 'Para', 'c': begin}) end = [RawInline('tex', '\n\\end{' + environment + '}')] content.append({'t': 'Para', 'c': end}) value[1] = content break
def replace_refs(key, value, fmt, meta): """Replaces references to labelled images.""" # Remove braces around references if key in ('Para', 'Plain'): if remove_braces(value): if key == 'Para': return Para(value) else: return Plain(value) # Replace references if is_ref(key, value): prefix, label, suffix = parse_ref(value) # The replacement depends on the output format if fmt == 'latex': return prefix + [RawInline('tex', r'\ref{%s}' % label)] + suffix elif fmt in ('html', 'html5'): link = '<a href="#%s">%s</a>' % (label, references[label]) return prefix + [RawInline('html', link)] + suffix else: return prefix + [Str('%d' % references[label])] + suffix
def plantuml(key, value, format_, _): if key == 'CodeBlock': [[ident, classes, keyvals], code] = value if "plantuml" in classes: caption, typef, keyvals = _get_caption(keyvals, format_) filename = get_filename4code("plantuml", code) filetype = get_extension(format_, "png", html5="svg", latex="svg", context="svg") src = filename + '.uml' dest = filename + '.' + filetype # Generate image only once if not os.path.isfile(dest): txt = code.encode(sys.getfilesystemencoding()) if not txt.startswith(b"@start"): txt = b"@startuml\n" + txt + b"\n@enduml\n" with open(src, "wb") as f: f.write(txt) # The PlantUML complementary jars (PDF modules) have log messages to stdout that corrupt the JSON stream with open('plantUMLErrors.log', "a+") as log_file: subprocess.check_call(PLANTUML_BIN.split() + ["-t" + filetype, src], stdout=log_file, stderr=log_file) sys.stderr.write('Created image ' + dest + '\n') # Update symlink each run for ind, keyval in enumerate(keyvals): if keyval[0] == 'plantuml-filename': link = keyval[1] keyvals.pop(ind) rel_mkdir_symlink(dest, link) dest = link break # print('Caption: ', caption, file=sys.stderr) return Para([Image([ident, [], keyvals], caption, [dest, typef])])