Commits

Jon Waltman committed a1da402

Add Texinfo support in ext.graphviz, ext.inheritance_diagram and ext.mathbase.

  • Participants
  • Parent commits e6bc92b

Comments (0)

Files changed (6)

sphinx/builders/texinfo.py

     """
     name = 'texinfo'
     format = 'texinfo'
-    supported_image_types = ['application/pdf', 'image/png',
-                             'image/gif', 'image/jpeg']
+    supported_image_types = ['image/png', 'image/jpeg',
+                             'image/gif',]
 
     def init(self):
         self.docnames = []

sphinx/ext/graphviz.py

 def latex_visit_graphviz(self, node):
     render_dot_latex(self, node, node['code'], node['options'])
 
+
+def render_dot_texinfo(self, node, code, options, prefix='graphviz'):
+    try:
+        fname, outfn = render_dot(self, code, options, 'png', prefix)
+    except GraphvizError, exc:
+        self.builder.warn('dot code %r: ' % code + str(exc))
+        raise nodes.SkipNode
+    if fname is not None:
+        self.body.append('\n\n@float\n')
+        if node.get('caption'):
+            self.body.append('@caption{%s}\n' % self.escape_arg(caption))
+        self.body.append('@image{%s,,,[graphviz],png}\n'
+                         '@end float\n\n' % fname[:-4])
+    raise nodes.SkipNode
+
+def texinfo_visit_graphviz(self, node):
+    render_dot_texinfo(self, node, node['code'], node['options'])
+
+
 def setup(app):
     app.add_node(graphviz,
                  html=(html_visit_graphviz, None),
-                 latex=(latex_visit_graphviz, None))
+                 latex=(latex_visit_graphviz, None),
+                 texinfo=(texinfo_visit_graphviz, None))
     app.add_directive('graphviz', Graphviz)
     app.add_directive('graph', GraphvizSimple)
     app.add_directive('digraph', GraphvizSimple)

sphinx/ext/inheritance_diagram.py

 from docutils import nodes
 from docutils.parsers.rst import directives
 
-from sphinx.ext.graphviz import render_dot_html, render_dot_latex
+from sphinx.ext.graphviz import render_dot_html, render_dot_latex, \
+    render_dot_texinfo
 from sphinx.util.compat import Directive
 
 
     raise nodes.SkipNode
 
 
+def texinfo_visit_inheritance_diagram(self, node):
+    """
+    Output the graph for Texinfo.  This will insert a PNG.
+    """
+    graph = node['graph']
+
+    graph_hash = get_graph_hash(node)
+    name = 'inheritance%s' % graph_hash
+
+    dotcode = graph.generate_dot(name, env=self.builder.env,
+                                 graph_attrs={'size': '"6.0,6.0"'})
+    render_dot_texinfo(self, node, dotcode, [], 'inheritance')
+    raise nodes.SkipNode
+
+
 def skip(self, node):
     raise nodes.SkipNode
 
         html=(html_visit_inheritance_diagram, None),
         text=(skip, None),
         man=(skip, None),
-        texinfo=(skip, None))
+        texinfo=(texinfo_visit_inheritance_diagram, None))
     app.add_directive('inheritance-diagram', InheritanceDiagram)
     app.add_config_value('inheritance_graph_attrs', {}, False),
     app.add_config_value('inheritance_node_attrs', {}, False),

sphinx/ext/mathbase.py

 from docutils import nodes, utils
 from docutils.parsers.rst import directives
 
-from sphinx.writers import texinfo
 from sphinx.util.compat import Directive
 
 
 
 
 def texinfo_visit_math(self, node):
-    self.body.append('@math{' + texinfo.escape_arg(node['latex']) + '}')
+    self.body.append('@math{' + self.escape_arg(node['latex']) + '}')
     raise nodes.SkipNode
 
 def texinfo_visit_displaymath(self, node):
-    self.visit_paragraph(node)
+    if node.get('label'):
+        self.add_anchor(node['label'], node)
+    self.body.append('\n\n@example\n%s\n@end example\n\n' %
+                     self.escape_arg(node['latex']))
 def texinfo_depart_displaymath(self, node):
-    self.depart_paragraph(node)
+    pass
 
 def texinfo_visit_eqref(self, node):
-    self.body.append(node['target'])
+    self.add_xref(node['docname'] + ':' + node['target'],
+                  node['target'], node)
     raise nodes.SkipNode
 
 

sphinx/writers/texinfo.py

     return result
 
 
-## Escaping
-# Which characters to escape depends on the context.  In some cases,
-# namely menus and node names, it's not possible to escape certain
-# characters.
-
-def escape(s):
-    """Return a string with Texinfo command characters escaped."""
-    s = s.replace('@', '@@')
-    s = s.replace('{', '@{')
-    s = s.replace('}', '@}')
-    # Prevent "--" from being converted to an "em dash"
-    # s = s.replace('-', '@w{-}')
-    return s
-
-def escape_arg(s):
-    """Return an escaped string suitable for use as an argument
-    to a Texinfo command."""
-    s = escape(s)
-    # Commas are the argument delimeters
-    s = s.replace(',', '@comma{}')
-    # Normalize white space
-    s = ' '.join(s.split()).strip()
-    return s
-
-def escape_id(s):
-    """Return an escaped string suitable for node names and xrefs anchors."""
-    bad_chars = ',:.()'
-    for bc in bad_chars:
-        s = s.replace(bc, ' ')
-    s = ' '.join(s.split()).strip()
-    return escape(s)
-
-def escape_menu(s):
-    """Return an escaped string suitable for menu entries."""
-    s = escape_arg(s)
-    s = s.replace(':', ';')
-    s = ' '.join(s.split()).strip()
-    return s
-
 class TexinfoWriter(writers.Writer):
     """Texinfo writer for generating Texinfo documents."""
     supported = ('texinfo', 'texi')
         self.curfilestack = []
         self.footnotestack = []
         self.in_footnote = 0
+        self.handled_abbrs = set()
 
     def finish(self):
         if self.previous_section is None:
                 'author': settings.author,
                 # if empty, use basename of input file
                 'filename': settings.texinfo_filename,
-                'release': escape(self.builder.config.release),
-                'project': escape(self.builder.config.project),
-                'copyright': escape(self.builder.config.copyright),
-                'date': escape(self.builder.config.today or
-                               ustrftime(self.builder.config.today_fmt
-                                                or _('%B %d, %Y')))
+                'release': self.escape(self.builder.config.release),
+                'project': self.escape(self.builder.config.project),
+                'copyright': self.escape(self.builder.config.copyright),
+                'date': self.escape(self.builder.config.today or
+                                    ustrftime(self.builder.config.today_fmt
+                                              or _('%B %d, %Y')))
                 })
         # title
         title = elements['title']
         if not title:
             title = self.document.next_node(nodes.title)
             title = (title and title.astext()) or '<untitled>'
-        elements['title'] = escape_id(title) or '<untitled>'
+        elements['title'] = self.escape_id(title) or '<untitled>'
         # filename
         if not elements['filename']:
             elements['filename'] = self.document.get('source') or 'untitled'
         # direntry
         if settings.texinfo_dir_entry:
             entry = self.format_menu_entry(
-                escape_menu(settings.texinfo_dir_entry),
+                self.escape_menu(settings.texinfo_dir_entry),
                 '(%s)' % elements['filename'],
-                escape_arg(settings.texinfo_dir_description))
+                self.escape_arg(settings.texinfo_dir_description))
             elements['direntry'] = ('@dircategory %s\n'
                                     '@direntry\n'
                                     '%s'
                                     '@end direntry\n') % (
-                escape_id(settings.texinfo_dir_category), entry)
+                self.escape_id(settings.texinfo_dir_category), entry)
         elements['copying'] = COPYING % elements
         # allow the user to override them all
         elements.update(settings.texinfo_elements)
         for section in self.document.traverse(nodes.section):
             title = section.next_node(nodes.Titular)
             name = (title and title.astext()) or '<untitled>'
-            node_id = escape_id(name) or '<untitled>'
+            node_id = self.escape_id(name) or '<untitled>'
             assert node_id and name
             nth, suffix = 1, ''
             while node_id + suffix in self.written_ids:
             rellinks['Top'][0] = first
             rellinks[first][1] = 'Top'
 
+    ## Escaping
+    # Which characters to escape depends on the context.  In some cases,
+    # namely menus and node names, it's not possible to escape certain
+    # characters.
+
+    def escape(self, s):
+        """Return a string with Texinfo command characters escaped."""
+        s = s.replace('@', '@@')
+        s = s.replace('{', '@{')
+        s = s.replace('}', '@}')
+        # prevent "--" from being converted to an "em dash"
+        # s = s.replace('-', '@w{-}')
+        return s
+
+    def escape_arg(self, s):
+        """Return an escaped string suitable for use as an argument
+        to a Texinfo command."""
+        s = self.escape(s)
+        # commas are the argument delimeters
+        s = s.replace(',', '@comma{}')
+        # normalize white space
+        s = ' '.join(s.split()).strip()
+        return s
+
+    def escape_id(self, s):
+        """Return an escaped string suitable for node names and anchors."""
+        bad_chars = ',:.()'
+        for bc in bad_chars:
+            s = s.replace(bc, ' ')
+        s = ' '.join(s.split()).strip()
+        return self.escape(s)
+
+    def escape_menu(self, s):
+        """Return an escaped string suitable for menu entries."""
+        s = self.escape_arg(s)
+        s = s.replace(':', ';')
+        s = ' '.join(s.split()).strip()
+        return s
+
     def format_menu_entry(self, name, node_name, desc):
         if name == node_name:
             s = '* %s:: ' % (name,)
                 name, desc = parts
             else:
                 desc = ''
-            name = escape_menu(name)
-            desc = escape(desc)
+            name = self.escape_menu(name)
+            desc = self.escape(desc)
             self.body.append(self.format_menu_entry(name, entry, desc))
 
     def add_menu(self, node_name):
             entries = self.node_menus[name]
             if not entries:
                 return
-            self.body.append('\n%s\n\n' % (escape(self.node_names[name],)))
+            self.body.append('\n%s\n\n' % (self.escape(self.node_names[name],)))
             self.add_menu_entries(entries)
             for subentry in entries:
                 _add_detailed_menu(subentry)
                 for entry in entries:
                     if not entry[3]:
                         continue
-                    name = escape_menu(entry[0])
+                    name = self.escape_menu(entry[0])
                     sid = self.get_short_id('%s:%s' % (entry[2], entry[3]))
-                    desc = escape_arg(entry[6])
+                    desc = self.escape_arg(entry[6])
                     me = self.format_menu_entry(name, sid, desc)
                     ret.append(me)
             ret.append('@end menu\n')
                         self.builder.docnames)
                     if not content:
                         continue
-                    node_name = escape_id(indexcls.localname)
+                    node_name = self.escape_id(indexcls.localname)
                     self.indices.append((node_name,
                                          generate(content, collapsed)))
         self.indices.append((_('Index'), '\n@printindex ge\n'))
         if id.startswith('index-'):
             return
         id = self.curfilestack[-1] + ':' + id
-        eid = escape_id(id)
+        eid = self.escape_id(id)
         sid = self.get_short_id(id)
         for id in (eid, sid):
             if id not in self.written_ids:
                 self.written_ids.add(id)
 
     def add_xref(self, id, name, node):
-        name = escape_menu(name)
+        name = self.escape_menu(name)
         sid = self.get_short_id(id)
         self.body.append('@pxref{%s,,%s}' % (sid, name))
         self.referenced_ids.add(sid)
-        self.referenced_ids.add(escape_id(id))
+        self.referenced_ids.add(self.escape_id(id))
 
     ## Visiting
 
         self.curfilestack.pop()
 
     def visit_Text(self, node):
-        s = escape(node.astext())
+        s = self.escape(node.astext())
         if self.escape_newlines:
             s = s.replace('\n', ' ')
         self.body.append(s)
         if not uri:
             return
         if uri.startswith('mailto:'):
-            uri = escape_arg(uri[7:])
-            name = escape_arg(name)
+            uri = self.escape_arg(uri[7:])
+            name = self.escape_arg(name)
             if not name or name == uri:
                 self.body.append('@email{%s}' % uri)
             else:
         elif uri.startswith('info:'):
             # references to an external Info file
             uri = uri[5:].replace('_', ' ')
-            uri = escape_arg(uri)
+            uri = self.escape_arg(uri)
             id = 'Top'
             if '#' in uri:
                 uri, id = uri.split('#', 1)
-            id = escape_id(id)
-            name = escape_menu(name)
+            id = self.escape_id(id)
+            name = self.escape_menu(name)
             if name == id:
                 self.body.append('@pxref{%s,,,%s}' % (id, uri))
             else:
                 self.body.append('@pxref{%s,,%s,%s}' % (id, name, uri))
         else:
-            uri = escape_arg(uri)
-            name = escape_arg(name)
+            uri = self.escape_arg(uri)
+            name = self.escape_arg(name)
             show_urls = 'footnote'
             if self.in_footnote:
                 show_urls = 'inline'
 
     def visit_title_reference(self, node):
         text = node.astext()
-        self.body.append('@cite{%s}' % escape_arg(text))
+        self.body.append('@cite{%s}' % self.escape_arg(text))
         raise nodes.SkipNode
 
     ## Blocks
 
     def visit_admonition(self, node, name=''):
         if not name:
-            name = escape(node[0].astext())
+            name = self.escape(node[0].astext())
         self.body.append('\n@cartouche\n'
                          '@quotation %s\n' % name)
     def depart_admonition(self, node):
     def _make_visit_admonition(typ):
         def visit(self, node):
             self.body.append('\n@cartouche\n'
-                             '@quotation %s\n' % escape(_(typ)))
+                             '@quotation %s\n' % self.escape(_(typ)))
         return visit
 
     visit_attention = _make_visit_admonition('Attention')
             raise nodes.SkipNode
         title = node[0]
         self.visit_rubric(title)
-        self.body.append('%s\n' % escape(title.astext()))
+        self.body.append('%s\n' % self.escape(title.astext()))
     def depart_topic(self, node):
         pass
 
             return
         name, ext = path.splitext(uri)
         attrs = node.attributes
-        # ignored in non-tex output
+        # width and height ignored in non-tex output
         width = self.tex_image_length(attrs.get('width', ''))
         height = self.tex_image_length(attrs.get('height', ''))
-        alt = escape_arg(attrs.get('alt', ''))
+        alt = self.escape_arg(attrs.get('alt', ''))
         self.body.append('\n\n@image{%s,%s,%s,%s,%s}\n\n' %
                          (name, width, height, alt, ext[1:]))
     def depart_image(self, node):
         raise nodes.SkipNode
 
     def visit_system_message(self, node):
-        self.body.append('\n\n@w{----------- System Message: %s/%s -----------} '
+        self.body.append('\n@w{----------- System Message: %s/%s -----------} '
                          '(%s, line %s)\n' % (
                 node.get('type', '?'),
                 node.get('level', '?'),
-                escape(node.get('source', '?')),
+                self.escape(node.get('source', '?')),
                 node.get('line', '?')))
     def depart_system_message(self, node):
         pass
                 lastname = production['tokenname']
             else:
                 s = '%s    ' % (' '*maxlen)
-            self.body.append(escape(s))
-            self.body.append(escape(production.astext() + '\n'))
+            self.body.append(self.escape(s))
+            self.body.append(self.escape(production.astext() + '\n'))
         self.depart_literal_block(None)
         raise nodes.SkipNode
 
     def visit_index(self, node):
         for entry in node['entries']:
             typ, text, tid, text2 = entry
-            text = escape_menu(text)
+            text = self.escape_menu(text)
             self.body.append('@geindex %s\n' % text)
 
     def visit_refcount(self, node):
             intro += ': '
         else:
             intro += '.'
-        self.body.append('\n\n%s' % escape(intro))
+        self.body.append('\n\n%s' % self.escape(intro))
     def depart_versionmodified(self, node):
         self.body.append('\n\n')
 
         self.footnotestack.pop()
 
     def visit_centered(self, node):
-        txt = escape_arg(node.astext())
+        txt = self.escape_arg(node.astext())
         self.body.append('\n\n@center %s\n\n' % txt)
         raise nodes.SkipNode
 
         pass
 
     def visit_acks(self, node):
-        pass
-    def depart_acks(self, node):
-        pass
+        self.body.append('\n\n')
+        self.body.append(', '.join(n.astext()
+                                for n in node.children[0].children) + '.')
+        self.body.append('\n\n')
+        raise nodes.SkipNode
 
     def visit_highlightlang(self, node):
         pass
     ## Desc
 
     desc_map = {
-        'function' : 'Function',
-        'class': 'Class',
-        'method': 'Method',
-        'classmethod': 'Class Method',
+        'cfunction':    'C Function',
+        'classmethod':  'Class Method',
+        'cmacro':       'C Macro',
+        'cmdoption':    'Command Option',
+        'cmember':      'C Member',
+        'confval':      'Configuration Value',
+        'ctype':        'C Type',
+        'cvar':         'C Variable',
+        'describe':     'Description',
+        'envvar':       'Environment Variable',
         'staticmethod': 'Static Method',
-        'exception': 'Exception',
-        'data': 'Data',
-        'attribute': 'Attribute',
-        'opcode': 'Opcode',
-        'cfunction': 'C Function',
-        'cmember': 'C Member',
-        'cmacro': 'C Macro',
-        'ctype': 'C Type',
-        'cvar': 'C Variable',
-        'cmdoption': 'Option',
-        'describe': 'Description',
+        'var':          'Variable',
         }
 
     def visit_desc(self, node):
                 self.add_anchor(id, node)
         typ = _(self.desc_map.get(self.desctype,
                                   self.desctype.capitalize()))
-        self.body.append('\n%s {%s} ' % (self.at_deffnx, escape_arg(typ)))
+        self.body.append('\n%s {%s} ' % (self.at_deffnx, self.escape_arg(typ)))
         self.at_deffnx = '@deffnx'
     def depart_desc_signature(self, node):
         self.body.append("\n")
             self.body.append(', ')
         else:
             self.first_param = 0
-        self.body.append(escape(node.astext()))
+        self.body.append(self.escape(node.astext()))
         raise nodes.SkipNode
 
     def visit_desc_optional(self, node):
         pass
 
     def visit_abbreviation(self, node):
+        abbr = node.astext()
         self.body.append('@abbr{')
-        if node.hasattr('explanation'):
-            self.context.append(', %s}' % escape_arg(node['explanation']))
+        if node.hasattr('explanation') and abbr not in self.handled_abbrs:
+            self.context.append(',%s}' % self.escape_arg(node['explanation']))
+            self.handled_abbrs.add(abbr)
         else:
             self.context.append('}')
     def depart_abbreviation(self, node):

tests/test_build_texinfo.py

 
 TEXINFO_WARNINGS = ENV_WARNINGS + """\
 None:None: WARNING: no matching candidate for image URI u'foo.\\*'
+None:None: WARNING: no matching candidate for image URI u'svgimg.\\*'
 """
 
 if sys.version_info >= (3, 0):