sphinx/sphinx/writers/texinfo.py
Takeshi KOMIYA c8d172b340 Fix typo
2019-02-14 00:56:48 +09:00

1757 lines
56 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
sphinx.writers.texinfo
~~~~~~~~~~~~~~~~~~~~~~
Custom docutils writer for Texinfo.
:copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
import re
import textwrap
import warnings
from os import path
from typing import Iterable, cast
from docutils import nodes, writers
from sphinx import addnodes, __display_version__
from sphinx.deprecation import RemovedInSphinx30Warning
from sphinx.errors import ExtensionError
from sphinx.locale import admonitionlabels, _, __
from sphinx.util import logging
from sphinx.util.docutils import SphinxTranslator
from sphinx.util.i18n import format_date
from sphinx.writers.latex import collected_footnote
if False:
# For type annotation
from typing import Any, Callable, Dict, Iterator, List, Pattern, Set, Tuple, Union # NOQA
from sphinx.builders.texinfo import TexinfoBuilder # NOQA
from sphinx.domains import IndexEntry # NOQA
logger = logging.getLogger(__name__)
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 """ + __display_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):
# type: (nodes.Element) -> List[nodes.section]
"""Return a list of subsections for the given ``section``."""
result = []
for child in section:
if isinstance(child, nodes.section):
result.append(child)
continue
elif isinstance(child, nodes.Element):
result.extend(find_subsections(child))
return result
def smart_capwords(s, sep=None):
# type: (str, str) -> str
"""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'}))) # type: Tuple[str, Any, Tuple[Tuple[str, List[str], Dict[str, str]], ...]] # NOQA
settings_defaults = {} # type: Dict
output = None # type: str
visitor_attributes = ('output', 'fragment')
def __init__(self, builder):
# type: (TexinfoBuilder) -> None
super().__init__()
self.builder = builder
def translate(self):
# type: () -> None
visitor = self.builder.create_translator(self.document, self.builder)
self.visitor = cast(TexinfoTranslator, visitor)
self.document.walkabout(visitor)
self.visitor.finish()
for attr in self.visitor_attributes:
setattr(self, attr, getattr(self.visitor, attr))
class TexinfoTranslator(SphinxTranslator):
builder = None # type: TexinfoBuilder
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):
# type: (nodes.document, TexinfoBuilder) -> None
super().__init__(document, builder)
self.init_settings()
self.written_ids = set() # type: Set[str]
# node names and anchors in output
# node names and anchors that should be in output
self.referenced_ids = set() # type: Set[str]
self.indices = [] # type: List[Tuple[str, str]]
# (node name, content)
self.short_ids = {} # type: Dict[str, str]
# anchors --> short ids
self.node_names = {} # type: Dict[str, str]
# node name --> node's name to display
self.node_menus = {} # type: Dict[str, List[str]]
# node name --> node's menu entries
self.rellinks = {} # type: Dict[str, List[str]]
# node name --> (next, previous, up)
self.collect_indices()
self.collect_node_names()
self.collect_node_menus()
self.collect_rellinks()
self.body = [] # type: List[str]
self.context = [] # type: List[str]
self.previous_section = None # type: nodes.section
self.section_level = 0
self.seen_title = False
self.next_section_ids = set() # type: Set[str]
self.escape_newlines = 0
self.escape_hyphens = 0
self.curfilestack = [] # type: List[str]
self.footnotestack = [] # type: List[Dict[str, List[Union[collected_footnote, bool]]]] # NOQA
self.in_footnote = 0
self.handled_abbrs = set() # type: Set[str]
self.colwidths = None # type: List[int]
def finish(self):
# type: () -> None
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) # type: ignore
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):
# type: () -> None
elements = self.elements = self.default_elements.copy()
elements.update({
# if empty, the title is set to the first section title
'title': self.settings.title,
'author': self.settings.author,
# if empty, use basename of input file
'filename': self.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
format_date(self.builder.config.today_fmt or _('%b %d, %Y'),
language=self.builder.config.language))
})
# title
title = self.settings.title # type: str
if not title:
title_node = self.document.next_node(nodes.title)
title = (title_node and title_node.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'): # type: ignore
elements['filename'] = elements['filename'][:-4] # type: ignore
elements['filename'] += '.info' # type: ignore
# direntry
if self.settings.texinfo_dir_entry:
entry = self.format_menu_entry(
self.escape_menu(self.settings.texinfo_dir_entry),
'(%s)' % elements['filename'],
self.escape_arg(self.settings.texinfo_dir_description))
elements['direntry'] = ('@dircategory %s\n'
'@direntry\n'
'%s'
'@end direntry\n') % (
self.escape_id(self.settings.texinfo_dir_category), entry)
elements['copying'] = COPYING % elements
# allow the user to override them all
elements.update(self.settings.texinfo_elements)
def collect_node_names(self):
# type: () -> None
"""Generates a unique id for each section.
Assigns the attribute ``node_name`` to each section."""
def add_node_name(name):
# type: (str) -> str
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 = cast(nodes.TextElement, section.next_node(nodes.Titular))
name = (title and title.astext()) or '<untitled>'
section['node_name'] = add_node_name(name)
def collect_node_menus(self):
# type: () -> None
"""Collect the menu entries for each "node" section."""
node_menus = self.node_menus
targets = [self.document] # type: List[nodes.Element]
targets.extend(self.document.traverse(nodes.section))
for node in targets:
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):
# type: () -> None
"""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):
# type: (str) -> str
"""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):
# type: (str) -> str
"""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):
# type: (str) -> str
"""Return an escaped string suitable for node names and anchors."""
bad_chars = ',:()'
for bc in bad_chars:
s = s.replace(bc, ' ')
if re.search('[^ .]', s):
# remove DOTs if name contains other characters
s = s.replace('.', ' ')
s = ' '.join(s.split()).strip()
return self.escape(s)
def escape_menu(self, s):
# type: (str) -> str
"""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):
# type: () -> None
"""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):
# type: (str, str, str) -> str
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+')):
# type: (List[str], Pattern) -> None
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):
# type: (str) -> None
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):
# type: (str) -> None
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):
# type: (str) -> str
match = re.match(r'(\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):
# type: () -> None
def generate(content, collapsed):
# type: (List[Tuple[str, List[IndexEntry]]], bool) -> str
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.values():
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):
# type: (nodes.Element) -> Dict[str, List[Union[collected_footnote, bool]]]
def footnotes_under(n):
# type: (nodes.Element) -> Iterator[nodes.footnote]
if isinstance(n, nodes.footnote):
yield n
else:
for c in n.children:
if isinstance(c, addnodes.start_of_file):
continue
elif isinstance(c, nodes.Element):
yield from footnotes_under(c)
fnotes = {} # type: Dict[str, List[Union[collected_footnote, bool]]]
for fn in footnotes_under(node):
label = cast(nodes.label, fn[0])
num = label.astext().strip()
fnotes[num] = [collected_footnote('', *fn.children), False]
return fnotes
# -- xref handling
def get_short_id(self, id):
# type: (str) -> str
"""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):
# type: (str, nodes.Node) -> None
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):
# type: (str, str, nodes.Node) -> None
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):
# type: (nodes.Element) -> None
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):
# type: (nodes.Element) -> None
self.footnotestack.pop()
self.curfilestack.pop()
def visit_Text(self, node):
# type: (nodes.Text) -> None
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):
# type: (nodes.Text) -> None
pass
def visit_section(self, node):
# type: (nodes.Element) -> None
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) # type: ignore
for id in sorted(self.next_section_ids):
self.add_anchor(id, node)
self.next_section_ids.clear()
self.previous_section = cast(nodes.section, node)
self.section_level += 1
def depart_section(self, node):
# type: (nodes.Element) -> None
self.section_level -= 1
headings = (
'@unnumbered',
'@chapter',
'@section',
'@subsection',
'@subsubsection',
)
rubrics = (
'@heading',
'@subheading',
'@subsubheading',
)
def visit_title(self, node):
# type: (nodes.Element) -> None
if not self.seen_title:
self.seen_title = True
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):
logger.warning(__('encountered title node not in section, topic, table, '
'admonition or sidebar'),
location=(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):
# type: (nodes.Element) -> None
self.body.append('\n\n')
def visit_rubric(self, node):
# type: (nodes.Element) -> None
if len(node) == 1 and node.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)
self.escape_newlines += 1
def depart_rubric(self, node):
# type: (nodes.Element) -> None
self.escape_newlines -= 1
self.body.append('\n\n')
def visit_subtitle(self, node):
# type: (nodes.Element) -> None
self.body.append('\n\n@noindent\n')
def depart_subtitle(self, node):
# type: (nodes.Element) -> None
self.body.append('\n\n')
# -- References
def visit_target(self, node):
# type: (nodes.Element) -> None
# 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, AttributeError):
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):
# type: (nodes.Element) -> None
pass
def visit_reference(self, node):
# type: (nodes.Element) -> None
# 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):
# type: (nodes.Element) -> None
pass
def visit_number_reference(self, node):
# type: (nodes.Element) -> None
text = nodes.Text(node.get('title', '#'))
self.visit_Text(text)
raise nodes.SkipNode
def visit_title_reference(self, node):
# type: (nodes.Element) -> None
text = node.astext()
self.body.append('@cite{%s}' % self.escape_arg(text))
raise nodes.SkipNode
# -- Blocks
def visit_paragraph(self, node):
# type: (nodes.Element) -> None
self.body.append('\n')
def depart_paragraph(self, node):
# type: (nodes.Element) -> None
self.body.append('\n')
def visit_block_quote(self, node):
# type: (nodes.Element) -> None
self.body.append('\n@quotation\n')
def depart_block_quote(self, node):
# type: (nodes.Element) -> None
self.ensure_eol()
self.body.append('@end quotation\n')
def visit_literal_block(self, node):
# type: (nodes.Element) -> None
self.body.append('\n@example\n')
def depart_literal_block(self, node):
# type: (nodes.Element) -> None
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):
# type: (nodes.Element) -> None
if not isinstance(node.parent, nodes.line_block):
self.body.append('\n\n')
self.body.append('@display\n')
def depart_line_block(self, node):
# type: (nodes.Element) -> None
self.body.append('@end display\n')
if not isinstance(node.parent, nodes.line_block):
self.body.append('\n\n')
def visit_line(self, node):
# type: (nodes.Element) -> None
self.escape_newlines += 1
def depart_line(self, node):
# type: (nodes.Element) -> None
self.body.append('@w{ }\n')
self.escape_newlines -= 1
# -- Inline
def visit_strong(self, node):
# type: (nodes.Element) -> None
self.body.append('@strong{')
def depart_strong(self, node):
# type: (nodes.Element) -> None
self.body.append('}')
def visit_emphasis(self, node):
# type: (nodes.Element) -> None
self.body.append('@emph{')
def depart_emphasis(self, node):
# type: (nodes.Element) -> None
self.body.append('}')
def visit_literal(self, node):
# type: (nodes.Element) -> None
self.body.append('@code{')
def depart_literal(self, node):
# type: (nodes.Element) -> None
self.body.append('}')
def visit_superscript(self, node):
# type: (nodes.Element) -> None
self.body.append('@w{^')
def depart_superscript(self, node):
# type: (nodes.Element) -> None
self.body.append('}')
def visit_subscript(self, node):
# type: (nodes.Element) -> None
self.body.append('@w{[')
def depart_subscript(self, node):
# type: (nodes.Element) -> None
self.body.append(']}')
# -- Footnotes
def visit_footnote(self, node):
# type: (nodes.Element) -> None
raise nodes.SkipNode
def visit_collected_footnote(self, node):
# type: (nodes.Element) -> None
self.in_footnote += 1
self.body.append('@footnote{')
def depart_collected_footnote(self, node):
# type: (nodes.Element) -> None
self.body.append('}')
self.in_footnote -= 1
def visit_footnote_reference(self, node):
# type: (nodes.Element) -> None
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) # type: ignore
raise nodes.SkipChildren
def visit_citation(self, node):
# type: (nodes.Element) -> None
self.body.append('\n')
for id in node.get('ids'):
self.add_anchor(id, node)
self.escape_newlines += 1
def depart_citation(self, node):
# type: (nodes.Element) -> None
self.escape_newlines -= 1
def visit_citation_reference(self, node):
# type: (nodes.Element) -> None
self.body.append('@w{[')
def depart_citation_reference(self, node):
# type: (nodes.Element) -> None
self.body.append(']}')
# -- Lists
def visit_bullet_list(self, node):
# type: (nodes.Element) -> None
bullet = node.get('bullet', '*')
self.body.append('\n\n@itemize %s\n' % bullet)
def depart_bullet_list(self, node):
# type: (nodes.Element) -> None
self.ensure_eol()
self.body.append('@end itemize\n')
def visit_enumerated_list(self, node):
# type: (nodes.Element) -> None
# 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):
# type: (nodes.Element) -> None
self.ensure_eol()
self.body.append('@end enumerate\n')
def visit_list_item(self, node):
# type: (nodes.Element) -> None
self.body.append('\n@item ')
def depart_list_item(self, node):
# type: (nodes.Element) -> None
pass
# -- Option List
def visit_option_list(self, node):
# type: (nodes.Element) -> None
self.body.append('\n\n@table @option\n')
def depart_option_list(self, node):
# type: (nodes.Element) -> None
self.ensure_eol()
self.body.append('@end table\n')
def visit_option_list_item(self, node):
# type: (nodes.Element) -> None
pass
def depart_option_list_item(self, node):
# type: (nodes.Element) -> None
pass
def visit_option_group(self, node):
# type: (nodes.Element) -> None
self.at_item_x = '@item'
def depart_option_group(self, node):
# type: (nodes.Element) -> None
pass
def visit_option(self, node):
# type: (nodes.Element) -> None
self.escape_hyphens += 1
self.body.append('\n%s ' % self.at_item_x)
self.at_item_x = '@itemx'
def depart_option(self, node):
# type: (nodes.Element) -> None
self.escape_hyphens -= 1
def visit_option_string(self, node):
# type: (nodes.Element) -> None
pass
def depart_option_string(self, node):
# type: (nodes.Element) -> None
pass
def visit_option_argument(self, node):
# type: (nodes.Element) -> None
self.body.append(node.get('delimiter', ' '))
def depart_option_argument(self, node):
# type: (nodes.Element) -> None
pass
def visit_description(self, node):
# type: (nodes.Element) -> None
self.body.append('\n')
def depart_description(self, node):
# type: (nodes.Element) -> None
pass
# -- Definitions
def visit_definition_list(self, node):
# type: (nodes.Element) -> None
self.body.append('\n\n@table @asis\n')
def depart_definition_list(self, node):
# type: (nodes.Element) -> None
self.ensure_eol()
self.body.append('@end table\n')
def visit_definition_list_item(self, node):
# type: (nodes.Element) -> None
self.at_item_x = '@item'
def depart_definition_list_item(self, node):
# type: (nodes.Element) -> None
pass
def visit_term(self, node):
# type: (nodes.Element) -> None
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):
# type: (nodes.Element) -> None
pass
def visit_classifier(self, node):
# type: (nodes.Element) -> None
self.body.append(' : ')
def depart_classifier(self, node):
# type: (nodes.Element) -> None
pass
def visit_definition(self, node):
# type: (nodes.Element) -> None
self.body.append('\n')
def depart_definition(self, node):
# type: (nodes.Element) -> None
pass
# -- Tables
def visit_table(self, node):
# type: (nodes.Element) -> None
self.entry_sep = '@item'
def depart_table(self, node):
# type: (nodes.Element) -> None
self.body.append('\n@end multitable\n\n')
def visit_tabular_col_spec(self, node):
# type: (nodes.Element) -> None
pass
def depart_tabular_col_spec(self, node):
# type: (nodes.Element) -> None
pass
def visit_colspec(self, node):
# type: (nodes.Element) -> None
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):
# type: (nodes.Element) -> None
pass
def visit_tgroup(self, node):
# type: (nodes.Element) -> None
self.colwidths = []
self.n_cols = node['cols']
def depart_tgroup(self, node):
# type: (nodes.Element) -> None
pass
def visit_thead(self, node):
# type: (nodes.Element) -> None
self.entry_sep = '@headitem'
def depart_thead(self, node):
# type: (nodes.Element) -> None
pass
def visit_tbody(self, node):
# type: (nodes.Element) -> None
pass
def depart_tbody(self, node):
# type: (nodes.Element) -> None
pass
def visit_row(self, node):
# type: (nodes.Element) -> None
pass
def depart_row(self, node):
# type: (nodes.Element) -> None
self.entry_sep = '@item'
def visit_entry(self, node):
# type: (nodes.Element) -> None
self.body.append('\n%s\n' % self.entry_sep)
self.entry_sep = '@tab'
def depart_entry(self, node):
# type: (nodes.Element) -> None
for i in range(node.get('morecols', 0)):
self.body.append('\n@tab\n')
# -- Field Lists
def visit_field_list(self, node):
# type: (nodes.Element) -> None
pass
def depart_field_list(self, node):
# type: (nodes.Element) -> None
pass
def visit_field(self, node):
# type: (nodes.Element) -> None
self.body.append('\n')
def depart_field(self, node):
# type: (nodes.Element) -> None
self.body.append('\n')
def visit_field_name(self, node):
# type: (nodes.Element) -> None
self.ensure_eol()
self.body.append('@*')
def depart_field_name(self, node):
# type: (nodes.Element) -> None
self.body.append(': ')
def visit_field_body(self, node):
# type: (nodes.Element) -> None
pass
def depart_field_body(self, node):
# type: (nodes.Element) -> None
pass
# -- Admonitions
def visit_admonition(self, node, name=''):
# type: (nodes.Element, str) -> None
if not name:
title = cast(nodes.title, node[0])
name = self.escape(title.astext())
self.body.append('\n@cartouche\n@quotation %s ' % name)
def _visit_named_admonition(self, node):
# type: (nodes.Element) -> None
label = admonitionlabels[node.tagname]
self.body.append('\n@cartouche\n@quotation %s ' % label)
def depart_admonition(self, node):
# type: (nodes.Element) -> None
self.ensure_eol()
self.body.append('@end quotation\n'
'@end cartouche\n')
visit_attention = _visit_named_admonition
depart_attention = depart_admonition
visit_caution = _visit_named_admonition
depart_caution = depart_admonition
visit_danger = _visit_named_admonition
depart_danger = depart_admonition
visit_error = _visit_named_admonition
depart_error = depart_admonition
visit_hint = _visit_named_admonition
depart_hint = depart_admonition
visit_important = _visit_named_admonition
depart_important = depart_admonition
visit_note = _visit_named_admonition
depart_note = depart_admonition
visit_tip = _visit_named_admonition
depart_tip = depart_admonition
visit_warning = _visit_named_admonition
depart_warning = depart_admonition
# -- Misc
def visit_docinfo(self, node):
# type: (nodes.Element) -> None
raise nodes.SkipNode
def visit_generated(self, node):
# type: (nodes.Element) -> None
raise nodes.SkipNode
def visit_header(self, node):
# type: (nodes.Element) -> None
raise nodes.SkipNode
def visit_footer(self, node):
# type: (nodes.Element) -> None
raise nodes.SkipNode
def visit_container(self, node):
# type: (nodes.Element) -> None
if node.get('literal_block'):
self.body.append('\n\n@float LiteralBlock\n')
def depart_container(self, node):
# type: (nodes.Element) -> None
if node.get('literal_block'):
self.body.append('\n@end float\n\n')
def visit_decoration(self, node):
# type: (nodes.Element) -> None
pass
def depart_decoration(self, node):
# type: (nodes.Element) -> None
pass
def visit_topic(self, node):
# type: (nodes.Element) -> None
# ignore TOC's since we have to have a "menu" anyway
if 'contents' in node.get('classes', []):
raise nodes.SkipNode
title = cast(nodes.title, node[0])
self.visit_rubric(title)
self.body.append('%s\n' % self.escape(title.astext()))
def depart_topic(self, node):
# type: (nodes.Element) -> None
pass
def visit_transition(self, node):
# type: (nodes.Element) -> None
self.body.append('\n\n%s\n\n' % ('_' * 66))
def depart_transition(self, node):
# type: (nodes.Element) -> None
pass
def visit_attribution(self, node):
# type: (nodes.Element) -> None
self.body.append('\n\n@center --- ')
def depart_attribution(self, node):
# type: (nodes.Element) -> None
self.body.append('\n\n')
def visit_raw(self, node):
# type: (nodes.Element) -> None
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):
# type: (nodes.Element) -> None
self.body.append('\n\n@float Figure\n')
def depart_figure(self, node):
# type: (nodes.Element) -> None
self.body.append('\n@end float\n\n')
def visit_caption(self, node):
# type: (nodes.Element) -> None
if (isinstance(node.parent, nodes.figure) or
(isinstance(node.parent, nodes.container) and
node.parent.get('literal_block'))):
self.body.append('\n@caption{')
else:
logger.warning(__('caption not inside a figure.'),
location=(self.curfilestack[-1], node.line))
def depart_caption(self, node):
# type: (nodes.Element) -> None
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):
# type: (nodes.Element) -> None
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):
# type: (nodes.Element) -> None
pass
def visit_compound(self, node):
# type: (nodes.Element) -> None
pass
def depart_compound(self, node):
# type: (nodes.Element) -> None
pass
def visit_sidebar(self, node):
# type: (nodes.Element) -> None
self.visit_topic(node)
def depart_sidebar(self, node):
# type: (nodes.Element) -> None
self.depart_topic(node)
def visit_label(self, node):
# type: (nodes.Element) -> None
self.body.append('@w{(')
def depart_label(self, node):
# type: (nodes.Element) -> None
self.body.append(')} ')
def visit_legend(self, node):
# type: (nodes.Element) -> None
pass
def depart_legend(self, node):
# type: (nodes.Element) -> None
pass
def visit_system_message(self, node):
# type: (nodes.Element) -> None
self.body.append('\n@verbatim\n'
'<SYSTEM MESSAGE: %s>\n'
'@end verbatim\n' % node.astext())
raise nodes.SkipNode
def visit_comment(self, node):
# type: (nodes.Element) -> None
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):
# type: (nodes.Element) -> None
self.body.append('>>')
def depart_problematic(self, node):
# type: (nodes.Element) -> None
self.body.append('<<')
def unimplemented_visit(self, node):
# type: (nodes.Element) -> None
logger.warning(__("unimplemented node type: %r"), node,
location=(self.curfilestack[-1], node.line))
def unknown_visit(self, node):
# type: (nodes.Node) -> None
logger.warning(__("unknown node type: %r"), node,
location=(self.curfilestack[-1], node.line))
def unknown_departure(self, node):
# type: (nodes.Node) -> None
pass
# -- Sphinx specific
def visit_productionlist(self, node):
# type: (nodes.Element) -> None
self.visit_literal_block(None)
names = []
productionlist = cast(Iterable[addnodes.production], node)
for production in productionlist:
names.append(production['tokenname'])
maxlen = max(len(name) for name in names)
for production in productionlist:
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):
# type: (nodes.Element) -> None
pass
def depart_production(self, node):
# type: (nodes.Element) -> None
pass
def visit_literal_emphasis(self, node):
# type: (nodes.Element) -> None
self.body.append('@code{')
def depart_literal_emphasis(self, node):
# type: (nodes.Element) -> None
self.body.append('}')
def visit_literal_strong(self, node):
# type: (nodes.Element) -> None
self.body.append('@code{')
def depart_literal_strong(self, node):
# type: (nodes.Element) -> None
self.body.append('}')
def visit_index(self, node):
# type: (nodes.Element) -> None
# 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, key_ = entry
text = self.escape_menu(text)
self.body.append('@geindex %s\n' % text)
def visit_versionmodified(self, node):
# type: (nodes.Element) -> None
self.body.append('\n')
def depart_versionmodified(self, node):
# type: (nodes.Element) -> None
self.body.append('\n')
def visit_start_of_file(self, node):
# type: (nodes.Element) -> None
# 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):
# type: (nodes.Element) -> None
self.curfilestack.pop()
self.footnotestack.pop()
def visit_centered(self, node):
# type: (nodes.Element) -> None
txt = self.escape_arg(node.astext())
self.body.append('\n\n@center %s\n\n' % txt)
raise nodes.SkipNode
def visit_seealso(self, node):
# type: (nodes.Element) -> None
self.body.append('\n\n@subsubheading %s\n\n' %
admonitionlabels['seealso'])
def depart_seealso(self, node):
# type: (nodes.Element) -> None
self.body.append('\n')
def visit_meta(self, node):
# type: (nodes.Element) -> None
raise nodes.SkipNode
def visit_glossary(self, node):
# type: (nodes.Element) -> None
pass
def depart_glossary(self, node):
# type: (nodes.Element) -> None
pass
def visit_acks(self, node):
# type: (nodes.Element) -> None
bullet_list = cast(nodes.bullet_list, node[0])
list_items = cast(Iterable[nodes.list_item], bullet_list)
self.body.append('\n\n')
self.body.append(', '.join(n.astext() for n in list_items) + '.')
self.body.append('\n\n')
raise nodes.SkipNode
# -- Desc
def visit_desc(self, node):
# type: (nodes.Element) -> None
self.desc = node
self.at_deffnx = '@deffn'
def depart_desc(self, node):
# type: (nodes.Element) -> None
self.desc = None
self.ensure_eol()
self.body.append('@end deffn\n')
def visit_desc_signature(self, node):
# type: (nodes.Element) -> None
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.get_domain(node.parent['domain'])
primary = self.builder.config.primary_domain
name = domain.get_type_name(domain.object_types[objtype],
primary == domain.name)
except (KeyError, ExtensionError):
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):
# type: (nodes.Element) -> None
self.body.append("\n")
self.escape_hyphens -= 1
self.desc_type_name = None
def visit_desc_name(self, node):
# type: (nodes.Element) -> None
pass
def depart_desc_name(self, node):
# type: (nodes.Element) -> None
pass
def visit_desc_addname(self, node):
# type: (nodes.Element) -> None
pass
def depart_desc_addname(self, node):
# type: (nodes.Element) -> None
pass
def visit_desc_type(self, node):
# type: (nodes.Element) -> None
pass
def depart_desc_type(self, node):
# type: (nodes.Element) -> None
pass
def visit_desc_returns(self, node):
# type: (nodes.Element) -> None
self.body.append(' -> ')
def depart_desc_returns(self, node):
# type: (nodes.Element) -> None
pass
def visit_desc_parameterlist(self, node):
# type: (nodes.Element) -> None
self.body.append(' (')
self.first_param = 1
def depart_desc_parameterlist(self, node):
# type: (nodes.Element) -> None
self.body.append(')')
def visit_desc_parameter(self, node):
# type: (nodes.Element) -> None
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(' ', '@w{ }')
self.body.append(text)
raise nodes.SkipNode
def visit_desc_optional(self, node):
# type: (nodes.Element) -> None
self.body.append('[')
def depart_desc_optional(self, node):
# type: (nodes.Element) -> None
self.body.append(']')
def visit_desc_annotation(self, node):
# type: (nodes.Element) -> None
# 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):
# type: (nodes.Element) -> None
pass
def visit_desc_content(self, node):
# type: (nodes.Element) -> None
pass
def depart_desc_content(self, node):
# type: (nodes.Element) -> None
pass
def visit_inline(self, node):
# type: (nodes.Element) -> None
pass
def depart_inline(self, node):
# type: (nodes.Element) -> None
pass
def visit_abbreviation(self, node):
# type: (nodes.Element) -> None
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):
# type: (nodes.Element) -> None
self.body.append(self.context.pop())
def visit_manpage(self, node):
# type: (nodes.Element) -> None
return self.visit_literal_emphasis(node)
def depart_manpage(self, node):
# type: (nodes.Element) -> None
return self.depart_literal_emphasis(node)
def visit_download_reference(self, node):
# type: (nodes.Element) -> None
pass
def depart_download_reference(self, node):
# type: (nodes.Element) -> None
pass
def visit_hlist(self, node):
# type: (nodes.Element) -> None
self.visit_bullet_list(node)
def depart_hlist(self, node):
# type: (nodes.Element) -> None
self.depart_bullet_list(node)
def visit_hlistcol(self, node):
# type: (nodes.Element) -> None
pass
def depart_hlistcol(self, node):
# type: (nodes.Element) -> None
pass
def visit_pending_xref(self, node):
# type: (nodes.Element) -> None
pass
def depart_pending_xref(self, node):
# type: (nodes.Element) -> None
pass
def visit_math(self, node):
# type: (nodes.Element) -> None
self.body.append('@math{' + self.escape_arg(node.astext()) + '}')
raise nodes.SkipNode
def visit_math_block(self, node):
# type: (nodes.Element) -> None
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.astext()))
raise nodes.SkipNode
def _make_visit_admonition(name): # type: ignore
# type: (str) -> Callable[[TexinfoTranslator, nodes.Element], None]
warnings.warn('TexinfoTranslator._make_visit_admonition() is deprecated.',
RemovedInSphinx30Warning)
def visit(self, node):
# type: (nodes.Element) -> None
self.visit_admonition(node, admonitionlabels[name])
return visit