1 # -*- coding: utf-8 -*-
6 Allow embeded ASCII art to be rendered as nice looking images
7 using the aafigure reStructuredText extension.
9 See the README file for details.
11 :author: Leandro Lucarella <llucax@gmail.com>
12 :license: BOLA, see LICENSE for details
18 from hashlib import sha1 as sha
22 from docutils import nodes
23 from docutils.parsers.rst import directives
25 from sphinx.errors import SphinxError
26 from sphinx.util import ensuredir
27 from sphinx.util.compat import Directive
35 DEFAULT_FORMATS = dict(html='svg', latex='pdf', text=None)
38 def merge_dict(dst, src):
39 for (k, v) in src.items():
45 def get_basename(text, options, prefix='aafig'):
46 options = options.copy()
47 if 'format' in options:
49 hashkey = text.encode('utf-8') + str(options)
50 id = sha(hashkey).hexdigest()
51 return '%s-%s' % (prefix, id)
54 class AafigError(SphinxError):
55 category = 'aafig error'
58 class AafigDirective(directives.images.Image):
60 Directive to insert an ASCII art figure to be rendered by aafigure.
63 required_arguments = 0
64 own_option_spec = dict(
69 aspect = directives.nonnegative_int,
70 textual = directives.flag,
71 proportional = directives.flag,
73 option_spec = directives.images.Image.option_spec.copy()
74 option_spec.update(own_option_spec)
77 aafig_options = dict()
79 own_options_keys = self.own_option_spec.keys() + ['scale']
80 for (k, v) in self.options.items():
81 if k in own_options_keys:
82 # convert flags to booleans
85 # convert percentage to float
86 if k == 'scale' or k == 'aspect':
91 (image_node,) = directives.images.Image.run(self)
92 if isinstance(image_node, nodes.system_message):
94 text = '\n'.join(self.content)
95 image_node.aafig = dict(options = aafig_options, text = text)
99 def render_aafig_images(app, doctree):
100 format_map = app.builder.config.aafig_format
101 merge_dict(format_map, DEFAULT_FORMATS)
102 for img in doctree.traverse(nodes.image):
103 if not hasattr(img, 'aafig'):
106 options = img.aafig['options']
107 text = img.aafig['text']
108 format = app.builder.format
109 merge_dict(options, app.builder.config.aafig_default_options)
110 if format in format_map:
111 options['format'] = format_map[format]
113 app.builder.warn('unsupported builder format "%s", please '
114 'add a custom entry in aafig_format config option '
115 'for this builder' % format)
116 img.replace_self(nodes.literal_block(text, text))
118 if options['format'] is None:
119 img.replace_self(nodes.literal_block(text, text))
122 fname, outfn, id, extra = render_aafigure(app, text, options)
123 except AafigError, exc:
124 app.builder.warn('aafigure error: ' + str(exc))
125 img.replace_self(nodes.literal_block(text, text))
128 # FIXME: find some way to avoid this hack in aafigure
130 (width, height) = [x.split('"')[1] for x in extra.split()]
131 if not img.has_key('width'):
133 if not img.has_key('height'):
134 img['height'] = height
137 def render_aafigure(app, text, options):
139 Render an ASCII art figure into the requested format output file.
143 raise AafigError('aafigure module not installed')
145 fname = get_basename(text, options)
146 fname = '%s.%s' % (get_basename(text, options), options['format'])
147 if hasattr(app.builder, 'imgpath'):
149 relfn = posixpath.join(app.builder.imgpath, fname)
150 outfn = path.join(app.builder.outdir, '_images', fname)
154 outfn = path.join(app.builder.outdir, fname)
155 metadata_fname = '%s.aafig' % outfn
158 if path.isfile(outfn):
160 if options['format'].lower() == 'svg':
164 f = file(metadata_fname, 'r')
171 return relfn, outfn, id, extra
175 ensuredir(path.dirname(outfn))
178 (visitor, output) = aafigure.render(text, outfn, options)
180 except aafigure.UnsupportedFormatError, e:
181 raise AafigError(str(e))
184 if options['format'].lower() == 'svg':
185 extra = visitor.get_size_attrs()
186 f = file(metadata_fname, 'w')
190 return relfn, outfn, id, extra
194 app.add_directive('aafig', AafigDirective)
195 app.connect('doctree-read', render_aafig_images)
196 app.add_config_value('aafig_format', DEFAULT_FORMATS, 'html')
197 app.add_config_value('aafig_default_options', dict(), 'html')