kr.motd.maven.sphinx.dist.sphinxcontrib.plantuml.py Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of sphinx-maven-plugin Show documentation
Show all versions of sphinx-maven-plugin Show documentation
Maven plugin that creates the site with Sphinx
# -*- coding: utf-8 -*-
"""
sphinxcontrib.plantuml
~~~~~~~~~~~~~~~~~~~~~~
Embed PlantUML diagrams on your documentation.
:copyright: Copyright 2010 by Yuya Nishihara .
:license: BSD, see LICENSE for details.
"""
import errno, hashlib, os, re, shlex, subprocess
from docutils import nodes
from docutils.parsers.rst import directives
from sphinx.errors import SphinxError
from sphinx.util.compat import Directive
from sphinx.util.osutil import ensuredir, ENOENT
try:
from PIL import Image
except ImportError:
Image = None
class PlantUmlError(SphinxError):
pass
class plantuml(nodes.General, nodes.Element):
pass
def align(argument):
align_values = ('left', 'center', 'right')
return directives.choice(argument, align_values)
class UmlDirective(Directive):
"""Directive to insert PlantUML markup
Example::
.. uml::
:alt: Alice and Bob
Alice -> Bob: Hello
Alice <- Bob: Hi
"""
has_content = True
option_spec = {'alt': directives.unchanged,
'caption': directives.unchanged,
'height': directives.length_or_unitless,
'width': directives.length_or_percentage_or_unitless,
'scale': directives.percentage,
'align': align,
}
def run(self):
node = plantuml(self.block_text, **self.options)
node['uml'] = '\n'.join(self.content)
# 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, align=self.options.get('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 generate_name(self, node, fileformat):
key = hashlib.sha1(node['uml'].encode('utf-8')).hexdigest()
fname = 'plantuml-%s.%s' % (key, fileformat)
imgpath = getattr(self.builder, 'imgpath', None)
if imgpath:
return ('/'.join((self.builder.imgpath, fname)),
os.path.join(self.builder.outdir, '_images', fname))
else:
return fname, os.path.join(self.builder.outdir, fname)
_ARGS_BY_FILEFORMAT = {
'eps': '-teps'.split(),
'png': (),
'svg': '-tsvg'.split(),
}
def generate_plantuml_args(self, fileformat):
if isinstance(self.builder.config.plantuml, (tuple, list)):
args = list(self.builder.config.plantuml)
else:
args = shlex.split(self.builder.config.plantuml)
args.extend('-pipe -charset utf-8'.split())
args.extend(_ARGS_BY_FILEFORMAT[fileformat])
return args
def render_plantuml(self, node, fileformat):
refname, outfname = generate_name(self, node, fileformat)
if os.path.exists(outfname):
return refname, outfname # don't regenerate
ensuredir(os.path.dirname(outfname))
f = open(outfname, 'wb')
try:
try:
p = subprocess.Popen(generate_plantuml_args(self, fileformat),
stdout=f, stdin=subprocess.PIPE,
stderr=subprocess.PIPE)
except OSError as err:
if err.errno != ENOENT:
raise
raise PlantUmlError('plantuml command %r cannot be run'
% self.builder.config.plantuml)
serr = p.communicate(node['uml'].encode('utf-8'))[1]
if p.returncode != 0:
raise PlantUmlError('error while running plantuml\n\n%s' % serr)
return refname, outfname
finally:
f.close()
def _get_png_tag(self, fnames, node):
refname, _outfname = fnames['png']
alt = node.get('alt', node['uml'])
# mimic StandaloneHTMLBuilder.post_process_images(). maybe we should
# process images prior to html_vist.
scale_keys = ('scale', 'width', 'height')
if all(key not in node for key in scale_keys) or Image is None:
return ('\n'
% (self.encode(refname), self.encode(alt)))
# Get sizes from the rendered image (defaults)
im = Image.open(_outfname)
im.load()
(fw, fh) = im.size
# Regex to get value and units
vu = re.compile(r"(?P\d+)\s*(?P[a-zA-Z%]+)?")
# Width
if 'width' in node:
m = vu.match(node['width'])
if not m:
raise PlantUmlError('Invalid width')
else:
m = m.groupdict()
w = int(m['value'])
wu = m['units'] if m['units'] else 'px'
else:
w = fw
wu = 'px'
# Height
if 'height' in node:
m = vu.match(node['height'])
if not m:
raise PlantUmlError('Invalid height')
else:
m = m.groupdict()
h = int(m['value'])
hu = m['units'] if m['units'] else 'px'
else:
h = fh
hu = 'px'
# Scale
if 'scale' not in node:
node['scale'] = 100
return (''
'\n'
% (self.encode(refname),
self.encode(refname),
self.encode(alt),
self.encode(w * node['scale'] / 100),
self.encode(wu),
self.encode(h * node['scale'] / 100),
self.encode(hu)))
def _get_svg_style(fname):
f = open(fname)
try:
for l in f:
m = re.search(r'