diff --git a/README.md b/README.md index 8aa76ff..4ef23b2 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,9 @@ desired command line to invoke `ditaa`, for example: where `{infile}` and `{outfile}` are placeholders for input and output file names. +### Testing +When running tests, be sure that your `PATH` contains the `ditaa` executable, or set `DTIAA_CMD` appropriately as described above. + Compatiblity: ditaa and fenced_code ----------------------------------- diff --git a/mdx_ditaa.py b/mdx_ditaa.py index cf9ba87..d6ca8a1 100644 --- a/mdx_ditaa.py +++ b/mdx_ditaa.py @@ -3,7 +3,7 @@ """ # The MIT License (MIT) -# +# # Copyright (c) 2014 Sergey Astanin # # Permission is hereby granted, free of charge, to any person obtaining a copy @@ -15,7 +15,7 @@ # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. -# +# # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -28,11 +28,11 @@ import ctypes import os import platform +import shutil import subprocess import tempfile import zlib - from markdown.preprocessors import Preprocessor from markdown.extensions import Extension @@ -45,39 +45,53 @@ def b(string): return string -DITAA_CMD = os.environ.get("DITAA_CMD", "ditaa {infile} {outfile} --overwrite") - - -def generate_image_path(plaintext): - adler32 = ctypes.c_uint32(zlib.adler32(b(plaintext))).value - imgbasename = "diagram-%x.png" % adler32 - ditaa_image_dir = os.environ.get("DITAA_IMAGE_DIR", ".") - imgpath = os.path.join(ditaa_image_dir, imgbasename) - return imgpath - - -def generate_diagram(plaintext): - """Run ditaa with plaintext input. - Return relative path to the generated image. - """ - imgpath = generate_image_path(plaintext) - srcfd, srcfname = tempfile.mkstemp(prefix="ditaasrc", text=True) - outfd, outfname = tempfile.mkstemp(prefix="ditaaout", text=True) - with os.fdopen(srcfd, "w") as src: - src.write(plaintext) - try: - cmd = DITAA_CMD.format(infile=srcfname, outfile=imgpath).split() - with os.fdopen(outfd, "w") as out: - retval = subprocess.check_call(cmd, stdout=out) - return os.path.relpath(imgpath, os.getcwd()) - except: - return None - finally: - os.unlink(srcfname) - os.unlink(outfname) +class DitaaPreprocessor(Preprocessor): + def __init__(self, *args, **config): + self.config = config + + def generate_image_path(self, plaintext): + """ + Return an image path based on a hash of the plaintext input. + """ + adler32 = ctypes.c_uint32(zlib.adler32(b(plaintext))).value + img_basename = "diagram-%x.png" % adler32 + image_path = os.path.join(self.config['ditaa_image_dir'], img_basename) + return image_path + + def generate_diagram(self, plaintext): + """ + Run ditaa with plaintext input. + Return relative path to the generated image. + """ + img_dest = self.generate_image_path(plaintext) + + if os.path.exists(img_dest): + return os.path.relpath(img_dest, os.getcwd()) + + src_fd, src_fname = tempfile.mkstemp(prefix="ditaasrc", text=True) + out_fd, out_fname = tempfile.mkstemp(prefix="ditaaout", text=True) + with os.fdopen(src_fd, "w") as src: + src.write(plaintext) + try: + ditaa_cmd = self.config['ditaa_cmd'] + cmd = ditaa_cmd.format(infile=src_fname, outfile=img_dest) + with os.fdopen(out_fd, "w") as out: + retval = subprocess.check_call(cmd.split(), stdout=out) + + if self.config.get('extra_copy_path', None): + try: + shutil.copy(img_dest, self.config['extra_copy_path']) + except: + pass + return os.path.relpath(img_dest, os.getcwd()) + + except Exception as e: + return None + finally: + os.unlink(src_fname) + os.unlink(out_fname) -class DitaaPreprocessor(Preprocessor): def run(self, lines): START_TAG = "```ditaa" END_TAG = "```" @@ -85,6 +99,7 @@ def run(self, lines): ditaa_prefix = "" ditaa_lines = [] in_diagram = False + path_override = None for ln in lines: if in_diagram: # lines of a diagram if ln == ditaa_prefix + END_TAG: @@ -92,9 +107,14 @@ def run(self, lines): plen = len(ditaa_prefix) ditaa_lines = [dln[plen:] for dln in ditaa_lines] ditaa_code = "\n".join(ditaa_lines) - filename = generate_diagram(ditaa_code) + filename = self.generate_diagram(ditaa_code) if filename: - new_lines.append(ditaa_prefix + "![%s](%s)" % (filename, filename)) + if path_override: + mkdocs_path = os.path.join(path_override, os.path.basename(filename)) + else: + mkdocs_path = os.path.join(self.config['extra_copy_path'], os.path.basename(filename)) + new_lines.append(ditaa_prefix + "![%s](%s)" % (mkdocs_path, mkdocs_path)) + path_override = None else: md_code = [ditaa_prefix + " " + dln for dln in ditaa_lines] new_lines.extend([""] + md_code + [""]) @@ -105,6 +125,11 @@ def run(self, lines): else: # normal lines start = ln.find(START_TAG) prefix = ln[:start] if start >= 0 else "" + postfix = ln[start + len(START_TAG) + 1:] if start >= 0 else "" + if postfix and postfix.startswith('path='): + path_override = postfix[5:] + ln = ln[:-len(postfix) - 1] + # code block may be nested within a list item or a blockquote if start >= 0 and ln.endswith(START_TAG) and not prefix.strip(" \t>"): in_diagram = True @@ -115,11 +140,40 @@ def run(self, lines): class DitaaExtension(Extension): + + PreprocessorClass = DitaaPreprocessor + + def __init__(self, **kwargs): + ditaa_cmd = kwargs.get('ditaa_cmd', 'ditaa {infile} {outfile} --overwrite') + ditaa_image_dir = kwargs.get('ditaa_image_dir', '.') + extra_copy_path = kwargs.get('extra_copy_path', None) + + if 'DITAA_CMD' in os.environ: + ditaa_cmd = os.environ.get("DITAA_CMD") + if 'DITAA_IMAGE_DIR' in os.environ: + ditaa_image_dir = os.environ.get("DITAA_IMAGE_DIR") + + self.config = { + 'ditaa_cmd': [ditaa_cmd, + "Full command line to launch ditaa. Defaults to:" + "{}".format(ditaa_cmd)], + 'ditaa_image_dir': [ditaa_image_dir, + "Full path to directory where images will be saved."], + 'extra_copy_path': [extra_copy_path, + "Set this path to save an extra copy into " + "the specified directory."] + } + + super(DitaaExtension, self).__init__(**kwargs) + def extendMarkdown(self, md, md_globals): + ditaa_ext = self.PreprocessorClass(md, self.config) + ditaa_ext.config = self.getConfigs() + md.registerExtension(self) location = "=2.5'], +)