[Python-checkins] r63855 - in doctools/trunk: doc/builders.rst sphinx/builder.py sphinx/environment.py sphinx/latexwriter.py sphinx/textwriter.py

georg.brandl python-checkins at python.org
Sun Jun 1 17:27:17 CEST 2008


Author: georg.brandl
Date: Sun Jun  1 17:27:16 2008
New Revision: 63855

Log:
Add a preliminary text writer, mainly for producing pydoc-compatible topic help.


Added:
   doctools/trunk/sphinx/textwriter.py
Modified:
   doctools/trunk/doc/builders.rst
   doctools/trunk/sphinx/builder.py
   doctools/trunk/sphinx/environment.py
   doctools/trunk/sphinx/latexwriter.py

Modified: doctools/trunk/doc/builders.rst
==============================================================================
--- doctools/trunk/doc/builders.rst	(original)
+++ doctools/trunk/doc/builders.rst	Sun Jun  1 17:27:16 2008
@@ -51,6 +51,16 @@
 
    Its name is ``latex``.
 
+.. class:: TextBuilder
+
+   This builder produces a text file for each reST file -- this is almost the
+   same as the reST source, but with much of the markup stripped for better
+   readability.
+
+   Its name is ``text``.
+
+   .. versionadded:: 0.4
+   
 .. class:: ChangesBuilder
 
    This builder produces an HTML overview of all :dir:`versionadded`,

Modified: doctools/trunk/sphinx/builder.py
==============================================================================
--- doctools/trunk/sphinx/builder.py	(original)
+++ doctools/trunk/sphinx/builder.py	Sun Jun  1 17:27:16 2008
@@ -28,6 +28,7 @@
 from sphinx.util import ensuredir, relative_uri, SEP, os_path
 from sphinx.htmlhelp import build_hhx
 from sphinx.htmlwriter import HTMLWriter, HTMLTranslator, SmartyPantsHTMLTranslator
+from sphinx.textwriter import TextWriter
 from sphinx.latexwriter import LaTeXWriter
 from sphinx.environment import BuildEnvironment, NoUri
 from sphinx.highlighting import PygmentsBridge
@@ -223,7 +224,7 @@
             self.info(bold('build succeeded.'))
 
     def write(self, build_docnames, updated_docnames, method='update'):
-        if build_docnames is None:
+        if build_docnames is None or build_docnames == ['__all__']:
             # build_all
             build_docnames = self.env.found_docs
         if method == 'update':
@@ -980,6 +981,56 @@
     def finish(self):
         pass
 
+
+class TextBuilder(Builder):
+    name = 'text'
+    out_suffix = '.txt'
+
+    def init(self):
+        pass
+
+    def get_outdated_docs(self):
+        for docname in self.env.found_docs:
+            if docname not in self.env.all_docs:
+                yield docname
+                continue
+            targetname = self.env.doc2path(docname, self.outdir, self.out_suffix)
+            try:
+                targetmtime = path.getmtime(targetname)
+            except Exception:
+                targetmtime = 0
+            try:
+                srcmtime = path.getmtime(self.env.doc2path(docname))
+                if srcmtime > targetmtime:
+                    yield docname
+            except EnvironmentError:
+                # source doesn't exist anymore
+                pass
+
+    def get_target_uri(self, docname, typ=None):
+        return ''
+
+    def prepare_writing(self, docnames):
+        self.writer = TextWriter(self)
+
+    def write_doc(self, docname, doctree):
+        destination = StringOutput(encoding='utf-8')
+        self.writer.write(doctree, destination)
+        outfilename = path.join(self.outdir, os_path(docname) + self.out_suffix)
+        ensuredir(path.dirname(outfilename)) # normally different from self.outdir
+        try:
+            f = codecs.open(outfilename, 'w', 'utf-8')
+            try:
+                f.write(self.writer.output)
+            finally:
+                f.close()
+        except (IOError, OSError), err:
+            self.warn("Error writing file %s: %s" % (outfilename, err))
+
+    def finish(self):
+        pass
+
+
 # compatibility alias
 WebHTMLBuilder = PickleHTMLBuilder
 
@@ -992,6 +1043,7 @@
     'web': PickleHTMLBuilder,
     'htmlhelp': HTMLHelpBuilder,
     'latex': LaTeXBuilder,
+    'text': TextBuilder,
     'changes': ChangesBuilder,
     'linkcheck': CheckExternalLinksBuilder,
 }

Modified: doctools/trunk/sphinx/environment.py
==============================================================================
--- doctools/trunk/sphinx/environment.py	(original)
+++ doctools/trunk/sphinx/environment.py	Sun Jun  1 17:27:16 2008
@@ -1004,8 +1004,12 @@
             includes = getinc(docname)
             # previous
             if not previous:
+                # if no previous sibling, go to parent
                 previous = parents[0][0]
             else:
+                # else, go to previous sibling, or if it has children, to
+                # the last of its children, or if that has children, to the
+                # last of those, and so forth
                 while 1:
                     previncs = getinc(previous)
                     if previncs:
@@ -1014,10 +1018,14 @@
                         break
             # next
             if includes:
+                # if it has children, go to first of them
                 next = includes[0]
             elif next:
+                # else, if next sibling, go to it
                 pass
             else:
+                # else, go to the next sibling of the parent, if present,
+                # else the grandparent's sibling, if present, and so forth
                 for parname, parindex in parents:
                     parincs = getinc(parname)
                     if parincs and parindex + 1 < len(parincs):

Modified: doctools/trunk/sphinx/latexwriter.py
==============================================================================
--- doctools/trunk/sphinx/latexwriter.py	(original)
+++ doctools/trunk/sphinx/latexwriter.py	Sun Jun  1 17:27:16 2008
@@ -993,6 +993,9 @@
     def depart_Text(self, node):
         pass
 
+    def visit_comment(self, node):
+        raise nodes.SkipNode
+
     def visit_system_message(self, node):
         pass
     def depart_system_message(self, node):

Added: doctools/trunk/sphinx/textwriter.py
==============================================================================
--- (empty file)
+++ doctools/trunk/sphinx/textwriter.py	Sun Jun  1 17:27:16 2008
@@ -0,0 +1,595 @@
+# -*- coding: utf-8 -*-
+"""
+    sphinx.textwriter
+    ~~~~~~~~~~~~~~~~~
+
+    Custom docutils writer for plain text.
+
+    :copyright: 2008 by Georg Brandl.
+    :license: BSD.
+"""
+
+import re
+import textwrap
+
+from docutils import nodes, writers
+
+from sphinx import addnodes
+
+
+class TextWriter(writers.Writer):
+    supported = ('text',)
+    settings_spec = ('No options here.', '', ())
+    settings_defaults = {}
+
+    output = None
+
+    def __init__(self, builder):
+        writers.Writer.__init__(self)
+        self.builder = builder
+
+    def translate(self):
+        visitor = TextTranslator(self.document, self.builder)
+        self.document.walkabout(visitor)
+        self.output = visitor.body
+
+# monkey-patch...
+new_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
+textwrap.TextWrapper.wordsep_re = new_wordsep_re
+
+MAXWIDTH = 70
+STDINDENT = 3
+
+
+class TextTranslator(nodes.NodeVisitor):
+    sectionchars = '*=-~"+'
+
+    def __init__(self, document, builder):
+        nodes.NodeVisitor.__init__(self, document)
+
+        self.states = [[]]
+        self.stateindent = [0]
+        self.sectionlevel = 0
+        self.table = None
+
+    def add_text(self, text):
+        self.states[-1].append((-1, text))
+    def new_state(self, indent=STDINDENT):
+        self.states.append([])
+        self.stateindent.append(indent)
+    def end_state(self, wrap=True, end=[''], first=None):
+        content = self.states.pop()
+        maxindent = sum(self.stateindent)
+        indent = self.stateindent.pop()
+        result = []
+        toformat = []
+        def do_format():
+            if not toformat:
+                return
+            if wrap:
+                res = textwrap.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)
+            else:
+                do_format()
+                result.append((indent + itemindent, item))
+                toformat = []
+        do_format()
+        if first is not None and result:
+            itemindent, item = result[0]
+            if item:
+                result.insert(0, (itemindent - indent, [first + item[0]]))
+                result[1] = (itemindent, item[1:])
+        self.states[-1].extend(result)
+
+    def visit_document(self, node):
+        self.new_state(0)
+    def depart_document(self, node):
+        self.end_state()
+        self.body = '\n'.join(line and (' '*indent + line)
+                              for indent, lines in self.states[0]
+                              for line in lines)
+        # XXX header/footer?
+
+    def visit_highlightlang(self, node):
+        raise nodes.SkipNode
+
+    def visit_section(self, node):
+        self._title_char = self.sectionchars[self.sectionlevel]
+        self.sectionlevel += 1
+    def depart_section(self, node):
+        self.sectionlevel -= 1
+
+    def visit_topic(self, node):
+        self.new_state(0)
+    def depart_topic(self, node):
+        self.end_state()
+
+    visit_sidebar = visit_topic
+    depart_sidebar = depart_topic
+
+    def visit_rubric(self, node):
+        self.new_state(0)
+        self.add_text('-[ ')
+    def depart_rubric(self, node):
+        self.add_text(' ]-')
+        self.end_state()
+
+    def visit_glossary(self, node):
+        pass
+    def depart_glossary(self, node):
+        pass
+
+    def visit_title(self, node):
+        if isinstance(node.parent, nodes.Admonition):
+            self.add_text(node.astext()+': ')
+            raise nodes.SkipNode
+        self.new_state(0)
+    def depart_title(self, node):
+        if isinstance(node.parent, nodes.section):
+            char = self._title_char
+        else:
+            char = '^'
+        text = ''.join(x[1] for x in self.states.pop() if x[0] == -1)
+        self.stateindent.pop()
+        self.states[-1].append((0, ['', text, '%s' % (char * len(text)), '']))
+
+    def visit_module(self, node):
+        if node.has_key('platform'):
+            self.new_state(0)
+            self.add_text('Platform: %s' % node['platform'])
+            self.end_state()
+        raise nodes.SkipNode
+
+    def visit_desc(self, node):
+        pass
+    def depart_desc(self, node):
+        pass
+
+    def visit_desc_signature(self, node):
+        self.new_state(0)
+        if node.parent['desctype'] in ('class', 'exception'):
+            self.add_text('%s ' % node.parent['desctype'])
+    def depart_desc_signature(self, node):
+        # XXX: wrap signatures in a way that makes sense
+        self.end_state(wrap=False, end=None)
+
+    def visit_desc_name(self, node):
+        pass
+    def depart_desc_name(self, node):
+        pass
+
+    def visit_desc_classname(self, node):
+        pass
+    def depart_desc_classname(self, node):
+        pass
+
+    def visit_desc_type(self, node):
+        pass
+    def depart_desc_type(self, node):
+        pass
+
+    def visit_desc_parameterlist(self, node):
+        self.add_text('(')
+        self.first_param = 1
+    def depart_desc_parameterlist(self, node):
+        self.add_text(')')
+
+    def visit_desc_parameter(self, node):
+        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):
+        self.add_text('[')
+    def depart_desc_optional(self, node):
+        self.add_text(']')
+
+    def visit_refcount(self, node):
+        pass
+    def depart_refcount(self, node):
+        pass
+
+    def visit_desc_content(self, node):
+        self.new_state()
+        self.add_text('\n')
+    def depart_desc_content(self, node):
+        self.end_state()
+
+    def visit_figure(self, node):
+        self.new_state()
+    def depart_figure(self, node):
+        self.end_state()
+
+    def visit_caption(self, node):
+        pass
+    def depart_caption(self, node):
+        pass
+
+    def visit_productionlist(self, node):
+        self.new_state()
+        names = []
+        for production in node:
+            names.append(production['tokenname'])
+        maxlen = max(len(name) for name in names)
+        for production in node:
+            if production['tokenname']:
+                self.add_text(production['tokenname'].ljust(maxlen) + ' ::=')
+                lastname = production['tokenname']
+            else:
+                self.add_text('%s    ' % (' '*len(lastname)))
+            self.add_text(production.astext() + '\n')
+        self.end_state(wrap=False)
+        raise nodes.SkipNode
+
+    def visit_seealso(self, node):
+        self.new_state()
+    def depart_seealso(self, node):
+        self.end_state(first='')
+
+    def visit_footnote(self, node):
+        self._footnote = node.children[0].astext().strip()
+        self.new_state(len(self._footnote) + 3)
+    def depart_footnote(self, node):
+        self.end_state(first='[%s] ' % self._footnote)
+
+    def visit_label(self, node):
+        raise nodes.SkipNode
+
+    def visit_tabular_col_spec(self, node):
+        raise nodes.SkipNode
+
+    def visit_colspec(self, node):
+        self.table[0].append(node['colwidth'])
+        raise nodes.SkipNode
+
+    def visit_tgroup(self, node):
+        pass
+    def depart_tgroup(self, node):
+        pass
+
+    def visit_thead(self, node):
+        pass
+    def depart_thead(self, node):
+        pass
+
+    def visit_tbody(self, node):
+        self.table.append('sep')
+    def depart_tbody(self, node):
+        pass
+
+    def visit_row(self, node):
+        self.table.append([])
+    def depart_row(self, node):
+        pass
+
+    def visit_entry(self, node):
+        if node.has_key('morerows') or node.has_key('morecols'):
+            raise NotImplementedError('Column or row spanning cells are '
+                                      'not implemented.')
+        self.new_state(0)
+    def depart_entry(self, node):
+        text = '\n'.join('\n'.join(x[1]) for x in self.states.pop())
+        self.stateindent.pop()
+        self.table[-1].append(text)
+
+    def visit_table(self, node):
+        if self.table:
+            raise NotImplementedError('Nested tables are not supported.')
+        self.new_state(0)
+        self.table = [[]]
+    def depart_table(self, node):
+        lines = self.table[1:]
+        fmted_rows = []
+        colwidths = self.table[0]
+        realwidths = colwidths[:]
+        separator = 0
+        # don't allow paragraphs in table cells for now
+        for line in lines:
+            if line == 'sep':
+                separator = len(fmted_rows)
+            else:
+                cells = []
+                for i, cell in enumerate(line):
+                    par = textwrap.wrap(cell, width=colwidths[i])
+                    maxwidth = max(map(len, par)) if par else 0
+                    realwidths[i] = max(realwidths[i], maxwidth)
+                    cells.append(par)
+                fmted_rows.append(cells)
+
+        def writesep(char='-'):
+            out = ['+']
+            for width in realwidths:
+                out.append(char * (width+2))
+                out.append('+')
+            self.add_text(''.join(out) + '\n')
+
+        def writerow(row):
+            lines = map(None, *row)
+            for line in lines:
+                out = ['|']
+                for i, cell in enumerate(line):
+                    if cell:
+                        out.append(' ' + cell.ljust(realwidths[i]+1))
+                    else:
+                        out.append(' ' * (realwidths[i] + 2))
+                    out.append('|')
+                self.add_text(''.join(out) + '\n')
+
+        for i, row in enumerate(fmted_rows):
+            if separator and i == separator:
+                writesep('=')
+            else:
+                writesep('-')
+            writerow(row)
+        writesep('-')
+        self.table = None
+        self.end_state(wrap=False)
+
+    def visit_acks(self, node):
+        self.new_state(0)
+        self.add_text(', '.join(n.astext() for n in node.children[0].children) + '.')
+        self.end_state()
+        raise nodes.SkipNode
+
+    def visit_image(self, node):
+        self.add_text('[image]')
+
+    def visit_transition(self, node):
+        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):
+        self._list_counter = -1
+    def depart_bullet_list(self, node):
+        pass
+
+    def visit_enumerated_list(self, node):
+        self._list_counter = 0
+    def depart_enumerated_list(self, node):
+        pass
+
+    def visit_definition_list(self, node):
+        self._list_counter = -2
+    def depart_definition_list(self, node):
+        pass
+
+    def visit_list_item(self, node):
+        if self._list_counter == -1:
+            # bullet list
+            self.new_state(2)
+        elif self._list_counter == -2:
+            # definition list
+            pass
+        else:
+            # enumerated list
+            self._list_counter += 1
+            self.new_state(len(str(self._list_counter)) + 2)
+    def depart_list_item(self, node):
+        if self._list_counter == -1:
+            self.end_state(first='* ', end=None)
+        elif self._list_counter == -2:
+            pass
+        else:
+            self.end_state(first='%s. ' % self._list_counter, end=None)
+
+    def visit_definition_list_item(self, node):
+        self._li_has_classifier = len(node) >= 2 and \
+                                  isinstance(node[1], nodes.classifier)
+    def depart_definition_list_item(self, node):
+        pass
+
+    def visit_term(self, node):
+        self.new_state(0)
+    def depart_term(self, node):
+        if not self._li_has_classifier:
+            self.end_state(end=None)
+
+    def visit_classifier(self, node):
+        self.add_text(' : ')
+    def depart_classifier(self, node):
+        self.end_state(end=None)
+
+    def visit_definition(self, node):
+        self.new_state()
+    def depart_definition(self, node):
+        self.end_state()
+
+    def visit_field_list(self, node):
+        pass
+    def depart_field_list(self, node):
+        pass
+
+    def visit_field(self, node):
+        pass
+    def depart_field(self, node):
+        pass
+
+    def visit_field_name(self, node):
+        self.new_state(0)
+    def depart_field_name(self, node):
+        self.add_text(':')
+        self.end_state(end=None)
+
+    def visit_field_body(self, node):
+        self.new_state()
+    def depart_field_body(self, node):
+        self.end_state()
+
+    def visit_centered(self, node):
+        pass
+    def depart_centered(self, node):
+        pass
+
+    def visit_admonition(self, node):
+        self.new_state(0)
+    def depart_admonition(self, node):
+        self.end_state()
+
+    def _make_visit_admonition(name):
+        def visit_admonition(self, node):
+            self.new_state(2)
+        return visit_admonition
+    def _make_depart_admonition(name):
+        def depart_admonition(self, node):
+            self.end_state(first=name.capitalize() + ': ')
+        return depart_admonition
+
+    visit_attention = _make_visit_admonition('attention')
+    depart_attention = _make_depart_admonition('attention')
+    visit_caution = _make_visit_admonition('caution')
+    depart_caution = _make_depart_admonition('caution')
+    visit_danger = _make_visit_admonition('danger')
+    depart_danger = _make_depart_admonition('danger')
+    visit_error = _make_visit_admonition('error')
+    depart_error = _make_depart_admonition('error')
+    visit_hint = _make_visit_admonition('hint')
+    depart_hint = _make_depart_admonition('hint')
+    visit_important = _make_visit_admonition('important')
+    depart_important = _make_depart_admonition('important')
+    visit_note = _make_visit_admonition('note')
+    depart_note = _make_depart_admonition('note')
+    visit_tip = _make_visit_admonition('tip')
+    depart_tip = _make_depart_admonition('tip')
+    visit_warning = _make_visit_admonition('warning')
+    depart_warning = _make_depart_admonition('warning')
+
+    def visit_versionmodified(self, node):
+        self.new_state(0)
+        if node['type'] == 'versionadded':
+            tmpl = 'Added in version %s'
+        elif node['type'] == 'versionchanged':
+            tmpl = 'Changed in version %s'
+        elif node['type'] == 'deprecated':
+            tmpl = 'Deprecated in version %s'
+        if node.children:
+            self.add_text(tmpl % node['version'] + ': ')
+        else:
+            self.add_text(tmpl % node['version'] + '.')
+    def depart_versionmodified(self, node):
+        self.end_state()
+
+    def visit_literal_block(self, node):
+        self.new_state()
+    def depart_literal_block(self, node):
+        self.end_state(wrap=False)
+
+    def visit_doctest_block(self, node):
+        self.new_state(0)
+    def depart_doctest_block(self, node):
+        self.end_state(wrap=False)
+
+    def visit_line_block(self, node):
+        self.new_state(0)
+    def depart_line_block(self, node):
+        self.end_state(wrap=False)
+
+    def visit_line(self, node):
+        pass
+    def depart_line(self, node):
+        pass
+
+    def visit_block_quote(self, node):
+        self.new_state()
+    def depart_block_quote(self, node):
+        self.end_state()
+
+    def visit_compact_paragraph(self, node):
+        pass
+    def depart_compact_paragraph(self, node):
+        pass
+
+    def visit_paragraph(self, node):
+        if not isinstance(node.parent, nodes.Admonition) or \
+               isinstance(node.parent, addnodes.seealso):
+            self.new_state(0)
+    def depart_paragraph(self, node):
+        if not isinstance(node.parent, nodes.Admonition) or \
+               isinstance(node.parent, addnodes.seealso):
+            self.end_state()
+
+    def visit_target(self, node):
+        raise nodes.SkipNode
+
+    def visit_index(self, node):
+        raise nodes.SkipNode
+
+    def visit_substitution_definition(self, node):
+        raise nodes.SkipNode
+
+    def visit_pending_xref(self, node):
+        pass
+    def depart_pending_xref(self, node):
+        pass
+
+    def visit_reference(self, node):
+        pass
+    def depart_reference(self, node):
+        pass
+
+    def visit_emphasis(self, node):
+        self.add_text('*')
+    def depart_emphasis(self, node):
+        self.add_text('*')
+
+    def visit_literal_emphasis(self, node):
+        self.add_text('*')
+    def depart_literal_emphasis(self, node):
+        self.add_text('*')
+
+    def visit_strong(self, node):
+        self.add_text('**')
+    def depart_strong(self, node):
+        self.add_text('**')
+
+    def visit_title_reference(self, node):
+        self.add_text('*')
+    def depart_title_reference(self, node):
+        self.add_text('*')
+
+    def visit_literal(self, node):
+        self.add_text('``')
+    def depart_literal(self, node):
+        self.add_text('``')
+
+    def visit_footnote_reference(self, node):
+        self.add_text('[%s]' % node.astext())
+        raise nodes.SkipNode
+
+    def visit_Text(self, node):
+        self.add_text(node.astext())
+    def depart_Text(self, node):
+        pass
+
+    def visit_problematic(self, node):
+        self.add_text('>>')
+    def depart_problematic(self, node):
+        self.add_text('<<')
+
+    def visit_system_message(self, node):
+        self.new_state(0)
+        self.add_text('<SYSTEM MESSAGE: %s>' % node.astext())
+        self.end_state()
+        raise nodes.SkipNode
+
+    def visit_comment(self, node):
+        raise nodes.SkipNode
+
+    def unknown_visit(self, node):
+        raise NotImplementedError("Unknown node: " + node.__class__.__name__)


More information about the Python-checkins mailing list