You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1179 lines
37 KiB
1179 lines
37 KiB
"""
|
|
sphinx.writers.text
|
|
~~~~~~~~~~~~~~~~~~~
|
|
|
|
Custom docutils writer for plain text.
|
|
|
|
:copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS.
|
|
:license: BSD, see LICENSE for details.
|
|
"""
|
|
import math
|
|
import os
|
|
import re
|
|
import textwrap
|
|
from itertools import chain, groupby
|
|
from typing import Any, Dict, Generator, Iterable, List, Optional, Set, Tuple, Union, cast
|
|
|
|
from docutils import nodes, writers
|
|
from docutils.nodes import Element, Node, Text
|
|
from docutils.utils import column_width
|
|
|
|
from sphinx import addnodes
|
|
from sphinx.locale import _, admonitionlabels
|
|
from sphinx.util.docutils import SphinxTranslator
|
|
|
|
if False:
|
|
# For type annotation
|
|
from sphinx.builders.text import TextBuilder
|
|
|
|
|
|
class Cell:
|
|
"""Represents a cell in a table.
|
|
It can span on multiple columns or on multiple lines.
|
|
"""
|
|
def __init__(self, text: str = "", rowspan: int = 1, colspan: int = 1) -> None:
|
|
self.text = text
|
|
self.wrapped = [] # type: List[str]
|
|
self.rowspan = rowspan
|
|
self.colspan = colspan
|
|
self.col = None # type: Optional[int]
|
|
self.row = None # type: Optional[int]
|
|
|
|
def __repr__(self) -> str:
|
|
return "<Cell {!r} {}v{}/{}>{}>".format(
|
|
self.text, self.row, self.rowspan, self.col, self.colspan
|
|
)
|
|
|
|
def __hash__(self) -> int:
|
|
return hash((self.col, self.row))
|
|
|
|
def wrap(self, width: int) -> None:
|
|
self.wrapped = my_wrap(self.text, width)
|
|
|
|
|
|
class Table:
|
|
"""Represents a table, handling cells that can span on multiple lines
|
|
or rows, like::
|
|
|
|
+-----------+-----+
|
|
| AAA | BBB |
|
|
+-----+-----+ |
|
|
| | XXX | |
|
|
| +-----+-----+
|
|
| DDD | CCC |
|
|
+-----+-----------+
|
|
|
|
This class can be used in two ways:
|
|
|
|
- Either with absolute positions: call ``table[line, col] = Cell(...)``,
|
|
this overwrite an existing cell if any.
|
|
|
|
- Either with relative positions: call the ``add_row()`` and
|
|
``add_cell(Cell(...))`` as needed.
|
|
|
|
Cell spanning on multiple rows or multiple columns (having a
|
|
colspan or rowspan greater than one) are automatically referenced
|
|
by all the table cells they covers. This is a useful
|
|
representation as we can simply check ``if self[x, y] is self[x,
|
|
y+1]`` to recognize a rowspan.
|
|
|
|
Colwidth is not automatically computed, it has to be given, either
|
|
at construction time, either during the table construction.
|
|
|
|
Example usage::
|
|
|
|
table = Table([6, 6])
|
|
table.add_cell(Cell("foo"))
|
|
table.add_cell(Cell("bar"))
|
|
table.set_separator()
|
|
table.add_row()
|
|
table.add_cell(Cell("FOO"))
|
|
table.add_cell(Cell("BAR"))
|
|
print(table)
|
|
+--------+--------+
|
|
| foo | bar |
|
|
|========|========|
|
|
| FOO | BAR |
|
|
+--------+--------+
|
|
|
|
"""
|
|
def __init__(self, colwidth: List[int] = None) -> None:
|
|
self.lines = [] # type: List[List[Cell]]
|
|
self.separator = 0
|
|
self.colwidth = (colwidth if colwidth is not None
|
|
else []) # type: List[int]
|
|
self.current_line = 0
|
|
self.current_col = 0
|
|
|
|
def add_row(self) -> None:
|
|
"""Add a row to the table, to use with ``add_cell()``. It is not needed
|
|
to call ``add_row()`` before the first ``add_cell()``.
|
|
"""
|
|
self.current_line += 1
|
|
self.current_col = 0
|
|
|
|
def set_separator(self) -> None:
|
|
"""Sets the separator below the current line.
|
|
"""
|
|
self.separator = len(self.lines)
|
|
|
|
def add_cell(self, cell: Cell) -> None:
|
|
"""Add a cell to the current line, to use with ``add_row()``. To add
|
|
a cell spanning on multiple lines or rows, simply set the
|
|
``cell.colspan`` or ``cell.rowspan`` BEFORE inserting it to
|
|
the table.
|
|
"""
|
|
while self[self.current_line, self.current_col]:
|
|
self.current_col += 1
|
|
self[self.current_line, self.current_col] = cell
|
|
self.current_col += cell.colspan
|
|
|
|
def __getitem__(self, pos: Tuple[int, int]) -> Cell:
|
|
line, col = pos
|
|
self._ensure_has_line(line + 1)
|
|
self._ensure_has_column(col + 1)
|
|
return self.lines[line][col]
|
|
|
|
def __setitem__(self, pos: Tuple[int, int], cell: Cell) -> None:
|
|
line, col = pos
|
|
self._ensure_has_line(line + cell.rowspan)
|
|
self._ensure_has_column(col + cell.colspan)
|
|
for dline in range(cell.rowspan):
|
|
for dcol in range(cell.colspan):
|
|
self.lines[line + dline][col + dcol] = cell
|
|
cell.row = line
|
|
cell.col = col
|
|
|
|
def _ensure_has_line(self, line: int) -> None:
|
|
while len(self.lines) < line:
|
|
self.lines.append([])
|
|
|
|
def _ensure_has_column(self, col: int) -> None:
|
|
for line in self.lines:
|
|
while len(line) < col:
|
|
line.append(None)
|
|
|
|
def __repr__(self) -> str:
|
|
return "\n".join(repr(line) for line in self.lines)
|
|
|
|
def cell_width(self, cell: Cell, source: List[int]) -> int:
|
|
"""Give the cell width, according to the given source (either
|
|
``self.colwidth`` or ``self.measured_widths``).
|
|
This take into account cells spanning on multiple columns.
|
|
"""
|
|
width = 0
|
|
for i in range(self[cell.row, cell.col].colspan):
|
|
width += source[cell.col + i]
|
|
return width + (cell.colspan - 1) * 3
|
|
|
|
@property
|
|
def cells(self) -> Generator[Cell, None, None]:
|
|
seen = set() # type: Set[Cell]
|
|
for lineno, line in enumerate(self.lines):
|
|
for colno, cell in enumerate(line):
|
|
if cell and cell not in seen:
|
|
yield cell
|
|
seen.add(cell)
|
|
|
|
def rewrap(self) -> None:
|
|
"""Call ``cell.wrap()`` on all cells, and measure each column width
|
|
after wrapping (result written in ``self.measured_widths``).
|
|
"""
|
|
self.measured_widths = self.colwidth[:]
|
|
for cell in self.cells:
|
|
cell.wrap(width=self.cell_width(cell, self.colwidth))
|
|
if not cell.wrapped:
|
|
continue
|
|
width = math.ceil(max(column_width(x) for x in cell.wrapped) / cell.colspan)
|
|
for col in range(cell.col, cell.col + cell.colspan):
|
|
self.measured_widths[col] = max(self.measured_widths[col], width)
|
|
|
|
def physical_lines_for_line(self, line: List[Cell]) -> int:
|
|
"""From a given line, compute the number of physical lines it spans
|
|
due to text wrapping.
|
|
"""
|
|
physical_lines = 1
|
|
for cell in line:
|
|
physical_lines = max(physical_lines, len(cell.wrapped))
|
|
return physical_lines
|
|
|
|
def __str__(self) -> str:
|
|
out = []
|
|
self.rewrap()
|
|
|
|
def writesep(char: str = "-", lineno: Optional[int] = None) -> str:
|
|
"""Called on the line *before* lineno.
|
|
Called with no *lineno* for the last sep.
|
|
"""
|
|
out = [] # type: List[str]
|
|
for colno, width in enumerate(self.measured_widths):
|
|
if (
|
|
lineno is not None and
|
|
lineno > 0 and
|
|
self[lineno, colno] is self[lineno - 1, colno]
|
|
):
|
|
out.append(" " * (width + 2))
|
|
else:
|
|
out.append(char * (width + 2))
|
|
head = "+" if out[0][0] == "-" else "|"
|
|
tail = "+" if out[-1][0] == "-" else "|"
|
|
glue = [
|
|
"+" if left[0] == "-" or right[0] == "-" else "|"
|
|
for left, right in zip(out, out[1:])
|
|
]
|
|
glue.append(tail)
|
|
return head + "".join(chain.from_iterable(zip(out, glue)))
|
|
|
|
for lineno, line in enumerate(self.lines):
|
|
if self.separator and lineno == self.separator:
|
|
out.append(writesep("=", lineno))
|
|
else:
|
|
out.append(writesep("-", lineno))
|
|
for physical_line in range(self.physical_lines_for_line(line)):
|
|
linestr = ["|"]
|
|
for colno, cell in enumerate(line):
|
|
if cell.col != colno:
|
|
continue
|
|
if lineno != cell.row:
|
|
physical_text = ""
|
|
elif physical_line >= len(cell.wrapped):
|
|
physical_text = ""
|
|
else:
|
|
physical_text = cell.wrapped[physical_line]
|
|
adjust_len = len(physical_text) - column_width(physical_text)
|
|
linestr.append(
|
|
" " +
|
|
physical_text.ljust(
|
|
self.cell_width(cell, self.measured_widths) + 1 + adjust_len
|
|
) + "|"
|
|
)
|
|
out.append("".join(linestr))
|
|
out.append(writesep("-"))
|
|
return "\n".join(out)
|
|
|
|
|
|
class TextWrapper(textwrap.TextWrapper):
|
|
"""Custom subclass that uses a different word separator regex."""
|
|
|
|
wordsep_re = re.compile(
|
|
r'(\s+|' # any whitespace
|
|
r'(?<=\s)(?::[a-z-]+:)?`\S+|' # interpreted text start
|
|
r'[^\s\w]*\w+[a-zA-Z]-(?=\w+[a-zA-Z])|' # hyphenated words
|
|
r'(?<=[\w\!\"\'\&\.\,\?])-{2,}(?=\w))') # em-dash
|
|
|
|
def _wrap_chunks(self, chunks: List[str]) -> List[str]:
|
|
"""_wrap_chunks(chunks : [string]) -> [string]
|
|
|
|
The original _wrap_chunks uses len() to calculate width.
|
|
This method respects wide/fullwidth characters for width adjustment.
|
|
"""
|
|
lines = [] # type: List[str]
|
|
if self.width <= 0:
|
|
raise ValueError("invalid width %r (must be > 0)" % self.width)
|
|
|
|
chunks.reverse()
|
|
|
|
while chunks:
|
|
cur_line = []
|
|
cur_len = 0
|
|
|
|
if lines:
|
|
indent = self.subsequent_indent
|
|
else:
|
|
indent = self.initial_indent
|
|
|
|
width = self.width - column_width(indent)
|
|
|
|
if self.drop_whitespace and chunks[-1].strip() == '' and lines:
|
|
del chunks[-1]
|
|
|
|
while chunks:
|
|
l = column_width(chunks[-1])
|
|
|
|
if cur_len + l <= width:
|
|
cur_line.append(chunks.pop())
|
|
cur_len += l
|
|
|
|
else:
|
|
break
|
|
|
|
if chunks and column_width(chunks[-1]) > width:
|
|
self._handle_long_word(chunks, cur_line, cur_len, width)
|
|
|
|
if self.drop_whitespace and cur_line and cur_line[-1].strip() == '':
|
|
del cur_line[-1]
|
|
|
|
if cur_line:
|
|
lines.append(indent + ''.join(cur_line))
|
|
|
|
return lines
|
|
|
|
def _break_word(self, word: str, space_left: int) -> Tuple[str, str]:
|
|
"""_break_word(word : string, space_left : int) -> (string, string)
|
|
|
|
Break line by unicode width instead of len(word).
|
|
"""
|
|
total = 0
|
|
for i, c in enumerate(word):
|
|
total += column_width(c)
|
|
if total > space_left:
|
|
return word[:i - 1], word[i - 1:]
|
|
return word, ''
|
|
|
|
def _split(self, text: str) -> List[str]:
|
|
"""_split(text : string) -> [string]
|
|
|
|
Override original method that only split by 'wordsep_re'.
|
|
This '_split' split wide-characters into chunk by one character.
|
|
"""
|
|
def split(t: str) -> List[str]:
|
|
return super(TextWrapper, self)._split(t)
|
|
chunks = [] # type: List[str]
|
|
for chunk in split(text):
|
|
for w, g in groupby(chunk, column_width):
|
|
if w == 1:
|
|
chunks.extend(split(''.join(g)))
|
|
else:
|
|
chunks.extend(list(g))
|
|
return chunks
|
|
|
|
def _handle_long_word(self, reversed_chunks: List[str], cur_line: List[str],
|
|
cur_len: int, width: int) -> None:
|
|
"""_handle_long_word(chunks : [string],
|
|
cur_line : [string],
|
|
cur_len : int, width : int)
|
|
|
|
Override original method for using self._break_word() instead of slice.
|
|
"""
|
|
space_left = max(width - cur_len, 1)
|
|
if self.break_long_words:
|
|
l, r = self._break_word(reversed_chunks[-1], space_left)
|
|
cur_line.append(l)
|
|
reversed_chunks[-1] = r
|
|
|
|
elif not cur_line:
|
|
cur_line.append(reversed_chunks.pop())
|
|
|
|
|
|
MAXWIDTH = 70
|
|
STDINDENT = 3
|
|
|
|
|
|
def my_wrap(text: str, width: int = MAXWIDTH, **kwargs: Any) -> List[str]:
|
|
w = TextWrapper(width=width, **kwargs)
|
|
return w.wrap(text)
|
|
|
|
|
|
class TextWriter(writers.Writer):
|
|
supported = ('text',)
|
|
settings_spec = ('No options here.', '', ())
|
|
settings_defaults = {} # type: Dict
|
|
|
|
output = None # type: str
|
|
|
|
def __init__(self, builder: "TextBuilder") -> None:
|
|
super().__init__()
|
|
self.builder = builder
|
|
|
|
def translate(self) -> None:
|
|
visitor = self.builder.create_translator(self.document, self.builder)
|
|
self.document.walkabout(visitor)
|
|
self.output = cast(TextTranslator, visitor).body
|
|
|
|
|
|
class TextTranslator(SphinxTranslator):
|
|
builder = None # type: TextBuilder
|
|
|
|
def __init__(self, document: nodes.document, builder: "TextBuilder") -> None:
|
|
super().__init__(document, builder)
|
|
|
|
newlines = self.config.text_newlines
|
|
if newlines == 'windows':
|
|
self.nl = '\r\n'
|
|
elif newlines == 'native':
|
|
self.nl = os.linesep
|
|
else:
|
|
self.nl = '\n'
|
|
self.sectionchars = self.config.text_sectionchars
|
|
self.add_secnumbers = self.config.text_add_secnumbers
|
|
self.secnumber_suffix = self.config.text_secnumber_suffix
|
|
self.states = [[]] # type: List[List[Tuple[int, Union[str, List[str]]]]]
|
|
self.stateindent = [0]
|
|
self.list_counter = [] # type: List[int]
|
|
self.sectionlevel = 0
|
|
self.lineblocklevel = 0
|
|
self.table = None # type: Table
|
|
|
|
def add_text(self, text: str) -> None:
|
|
self.states[-1].append((-1, text))
|
|
|
|
def new_state(self, indent: int = STDINDENT) -> None:
|
|
self.states.append([])
|
|
self.stateindent.append(indent)
|
|
|
|
def end_state(self, wrap: bool = True, end: List[str] = [''], first: str = None) -> None:
|
|
content = self.states.pop()
|
|
maxindent = sum(self.stateindent)
|
|
indent = self.stateindent.pop()
|
|
result = [] # type: List[Tuple[int, List[str]]]
|
|
toformat = [] # type: List[str]
|
|
|
|
def do_format() -> None:
|
|
if not toformat:
|
|
return
|
|
if wrap:
|
|
res = my_wrap(''.join(toformat), width=MAXWIDTH - maxindent)
|
|
else:
|
|
res = ''.join(toformat).splitlines()
|
|
if end:
|
|
res += end
|
|
result.append((indent, res))
|
|
for itemindent, item in content:
|
|
if itemindent == -1:
|
|
toformat.append(item) # type: ignore
|
|
else:
|
|
do_format()
|
|
result.append((indent + itemindent, item)) # type: ignore
|
|
toformat = []
|
|
do_format()
|
|
if first is not None and result:
|
|
# insert prefix into first line (ex. *, [1], See also, etc.)
|
|
newindent = result[0][0] - indent
|
|
if result[0][1] == ['']:
|
|
result.insert(0, (newindent, [first]))
|
|
else:
|
|
text = first + result[0][1].pop(0)
|
|
result.insert(0, (newindent, [text]))
|
|
|
|
self.states[-1].extend(result)
|
|
|
|
def visit_document(self, node: Element) -> None:
|
|
self.new_state(0)
|
|
|
|
def depart_document(self, node: Element) -> None:
|
|
self.end_state()
|
|
self.body = self.nl.join(line and (' ' * indent + line)
|
|
for indent, lines in self.states[0]
|
|
for line in lines)
|
|
# XXX header/footer?
|
|
|
|
def visit_section(self, node: Element) -> None:
|
|
self._title_char = self.sectionchars[self.sectionlevel]
|
|
self.sectionlevel += 1
|
|
|
|
def depart_section(self, node: Element) -> None:
|
|
self.sectionlevel -= 1
|
|
|
|
def visit_topic(self, node: Element) -> None:
|
|
self.new_state(0)
|
|
|
|
def depart_topic(self, node: Element) -> None:
|
|
self.end_state()
|
|
|
|
visit_sidebar = visit_topic
|
|
depart_sidebar = depart_topic
|
|
|
|
def visit_rubric(self, node: Element) -> None:
|
|
self.new_state(0)
|
|
self.add_text('-[ ')
|
|
|
|
def depart_rubric(self, node: Element) -> None:
|
|
self.add_text(' ]-')
|
|
self.end_state()
|
|
|
|
def visit_compound(self, node: Element) -> None:
|
|
pass
|
|
|
|
def depart_compound(self, node: Element) -> None:
|
|
pass
|
|
|
|
def visit_glossary(self, node: Element) -> None:
|
|
pass
|
|
|
|
def depart_glossary(self, node: Element) -> None:
|
|
pass
|
|
|
|
def visit_title(self, node: Element) -> None:
|
|
if isinstance(node.parent, nodes.Admonition):
|
|
self.add_text(node.astext() + ': ')
|
|
raise nodes.SkipNode
|
|
self.new_state(0)
|
|
|
|
def get_section_number_string(self, node: Element) -> str:
|
|
if isinstance(node.parent, nodes.section):
|
|
anchorname = '#' + node.parent['ids'][0]
|
|
numbers = self.builder.secnumbers.get(anchorname)
|
|
if numbers is None:
|
|
numbers = self.builder.secnumbers.get('')
|
|
if numbers is not None:
|
|
return '.'.join(map(str, numbers)) + self.secnumber_suffix
|
|
return ''
|
|
|
|
def depart_title(self, node: Element) -> None:
|
|
if isinstance(node.parent, nodes.section):
|
|
char = self._title_char
|
|
else:
|
|
char = '^'
|
|
text = ''
|
|
text = ''.join(x[1] for x in self.states.pop() if x[0] == -1) # type: ignore
|
|
if self.add_secnumbers:
|
|
text = self.get_section_number_string(node) + text
|
|
self.stateindent.pop()
|
|
title = ['', text, '%s' % (char * column_width(text)), '']
|
|
if len(self.states) == 2 and len(self.states[-1]) == 0:
|
|
# remove an empty line before title if it is first section title in the document
|
|
title.pop(0)
|
|
self.states[-1].append((0, title))
|
|
|
|
def visit_subtitle(self, node: Element) -> None:
|
|
pass
|
|
|
|
def depart_subtitle(self, node: Element) -> None:
|
|
pass
|
|
|
|
def visit_attribution(self, node: Element) -> None:
|
|
self.add_text('-- ')
|
|
|
|
def depart_attribution(self, node: Element) -> None:
|
|
pass
|
|
|
|
def visit_desc(self, node: Element) -> None:
|
|
pass
|
|
|
|
def depart_desc(self, node: Element) -> None:
|
|
pass
|
|
|
|
def visit_desc_signature(self, node: Element) -> None:
|
|
self.new_state(0)
|
|
|
|
def depart_desc_signature(self, node: Element) -> None:
|
|
# XXX: wrap signatures in a way that makes sense
|
|
self.end_state(wrap=False, end=None)
|
|
|
|
def visit_desc_signature_line(self, node: Element) -> None:
|
|
pass
|
|
|
|
def depart_desc_signature_line(self, node: Element) -> None:
|
|
self.add_text('\n')
|
|
|
|
def visit_desc_name(self, node: Element) -> None:
|
|
pass
|
|
|
|
def depart_desc_name(self, node: Element) -> None:
|
|
pass
|
|
|
|
def visit_desc_addname(self, node: Element) -> None:
|
|
pass
|
|
|
|
def depart_desc_addname(self, node: Element) -> None:
|
|
pass
|
|
|
|
def visit_desc_type(self, node: Element) -> None:
|
|
pass
|
|
|
|
def depart_desc_type(self, node: Element) -> None:
|
|
pass
|
|
|
|
def visit_desc_returns(self, node: Element) -> None:
|
|
self.add_text(' -> ')
|
|
|
|
def depart_desc_returns(self, node: Element) -> None:
|
|
pass
|
|
|
|
def visit_desc_parameterlist(self, node: Element) -> None:
|
|
self.add_text('(')
|
|
self.first_param = 1
|
|
|
|
def depart_desc_parameterlist(self, node: Element) -> None:
|
|
self.add_text(')')
|
|
|
|
def visit_desc_parameter(self, node: Element) -> None:
|
|
if not self.first_param:
|
|
self.add_text(', ')
|
|
else:
|
|
self.first_param = 0
|
|
self.add_text(node.astext())
|
|
raise nodes.SkipNode
|
|
|
|
def visit_desc_optional(self, node: Element) -> None:
|
|
self.add_text('[')
|
|
|
|
def depart_desc_optional(self, node: Element) -> None:
|
|
self.add_text(']')
|
|
|
|
def visit_desc_annotation(self, node: Element) -> None:
|
|
pass
|
|
|
|
def depart_desc_annotation(self, node: Element) -> None:
|
|
pass
|
|
|
|
def visit_desc_content(self, node: Element) -> None:
|
|
self.new_state()
|
|
self.add_text(self.nl)
|
|
|
|
def depart_desc_content(self, node: Element) -> None:
|
|
self.end_state()
|
|
|
|
def visit_figure(self, node: Element) -> None:
|
|
self.new_state()
|
|
|
|
def depart_figure(self, node: Element) -> None:
|
|
self.end_state()
|
|
|
|
def visit_caption(self, node: Element) -> None:
|
|
pass
|
|
|
|
def depart_caption(self, node: Element) -> None:
|
|
pass
|
|
|
|
def visit_productionlist(self, node: Element) -> None:
|
|
self.new_state()
|
|
names = []
|
|
productionlist = cast(Iterable[addnodes.production], node)
|
|
for production in productionlist:
|
|
names.append(production['tokenname'])
|
|
maxlen = max(len(name) for name in names)
|
|
lastname = None
|
|
for production in productionlist:
|
|
if production['tokenname']:
|
|
self.add_text(production['tokenname'].ljust(maxlen) + ' ::=')
|
|
lastname = production['tokenname']
|
|
elif lastname is not None:
|
|
self.add_text('%s ' % (' ' * len(lastname)))
|
|
self.add_text(production.astext() + self.nl)
|
|
self.end_state(wrap=False)
|
|
raise nodes.SkipNode
|
|
|
|
def visit_footnote(self, node: Element) -> None:
|
|
label = cast(nodes.label, node[0])
|
|
self._footnote = label.astext().strip()
|
|
self.new_state(len(self._footnote) + 3)
|
|
|
|
def depart_footnote(self, node: Element) -> None:
|
|
self.end_state(first='[%s] ' % self._footnote)
|
|
|
|
def visit_citation(self, node: Element) -> None:
|
|
if len(node) and isinstance(node[0], nodes.label):
|
|
self._citlabel = node[0].astext()
|
|
else:
|
|
self._citlabel = ''
|
|
self.new_state(len(self._citlabel) + 3)
|
|
|
|
def depart_citation(self, node: Element) -> None:
|
|
self.end_state(first='[%s] ' % self._citlabel)
|
|
|
|
def visit_label(self, node: Element) -> None:
|
|
raise nodes.SkipNode
|
|
|
|
def visit_legend(self, node: Element) -> None:
|
|
pass
|
|
|
|
def depart_legend(self, node: Element) -> None:
|
|
pass
|
|
|
|
# XXX: option list could use some better styling
|
|
|
|
def visit_option_list(self, node: Element) -> None:
|
|
pass
|
|
|
|
def depart_option_list(self, node: Element) -> None:
|
|
pass
|
|
|
|
def visit_option_list_item(self, node: Element) -> None:
|
|
self.new_state(0)
|
|
|
|
def depart_option_list_item(self, node: Element) -> None:
|
|
self.end_state()
|
|
|
|
def visit_option_group(self, node: Element) -> None:
|
|
self._firstoption = True
|
|
|
|
def depart_option_group(self, node: Element) -> None:
|
|
self.add_text(' ')
|
|
|
|
def visit_option(self, node: Element) -> None:
|
|
if self._firstoption:
|
|
self._firstoption = False
|
|
else:
|
|
self.add_text(', ')
|
|
|
|
def depart_option(self, node: Element) -> None:
|
|
pass
|
|
|
|
def visit_option_string(self, node: Element) -> None:
|
|
pass
|
|
|
|
def depart_option_string(self, node: Element) -> None:
|
|
pass
|
|
|
|
def visit_option_argument(self, node: Element) -> None:
|
|
self.add_text(node['delimiter'])
|
|
|
|
def depart_option_argument(self, node: Element) -> None:
|
|
pass
|
|
|
|
def visit_description(self, node: Element) -> None:
|
|
pass
|
|
|
|
def depart_description(self, node: Element) -> None:
|
|
pass
|
|
|
|
def visit_tabular_col_spec(self, node: Element) -> None:
|
|
raise nodes.SkipNode
|
|
|
|
def visit_colspec(self, node: Element) -> None:
|
|
self.table.colwidth.append(node["colwidth"])
|
|
raise nodes.SkipNode
|
|
|
|
def visit_tgroup(self, node: Element) -> None:
|
|
pass
|
|
|
|
def depart_tgroup(self, node: Element) -> None:
|
|
pass
|
|
|
|
def visit_thead(self, node: Element) -> None:
|
|
pass
|
|
|
|
def depart_thead(self, node: Element) -> None:
|
|
pass
|
|
|
|
def visit_tbody(self, node: Element) -> None:
|
|
self.table.set_separator()
|
|
|
|
def depart_tbody(self, node: Element) -> None:
|
|
pass
|
|
|
|
def visit_row(self, node: Element) -> None:
|
|
if self.table.lines:
|
|
self.table.add_row()
|
|
|
|
def depart_row(self, node: Element) -> None:
|
|
pass
|
|
|
|
def visit_entry(self, node: Element) -> None:
|
|
self.entry = Cell(
|
|
rowspan=node.get("morerows", 0) + 1, colspan=node.get("morecols", 0) + 1
|
|
)
|
|
self.new_state(0)
|
|
|
|
def depart_entry(self, node: Element) -> None:
|
|
text = self.nl.join(self.nl.join(x[1]) for x in self.states.pop())
|
|
self.stateindent.pop()
|
|
self.entry.text = text
|
|
self.table.add_cell(self.entry)
|
|
self.entry = None
|
|
|
|
def visit_table(self, node: Element) -> None:
|
|
if self.table:
|
|
raise NotImplementedError('Nested tables are not supported.')
|
|
self.new_state(0)
|
|
self.table = Table()
|
|
|
|
def depart_table(self, node: Element) -> None:
|
|
self.add_text(str(self.table))
|
|
self.table = None
|
|
self.end_state(wrap=False)
|
|
|
|
def visit_acks(self, node: Element) -> None:
|
|
bullet_list = cast(nodes.bullet_list, node[0])
|
|
list_items = cast(Iterable[nodes.list_item], bullet_list)
|
|
self.new_state(0)
|
|
self.add_text(', '.join(n.astext() for n in list_items) + '.')
|
|
self.end_state()
|
|
raise nodes.SkipNode
|
|
|
|
def visit_image(self, node: Element) -> None:
|
|
if 'alt' in node.attributes:
|
|
self.add_text(_('[image: %s]') % node['alt'])
|
|
self.add_text(_('[image]'))
|
|
raise nodes.SkipNode
|
|
|
|
def visit_transition(self, node: Element) -> None:
|
|
indent = sum(self.stateindent)
|
|
self.new_state(0)
|
|
self.add_text('=' * (MAXWIDTH - indent))
|
|
self.end_state()
|
|
raise nodes.SkipNode
|
|
|
|
def visit_bullet_list(self, node: Element) -> None:
|
|
self.list_counter.append(-1)
|
|
|
|
def depart_bullet_list(self, node: Element) -> None:
|
|
self.list_counter.pop()
|
|
|
|
def visit_enumerated_list(self, node: Element) -> None:
|
|
self.list_counter.append(node.get('start', 1) - 1)
|
|
|
|
def depart_enumerated_list(self, node: Element) -> None:
|
|
self.list_counter.pop()
|
|
|
|
def visit_definition_list(self, node: Element) -> None:
|
|
self.list_counter.append(-2)
|
|
|
|
def depart_definition_list(self, node: Element) -> None:
|
|
self.list_counter.pop()
|
|
|
|
def visit_list_item(self, node: Element) -> None:
|
|
if self.list_counter[-1] == -1:
|
|
# bullet list
|
|
self.new_state(2)
|
|
elif self.list_counter[-1] == -2:
|
|
# definition list
|
|
pass
|
|
else:
|
|
# enumerated list
|
|
self.list_counter[-1] += 1
|
|
self.new_state(len(str(self.list_counter[-1])) + 2)
|
|
|
|
def depart_list_item(self, node: Element) -> None:
|
|
if self.list_counter[-1] == -1:
|
|
self.end_state(first='* ')
|
|
elif self.list_counter[-1] == -2:
|
|
pass
|
|
else:
|
|
self.end_state(first='%s. ' % self.list_counter[-1])
|
|
|
|
def visit_definition_list_item(self, node: Element) -> None:
|
|
self._classifier_count_in_li = len(node.traverse(nodes.classifier))
|
|
|
|
def depart_definition_list_item(self, node: Element) -> None:
|
|
pass
|
|
|
|
def visit_term(self, node: Element) -> None:
|
|
self.new_state(0)
|
|
|
|
def depart_term(self, node: Element) -> None:
|
|
if not self._classifier_count_in_li:
|
|
self.end_state(end=None)
|
|
|
|
def visit_classifier(self, node: Element) -> None:
|
|
self.add_text(' : ')
|
|
|
|
def depart_classifier(self, node: Element) -> None:
|
|
self._classifier_count_in_li -= 1
|
|
if not self._classifier_count_in_li:
|
|
self.end_state(end=None)
|
|
|
|
def visit_definition(self, node: Element) -> None:
|
|
self.new_state()
|
|
|
|
def depart_definition(self, node: Element) -> None:
|
|
self.end_state()
|
|
|
|
def visit_field_list(self, node: Element) -> None:
|
|
pass
|
|
|
|
def depart_field_list(self, node: Element) -> None:
|
|
pass
|
|
|
|
def visit_field(self, node: Element) -> None:
|
|
pass
|
|
|
|
def depart_field(self, node: Element) -> None:
|
|
pass
|
|
|
|
def visit_field_name(self, node: Element) -> None:
|
|
self.new_state(0)
|
|
|
|
def depart_field_name(self, node: Element) -> None:
|
|
self.add_text(':')
|
|
self.end_state(end=None)
|
|
|
|
def visit_field_body(self, node: Element) -> None:
|
|
self.new_state()
|
|
|
|
def depart_field_body(self, node: Element) -> None:
|
|
self.end_state()
|
|
|
|
def visit_centered(self, node: Element) -> None:
|
|
pass
|
|
|
|
def depart_centered(self, node: Element) -> None:
|
|
pass
|
|
|
|
def visit_hlist(self, node: Element) -> None:
|
|
pass
|
|
|
|
def depart_hlist(self, node: Element) -> None:
|
|
pass
|
|
|
|
def visit_hlistcol(self, node: Element) -> None:
|
|
pass
|
|
|
|
def depart_hlistcol(self, node: Element) -> None:
|
|
pass
|
|
|
|
def visit_admonition(self, node: Element) -> None:
|
|
self.new_state(0)
|
|
|
|
def depart_admonition(self, node: Element) -> None:
|
|
self.end_state()
|
|
|
|
def _visit_admonition(self, node: Element) -> None:
|
|
self.new_state(2)
|
|
|
|
def _depart_admonition(self, node: Element) -> None:
|
|
label = admonitionlabels[node.tagname]
|
|
indent = sum(self.stateindent) + len(label)
|
|
if (len(self.states[-1]) == 1 and
|
|
self.states[-1][0][0] == 0 and
|
|
MAXWIDTH - indent >= sum(len(s) for s in self.states[-1][0][1])):
|
|
# short text: append text after admonition label
|
|
self.stateindent[-1] += len(label)
|
|
self.end_state(first=label + ': ')
|
|
else:
|
|
# long text: append label before the block
|
|
self.states[-1].insert(0, (0, [self.nl]))
|
|
self.end_state(first=label + ':')
|
|
|
|
visit_attention = _visit_admonition
|
|
depart_attention = _depart_admonition
|
|
visit_caution = _visit_admonition
|
|
depart_caution = _depart_admonition
|
|
visit_danger = _visit_admonition
|
|
depart_danger = _depart_admonition
|
|
visit_error = _visit_admonition
|
|
depart_error = _depart_admonition
|
|
visit_hint = _visit_admonition
|
|
depart_hint = _depart_admonition
|
|
visit_important = _visit_admonition
|
|
depart_important = _depart_admonition
|
|
visit_note = _visit_admonition
|
|
depart_note = _depart_admonition
|
|
visit_tip = _visit_admonition
|
|
depart_tip = _depart_admonition
|
|
visit_warning = _visit_admonition
|
|
depart_warning = _depart_admonition
|
|
visit_seealso = _visit_admonition
|
|
depart_seealso = _depart_admonition
|
|
|
|
def visit_versionmodified(self, node: Element) -> None:
|
|
self.new_state(0)
|
|
|
|
def depart_versionmodified(self, node: Element) -> None:
|
|
self.end_state()
|
|
|
|
def visit_literal_block(self, node: Element) -> None:
|
|
self.new_state()
|
|
|
|
def depart_literal_block(self, node: Element) -> None:
|
|
self.end_state(wrap=False)
|
|
|
|
def visit_doctest_block(self, node: Element) -> None:
|
|
self.new_state(0)
|
|
|
|
def depart_doctest_block(self, node: Element) -> None:
|
|
self.end_state(wrap=False)
|
|
|
|
def visit_line_block(self, node: Element) -> None:
|
|
self.new_state()
|
|
self.lineblocklevel += 1
|
|
|
|
def depart_line_block(self, node: Element) -> None:
|
|
self.lineblocklevel -= 1
|
|
self.end_state(wrap=False, end=None)
|
|
if not self.lineblocklevel:
|
|
self.add_text('\n')
|
|
|
|
def visit_line(self, node: Element) -> None:
|
|
pass
|
|
|
|
def depart_line(self, node: Element) -> None:
|
|
self.add_text('\n')
|
|
|
|
def visit_block_quote(self, node: Element) -> None:
|
|
self.new_state()
|
|
|
|
def depart_block_quote(self, node: Element) -> None:
|
|
self.end_state()
|
|
|
|
def visit_compact_paragraph(self, node: Element) -> None:
|
|
pass
|
|
|
|
def depart_compact_paragraph(self, node: Element) -> None:
|
|
pass
|
|
|
|
def visit_paragraph(self, node: Element) -> None:
|
|
if not isinstance(node.parent, nodes.Admonition) or \
|
|
isinstance(node.parent, addnodes.seealso):
|
|
self.new_state(0)
|
|
|
|
def depart_paragraph(self, node: Element) -> None:
|
|
if not isinstance(node.parent, nodes.Admonition) or \
|
|
isinstance(node.parent, addnodes.seealso):
|
|
self.end_state()
|
|
|
|
def visit_target(self, node: Element) -> None:
|
|
raise nodes.SkipNode
|
|
|
|
def visit_index(self, node: Element) -> None:
|
|
raise nodes.SkipNode
|
|
|
|
def visit_toctree(self, node: Element) -> None:
|
|
raise nodes.SkipNode
|
|
|
|
def visit_substitution_definition(self, node: Element) -> None:
|
|
raise nodes.SkipNode
|
|
|
|
def visit_pending_xref(self, node: Element) -> None:
|
|
pass
|
|
|
|
def depart_pending_xref(self, node: Element) -> None:
|
|
pass
|
|
|
|
def visit_reference(self, node: Element) -> None:
|
|
if self.add_secnumbers:
|
|
numbers = node.get("secnumber")
|
|
if numbers is not None:
|
|
self.add_text('.'.join(map(str, numbers)) + self.secnumber_suffix)
|
|
|
|
def depart_reference(self, node: Element) -> None:
|
|
pass
|
|
|
|
def visit_number_reference(self, node: Element) -> None:
|
|
text = nodes.Text(node.get('title', '#'))
|
|
self.visit_Text(text)
|
|
raise nodes.SkipNode
|
|
|
|
def visit_download_reference(self, node: Element) -> None:
|
|
pass
|
|
|
|
def depart_download_reference(self, node: Element) -> None:
|
|
pass
|
|
|
|
def visit_emphasis(self, node: Element) -> None:
|
|
self.add_text('*')
|
|
|
|
def depart_emphasis(self, node: Element) -> None:
|
|
self.add_text('*')
|
|
|
|
def visit_literal_emphasis(self, node: Element) -> None:
|
|
self.add_text('*')
|
|
|
|
def depart_literal_emphasis(self, node: Element) -> None:
|
|
self.add_text('*')
|
|
|
|
def visit_strong(self, node: Element) -> None:
|
|
self.add_text('**')
|
|
|
|
def depart_strong(self, node: Element) -> None:
|
|
self.add_text('**')
|
|
|
|
def visit_literal_strong(self, node: Element) -> None:
|
|
self.add_text('**')
|
|
|
|
def depart_literal_strong(self, node: Element) -> None:
|
|
self.add_text('**')
|
|
|
|
def visit_abbreviation(self, node: Element) -> None:
|
|
self.add_text('')
|
|
|
|
def depart_abbreviation(self, node: Element) -> None:
|
|
if node.hasattr('explanation'):
|
|
self.add_text(' (%s)' % node['explanation'])
|
|
|
|
def visit_manpage(self, node: Element) -> None:
|
|
return self.visit_literal_emphasis(node)
|
|
|
|
def depart_manpage(self, node: Element) -> None:
|
|
return self.depart_literal_emphasis(node)
|
|
|
|
def visit_title_reference(self, node: Element) -> None:
|
|
self.add_text('*')
|
|
|
|
def depart_title_reference(self, node: Element) -> None:
|
|
self.add_text('*')
|
|
|
|
def visit_literal(self, node: Element) -> None:
|
|
self.add_text('"')
|
|
|
|
def depart_literal(self, node: Element) -> None:
|
|
self.add_text('"')
|
|
|
|
def visit_subscript(self, node: Element) -> None:
|
|
self.add_text('_')
|
|
|
|
def depart_subscript(self, node: Element) -> None:
|
|
pass
|
|
|
|
def visit_superscript(self, node: Element) -> None:
|
|
self.add_text('^')
|
|
|
|
def depart_superscript(self, node: Element) -> None:
|
|
pass
|
|
|
|
def visit_footnote_reference(self, node: Element) -> None:
|
|
self.add_text('[%s]' % node.astext())
|
|
raise nodes.SkipNode
|
|
|
|
def visit_citation_reference(self, node: Element) -> None:
|
|
self.add_text('[%s]' % node.astext())
|
|
raise nodes.SkipNode
|
|
|
|
def visit_Text(self, node: Text) -> None:
|
|
self.add_text(node.astext())
|
|
|
|
def depart_Text(self, node: Text) -> None:
|
|
pass
|
|
|
|
def visit_generated(self, node: Element) -> None:
|
|
pass
|
|
|
|
def depart_generated(self, node: Element) -> None:
|
|
pass
|
|
|
|
def visit_inline(self, node: Element) -> None:
|
|
if 'xref' in node['classes'] or 'term' in node['classes']:
|
|
self.add_text('*')
|
|
|
|
def depart_inline(self, node: Element) -> None:
|
|
if 'xref' in node['classes'] or 'term' in node['classes']:
|
|
self.add_text('*')
|
|
|
|
def visit_container(self, node: Element) -> None:
|
|
pass
|
|
|
|
def depart_container(self, node: Element) -> None:
|
|
pass
|
|
|
|
def visit_problematic(self, node: Element) -> None:
|
|
self.add_text('>>')
|
|
|
|
def depart_problematic(self, node: Element) -> None:
|
|
self.add_text('<<')
|
|
|
|
def visit_system_message(self, node: Element) -> None:
|
|
self.new_state(0)
|
|
self.add_text('<SYSTEM MESSAGE: %s>' % node.astext())
|
|
self.end_state()
|
|
raise nodes.SkipNode
|
|
|
|
def visit_comment(self, node: Element) -> None:
|
|
raise nodes.SkipNode
|
|
|
|
def visit_meta(self, node: Element) -> None:
|
|
# only valid for HTML
|
|
raise nodes.SkipNode
|
|
|
|
def visit_raw(self, node: Element) -> None:
|
|
if 'text' in node.get('format', '').split():
|
|
self.new_state(0)
|
|
self.add_text(node.astext())
|
|
self.end_state(wrap = False)
|
|
raise nodes.SkipNode
|
|
|
|
def visit_math(self, node: Element) -> None:
|
|
pass
|
|
|
|
def depart_math(self, node: Element) -> None:
|
|
pass
|
|
|
|
def visit_math_block(self, node: Element) -> None:
|
|
self.new_state()
|
|
|
|
def depart_math_block(self, node: Element) -> None:
|
|
self.end_state()
|
|
|
|
def unknown_visit(self, node: Node) -> None:
|
|
raise NotImplementedError('Unknown node: ' + node.__class__.__name__)
|