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