Hello. I've created a tasty sphinx extension to include embeded Message
Sequence Charts (MSC) using mscgen[1]. I based the plugin in the grpahviz
extension (as mscgen was inspired in graphviz too =) and I hope mscgen
extension can make it into sphinx, because it's a great tool and it
spreading quickly (for example Doxygen alredy support msggen embeded
charts, as well as graphviz graphs).

Attached is the simple module. It requires epstopdf tool for now because
mscgen can't create PDFs directly yet (this is needed only if you want
latex/PDF output).

I've attached a complete test project too as an example (I hope it's OK).

Thanks for the great tool =)


[1] http://www.mcternan.me.uk/mscgen/

-- 
Leandro Lucarella (luca) | Blog colectivo: http://www.mazziblog.com.ar/blog/
----------------------------------------------------------------------------
GPG Key: 5F5A8D05 (F8CD F9A7 BF00 5431 4145  104C 949E BFB6 5F5A 8D05)
----------------------------------------------------------------------------

--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"sphinx-dev" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to 
[email protected]
For more options, visit this group at 
http://groups.google.com/group/sphinx-dev?hl=en
-~----------~----~----~----~------~----~------~--~---

Attachment: mscgen-example.tar.gz
Description: application/tar-gz

# -*- coding: utf-8 -*-
"""
    sphinx.ext.mscgen
    ~~~~~~~~~~~~~~~~~

    Allow mscgen-formatted Message Sequence Chart graphs to be included in
    Sphinx-generated documents inline.

    :copyright: Copyright 2007-2009 by the Sphinx team, see AUTHORS.
    :license: BSD, see LICENSE for details.
"""

import sys
import posixpath
from os import path
from subprocess import Popen, PIPE
try:
    from hashlib import sha1 as sha
except ImportError:
    from sha import sha

from docutils import nodes

from sphinx.errors import SphinxError
from sphinx.util import ensuredir
from sphinx.util.compat import Directive


class MscgenError(SphinxError):
    category = 'mscgen error'


class mscgen(nodes.General, nodes.Element):
    pass


class Mscgen(Directive):
    """
    Directive to insert arbitrary mscgen markup.
    """
    has_content = True
    required_arguments = 0
    optional_arguments = 0
    final_argument_whitespace = False
    option_spec = {}

    def run(self):
        node = mscgen()
        node['code'] = '\n'.join(self.content)
        node['options'] = []
        return [node]


class MscgenSimple(Directive):
    """
    Directive to insert arbitrary mscgen markup.
    """
    has_content = True
    required_arguments = 0
    optional_arguments = 0
    final_argument_whitespace = False
    option_spec = {}

    def run(self):
        node = mscgen()
        node['code'] = 'msc {\n%s\n}\n' % ('\n'.join(self.content))
        node['options'] = []
        return [node]


def run_cmd(builder, cmd, cmd_name, cfg_name, stdin=''):
    try:
        p = Popen(cmd, stdout=PIPE, stdin=PIPE, stderr=PIPE)
    except OSError, err:
        if err.errno != 2:   # No such file or directory
            raise
        builder.warn('%s command %r cannot be run (needed for mscgen '
                          'output), check the %s setting' %
                          cmd_name, builder.config.mscgen, cfg_name)
        builder._mscgen_warned = True
        return False
    stdout, stderr = p.communicate(stdin)
    if p.returncode != 0:
        raise MscgenError('%s exited with error:\n[stderr]\n%s\n'
                            '[stdout]\n%s' % (cmd_name, stderr, stdout))
    return True


def eps_to_pdf(builder, epsfn, pdffn):
    epstopdf_args = [builder.config.mscgen_epstopdf]
    epstopdf_args.extend(builder.config.mscgen_epstopdf_args)
    epstopdf_args.extend(['--outfile=' + pdffn, epsfn])
    return run_cmd(builder, epstopdf_args, 'epstopdf', 'mscgen_epstopdf')


def render_msc(self, code, options, format, prefix='mscgen'):
    """
    Render mscgen code into a PNG or PDF output file.
    """
    hashkey = code.encode('utf-8') + str(options) + \
              str(self.builder.config.mscgen_args)
    fname = '%s-%s.%s' % (prefix, sha(hashkey).hexdigest(), format)
    if hasattr(self.builder, 'imgpath'):
        # HTML
        relfn = posixpath.join(self.builder.imgpath, fname)
        outfn = path.join(self.builder.outdir, '_images', fname)
        epsfn = outfn
    else:
        # LaTeX
        relfn = fname
        outfn = path.join(self.builder.outdir, fname)
        format = 'eps'
        epsfn = outfn[:-3] + format

    if path.isfile(outfn):
        return relfn, outfn

    if hasattr(self.builder, '_mscgen_warned'):
        return None

    ensuredir(path.dirname(outfn))

    # mscgen don't support encodings very well. ISO-8859-1 seems to work best,
    # at least for PNG.
    if isinstance(code, unicode):
        code = code.encode('iso-8859-1')

    mscgen_args = [self.builder.config.mscgen]
    mscgen_args.extend(self.builder.config.mscgen_args)
    mscgen_args.extend(options)
    mscgen_args.extend(['-T', format, '-o', epsfn])
    #if format == 'png':  TODO
    #    mscgen_args.extend(['-Tismap', '-o%s.map' % outfn])
    if not run_cmd(self.builder, mscgen_args, 'mscgen', 'mscgen', code):
        return None

    if epsfn != outfn and not eps_to_pdf(self.builder, epsfn, outfn):
        return None

    return relfn, outfn


def render_msc_html(self, node, code, options, prefix='mscgen', imgcls=None):
    try:
        fname, outfn = render_msc(self, code, options, 'png', prefix)
    except MscgenError, exc:
        self.builder.warn('mscgen code %r: ' % code + str(exc))
        raise nodes.SkipNode

    self.body.append(self.starttag(node, 'p', CLASS='mscgen'))
    if fname is None:
        self.body.append(self.encode(code))
    else:
        #mapfile = open(outfn + '.map', 'rb')
        #try:
        #    imgmap = mapfile.readlines()
        #finally:
        #    mapfile.close()
        imgcss = imgcls and 'class="%s"' % imgcls or ''
        #TODO: if not imgmap:
        if True:
            # nothing in image map
            self.body.append('<img src="%s" alt="%s" %s/>\n' %
                             (fname, self.encode(code).strip(), imgcss))
        else:
            #TODO: mscgen ismap return a space separated list of mappings,
            #      the whole <map>...</map> should be generated by this
            #      plug-in.
            # has a map: get the name of the map and connect the parts
            mapname = mapname_re.match(imgmap[0]).group(1)
            self.body.append('<img src="%s" alt="%s" usemap="#%s" %s/>\n' %
                             (fname, self.encode(code).strip(),
                              mapname, imgcss))
            self.body.extend(imgmap)
    self.body.append('</p>\n')
    raise nodes.SkipNode


def html_visit_mscgen(self, node):
    render_msc_html(self, node, node['code'], node['options'])


def render_msc_latex(self, node, code, options, prefix='mscgen'):
    try:
        fname, outfn = render_msc(self, code, options, 'pdf', prefix)
    except MscgenError, exc:
        self.builder.warn('mscgen code %r: ' % code + str(exc))
        raise nodes.SkipNode

    if fname is not None:
        self.body.append('\\includegraphics{%s}' % fname)
    raise nodes.SkipNode


def latex_visit_mscgen(self, node):
    render_msc_latex(self, node, node['code'], node['options'])

def setup(app):
    app.add_node(mscgen,
                 html=(html_visit_mscgen, None),
                 latex=(latex_visit_mscgen, None))
    app.add_directive('mscgen', Mscgen)
    app.add_directive('msc', MscgenSimple)
    app.add_config_value('mscgen', 'mscgen', 'html')
    app.add_config_value('mscgen_args', [], 'html')
    app.add_config_value('mscgen_epstopdf', 'epstopdf', 'html')
    app.add_config_value('mscgen_epstopdf_args', [], 'html')

Reply via email to