# -*- coding: utf-8 -*- """ sphinx.writers.texinfo ~~~~~~~~~~~~~~~~~~~~~~ Custom docutils writer for Texinfo. :copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ import re import textwrap from os import path from six import itervalues from six.moves import range from docutils import nodes, writers from sphinx import addnodes, __version__ from sphinx.locale import admonitionlabels, _ 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 @finalout %(direntry)s @definfoenclose strong,`,' @definfoenclose emph,`,' @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 def smart_capwords(s, sep=None): """Like string.capwords() but does not capitalize words that already contain a capital letter.""" words = s.split(sep) for i, word in enumerate(words): if all(x.islower() for x in word): words[i] = word.capitalize() return (sep or ' ').join(words) 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 self.translator_class = ( self.builder.translator_class or TexinfoTranslator) def translate(self): self.visitor = visitor = self.translator_class( 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': 0, '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.escape_hyphens = 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.ensure_eol() self.fragment = ''.join(self.body) 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 '' elements['title'] = self.escape_id(title) or '' # 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.""" def add_node_name(name): node_id = self.escape_id(name) nth, suffix = 1, '' while node_id + suffix in self.written_ids or \ node_id + suffix in self.node_names: nth += 1 suffix = '<%s>' % nth node_id += suffix self.written_ids.add(node_id) self.node_names[node_id] = name return node_id # must have a "Top" node self.document['node_name'] = 'Top' add_node_name('Top') add_node_name('top') # each index is a node self.indices = [(add_node_name(name), content) for name, content in self.indices] # 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 '' section['node_name'] = add_node_name(name) 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{'}") 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 try: parts = reg.split(name, 1) except TypeError: # could be a gettext proxy parts = [name] 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 (node_name != 'Top' or not self.node_menus[entries[0]] or self.builder.config.texinfo_no_detailmenu): 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) self.body.append('\n@detailmenu\n' ' --- The Detailed Node Listing ---\n') for entry in entries: _add_detailed_menu(entry) self.body.append('\n@end detailmenu\n' '@end menu\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 itervalues(self.builder.env.domains): 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 self.indices.append((indexcls.localname, generate(content, collapsed))) # only add the main Index if it's not empty for docname in self.builder.docnames: if self.builder.env.indexentries[docname]: self.indices.append((_('Index'), '\n@printindex ge\n')) break # 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('@ref{%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', ' ') if self.escape_hyphens: # prevent "--" and "---" conversion s = s.replace('-', '@w{-}') 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('@ref{%s,,,%s}' % (id, uri)) else: self.body.append('@ref{%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_number_reference(self, node): text = nodes.Text(node.get('title', '#')) self.visit_Text(text) raise nodes.SkipNode 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): 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.ensure_eol() self.body.append('@end example\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.escape_hyphens += 1 self.body.append('\n%s ' % self.at_item_x) self.at_item_x = '@itemx' def depart_option(self, node): self.escape_hyphens -= 1 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 range(node.get('morecols', 0)): self.body.append('\n@tab\n') ## Field Lists def visit_field_list(self, node): pass def depart_field_list(self, node): pass def visit_field(self, node): self.body.append('\n') def depart_field(self, node): self.body.append('\n') def visit_field_name(self, node): self.ensure_eol() self.body.append('@*') 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(u'\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(name): def visit(self, node): self.visit_admonition(node, admonitionlabels[name]) 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_hint = _make_visit_admonition('hint') depart_hint = 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_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): if node.get('literal_block'): self.body.append('\n\n@float LiteralBlock\n') def depart_container(self, node): if node.get('literal_block'): self.body.append('\n@end float\n\n') 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%s\n\n' % ('_' * 66)) 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 (isinstance(node.parent, nodes.figure) or (isinstance(node.parent, nodes.container) and node.parent.get('literal_block'))): self.body.append('\n@caption{') else: self.builder.warn('caption not inside a figure.', (self.curfilestack[-1], node.line)) def depart_caption(self, node): if (isinstance(node.parent, nodes.figure) or (isinstance(node.parent, nodes.container) and node.parent.get('literal_block'))): 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@verbatim\n' '\n' '@end verbatim\n' % node.astext()) raise nodes.SkipNode 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) + ' ::=' 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_literal_strong(self, node): self.body.append('@code{') def depart_literal_strong(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_versionmodified(self, node): self.body.append('\n') 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.body.append(u'\n\n@subsubheading %s\n\n' % admonitionlabels['seealso']) def depart_seealso(self, node): self.body.append('\n') 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.desc = node self.at_deffnx = '@deffn' def depart_desc(self, node): self.desc = None self.ensure_eol() self.body.append('@end deffn\n') def visit_desc_signature(self, node): self.escape_hyphens += 1 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 # by convention, the deffn category should be capitalized like a title category = self.escape_arg(smart_capwords(name)) self.body.append('\n%s {%s} ' % (self.at_deffnx, category)) self.at_deffnx = '@deffnx' self.desc_type_name = name def depart_desc_signature(self, node): self.body.append("\n") self.escape_hyphens -= 1 self.desc_type_name = None 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): # Try to avoid duplicating info already displayed by the deffn category. # e.g. # @deffn {Class} Foo # -- instead of -- # @deffn {Class} class Foo txt = node.astext().strip() if txt == self.desc['desctype'] or \ txt == self.desc['objtype'] or \ txt in self.desc_type_name.split(): raise nodes.SkipNode def depart_desc_annotation(self, node): pass 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 def visit_math(self, node): self.builder.warn('using "math" markup without a Sphinx math extension ' 'active, please use one of the math extensions ' 'described at http://sphinx-doc.org/ext/math.html') raise nodes.SkipNode visit_math_block = visit_math