Rev 5213: Use a better design to better control the texinfo generation. in file:///home/vila/src/bzr/bugs/219334-texinfo/

Vincent Ladeuil v.ladeuil+lp at free.fr
Fri May 7 19:30:33 BST 2010


At file:///home/vila/src/bzr/bugs/219334-texinfo/

------------------------------------------------------------
revno: 5213
revision-id: v.ladeuil+lp at free.fr-20100507183032-64eligeqyxpqe44c
parent: v.ladeuil+lp at free.fr-20100506202058-ugmnv6w9738lyu44
committer: Vincent Ladeuil <v.ladeuil+lp at free.fr>
branch nick: texinfo
timestamp: Fri 2010-05-07 20:30:32 +0200
message:
  Use a better design to better control the texinfo generation.
  
  * bzrlib/tests/doc_generate/builders/test_texinfo.py:
  (TestTextGeneration.test_block_quote): Add a test that were missed
  because the previous design was relying on side-effects.
  (TestTableGeneration.test_table): Note that sphinx seems to have a
  bug here.
  (TestTocTreeGeneration.test_toctree): The syntax was wrong.
  
  * bzrlib/doc_generate/writers/texinfo.py:
  (TexinfoTranslator): Change the design by letting nodes collect
  the data and processed them as they see fit instead of relying on
  translator variables (which doesn't scale).
-------------- next part --------------
=== modified file 'bzrlib/doc_generate/writers/texinfo.py'
--- a/bzrlib/doc_generate/writers/texinfo.py	2010-05-06 20:05:01 +0000
+++ b/bzrlib/doc_generate/writers/texinfo.py	2010-05-07 18:30:32 +0000
@@ -67,7 +67,7 @@
         nodes.NodeVisitor.__init__(self, document)
         self.chunks = []
         # toctree uses some nodes for different purposes (namely:
-        # caompact_paragraph, bullet_list, reference, list_item) that needs to
+        # compact_paragraph, bullet_list, reference, list_item) that needs to
         # know when they are proessing a toctree. The following attributes take
         # care of the needs.
         self.in_toctree = False
@@ -83,26 +83,34 @@
         self.tab_item_cmd = None
         self.tab_tab_cmd = None
         self.tab_entry_num = None
-
-    def add_text(self, text):
-        self.chunks.append(text)
+        self.paragraph_sep = '\n'
 
     # The whole document
 
     def visit_document(self, node):
         # The debug killer trick
-        # print node.pformat()
-        pass
+        #import sys
+        #sys.stderr.write(node.pformat())
+        set_item_list_collector(node, 'chunk')
 
     def depart_document(self, node):
-        self.body = ''.join(chunk for chunk in self.chunks)
+        self.body = ''.join(node['chunk'])
 
     # Layout
 
     def visit_section(self, node):
         self.section_level += 1
+        set_item_list_collector(node, 'chunk')
 
     def depart_section(self, node):
+        try:
+            section_name = self.section_names[self.section_level]
+        except IndexError:
+            # Just use @heading, it's not numbered anyway
+            section_name = 'heading'
+        section_cmd = '@%s %s\n' % (section_name, node['title'])
+        text = ''.join(node['chunk'])
+        node.parent.collect_chunk(section_cmd + text)
         self.section_level -= 1
 
     def visit_topic(self, node):
@@ -112,52 +120,59 @@
         pass
 
     def visit_paragraph(self, node):
-        pass
+        set_item_list_collector(node, 'text')
 
     def depart_paragraph(self, node):
-        if not self.in_table:
-            # End the paragraph with a new line and leave a blank line after
-            # it.
-            self.add_text('\n\n')
+        # End the paragraph with a new line (or '' depending on the parent) and
+        # leave a blank line after it.
+        text = ''.join(node['text']) + self.paragraph_sep * 2
+        node.parent.collect_chunk(text)
 
     def visit_compact_paragraph(self, node):
+        set_item_list_collector(node, 'text')
         if node.has_key('toctree'):
             self.in_toctree = True
-            self.add_text('@menu\n')
         elif self.in_toctree:
-            self.toctree_current_ref = None
+            set_item_collector(node, 'reference')
 
     def depart_compact_paragraph(self, node):
         if node.has_key('toctree'):
-            self.add_text('@end menu\n')
+            node.parent.collect_chunk('@menu\n')
+            node.parent.collect_chunk(''.join(node['text']))
+            node.parent.collect_chunk('@end menu\n')
             self.in_toctree = False
         elif self.in_toctree:
             # * FIRST-ENTRY-NAME:(FILENAME)NODENAME.     DESCRIPTION
-            entry_name = node.astext()
             # XXX: the file name should probably be adjusted to the targeted
             # info file name
-            file_name = self.toctree_current_ref
-            node_name = entry_name
-            description = ''
+            node_name, file_name, entry_name = node['reference']
+            if not node_name:
+                node_name = entry_name
+            description = '' # We can't specify a description in rest AFAICS
             # XXX: What if :maxdepth: is not 1 ?
-            self.add_text('* %s:(%s)%s. %s\n' % (entry_name, file_name,
-                                                 node_name, description))
-            self.toctree_current_ref = None
+            text = '* %s: (%s)%s. %s\n' % (entry_name, file_name,
+                                           node_name, description)
+            node.parent.collect_chunk(text)
         else:
-            # End the paragraph with a new line and leave a blank line after it.
-            self.add_text('\n\n')
+            # End the paragraph with a new line (or '' depending on the parent)
+            # and leave a blank line after it.
+            text = ''.join(node['text']) + self.paragraph_sep * 2
+            node.parent.collect_chunk(text)
 
     def visit_literal_block(self, node):
-        self.add_text('@samp{')
+        set_item_collector(node, 'text')
 
     def depart_literal_block(self, node):
-        self.add_text('}\n')
+        text = '@samp{%s}' % ''.join(node['text']) + self.paragraph_sep * 2
+        node.parent.collect_chunk(text)
 
     def visit_block_quote(self, node):
-        pass
+        set_item_list_collector(node, 'chunk')
 
     def depart_block_quote(self, node):
-        pass
+        node.parent.collect_chunk('@example\n')
+        node.parent.collect_chunk(''.join(node['chunk']))
+        node.parent.collect_chunk('@end example\n')
 
     def visit_note(self, node):
         pass
@@ -180,19 +195,14 @@
     def visit_comment(self, node):
         raise nodes.SkipNode
 
-    # Document attributes
+    # Attributes
 
     def visit_title(self, node):
-        self.in_title = True
-        try:
-            section_name = self.section_names[self.section_level]
-        except IndexError:
-            # Just use @heading, it's not numbered anyway
-            section_name = 'heading'
-        self.add_text('@%s %s\n' % (section_name, node.astext()))
+        set_item_collector(node, 'text')
 
     def depart_title(self, node):
-        self.in_title = False
+        text = get_collected_item(node, 'text')
+        node.parent['title'] = text
 
     def visit_label(self, node):
         raise nodes.SkipNode
@@ -206,55 +216,71 @@
         pass
 
     def depart_Text(self, node):
-        if not self.in_toctree and not self.in_title and not self.in_table:
-            text = node.astext()
-            if '@' in text:
-                text = text.replace('@', '@@')
-            if '{' in text:
-                text = text.replace('{', '@{')
-            if '}' in text:
-                text = text.replace('}', '@}')
-            self.add_text(text)
+        text = node.data
+        if '@' in text:
+            text = text.replace('@', '@@')
+        if '{' in text:
+            text = text.replace('{', '@{')
+        if '}' in text:
+            text = text.replace('}', '@}')
+        if node.parent is None:
+            import pdb; pdb.set_trace()
+        node.parent.collect_text(text)
 
     # Styled text
 
     def visit_emphasis(self, node):
-        self.add_text('@emph{')
+        set_item_collector(node, 'text')
 
     def depart_emphasis(self, node):
-        self.add_text('}')
+        text = '@emph{%s}' % get_collected_item(node, 'text')
+        node.parent.collect_text(text)
 
     def visit_strong(self, node):
-        self.add_text('@strong{')
+        set_item_collector(node, 'text')
 
     def depart_strong(self, node):
-        self.add_text('}')
+        text = '@strong{%s}' % get_collected_item(node, 'text')
+        node.parent.collect_text(text)
 
     def visit_literal(self, node):
-        self.add_text('@code{')
+        set_item_collector(node, 'text')
 
     def depart_literal(self, node):
-        self.add_text('}')
+        text = '@code{%s}' % get_collected_item(node, 'text')
+        node.parent.collect_text(text)
 
     # Lists
 
+    def _decorate_list(self, item_list, collect, item_fmt='%s',
+                       head=None, foot=None):
+        if head is not None:
+            collect(head)
+        for item in item_list:
+            collect(item_fmt % item)
+        if foot is not None:
+            collect(foot)
+
     def visit_bullet_list(self, node):
-        if self.in_toctree:
-            pass
-        else:
-            self.add_text('@itemize @bullet\n')
+        set_item_list_collector(node, 'list_item')
 
     def depart_bullet_list(self, node):
+        l = node['list_item']
         if self.in_toctree:
-            pass
+            self._decorate_list(node['list_item'], node.parent.collect_text)
         else:
-            self.add_text('@end itemize\n')
+            # FIXME: Should respect the 'bullet' attribute
+            self._decorate_list(node['list_item'], node.parent.collect_chunk,
+                                '@item\n%s',
+                                '@itemize @bullet\n', '@end itemize\n')
 
     def visit_enumerated_list(self, node):
-        self.add_text('@enumerate\n')
+        set_item_list_collector(node, 'list_item')
 
     def depart_enumerated_list(self, node):
-        self.add_text('@end enumerate\n')
+        self._decorate_list(node['list_item'], node.parent.collect_chunk,
+                            '@item\n%s',
+                            '@enumerate\n', '@end enumerate\n')
 
     def visit_definition_list(self, node):
         pass
@@ -303,12 +329,11 @@
         pass
 
     def visit_list_item(self, node):
-        if not self.in_toctree:
-            self.add_text('@item\n')
+        set_item_list_collector(node, 'chunk')
 
     def depart_list_item(self, node):
-        # The item contains a paragraph which already ends with a blank line.
-        pass
+        text = ''.join(node['chunk'])
+        node.parent.collect_list_item(text)
 
     def visit_option_list(self, node):
         pass
@@ -352,72 +377,83 @@
 
     # Tables
     def visit_table(self, node):
-        self.in_table = True
-        self.add_text('@multitable ')
+        set_item_collector(node, 'table')
 
     def depart_table(self, node):
-        self.add_text('@end multitable\n')
-        # Leave a blank line after a table
-        self.add_text('\n')
-        self.in_table = False
+        node.parent.collect_chunk(node['table'])
 
     def visit_tgroup(self, node):
-        self.tab_nb_cols = node['cols']
+        set_item_list_collector(node, 'colspec')
+        set_item_collector(node, 'head_entries')
+        set_item_collector(node, 'body_rows')
 
     def depart_tgroup(self, node):
-        self.tab_nb_cols = None
+        header = []
+        # The '@multitable {xxx}{xxx}' line
+        self._decorate_list(node['colspec'], header.append,
+                            '{%s}', '@multitable ', '\n')
+        # The '@headitem xxx @tab yyy...' line
+        head_entries = node['head_entries']
+        self._decorate_list(head_entries[1:], header.append,
+                            ' @tab %s', '@headitem %s' % head_entries[0], '\n')
+        header = ''.join(header)
+        # The '@item xxx\n @tab yyy\n ...' lines
+        body_rows = node['body_rows']
+        rows = []
+        for r in body_rows:
+            self._decorate_list(r[1:], rows.append,
+                                '@tab %s\n', '@item %s\n' % r[0])
+        # Leave a blank line after a table
+        footer = '@end multitable\n\n'
+        node.parent.collect_table(header + ''.join(rows) + footer)
 
     def visit_colspec(self, node):
-        self.add_text('{%s}' % ('x' * node['colwidth']))
+        pass
 
     def depart_colspec(self, node):
-        self.tab_nb_cols -= 1
-        if self.tab_nb_cols == 0:
-            self.add_text('\n') # end the @multitable line
+        node.parent.collect_colspec('x' * node['colwidth'])
 
     def visit_thead(self, node):
-        self.tab_item_cmd = '@headitem %s '
-        self.tab_tab_cmd = '@tab %s'
+        set_item_collector(node, 'row')
 
     def depart_thead(self, node):
-        self.add_text('\n')
-        self.tab_item_cmd = None
-        self.tab_tab_cmd = None
+        node.parent.collect_head_entries(node['row'])
 
     def visit_tbody(self, node):
-        self.tab_item_cmd = '@item %s\n'
-        self.tab_tab_cmd = '@tab %s\n'
+        set_item_list_collector(node, 'row')
 
     def depart_tbody(self, node):
-        self.tab_item_cmd = None
-        self.tab_tab_cmd = None
+        node.parent.collect_body_rows(node['row'])
 
     def visit_row(self, node):
-        self.tab_entry_num = 0
+        set_item_list_collector(node, 'entry')
 
     def depart_row(self, node):
-        self.tab_entry_num = None
+        node.parent.collect_row(node['entry'])
 
     def visit_entry(self, node):
-        if self.tab_entry_num == 0:
-            cmd = self.tab_item_cmd
-        else:
-            cmd = self.tab_tab_cmd
-        self.add_text(cmd % node.astext())
-        self.tab_entry_num += 1
+        set_item_list_collector(node, 'chunk')
+        node['par_sep_orig'] = self.paragraph_sep
+        self.paragraph_sep = ''
 
     def depart_entry(self, node):
-        pass
+        node.parent.collect_entry(''.join(node['chunk']))
+        self.paragraph_sep = node['par_sep_orig']
 
     # References
 
     def visit_reference(self, node):
-        uri = node.get('refuri', '')
-        if self.in_toctree:
-            self.toctree_current_ref = uri
+        for c in node.children:
+            if getattr(c, 'parent', None) is None:
+                # Bug sphinx
+                node.setup_child(c)
+        set_item_collector(node, 'text')
 
     def depart_reference(self, node):
-        pass
+        node.parent.collect_reference((node.get('anchorname', ''),
+                                       node.get('refuri', ''),
+                                       ''.join(node['text']),
+                                       ))
 
     def visit_footnote_reference(self, node):
         raise nodes.SkipNode
@@ -441,3 +477,28 @@
         self.add_text(_('[image]'))
         raise nodes.SkipNode
 
+# Helpers to collect data in parent node
+
+def set_item_collector(node, name):
+    node[name] = None
+    def set_item(item):
+        node[name] = item
+    setattr(node, 'collect_' + name, set_item)
+
+
+def get_collected_item(node, name):
+    return node[name]
+
+
+def set_item_list_collector(node, name, sep=''):
+    node[name] = []
+    node[name + '_sep'] = sep
+    def append_item(item):
+        node[name].append(item)
+    setattr(node, 'collect_' + name, append_item)
+
+
+def get_collected_item_list(node, name):
+    return node[name + '_sep'].join(node[name])
+
+

=== modified file 'bzrlib/tests/doc_generate/builders/test_texinfo.py'
--- a/bzrlib/tests/doc_generate/builders/test_texinfo.py	2010-05-06 20:05:01 +0000
+++ b/bzrlib/tests/doc_generate/builders/test_texinfo.py	2010-05-07 18:30:32 +0000
@@ -114,13 +114,35 @@
 Do this::
 
    bzr xxx
+   bzr yyy
 """),])
         app, out, err = self.make_sphinx()
         app.build(True, [])
         self.assertFileEqual("""\
 Do this:
 
- at samp{bzr xxx}
+ at samp{bzr xxx
+bzr yyy}
+
+""",
+                             'index.texi')
+
+    def test_block_quote(self):
+        self.build_tree_contents([('index.txt', """\
+This is an ordinary paragraph, introducing a block quote.
+
+    "It is my business to know things.  That is my trade."
+
+"""),])
+        app, out, err = self.make_sphinx()
+        app.build(True, [])
+        self.assertFileEqual("""\
+This is an ordinary paragraph, introducing a block quote.
+
+ at example
+"It is my business to know things.  That is my trade."
+
+ at end example
 """,
                              'index.texi')
 
@@ -201,7 +223,10 @@
         app, out, err = self.make_sphinx()
         app.build(True, [])
         print err.getvalue()
+        # FIXME: Sphinx bug ? Why are tables enclosed in a block_quote
+        # (translated as an @example).
         self.assertFileEqual("""\
+ at example
 @multitable {xxxxxxxxxxx}{xxxxxxxxxxxxxxxx}
 @headitem Prefix @tab Description
 @item first
@@ -212,6 +237,7 @@
 @tab The last
 @end multitable
 
+ at end example
 """,
                              'index.texi')
 
@@ -246,7 +272,7 @@
         self.assertFileEqual("""\
 @chapter Table of Contents
 @menu
-* bzr 0.0.8:(bzr-0.0.8.info)bzr 0.0.8. 
+* bzr 0.0.8: (bzr-0.0.8.info)bzr 0.0.8. 
 @end menu
 """,
                              'index.texi')



More information about the bazaar-commits mailing list