text: # -*- coding: utf-8 -*-
  """
      sphinx.writers.texinfo
      ~~~~~~~~~~~~~~~~~~~~~~
  
      Custom docutils writer for Texinfo.
  
      :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
      :license: BSD, see LICENSE for details.
  """
  
  import re
  import string
  import textwrap
  from os import path
  
  from docutils import nodes, writers
  
  from sphinx import addnodes, __version__
  from sphinx.locale import versionlabels, _
  from sphinx.util import ustrftime
  from sphinx.writers.latex import collected_footnote
  
  
  COPYING = """\
  @quotation
  %(project)s %(release)s, %(date)s
  
  %(author)s
  
  Copyright @copyright{} %(copyright)s
  @end quotation
  """
  
  TEMPLATE = """\
  \\input texinfo   @c -*-texinfo-*-
  @c %%**start of header
  @setfilename %(filename)s
  @documentencoding UTF-8
  @ifinfo
  @*Generated by Sphinx """ + __version__ + """.@*
  @end ifinfo
  @settitle %(title)s
  @defindex ge
  @paragraphindent %(paragraphindent)s
  @exampleindent %(exampleindent)s
  @afourlatex
  %(direntry)s
  @c %%**end of header
  
  @copying
  %(copying)s
  @end copying
  
  @titlepage
  @title %(title)s
  @insertcopying
  @end titlepage
  @contents
  
  @c %%** start of user preamble
  %(preamble)s
  @c %%** end of user preamble
  
  @ifnottex
  @node Top
  @top %(title)s
  @insertcopying
  @end ifnottex
  
  @c %%**start of body
  %(body)s
  @c %%**end of body
  @bye
  """
  
  
  def find_subsections(section):
      """Return a list of subsections for the given ``section``."""
      result = []
      for child in section.children:
          if isinstance(child, nodes.section):
              result.append(child)
              continue
          result.extend(find_subsections(child))
      return result
  
  
  class TexinfoWriter(writers.Writer):
      """Texinfo writer for generating Texinfo documents."""
      supported = ('texinfo', 'texi')
  
      settings_spec = (
          'Texinfo Specific Options', None, (
              ("Name of the Info file", ['--texinfo-filename'], {'default': ''}),
              ('Dir entry', ['--texinfo-dir-entry'], {'default': ''}),
              ('Description', ['--texinfo-dir-description'], {'default': ''}),
              ('Category', ['--texinfo-dir-category'], {'default':
                                                            'Miscellaneous'})))
  
      settings_defaults = {}
  
      output = None
  
      visitor_attributes = ('output', 'fragment')
  
      def __init__(self, builder):
          writers.Writer.__init__(self)
          self.builder = builder
  
      def translate(self):
          self.visitor = visitor = TexinfoTranslator(self.document, self.builder)
          self.document.walkabout(visitor)
          visitor.finish()
          for attr in self.visitor_attributes:
              setattr(self, attr, getattr(visitor, attr))
  
  
  class TexinfoTranslator(nodes.NodeVisitor):
  
      ignore_missing_images = False
  
      default_elements = {
          'author': '',
          'body': '',
          'copying': '',
          'date': '',
          'direntry': '',
          'exampleindent': 4,
          'filename': '',
          'paragraphindent': 2,
          'preamble': '',
          'project': '',
          'release': '',
          'title': '',
          }
  
      def __init__(self, document, builder):
          nodes.NodeVisitor.__init__(self, document)
          self.builder = builder
          self.init_settings()
  
          self.written_ids = set()    # node names and anchors in output
          self.referenced_ids = set() # node names and anchors that should
                                      # be in output
          self.indices = []     # (node name, content)
          self.short_ids = {}   # anchors --> short ids
          self.node_names = {}  # node name --> node's name to display
          self.node_menus = {}  # node name --> node's menu entries
          self.rellinks = {}    # node name --> (next, previous, up)
  
          self.collect_indices()
          self.collect_node_names()
          self.collect_node_menus()
          self.collect_rellinks()
  
          self.body = []
          self.context = []
          self.previous_section = None
          self.section_level = 0
          self.seen_title = False
          self.next_section_ids = set()
          self.escape_newlines = 0
          self.curfilestack = []
          self.footnotestack = []
          self.in_footnote = 0
          self.handled_abbrs = set()
  
      def finish(self):
          if self.previous_section is None:
              self.add_menu('Top')
          for index in self.indices:
              name, content = index
              pointers = tuple([name] + self.rellinks[name])
              self.body.append('\n@node %s,%s,%s,%s\n' % pointers)
              self.body.append('@unnumbered %s\n\n%s\n' % (name, content))
  
          while self.referenced_ids:
              # handle xrefs with missing anchors
              r = self.referenced_ids.pop()
              if r not in self.written_ids:
                  self.body.append('@anchor{%s}@w{%s}\n' % (r, ' ' * 30))
          self.fragment = ''.join(self.body).strip() + '\n'
          self.elements['body'] = self.fragment
          self.output = TEMPLATE % self.elements
  
      ## Helper routines
  
      def init_settings(self):
          settings = self.settings = self.document.settings
          elements = self.elements = self.default_elements.copy()
          elements.update({
                  # if empty, the title is set to the first section title
                  'title': settings.title,
                  'author': settings.author,
                  # if empty, use basename of input file
                  'filename': settings.texinfo_filename,
                  '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'] = self.escape_id(title) or '<untitled>'
          # filename
          if not elements['filename']:
              elements['filename'] = self.document.get('source') or 'untitled'
              if elements['filename'][-4:] in ('.txt', '.rst'):
                  elements['filename'] = elements['filename'][:-4]
              elements['filename'] += '.info'
          # direntry
          if settings.texinfo_dir_entry:
              entry = self.format_menu_entry(
                  self.escape_menu(settings.texinfo_dir_entry),
                  '(%s)' % elements['filename'],
                  self.escape_arg(settings.texinfo_dir_description))
              elements['direntry'] = ('@dircategory %s\n'
                                      '@direntry\n'
                                      '%s'
                                      '@end direntry\n') % (
                  self.escape_id(settings.texinfo_dir_category), entry)
          elements['copying'] = COPYING % elements
          # allow the user to override them all
          elements.update(settings.texinfo_elements)
  
      def collect_node_names(self):
          """Generates a unique id for each section.
  
          Assigns the attribute ``node_name`` to each section."""
          # must have a "Top" node
          self.document['node_name'] = 'Top'
          self.node_names['Top'] = 'Top'
          self.written_ids.update(('Top', 'top'))
          # each index is a node
          for name, content in self.indices:
              self.node_names[name] = name
              self.written_ids.add(name)
          # each section is also a node
          for section in self.document.traverse(nodes.section):
              title = section.next_node(nodes.Titular)
              name = (title and title.astext()) 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:
                  nth += 1
                  suffix = '<%s>' % nth
              node_id += suffix
              assert node_id not in self.node_names
              assert node_id not in self.written_ids
              section['node_name'] = node_id
              self.node_names[node_id] = name
              self.written_ids.add(node_id)
  
      def collect_node_menus(self):
          """Collect the menu entries for each "node" section."""
          node_menus = self.node_menus
          for node in ([self.document] +
                       self.document.traverse(nodes.section)):
              assert 'node_name' in node and node['node_name']
              entries = [s['node_name'] for s in find_subsections(node)]
              node_menus[node['node_name']] = entries
          # try to find a suitable "Top" node
          title = self.document.next_node(nodes.title)
          top = (title and title.parent) or self.document
          if not isinstance(top, (nodes.document, nodes.section)):
              top = self.document
          if top is not self.document:
              entries = node_menus[top['node_name']]
              entries += node_menus['Top'][1:]
              node_menus['Top'] = entries
              del node_menus[top['node_name']]
              top['node_name'] = 'Top'
          # handle the indices
          for name, content in self.indices:
              node_menus[name] = ()
              node_menus['Top'].append(name)
  
      def collect_rellinks(self):
          """Collect the relative links (next, previous, up) for each "node"."""
          rellinks = self.rellinks
          node_menus = self.node_menus
          for id, entries in node_menus.items():
              rellinks[id] = ['', '', '']
          # up's
          for id, entries in node_menus.items():
              for e in entries:
                  rellinks[e][2] = id
          # next's and prev's
          for id, entries in node_menus.items():
              for i, id in enumerate(entries):
                  # First child's prev is empty
                  if i != 0:
                      rellinks[id][1] = entries[i-1]
                  # Last child's next is empty
                  if i != len(entries) - 1:
                      rellinks[id][0] = entries[i+1]
          # top's next is its first child
          try:
              first = node_menus['Top'][0]
          except IndexError:
              pass
          else:
              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 `` and '' quote conversion
          s = s.replace('``', "`@w{`}")
          s = s.replace("''", "'@w{'}")
          # 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 ensure_eol(self):
          """Ensure the last line in body is terminated by new line."""
          if self.body and self.body[-1][-1:] != '\n':
              self.body.append('\n')
  
      def format_menu_entry(self, name, node_name, desc):
          if name == node_name:
              s = '* %s:: ' % (name,)
          else:
              s = '* %s: %s. ' % (name, node_name)
          offset = max((24, (len(name) + 4) % 78))
          wdesc = '\n'.join(' ' * offset + l for l in
                            textwrap.wrap(desc, width=78-offset))
          return s + wdesc.strip() + '\n'
  
      def add_menu_entries(self, entries, reg=re.compile(r'\s+---?\s+')):
          for entry in entries:
              name = self.node_names[entry]
              # special formatting for entries that are divided by an em-dash
              parts = reg.split(name, 1)
              if len(parts) == 2:
                  name, desc = parts
              else:
                  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[node_name]
          if not entries:
              return
          self.body.append('\n@menu\n')
          self.add_menu_entries(entries)
          if not self.node_menus[entries[0]]:
              self.body.append('\n@end menu\n')
              return
  
          def _add_detailed_menu(name):
              entries = self.node_menus[name]
              if not entries:
                  return
              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)
  
          if node_name == 'Top':
              self.body.append('\n@detailmenu\n'
                               ' --- The Detailed Node Listing ---\n')
          for entry in entries:
              _add_detailed_menu(entry)
          if node_name == 'Top':
              self.body.append('\n@end detailmenu')
          self.body.append('\n@end menu\n\n')
  
      def tex_image_length(self, width_str):
          match = re.match('(\d*\.?\d*)\s*(\S*)', width_str)
          if not match:
              # fallback
              return width_str
          res = width_str
          amount, unit = match.groups()[:2]
          if not unit or unit == "px":
              # pixels: let TeX alone
              return ''
          elif unit == "%":
              # a4paper: textwidth=418.25368pt
              res = "%d.0pt" % (float(amount) * 4.1825368)
          return res
  
      def collect_indices(self):
          def generate(content, collapsed):
              ret = ['\n@menu\n']
              for letter, entries in content:
                  for entry in entries:
                      if not entry[3]:
                          continue
                      name = self.escape_menu(entry[0])
                      sid = self.get_short_id('%s:%s' % (entry[2], entry[3]))
                      desc = self.escape_arg(entry[6])
                      me = self.format_menu_entry(name, sid, desc)
                      ret.append(me)
              ret.append('@end menu\n')
              return ''.join(ret)
  
          indices_config = self.builder.config.texinfo_domain_indices
          if indices_config:
              for domain in self.builder.env.domains.itervalues():
                  for indexcls in domain.indices:
                      indexname = '%s-%s' % (domain.name, indexcls.name)
                      if isinstance(indices_config, list):
                          if indexname not in indices_config:
                              continue
                      content, collapsed = indexcls(domain).generate(
                          self.builder.docnames)
                      if not content:
                          continue
                      node_name = self.escape_id(indexcls.localname)
                      self.indices.append((node_name,
                                           generate(content, collapsed)))
          self.indices.append((_('Index'), '\n@printindex ge\n'))
  
      # this is copied from the latex writer
      # TODO: move this to sphinx.util
  
      def collect_footnotes(self, node):
          fnotes = {}
          def footnotes_under(n):
              if isinstance(n, nodes.footnote):
                  yield n
              else:
                  for c in n.children:
                      if isinstance(c, addnodes.start_of_file):
                          continue
                      for k in footnotes_under(c):
                          yield k
          for fn in footnotes_under(node):
              num = fn.children[0].astext().strip()
              fnotes[num] = [collected_footnote(*fn.children), False]
          return fnotes
  
      ## xref handling
  
      def get_short_id(self, id):
          """Return a shorter 'id' associated with ``id``."""
          # Shorter ids improve paragraph filling in places
          # that the id is hidden by Emacs.
          try:
              sid = self.short_ids[id]
          except KeyError:
              sid = hex(len(self.short_ids))[2:]
              self.short_ids[id] = sid
          return sid
  
      def add_anchor(self, id, node):
          if id.startswith('index-'):
              return
          id = self.curfilestack[-1] + ':' + 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.body.append('@anchor{%s}' % id)
                  self.written_ids.add(id)
  
      def add_xref(self, id, name, node):
          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(self.escape_id(id))
  
      ## Visiting
  
      def visit_document(self, node):
          self.footnotestack.append(self.collect_footnotes(node))
          self.curfilestack.append(node.get('docname', ''))
          if 'docname' in node:
              self.add_anchor(':doc', node)
      def depart_document(self, node):
          self.footnotestack.pop()
          self.curfilestack.pop()
  
      def visit_Text(self, node):
          s = self.escape(node.astext())
          if self.escape_newlines:
              s = s.replace('\n', ' ')
          self.body.append(s)
      def depart_Text(self, node):
          pass
  
      def visit_section(self, node):
          self.next_section_ids.update(node.get('ids', []))
          if not self.seen_title:
              return
          if self.previous_section:
              self.add_menu(self.previous_section['node_name'])
          else:
              self.add_menu('Top')
  
          node_name = node['node_name']
          pointers = tuple([node_name] + self.rellinks[node_name])
          self.body.append('\n@node %s,%s,%s,%s\n' % pointers)
          for id in self.next_section_ids:
              self.add_anchor(id, node)
  
          self.next_section_ids.clear()
          self.previous_section = node
          self.section_level += 1
  
      def depart_section(self, node):
          self.section_level -= 1
  
      headings = (
          '@unnumbered',
          '@chapter',
          '@section',
          '@subsection',
          '@subsubsection',
          )
  
      rubrics = (
          '@heading',
          '@subheading',
          '@subsubheading',
          )
  
      def visit_title(self, node):
          if not self.seen_title:
              self.seen_title = 1
              raise nodes.SkipNode
          parent = node.parent
          if isinstance(parent, nodes.table):
              return
          if isinstance(parent, (nodes.Admonition, nodes.sidebar, nodes.topic)):
              raise nodes.SkipNode
          elif not isinstance(parent, nodes.section):
              self.builder.warn(
                  'encountered title node not in section, topic, table, '
                  'admonition or sidebar', (self.curfilestack[-1], node.line))
              self.visit_rubric(node)
          else:
              try:
                  heading = self.headings[self.section_level]
              except IndexError:
                  heading = self.headings[-1]
              self.body.append('\n%s ' % heading)
  
      def depart_title(self, node):
          self.body.append('\n\n')
  
      def visit_rubric(self, node):
          if len(node.children) == 1 and node.children[0].astext() in \
                  ('Footnotes', _('Footnotes')):
              raise nodes.SkipNode
          try:
              rubric = self.rubrics[self.section_level]
          except IndexError:
              rubric = self.rubrics[-1]
          self.body.append('\n%s ' % rubric)
      def depart_rubric(self, node):
          self.body.append('\n\n')
  
      def visit_subtitle(self, node):
          self.body.append('\n\n@noindent\n')
      def depart_subtitle(self, node):
          self.body.append('\n\n')
  
      ## References
  
      def visit_target(self, node):
          # postpone the labels until after the sectioning command
          parindex = node.parent.index(node)
          try:
              try:
                  next = node.parent[parindex+1]
              except IndexError:
                  # last node in parent, look at next after parent
                  # (for section of equal level)
                  next = node.parent.parent[node.parent.parent.index(node.parent)]
              if isinstance(next, nodes.section):
                  if node.get('refid'):
                      self.next_section_ids.add(node['refid'])
                  self.next_section_ids.update(node['ids'])
                  return
          except IndexError:
              pass
          if 'refuri' in node:
              return
          if node.get('refid'):
              self.add_anchor(node['refid'], node)
          for id in node['ids']:
              self.add_anchor(id, node)
      def depart_target(self, node):
          pass
  
      def visit_reference(self, node):
          # an xref's target is displayed in Info so we ignore a few
          # cases for the sake of appearance
          if isinstance(node.parent, (nodes.title, addnodes.desc_type,)):
              return
          if isinstance(node[0], nodes.image):
              return
          name = node.get('name', node.astext()).strip()
          uri = node.get('refuri', '')
          if not uri and node.get('refid'):
              uri = '%' + self.curfilestack[-1] + '#' + node['refid']
          if not uri:
              return
          if uri.startswith('mailto:'):
              uri = self.escape_arg(uri[7:])
              name = self.escape_arg(name)
              if not name or name == uri:
                  self.body.append('@email{%s}' % uri)
              else:
                  self.body.append('@email{%s,%s}' % (uri, name))
          elif uri.startswith('#'):
              # references to labels in the same document
              id = self.curfilestack[-1] + ':' + uri[1:]
              self.add_xref(id, name, node)
          elif uri.startswith('%'):
              # references to documents or labels inside documents
              hashindex = uri.find('#')
              if hashindex == -1:
                  # reference to the document
                  id = uri[1:] + '::doc'
              else:
                  # reference to a label
                  id = uri[1:].replace('#', ':')
              self.add_xref(id, name, node)
          elif uri.startswith('info:'):
              # references to an external Info file
              uri = uri[5:].replace('_', ' ')
              uri = self.escape_arg(uri)
              id = 'Top'
              if '#' in uri:
                  uri, id = uri.split('#', 1)
              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 = self.escape_arg(uri)
              name = self.escape_arg(name)
              show_urls = self.builder.config.texinfo_show_urls
              if self.in_footnote:
                  show_urls = 'inline'
              if not name or uri == name:
                  self.body.append('@indicateurl{%s}' % uri)
              elif show_urls == 'inline':
                  self.body.append('@uref{%s,%s}' % (uri, name))
              elif show_urls == 'no':
                  self.body.append('@uref{%s,,%s}' % (uri, name))
              else:
                  self.body.append('%s@footnote{%s}' % (name, uri))
          raise nodes.SkipNode
  
      def depart_reference(self, node):
          pass
  
      def visit_title_reference(self, node):
          text = node.astext()
          self.body.append('@cite{%s}' % self.escape_arg(text))
          raise nodes.SkipNode
  
      ## Blocks
  
      def visit_paragraph(self, node):
          if 'continued' in node or isinstance(node.parent, nodes.compound):
              self.body.append('\n@noindent')
          self.body.append('\n')
      def depart_paragraph(self, node):
          self.body.append('\n')
  
      def visit_block_quote(self, node):
          self.body.append('\n@quotation\n')
      def depart_block_quote(self, node):
          self.ensure_eol()
          self.body.append('@end quotation\n')
  
      def visit_literal_block(self, node):
          self.body.append('\n@example\n')
      def depart_literal_block(self, node):
          self.body.append('\n@end example\n\n'
                           '@noindent\n')
  
      visit_doctest_block = visit_literal_block
      depart_doctest_block = depart_literal_block
  
      def visit_line_block(self, node):
          if not isinstance(node.parent, nodes.line_block):
              self.body.append('\n\n')
          self.body.append('@display\n')
      def depart_line_block(self, node):
          self.body.append('@end display\n')
          if not isinstance(node.parent, nodes.line_block):
              self.body.append('\n\n')
  
      def visit_line(self, node):
          self.escape_newlines += 1
      def depart_line(self, node):
          self.body.append('@w{ }\n')
          self.escape_newlines -= 1
  
      ## Inline
  
      def visit_strong(self, node):
          self.body.append('@strong{')
      def depart_strong(self, node):
          self.body.append('}')
  
      def visit_emphasis(self, node):
          self.body.append('@emph{')
      def depart_emphasis(self, node):
          self.body.append('}')
  
      def visit_literal(self, node):
          self.body.append('@code{')
      def depart_literal(self, node):
          self.body.append('}')
  
      def visit_superscript(self, node):
          self.body.append('@w{^')
      def depart_superscript(self, node):
          self.body.append('}')
  
      def visit_subscript(self, node):
          self.body.append('@w{[')
      def depart_subscript(self, node):
          self.body.append(']}')
  
      ## Footnotes
  
      def visit_footnote(self, node):
          raise nodes.SkipNode
  
      def visit_collected_footnote(self, node):
          self.in_footnote += 1
          self.body.append('@footnote{')
      def depart_collected_footnote(self, node):
          self.body.append('}')
          self.in_footnote -= 1
  
      def visit_footnote_reference(self, node):
          num = node.astext().strip()
          try:
              footnode, used = self.footnotestack[-1][num]
          except (KeyError, IndexError):
              raise nodes.SkipNode
          # footnotes are repeated for each reference
          footnode.walkabout(self)
          raise nodes.SkipChildren
  
      def visit_citation(self, node):
          for id in node.get('ids'):
              self.add_anchor(id, node)
      def depart_citation(self, node):
          pass
  
      def visit_citation_reference(self, node):
          self.body.append('@w{[')
      def depart_citation_reference(self, node):
          self.body.append(']}')
  
      ## Lists
  
      def visit_bullet_list(self, node):
          bullet = node.get('bullet', '*')
          self.body.append('\n\n@itemize %s\n' % bullet)
      def depart_bullet_list(self, node):
          self.ensure_eol()
          self.body.append('@end itemize\n')
  
      def visit_enumerated_list(self, node):
          # doesn't support Roman numerals
          enum = node.get('enumtype', 'arabic')
          starters = {'arabic': '',
                      'loweralpha': 'a',
                      'upperalpha': 'A',}
          start = node.get('start', starters.get(enum, ''))
          self.body.append('\n\n@enumerate %s\n' % start)
      def depart_enumerated_list(self, node):
          self.ensure_eol()
          self.body.append('@end enumerate\n')
  
      def visit_list_item(self, node):
          self.body.append('\n@item ')
      def depart_list_item(self, node):
          pass
  
      ## Option List
  
      def visit_option_list(self, node):
          self.body.append('\n\n@table @option\n')
      def depart_option_list(self, node):
          self.ensure_eol()
          self.body.append('@end table\n')
  
      def visit_option_list_item(self, node):
          pass
      def depart_option_list_item(self, node):
          pass
  
      def visit_option_group(self, node):
          self.at_item_x = '@item'
      def depart_option_group(self, node):
          pass
  
      def visit_option(self, node):
          self.body.append('\n%s ' % self.at_item_x)
          self.at_item_x = '@itemx'
      def depart_option(self, node):
          pass
  
      def visit_option_string(self, node):
          pass
      def depart_option_string(self, node):
          pass
  
      def visit_option_argument(self, node):
          self.body.append(node.get('delimiter', ' '))
      def depart_option_argument(self, node):
          pass
  
      def visit_description(self, node):
          self.body.append('\n')
      def depart_description(self, node):
          pass
  
      ## Definitions
  
      def visit_definition_list(self, node):
          self.body.append('\n\n@table @asis\n')
      def depart_definition_list(self, node):
          self.ensure_eol()
          self.body.append('@end table\n')
  
      def visit_definition_list_item(self, node):
          self.at_item_x = '@item'
      def depart_definition_list_item(self, node):
          pass
  
      def visit_term(self, node):
          for id in node.get('ids'):
              self.add_anchor(id, node)
          # anchors and indexes need to go in front
          for n in node[::]:
              if isinstance(n, (addnodes.index, nodes.target)):
                  n.walkabout(self)
                  node.remove(n)
          self.body.append('\n%s ' % self.at_item_x)
          self.at_item_x = '@itemx'
      def depart_term(self, node):
          pass
  
      def visit_termsep(self, node):
          self.body.append('\n%s ' % self.at_item_x)
      def depart_termsep(self, node):
          pass
  
      def visit_classifier(self, node):
          self.body.append(' : ')
      def depart_classifier(self, node):
          pass
  
      def visit_definition(self, node):
          self.body.append('\n')
      def depart_definition(self, node):
          pass
  
      ## Tables
  
      def visit_table(self, node):
          self.entry_sep = '@item'
      def depart_table(self, node):
          self.body.append('\n@end multitable\n\n')
  
      def visit_tabular_col_spec(self, node):
          pass
      def depart_tabular_col_spec(self, node):
          pass
  
      def visit_colspec(self, node):
          self.colwidths.append(node['colwidth'])
          if len(self.colwidths) != self.n_cols:
              return
          self.body.append('\n\n@multitable ')
          for i, n in enumerate(self.colwidths):
              self.body.append('{%s} ' %('x' * (n+2)))
      def depart_colspec(self, node):
          pass
  
      def visit_tgroup(self, node):
          self.colwidths = []
          self.n_cols = node['cols']
      def depart_tgroup(self, node):
          pass
  
      def visit_thead(self, node):
          self.entry_sep = '@headitem'
      def depart_thead(self, node):
          pass
  
      def visit_tbody(self, node):
          pass
      def depart_tbody(self, node):
          pass
  
      def visit_row(self, node):
          pass
      def depart_row(self, node):
          self.entry_sep = '@item'
  
      def visit_entry(self, node):
          self.body.append('\n%s\n' % self.entry_sep)
          self.entry_sep = '@tab'
      def depart_entry(self, node):
          for i in xrange(node.get('morecols', 0)):
              self.body.append('\n@tab\n')
  
      ## Field Lists
  
      def visit_field_list(self, node):
          self.body.append('\n\n@itemize @w\n')
      def depart_field_list(self, node):
          self.ensure_eol()
          self.body.append('@end itemize\n')
  
      def visit_field(self, node):
          if not isinstance(node.parent, nodes.field_list):
              self.visit_field_list(node)
      def depart_field(self, node):
          if not isinstance(node.parent, nodes.field_list):
              self.depart_field_list(node)
  
      def visit_field_name(self, node):
          self.body.append('\n@item ')
      def depart_field_name(self, node):
          self.body.append(': ')
  
      def visit_field_body(self, node):
          pass
      def depart_field_body(self, node):
          pass
  
      ## Admonitions
  
      def visit_admonition(self, node, name=''):
          if not name:
              name = self.escape(node[0].astext())
          self.body.append('\n@cartouche\n'
                           '@quotation %s ' % name)
      def depart_admonition(self, node):
          self.ensure_eol()
          self.body.append('@end quotation\n'
                           '@end cartouche\n')
  
      def _make_visit_admonition(typ):
          def visit(self, node):
              self.visit_admonition(node, self.escape(_(typ)))
          return visit
  
      visit_attention = _make_visit_admonition('Attention')
      depart_attention = depart_admonition
      visit_caution = _make_visit_admonition('Caution')
      depart_caution = depart_admonition
      visit_danger = _make_visit_admonition('Danger')
      depart_danger = depart_admonition
      visit_error = _make_visit_admonition('Error')
      depart_error = depart_admonition
      visit_important = _make_visit_admonition('Important')
      depart_important = depart_admonition
      visit_note = _make_visit_admonition('Note')
      depart_note = depart_admonition
      visit_tip = _make_visit_admonition('Tip')
      depart_tip = depart_admonition
      visit_hint = _make_visit_admonition('Hint')
      depart_hint = depart_admonition
      visit_warning = _make_visit_admonition('Warning')
      depart_warning = depart_admonition
  
      ## Misc
  
      def visit_docinfo(self, node):
          raise nodes.SkipNode
  
      def visit_generated(self, node):
          raise nodes.SkipNode
  
      def visit_header(self, node):
          raise nodes.SkipNode
  
      def visit_footer(self, node):
          raise nodes.SkipNode
  
      def visit_container(self, node):
          pass
      def depart_container(self, node):
          pass
  
      def visit_decoration(self, node):
          pass
      def depart_decoration(self, node):
          pass
  
      def visit_topic(self, node):
          # ignore TOC's since we have to have a "menu" anyway
          if 'contents' in node.get('classes', []):
              raise nodes.SkipNode
          title = node[0]
          self.visit_rubric(title)
          self.body.append('%s\n' % self.escape(title.astext()))
      def depart_topic(self, node):
          pass
  
      def visit_transition(self, node):
          self.body.append('\n\n@exdent @w{    %s}\n\n' % ('* ' * 30))
      def depart_transition(self, node):
          pass
  
      def visit_attribution(self, node):
          self.body.append('\n\n@center --- ')
      def depart_attribution(self, node):
          self.body.append('\n\n')
  
      def visit_raw(self, node):
          format = node.get('format', '').split()
          if 'texinfo' in format or 'texi' in format:
              self.body.append(node.astext())
          raise nodes.SkipNode
  
      def visit_figure(self, node):
          self.body.append('\n\n@float Figure\n')
      def depart_figure(self, node):
          self.body.append('\n@end float\n\n')
  
      def visit_caption(self, node):
          if not isinstance(node.parent, nodes.figure):
              self.builder.warn('caption not inside a figure.',
                                (self.curfilestack[-1], node.line))
              return
          self.body.append('\n@caption{')
      def depart_caption(self, node):
          if isinstance(node.parent, nodes.figure):
              self.body.append('}\n')
  
      def visit_image(self, node):
          if node['uri'] in self.builder.images:
              uri = self.builder.images[node['uri']]
          else:
              # missing image!
              if self.ignore_missing_images:
                  return
              uri = node['uri']
          if uri.find('://') != -1:
              # ignore remote images
              return
          name, ext = path.splitext(uri)
          attrs = node.attributes
          # 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 = self.escape_arg(attrs.get('alt', ''))
          self.body.append('\n@image{%s,%s,%s,%s,%s}\n' %
                           (name, width, height, alt, ext[1:]))
      def depart_image(self, node):
          pass
  
      def visit_compound(self, node):
          pass
      def depart_compound(self, node):
          pass
  
      def visit_sidebar(self, node):
          self.visit_topic(node)
      def depart_sidebar(self, node):
          self.depart_topic(node)
  
      def visit_label(self, node):
          self.body.append('@w{(')
      def depart_label(self, node):
          self.body.append(')} ')
  
      def visit_legend(self, node):
          pass
      def depart_legend(self, node):
          pass
  
      def visit_substitution_reference(self, node):
          pass
      def depart_substitution_reference(self, node):
          pass
  
      def visit_substitution_definition(self, node):
          raise nodes.SkipNode
  
      def visit_system_message(self, node):
          self.body.append('\n@w{----------- System Message: %s/%s -----------} '
                           '(%s, line %s)\n' % (
                  node.get('type', '?'),
                  node.get('level', '?'),
                  self.escape(node.get('source', '?')),
                  node.get('line', '?')))
      def depart_system_message(self, node):
          pass
  
      def visit_comment(self, node):
          self.body.append('\n')
          for line in node.astext().splitlines():
              self.body.append('@c %s\n' % line)
          raise nodes.SkipNode
  
      def visit_problematic(self, node):
          self.body.append('>')
      def depart_problematic(self, node):
          self.body.append('<')
  
      def unimplemented_visit(self, node):
          self.builder.warn("unimplemented node type: %r" % node,
                            (self.curfilestack[-1], node.line))
  
      def unknown_visit(self, node):
          self.builder.warn("unknown node type: %r" % node,
                            (self.curfilestack[-1], node.line))
      def unknown_departure(self, node):
          pass
  
      ### Sphinx specific
  
      def visit_productionlist(self, node):
          self.visit_literal_block(None)
          names = []
          for production in node:
              names.append(production['tokenname'])
          maxlen = max(len(name) for name in names)
          for production in node:
              if production['tokenname']:
                  for id in production.get('ids'):
                      self.add_anchor(id, production)
                  s = production['tokenname'].ljust(maxlen) + ' ::='
                  lastname = production['tokenname']
              else:
                  s = '%s    ' % (' '*maxlen)
              self.body.append(self.escape(s))
              self.body.append(self.escape(production.astext() + '\n'))
          self.depart_literal_block(None)
          raise nodes.SkipNode
  
      def visit_production(self, node):
          pass
      def depart_production(self, node):
          pass
  
      def visit_literal_emphasis(self, node):
          self.body.append('@code{')
      def depart_literal_emphasis(self, node):
          self.body.append('}')
  
      def visit_index(self, node):
          # terminate the line but don't prevent paragraph breaks
          if isinstance(node.parent, nodes.paragraph):
              self.ensure_eol()
          else:
              self.body.append('\n')
          for entry in node['entries']:
              typ, text, tid, text2 = entry
              text = self.escape_menu(text)
              self.body.append('@geindex %s\n' % text)
  
      def visit_refcount(self, node):
          self.body.append('\n')
      def depart_refcount(self, node):
          self.body.append('\n')
  
      def visit_versionmodified(self, node):
          intro = versionlabels[node['type']] % node['version']
          if node.children:
              intro += ': '
          else:
              intro += '.'
          self.body.append('\n%s' % self.escape(intro))
      def depart_versionmodified(self, node):
          self.body.append('\n')
  
      def visit_start_of_file(self, node):
          # add a document target
          self.next_section_ids.add(':doc')
          self.curfilestack.append(node['docname'])
          self.footnotestack.append(self.collect_footnotes(node))
      def depart_start_of_file(self, node):
          self.curfilestack.pop()
          self.footnotestack.pop()
  
      def visit_centered(self, node):
          txt = self.escape_arg(node.astext())
          self.body.append('\n\n@center %s\n\n' % txt)
          raise nodes.SkipNode
  
      def visit_seealso(self, node):
          self.visit_topic(node)
      def depart_seealso(self, node):
          self.depart_topic(node)
  
      def visit_meta(self, node):
          raise nodes.SkipNode
  
      def visit_glossary(self, node):
          pass
      def depart_glossary(self, node):
          pass
  
      def visit_acks(self, node):
          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
      def depart_highlightlang(self, node):
          pass
  
      ## Desc
  
      def visit_desc(self, node):
          self.at_deffnx = '@deffn'
      def depart_desc(self, node):
          self.ensure_eol()
          self.body.append('@end deffn\n')
  
      def visit_desc_signature(self, node):
          objtype = node.parent['objtype']
          if objtype != 'describe':
              for id in node.get('ids'):
                  self.add_anchor(id, node)
          # use the full name of the objtype for the category
          try:
              domain = self.builder.env.domains[node.parent['domain']]
              primary = self.builder.config.primary_domain
              name = domain.get_type_name(domain.object_types[objtype],
                                          primary == domain.name)
          except KeyError:
              name = objtype
          category = self.escape_arg(string.capwords(name))
          self.body.append('\n%s {%s} ' % (self.at_deffnx, category))
          self.at_deffnx = '@deffnx'
      def depart_desc_signature(self, node):
          self.body.append("\n")
  
      def visit_desc_name(self, node):
          pass
      def depart_desc_name(self, node):
          pass
  
      def visit_desc_addname(self, node):
          pass
      def depart_desc_addname(self, node):
          pass
  
      def visit_desc_type(self, node):
          pass
      def depart_desc_type(self, node):
          pass
  
      def visit_desc_returns(self, node):
          self.body.append(' -> ')
      def depart_desc_returns(self, node):
          pass
  
      def visit_desc_parameterlist(self, node):
          self.body.append(' (')
          self.first_param = 1
      def depart_desc_parameterlist(self, node):
          self.body.append(')')
  
      def visit_desc_parameter(self, node):
          if not self.first_param:
              self.body.append(', ')
          else:
              self.first_param = 0
          text = self.escape(node.astext())
          # replace no-break spaces with normal ones
          text = text.replace(u' ', '@w{ }')
          self.body.append(text)
          raise nodes.SkipNode
  
      def visit_desc_optional(self, node):
          self.body.append('[')
      def depart_desc_optional(self, node):
          self.body.append(']')
  
      def visit_desc_annotation(self, node):
          raise nodes.SkipNode
  
      def visit_desc_content(self, node):
          pass
      def depart_desc_content(self, node):
          pass
  
      def visit_inline(self, node):
          pass
      def depart_inline(self, node):
          pass
  
      def visit_abbreviation(self, node):
          abbr = node.astext()
          self.body.append('@abbr{')
          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):
          self.body.append(self.context.pop())
  
      def visit_download_reference(self, node):
          pass
      def depart_download_reference(self, node):
          pass
  
      def visit_hlist(self, node):
          self.visit_bullet_list(node)
      def depart_hlist(self, node):
          self.depart_bullet_list(node)
  
      def visit_hlistcol(self, node):
          pass
      def depart_hlistcol(self, node):
          pass
  
      def visit_pending_xref(self, node):
          pass
      def depart_pending_xref(self, node):
          pass
  