Merge lp:~vila/bzr/219334-texinfo into lp:bzr

Proposed by Vincent Ladeuil
Status: Merged
Approved by: Martin Pool
Approved revision: no longer in the source branch.
Merged at revision: 5356
Proposed branch: lp:~vila/bzr/219334-texinfo
Merge into: lp:bzr
Prerequisite: lp:~vila/bzr/cleanup-docs
Diff against target: 1450 lines (+1229/-15)
21 files modified
NEWS (+8/-0)
bzrlib/doc_generate/builders/__init__.py (+18/-0)
bzrlib/doc_generate/builders/texinfo.py (+42/-0)
bzrlib/doc_generate/conf.py (+5/-1)
bzrlib/doc_generate/writers/__init__.py (+18/-0)
bzrlib/doc_generate/writers/texinfo.py (+520/-0)
bzrlib/tests/__init__.py (+1/-0)
bzrlib/tests/doc_generate/__init__.py (+108/-0)
bzrlib/tests/doc_generate/builders/__init__.py (+36/-0)
bzrlib/tests/doc_generate/builders/test_texinfo.py (+71/-0)
bzrlib/tests/doc_generate/writers/__init__.py (+36/-0)
bzrlib/tests/doc_generate/writers/test_texinfo.py (+335/-0)
bzrlib/tests/features.py (+1/-0)
bzrlib/tests/test_source.py (+10/-4)
doc/developers/conf.py (+1/-1)
doc/en/Makefile (+4/-0)
doc/en/conf.py (+1/-2)
doc/es/conf.py (+1/-1)
doc/ja/conf.py (+1/-1)
doc/ru/conf.py (+1/-1)
tools/generate_release_notes.py (+11/-4)
To merge this branch: bzr merge lp:~vila/bzr/219334-texinfo
Reviewer Review Type Date Requested Status
Martin Pool Approve
Review via email: mp+29533@code.launchpad.net

Commit message

texinfo extension for shpinx

Description of the change

This adds a sphinx extension that allows us to generate texinfo
files for our documentation.

The generated files are syntactically correct but still lack some
navigation pointers. Adding them requires some decisions about
the overall documentation layout. We haven't fully switch to
shpinx and even for that I think there are a few quirks to iron
out.

I'd like to land this patch first to make further submissions
clearer (either for adjusting the documentation layout or
enhancing the generation of texinfo files).

I followed the shpinx extension design which requires a builder
(which decides which files need to be built or rebuilt) and a
writer (which generates the documentation in the the required
format).

I have targeted lucid as the reference platform and didn't test
other platforms, the test themselves requires sphinx via a
test.feature so they should just be skipped if sphinx is not
available.

The current limitations are:

- not all possible rest constructs are taken into account (but
  all the ones we use today are),

- running makeinfo (to produce the '.info' files from the '.texi'
  texinfo files, requires the '--no-validate' option

- each source file produces a single '.texi' file, but we need to
  decide how we want to group them to produce a set of '.info'
  files (this is required to generate correct references and
  resemble the choices made for latex files) and implement the
  corresponding features in the builder/writer.

To post a comment you must log in.
Revision history for this message
Vincent Ladeuil (vila) wrote :

Oh, I failed to mention: many documents use HTML references which works when we generate HTML only.
This is wrong of course and interfere with the desire to produce info files which are aggregated from
several source files and need to be addressed by using a proper rest reference scheme.

Revision history for this message
John A Meinel (jameinel) wrote :

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Vincent Ladeuil wrote:
> Oh, I failed to mention: many documents use HTML references which works when we generate HTML only.
> This is wrong of course and interfere with the desire to produce info files which are aggregated from
> several source files and need to be addressed by using a proper rest reference scheme.

Unfortunately, I don't think I've found a "proper rest reference scheme"
for us to be using. (For example, one that would reference .txt in
source form, and then .html in generated form.)

I don't have a great answer there.

John
=:->

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAkw3V/0ACgkQJdeBCYSNAAOK1gCeKDpZ4Dc5fk6Hfsi+k2ofUjbp
9mkAn1kfOn8i4Oi06EwyTg3nUr+dm8kD
=Ty94
-----END PGP SIGNATURE-----

Revision history for this message
Vincent Ladeuil (vila) wrote :

s/proper rest ref/proper sphinx ref/ sorry:
http://sphinx.pocoo.org/markup/inline.html#xref-syntax

But this requires a full switch to sphinx as mentioned.

Revision history for this message
Martin Pool (mbp) wrote :

On 10 July 2010 03:31, Vincent Ladeuil <email address hidden> wrote:
> s/proper rest ref/proper sphinx ref/ sorry:
> http://sphinx.pocoo.org/markup/inline.html#xref-syntax
>
> But this requires a full switch to sphinx as mentioned.

I thought we had agreed, at Ian's suggestion(?) that we would switch
to requiring Sphinx? But perhaps it wasn't actually done because of
pqm problems?

Thanks for putting that patch up Vincent, it will be very nice for
emacs users and gnu supporters.

--
Martin

Revision history for this message
John A Meinel (jameinel) wrote :

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Martin Pool wrote:
> On 10 July 2010 03:31, Vincent Ladeuil <email address hidden> wrote:
>> s/proper rest ref/proper sphinx ref/ sorry:
>> http://sphinx.pocoo.org/markup/inline.html#xref-syntax
>>
>> But this requires a full switch to sphinx as mentioned.
>
> I thought we had agreed, at Ian's suggestion(?) that we would switch
> to requiring Sphinx? But perhaps it wasn't actually done because of
> pqm problems?
>
> Thanks for putting that patch up Vincent, it will be very nice for
> emacs users and gnu supporters.
>
I think we did decide to move that way, but it wasn't ever finished.

John
=:->

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAkw3ikwACgkQJdeBCYSNAAPNRQCfcbqpqzu0tM8uQSMOzh8vjRG5
QsAAoMVxBiWCO9fSyNfXEKIsmCumMUi8
=2FbL
-----END PGP SIGNATURE-----

Revision history for this message
Vincent Ladeuil (vila) wrote :

>>>>> John A Meinel <email address hidden> writes:

    > Martin Pool wrote:
    >> On 10 July 2010 03:31, Vincent Ladeuil <email address hidden> wrote:
    >>> s/proper rest ref/proper sphinx ref/ sorry:
    >>> http://sphinx.pocoo.org/markup/inline.html#xref-syntax
    >>>
    >>> But this requires a full switch to sphinx as mentioned.
    >>
    >> I thought we had agreed, at Ian's suggestion(?) that we would switch
    >> to requiring Sphinx? But perhaps it wasn't actually done because of
    >> pqm problems?
    >>
    >> Thanks for putting that patch up Vincent, it will be very nice for
    >> emacs users and gnu supporters.
    >>
    > I think we did decide to move that way, but it wasn't ever finished.

That's my understanding too and like Martin said, pqm is one problem
among others. I think I should summarize the quirks I encountered but I
also need help about the current workflow used to produce the docs (I
know there is a cron job involved but I have no idea of the setup used
there nor the commands used).

Revision history for this message
Vincent Ladeuil (vila) wrote :

Ping, the pre-requisite branch has landed, can I land this one ?
It's not used yet but will make further submissions possible and give a base for interested people to help provide '.info' files once the remaining quirks have been ironed out.

Revision history for this message
Martin Pool (mbp) wrote :

This would be a nice change to land, maybe even into 2.2.

Eventually we should send this to upstream sphinx.

<poolie> can you do two followons for me?
<poolie> put the rendered .info file on the lp download site
<poolie> and blog about it, with a link

review: Approve
Revision history for this message
Vincent Ladeuil (vila) wrote :

sent to pqm by email

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'NEWS'
2--- NEWS 2010-07-20 14:36:42 +0000
3+++ NEWS 2010-07-21 08:08:51 +0000
4@@ -537,6 +537,14 @@
5 Documentation
6 *************
7
8+* First tests defined for sphinx, including a new bzrlib.tests.features.sphinx
9+ to make the tests conditional.
10+ (Vincent Ladeuil)
11+
12+* Added a builder/writer sphinx extension that can generate texinfo files. The
13+ generated files are syntactically correct but the info navigation nodes
14+ needs more work. (Vincent Ladeuil, #219334)
15+
16 API Changes
17 ***********
18
19
20=== added directory 'bzrlib/doc_generate/builders'
21=== added file 'bzrlib/doc_generate/builders/__init__.py'
22--- bzrlib/doc_generate/builders/__init__.py 1970-01-01 00:00:00 +0000
23+++ bzrlib/doc_generate/builders/__init__.py 2010-07-21 08:08:51 +0000
24@@ -0,0 +1,18 @@
25+# Copyright (C) 2010 Canonical Ltd
26+#
27+# This program is free software; you can redistribute it and/or modify
28+# it under the terms of the GNU General Public License as published by
29+# the Free Software Foundation; either version 2 of the License, or
30+# (at your option) any later version.
31+#
32+# This program is distributed in the hope that it will be useful,
33+# but WITHOUT ANY WARRANTY; without even the implied warranty of
34+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
35+# GNU General Public License for more details.
36+#
37+# You should have received a copy of the GNU General Public License
38+# along with this program; if not, write to the Free Software
39+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
40+
41+"""Placeholder to allow sphinx builders imports."""
42+
43
44=== added file 'bzrlib/doc_generate/builders/texinfo.py'
45--- bzrlib/doc_generate/builders/texinfo.py 1970-01-01 00:00:00 +0000
46+++ bzrlib/doc_generate/builders/texinfo.py 2010-07-21 08:08:51 +0000
47@@ -0,0 +1,42 @@
48+# Copyright (C) 2010 Canonical Ltd
49+#
50+# This program is free software; you can redistribute it and/or modify
51+# it under the terms of the GNU General Public License as published by
52+# the Free Software Foundation; either version 2 of the License, or
53+# (at your option) any later version.
54+#
55+# This program is distributed in the hope that it will be useful,
56+# but WITHOUT ANY WARRANTY; without even the implied warranty of
57+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
58+# GNU General Public License for more details.
59+#
60+# You should have received a copy of the GNU General Public License
61+# along with this program; if not, write to the Free Software
62+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
63+
64+"""A sphinx builder producing texinfo output."""
65+
66+from sphinx import builders
67+from sphinx.builders import text as _text_builder
68+
69+from bzrlib.doc_generate.writers import texinfo as texinfo_writer
70+
71+class TexinfoBuilder(_text_builder.TextBuilder):
72+
73+ name = 'texinfo'
74+ format = 'texinfo'
75+ out_suffix = '.texi'
76+
77+ def prepare_writing(self, docnames):
78+ self.writer = texinfo_writer.TexinfoWriter(self)
79+
80+ def get_target_uri(self, docname, typ=None):
81+ # FIXME: Revisit when info file generation is defined (the suffix is
82+ # left here for clarity but the final version may just get rid of
83+ # it). And we probalby will join several files into bigger info files
84+ # anyway. -- vila 20100506
85+ return docname + '.info'
86+
87+
88+def setup(app):
89+ app.add_builder(TexinfoBuilder)
90
91=== renamed file 'bzrlib/doc_generate/sphinx_conf.py' => 'bzrlib/doc_generate/conf.py'
92--- bzrlib/doc_generate/sphinx_conf.py 2010-07-07 10:27:54 +0000
93+++ bzrlib/doc_generate/conf.py 2010-07-21 08:08:51 +0000
94@@ -8,6 +8,9 @@
95
96 import sys, os
97
98+# FIXME: better move the content of doc/en/conf.py here and cleanup the result
99+# -- vila 20100428
100+
101 # If extensions (or modules to document with autodoc) are in another directory,
102 # add these directories to sys.path here. If the directory is relative to the
103 # documentation root, use os.path.abspath to make it absolute, like shown here.
104@@ -18,7 +21,8 @@
105
106 # Add any Sphinx extension module names here, as strings. They can be extensions
107 # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
108-extensions = ['sphinx.ext.ifconfig']
109+extensions = ['sphinx.ext.ifconfig',
110+ 'bzrlib.doc_generate.builders.texinfo']
111
112 # Add any paths that contain templates here, relative to this directory.
113 templates_path = ['_templates']
114
115=== added directory 'bzrlib/doc_generate/writers'
116=== added file 'bzrlib/doc_generate/writers/__init__.py'
117--- bzrlib/doc_generate/writers/__init__.py 1970-01-01 00:00:00 +0000
118+++ bzrlib/doc_generate/writers/__init__.py 2010-07-21 08:08:51 +0000
119@@ -0,0 +1,18 @@
120+# Copyright (C) 2010 Canonical Ltd
121+#
122+# This program is free software; you can redistribute it and/or modify
123+# it under the terms of the GNU General Public License as published by
124+# the Free Software Foundation; either version 2 of the License, or
125+# (at your option) any later version.
126+#
127+# This program is distributed in the hope that it will be useful,
128+# but WITHOUT ANY WARRANTY; without even the implied warranty of
129+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
130+# GNU General Public License for more details.
131+#
132+# You should have received a copy of the GNU General Public License
133+# along with this program; if not, write to the Free Software
134+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
135+
136+"""Placeholder to allow sphinx writer imports."""
137+
138
139=== added file 'bzrlib/doc_generate/writers/texinfo.py'
140--- bzrlib/doc_generate/writers/texinfo.py 1970-01-01 00:00:00 +0000
141+++ bzrlib/doc_generate/writers/texinfo.py 2010-07-21 08:08:51 +0000
142@@ -0,0 +1,520 @@
143+# Copyright (C) 2010 Canonical Ltd
144+#
145+# This program is free software; you can redistribute it and/or modify
146+# it under the terms of the GNU General Public License as published by
147+# the Free Software Foundation; either version 2 of the License, or
148+# (at your option) any later version.
149+#
150+# This program is distributed in the hope that it will be useful,
151+# but WITHOUT ANY WARRANTY; without even the implied warranty of
152+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
153+# GNU General Public License for more details.
154+#
155+# You should have received a copy of the GNU General Public License
156+# along with this program; if not, write to the Free Software
157+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
158+
159+"""A sphinx/docutil writer producing texinfo output."""
160+
161+from docutils import (
162+ nodes,
163+ writers,
164+ )
165+
166+DEBUG = 0
167+
168+class TexinfoWriter(writers.Writer):
169+
170+ supported = ('texinfo',)
171+ settings_spec = ('No options here.', '', ())
172+ settings_defaults = {}
173+
174+ output = None
175+
176+ def __init__(self, builder):
177+ writers.Writer.__init__(self)
178+ self.builder = builder
179+
180+ def translate(self):
181+ visitor = TexinfoTranslator(self.document, self.builder)
182+ self.document.walkabout(visitor)
183+ self.output = visitor.body
184+
185+
186+class TexinfoTranslator(nodes.NodeVisitor):
187+
188+ section_names = ['chapter', 'section', 'subsection', 'subsubsection']
189+ """texinfo section names differ from the sphinx ones.
190+
191+ Since this can be confusing, the correspondences are shown below
192+ (shpinx -> texinfo):
193+ part -> chapter
194+ chapter -> section
195+ section -> subsection
196+ subsection -> subsubsection
197+
198+ Additionally, sphinx defines subsubsections and paragraphs which are
199+ handled as @heading (unnumbered).
200+ """
201+
202+ def __init__(self, document, builder):
203+ nodes.NodeVisitor.__init__(self, document)
204+ self.builder = builder
205+ # toctree uses some nodes for different purposes (namely:
206+ # compact_paragraph, bullet_list) that needs to know when they are
207+ # processing a toctree.
208+ self.in_toctree = False
209+ # sections can be embedded and produce different directives depending
210+ # on the depth.
211+ self.section_level = -1
212+ # By default paragraghs are separated by newlines, but there are some
213+ # exceptions that set it to '' for some subtrees instead
214+ self.paragraph_sep = '\n'
215+
216+ # The whole document
217+
218+ def visit_document(self, node):
219+ if DEBUG:
220+ import sys
221+ sys.stdout.write(node.pformat().encode('utf8'))
222+ set_item_list_collector(node, 'text')
223+
224+ def depart_document(self, node):
225+ # FIXME: info requires a Top node for each info file, but unless we
226+ # chose a global layout to divide the overall documentation into a set
227+ # of info files, there is no criteria to decide for a title.
228+ top_cmd = '''\
229+This file has been converted using a beta rst->texinfo converter.
230+Most of the info links are currently bogus, don't report bugs about them,
231+this is currently worked on.
232+@node Top
233+@top Placeholder
234+'''
235+ self.body = top_cmd + ''.join(node['text'])
236+
237+ # Layout
238+
239+ def visit_section(self, node):
240+ self.section_level += 1
241+ set_item_list_collector(node, 'text')
242+
243+ def depart_section(self, node):
244+ title = node['title']
245+ ids = node.get('ids', [])
246+ try:
247+ section_name = self.section_names[self.section_level]
248+ except IndexError:
249+ # Just use @heading, it's not numbered anyway
250+ section_name = 'heading'
251+ if ids:
252+ # There shouldn't be different ids for a section, so until we
253+ # encounter bugs, just take the first one.
254+ node_cmd = '@node %s\n' % (ids[0],)
255+ else:
256+ node_cmd = ''
257+ section_cmd = '@%s %s\n' % (section_name, title)
258+ text = ''.join(node['text'])
259+ node.parent.collect_text(node_cmd + section_cmd + text)
260+ self.section_level -= 1
261+
262+ def visit_topic(self, node):
263+ pass
264+
265+ def depart_topic(self, node):
266+ pass
267+
268+ def visit_paragraph(self, node):
269+ set_item_list_collector(node, 'text')
270+
271+ def depart_paragraph(self, node):
272+ # End the paragraph with a new line (or '' depending on the parent) and
273+ # leave a blank line after it.
274+ text = ''.join(node['text']) + self.paragraph_sep * 2
275+ node.parent.collect_text(text)
276+
277+ def visit_compact_paragraph(self, node):
278+ set_item_list_collector(node, 'text')
279+ if node.has_key('toctree'):
280+ self.in_toctree = True
281+ elif self.in_toctree:
282+ set_item_collector(node, 'reference')
283+
284+ def depart_compact_paragraph(self, node):
285+ # FIXME: Using a different visitor specific to toctree may be a better
286+ # design and makes code clearer. -- vila 20100708
287+ if node.has_key('toctree'):
288+ node.parent.collect_text('@menu\n')
289+ node.parent.collect_text(''.join(node['text']))
290+ node.parent.collect_text('@end menu\n')
291+ self.in_toctree = False
292+ elif self.in_toctree:
293+ # * FIRST-ENTRY-NAME:(FILENAME)NODENAME. DESCRIPTION
294+ # XXX: the file name should probably be adjusted to the targeted
295+ # info file name
296+ node_name, file_name, entry_name = node['reference']
297+ if not node_name:
298+ node_name = entry_name
299+ description = '' # We can't specify a description in rest AFAICS
300+ # XXX: What if :maxdepth: is not 1 ?
301+ text = '* %s: (%s)%s. %s\n' % (entry_name, file_name,
302+ node_name, description)
303+ node.parent.collect_text(text)
304+ else:
305+ # End the paragraph with a new line (or '' depending on the parent)
306+ # and leave a blank line after it.
307+ text = ''.join(node['text']) + self.paragraph_sep * 2
308+ node.parent.collect_text(text)
309+
310+ def visit_literal_block(self, node):
311+ set_item_collector(node, 'text')
312+
313+ def depart_literal_block(self, node):
314+ text = '@samp{%s}' % ''.join(node['text']) + self.paragraph_sep * 2
315+ node.parent.collect_text(text)
316+
317+ def visit_block_quote(self, node):
318+ set_item_list_collector(node, 'text')
319+
320+ def depart_block_quote(self, node):
321+ node.parent.collect_text('@example\n')
322+ node.parent.collect_text(''.join(node['text']))
323+ node.parent.collect_text('@end example\n\n')
324+
325+ def depart_warning(self, node):
326+ pass
327+
328+ def visit_warning(self, node):
329+ raise nodes.SkipNode # Not implemented yet
330+
331+ def visit_note(self, node):
332+ raise nodes.SkipNode # Not implemented yet
333+
334+ def depart_note(self, node):
335+ pass
336+
337+ def visit_footnote(self, node):
338+ raise nodes.SkipNode # Not implemented yet
339+
340+ def depart_footnote(self, node):
341+ pass
342+
343+ def visit_comment(self, node):
344+ raise nodes.SkipNode # Not implemented yet
345+
346+ # Attributes
347+
348+ def visit_title(self, node):
349+ set_item_collector(node, 'text')
350+
351+ def depart_title(self, node):
352+ node.parent['title'] = node['text']
353+
354+ def visit_label(self, node):
355+ raise nodes.SkipNode # Not implemented yet
356+
357+ def visit_substitution_definition(self, node):
358+ raise nodes.SkipNode # Not implemented yet
359+
360+ # Plain text
361+
362+ def visit_Text(self, node):
363+ pass
364+
365+ def depart_Text(self, node):
366+ text = node.astext()
367+ if '@' in text:
368+ text = text.replace('@', '@@')
369+ if '{' in text:
370+ text = text.replace('{', '@{')
371+ if '}' in text:
372+ text = text.replace('}', '@}')
373+ node.parent.collect_text(text)
374+
375+
376+ # Styled text
377+
378+ def visit_emphasis(self, node):
379+ set_item_collector(node, 'text')
380+
381+ def depart_emphasis(self, node):
382+ text = '@emph{%s}' % node['text']
383+ node.parent.collect_text(text)
384+
385+ def visit_strong(self, node):
386+ set_item_collector(node, 'text')
387+
388+ def depart_strong(self, node):
389+ text = '@strong{%s}' % node['text']
390+ node.parent.collect_text(text)
391+
392+ def visit_literal(self, node):
393+ set_item_collector(node, 'text')
394+
395+ def depart_literal(self, node):
396+ text = '@code{%s}' % node['text']
397+ node.parent.collect_text(text)
398+
399+ # Lists
400+
401+ def _decorate_list(self, item_list, collect, item_fmt='%s',
402+ head=None, foot=None):
403+ if head is not None:
404+ collect(head)
405+ for item in item_list:
406+ collect(item_fmt % item)
407+ if foot is not None:
408+ collect(foot)
409+
410+ def visit_bullet_list(self, node):
411+ set_item_list_collector(node, 'list_item')
412+
413+ def depart_bullet_list(self, node):
414+ l = node['list_item']
415+ if self.in_toctree:
416+ self._decorate_list(node['list_item'], node.parent.collect_text)
417+ else:
418+ self._decorate_list(node['list_item'], node.parent.collect_text,
419+ '@item\n%s',
420+ # FIXME: Should respect the 'bullet' attribute
421+ '@itemize @bullet\n', '@end itemize\n')
422+
423+ def visit_enumerated_list(self, node):
424+ set_item_list_collector(node, 'list_item')
425+
426+ def depart_enumerated_list(self, node):
427+ self._decorate_list(node['list_item'], node.parent.collect_text,
428+ '@item\n%s',
429+ '@enumerate\n', '@end enumerate\n')
430+
431+ def visit_definition_list(self, node):
432+ raise nodes.SkipNode # Not implemented yet
433+
434+ def depart_definition_list(self, node):
435+ raise nodes.SkipNode # Not implemented yet
436+
437+ def visit_definition_list_item(self, node):
438+ raise nodes.SkipNode # Not implemented yet
439+
440+ def depart_definition_list_item(self, node):
441+ pass
442+
443+ def visit_term(self, node):
444+ raise nodes.SkipNode # Not implemented yet
445+
446+ def depart_term(self, node):
447+ pass
448+
449+ def visit_definition(self, node):
450+ raise nodes.SkipNode # Not implemented yet
451+
452+ def depart_definition(self, node):
453+ pass
454+
455+ def visit_field_list(self, node):
456+ raise nodes.SkipNode # Not implemented yet
457+
458+ def depart_field_list(self, node):
459+ pass
460+
461+ def visit_field(self, node):
462+ raise nodes.SkipNode # Not implemented yet
463+
464+ def depart_field(self, node):
465+ pass
466+
467+ def visit_field_name(self, node):
468+ raise nodes.SkipNode # Not implemented yet
469+
470+ def depart_field_name(self, node):
471+ pass
472+
473+ def visit_field_body(self, node):
474+ raise nodes.SkipNode # Not implemented yet
475+
476+ def depart_field_body(self, node):
477+ pass
478+
479+ def visit_list_item(self, node):
480+ set_item_list_collector(node, 'text')
481+
482+ def depart_list_item(self, node):
483+ text = ''.join(node['text'])
484+ node.parent.collect_list_item(text)
485+
486+ def visit_option_list(self, node):
487+ raise nodes.SkipNode # Not implemented yet
488+
489+ def depart_option_list(self, node):
490+ pass
491+
492+ def visit_option_list_item(self, node):
493+ pass
494+
495+ def depart_option_list_item(self, node):
496+ pass
497+
498+ def visit_option_group(self, node):
499+ pass
500+
501+ def depart_option_group(self, node):
502+ pass
503+
504+ def visit_option(self, node):
505+ pass
506+
507+ def depart_option(self, node):
508+ pass
509+
510+ def visit_option_string(self, node):
511+ pass
512+ def depart_option_string(self, node):
513+ pass
514+
515+ def visit_option_argument(self, node):
516+ pass
517+
518+ def depart_option_argument(self, node):
519+ pass
520+
521+ def visit_description(self, node):
522+ pass
523+ def depart_description(self, node):
524+ pass
525+
526+ # Tables
527+ def visit_table(self, node):
528+ set_item_collector(node, 'table')
529+
530+ def depart_table(self, node):
531+ node.parent.collect_text(node['table'])
532+
533+ def visit_tgroup(self, node):
534+ set_item_list_collector(node, 'colspec')
535+ set_item_collector(node, 'head_entries')
536+ set_item_collector(node, 'body_rows')
537+
538+ def depart_tgroup(self, node):
539+ header = []
540+ # The '@multitable {xxx}{xxx}' line
541+ self._decorate_list(node['colspec'], header.append,
542+ '{%s}', '@multitable ', '\n')
543+ # The '@headitem xxx @tab yyy...' line
544+ head_entries = node['head_entries']
545+ if head_entries is not None:
546+ # Not all tables define titles for the columns... rest parser bug ?
547+ # FIXME: need a test
548+ self._decorate_list(head_entries[1:], header.append,
549+ ' @tab %s',
550+ '@headitem %s' % head_entries[0], '\n')
551+ header = ''.join(header)
552+ # The '@item xxx\n @tab yyy\n ...' lines
553+ body_rows = node['body_rows']
554+ rows = []
555+ for r in body_rows:
556+ self._decorate_list(r[1:], rows.append,
557+ '@tab %s\n', '@item %s\n' % r[0])
558+ footer = '@end multitable\n'
559+ node.parent.collect_table(header + ''.join(rows) + footer)
560+
561+ def visit_colspec(self, node):
562+ pass
563+
564+ def depart_colspec(self, node):
565+ node.parent.collect_colspec('x' * node['colwidth'])
566+
567+ def visit_thead(self, node):
568+ set_item_collector(node, 'row')
569+
570+ def depart_thead(self, node):
571+ node.parent.collect_head_entries(node['row'])
572+
573+ def visit_tbody(self, node):
574+ set_item_list_collector(node, 'row')
575+
576+ def depart_tbody(self, node):
577+ node.parent.collect_body_rows(node['row'])
578+
579+ def visit_row(self, node):
580+ set_item_list_collector(node, 'entry')
581+
582+ def depart_row(self, node):
583+ node.parent.collect_row(node['entry'])
584+
585+ def visit_entry(self, node):
586+ set_item_list_collector(node, 'text')
587+ node['par_sep_orig'] = self.paragraph_sep
588+ self.paragraph_sep = ''
589+
590+ def depart_entry(self, node):
591+ node.parent.collect_entry(''.join(node['text']))
592+ self.paragraph_sep = node['par_sep_orig']
593+
594+ # References
595+
596+ def visit_reference(self, node):
597+ for c in node.children:
598+ if getattr(c, 'parent', None) is None:
599+ # Bug sphinx
600+ node.setup_child(c)
601+ set_item_collector(node, 'text')
602+
603+ def depart_reference(self, node):
604+ anchorname = node.get('anchorname', None)
605+ refuri = node.get('refuri', None)
606+ refid = node.get('refid', None)
607+ text = ''.join(node['text'])
608+ collect = getattr(node.parent, 'collect_reference', None)
609+ if collect is not None:
610+ if not self.in_toctree:
611+ raise AssertionError('collect_reference is specific to toctree')
612+ if anchorname is None:
613+ anchorname = ''
614+ if refuri is None:
615+ refuri = ''
616+ collect((anchorname, refuri, text))
617+ elif refuri is not None:
618+ node.parent.collect_text('@uref{%s,%s}' % (refuri, text))
619+ elif refid is not None:
620+ # Info format requires that a reference is followed by some
621+ # punctuation char ('.', ','. ')', etc). Rest is more liberal. To
622+ # accommodate, we use pxref inside parenthesis.
623+ node.parent.collect_text('%s (@pxref{%s})' % (text, refid))
624+
625+ def visit_footnote_reference(self, node):
626+ raise nodes.SkipNode # Not implemented yet
627+
628+ def visit_citation_reference(self, node):
629+ raise nodes.SkipNode # Not implemented yet
630+
631+ def visit_title_reference(self, node):
632+ raise nodes.SkipNode # Not implemented yet
633+
634+ def depart_title_reference(self, node):
635+ pass
636+
637+ def visit_target(self, node):
638+ raise nodes.SkipNode # Not implemented yet
639+
640+ def depart_target(self, node):
641+ pass
642+
643+ def visit_image(self, node):
644+ raise nodes.SkipNode # Not implemented yet
645+
646+# Helpers to collect data in parent node
647+
648+def set_item_collector(node, name):
649+ node[name] = None
650+ def set_item(item):
651+ node[name] = item
652+ setattr(node, 'collect_' + name, set_item)
653+
654+
655+def set_item_list_collector(node, name, sep=''):
656+ node[name] = []
657+ node[name + '_sep'] = sep
658+ def append_item(item):
659+ node[name].append(item)
660+ setattr(node, 'collect_' + name, append_item)
661+
662+
663
664=== modified file 'bzrlib/tests/__init__.py'
665--- bzrlib/tests/__init__.py 2010-07-16 13:06:33 +0000
666+++ bzrlib/tests/__init__.py 2010-07-21 08:08:51 +0000
667@@ -3643,6 +3643,7 @@
668 'bzrlib.doc',
669 'bzrlib.tests.blackbox',
670 'bzrlib.tests.commands',
671+ 'bzrlib.tests.doc_generate',
672 'bzrlib.tests.per_branch',
673 'bzrlib.tests.per_bzrdir',
674 'bzrlib.tests.per_bzrdir_colo',
675
676=== added directory 'bzrlib/tests/doc_generate'
677=== added file 'bzrlib/tests/doc_generate/__init__.py'
678--- bzrlib/tests/doc_generate/__init__.py 1970-01-01 00:00:00 +0000
679+++ bzrlib/tests/doc_generate/__init__.py 2010-07-21 08:08:51 +0000
680@@ -0,0 +1,108 @@
681+# Copyright (C) 2010 Canonical Ltd
682+#
683+# This program is free software; you can redistribute it and/or modify
684+# it under the terms of the GNU General Public License as published by
685+# the Free Software Foundation; either version 2 of the License, or
686+# (at your option) any later version.
687+#
688+# This program is distributed in the hope that it will be useful,
689+# but WITHOUT ANY WARRANTY; without even the implied warranty of
690+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
691+# GNU General Public License for more details.
692+#
693+# You should have received a copy of the GNU General Public License
694+# along with this program; if not, write to the Free Software
695+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
696+
697+"""Documentation generation tests."""
698+
699+import os
700+from bzrlib import tests
701+from bzrlib.doc_generate import (
702+ # FIXME: doc/en/conf.py should be used here, or rather merged into
703+ # bzrlib/doc_generate/conf.py -- vila 20100429
704+ conf,
705+ )
706+from bzrlib.tests import features
707+
708+
709+def load_tests(basic_tests, module, loader):
710+ suite = loader.suiteClass()
711+ # add the tests for this module
712+ suite.addTests(basic_tests)
713+
714+ testmod_names = [
715+ 'builders',
716+ 'writers',
717+ ]
718+ # add the tests for the sub modules
719+ suite.addTests(loader.loadTestsFromModuleNames(
720+ ['bzrlib.tests.doc_generate.' + name
721+ for name in testmod_names]))
722+
723+ return suite
724+
725+
726+class TestSphinx(tests.TestCaseInTempDir):
727+ """Base class for sphinx tests.
728+
729+ This is used for both the builder and the writer until a better solution is
730+ found to test at a lower level.
731+ """
732+
733+ _test_needs_features = [features.sphinx]
734+
735+ def make_sphinx(self):
736+ out = tests.StringIOWrapper()
737+ err = tests.StringIOWrapper()
738+ from sphinx import application
739+ app = application.Sphinx(
740+ '.', confdir=os.path.dirname(conf.__file__), outdir='.',
741+ doctreedir='.',
742+ buildername='texinfo',
743+ confoverrides={},
744+ status=out, warning=err,
745+ freshenv=True)
746+ return app, out, err
747+
748+ def build(self, app, all_files=True, file_names=None):
749+ if file_names is None:
750+ file_names = []
751+ app.build(all_files, file_names)
752+
753+ # FIXME: something smells wrong here as we can't process a single file
754+ # alone. On top of that, it seems the doc tree must contain an index.txt
755+ # file. We may need a texinfo builder ? -- vila 20100505
756+
757+ def create_content(self, content):
758+ """Put content into a single file.
759+
760+ This is appropriate for simple tests about the content of a single file.
761+ """
762+ app, out, err = self.make_sphinx()
763+ self.build_tree_contents([('index.txt', content),])
764+ self.build(app)
765+
766+ def assertContent(self, expected, header=None, end=None):
767+ """Check the content of the file created with creste_content().
768+
769+ Most texinfo constructs can be tested this way without caring for any
770+ boilerplate that texinfo may require at the beginning or the end of the
771+ file.
772+ """
773+ if header is None:
774+ # default boilerplate
775+ header = '''\
776+This file has been converted using a beta rst->texinfo converter.
777+Most of the info links are currently bogus, don't report bugs about them,
778+this is currently worked on.
779+@node Top
780+@top Placeholder
781+'''
782+ if end is None:
783+ # By default we test constructs that are embedded into a paragraph
784+ # which always end with two \n (even if the input has none)
785+ end = '\n\n'
786+ self.assertFileEqual(header + expected + end, 'index.texi')
787+
788+
789
790=== added directory 'bzrlib/tests/doc_generate/builders'
791=== added file 'bzrlib/tests/doc_generate/builders/__init__.py'
792--- bzrlib/tests/doc_generate/builders/__init__.py 1970-01-01 00:00:00 +0000
793+++ bzrlib/tests/doc_generate/builders/__init__.py 2010-07-21 08:08:51 +0000
794@@ -0,0 +1,36 @@
795+# Copyright (C) 2010 Canonical Ltd
796+#
797+# This program is free software; you can redistribute it and/or modify
798+# it under the terms of the GNU General Public License as published by
799+# the Free Software Foundation; either version 2 of the License, or
800+# (at your option) any later version.
801+#
802+# This program is distributed in the hope that it will be useful,
803+# but WITHOUT ANY WARRANTY; without even the implied warranty of
804+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
805+# GNU General Public License for more details.
806+#
807+# You should have received a copy of the GNU General Public License
808+# along with this program; if not, write to the Free Software
809+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
810+
811+
812+"""Sphinx builders tests."""
813+
814+from bzrlib.tests import features
815+
816+def load_tests(basic_tests, module, loader):
817+ suite = loader.suiteClass()
818+ # add the tests for this module
819+ suite.addTests(basic_tests)
820+
821+ if features.sphinx.available():
822+ testmod_names = [
823+ 'texinfo',
824+ ]
825+ # add the tests for the sub modules
826+ suite.addTests(loader.loadTestsFromModuleNames(
827+ ['bzrlib.tests.doc_generate.builders.test_' + name
828+ for name in testmod_names]))
829+
830+ return suite
831
832=== added file 'bzrlib/tests/doc_generate/builders/test_texinfo.py'
833--- bzrlib/tests/doc_generate/builders/test_texinfo.py 1970-01-01 00:00:00 +0000
834+++ bzrlib/tests/doc_generate/builders/test_texinfo.py 2010-07-21 08:08:51 +0000
835@@ -0,0 +1,71 @@
836+# Copyright (C) 2010 Canonical Ltd
837+#
838+# This program is free software; you can redistribute it and/or modify
839+# it under the terms of the GNU General Public License as published by
840+# the Free Software Foundation; either version 2 of the License, or
841+# (at your option) any later version.
842+#
843+# This program is distributed in the hope that it will be useful,
844+# but WITHOUT ANY WARRANTY; without even the implied warranty of
845+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
846+# GNU General Public License for more details.
847+#
848+# You should have received a copy of the GNU General Public License
849+# along with this program; if not, write to the Free Software
850+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
851+
852+"""sphinx texinfo builder tests."""
853+
854+from bzrlib import tests
855+from bzrlib.doc_generate import (
856+ # FIXME: doc/en/conf.py should be used here, or rather merged into
857+ # bzrlib/doc_generate/conf.py -- vila 20100429
858+ conf,
859+ )
860+from bzrlib.tests import (
861+ doc_generate as test_dg, # Avoid clash with from bzrlib import doc_generate
862+ )
863+
864+
865+class TestBuilderDefined(tests.TestCase):
866+
867+ def test_builder_defined(self):
868+ self.assertTrue('bzrlib.doc_generate.builders.texinfo'
869+ in conf.extensions)
870+
871+class TestBuilderLoaded(test_dg.TestSphinx):
872+
873+ def test_builder_loaded(self):
874+ app, out, err = self.make_sphinx()
875+ self.assertTrue('texinfo' in app.builderclasses)
876+
877+
878+class TestFileProduction(test_dg.TestSphinx):
879+
880+ def test_files_generated(self):
881+ self.build_tree_contents(
882+ [('index.txt', """
883+Table of Contents
884+=================
885+
886+.. toctree::
887+ :maxdepth: 1
888+
889+ content
890+"""),
891+ ('content.txt', """
892+
893+bzr 0.0.8
894+*********
895+
896+Improvements
897+============
898+
899+* Adding a file whose parent directory is not versioned will
900+ implicitly add the parent, and so on up to the root.
901+"""),
902+ ])
903+ app, out, err = self.make_sphinx()
904+ self.build(app)
905+ self.failUnlessExists('index.texi')
906+ self.failUnlessExists('content.texi')
907
908=== added directory 'bzrlib/tests/doc_generate/writers'
909=== added file 'bzrlib/tests/doc_generate/writers/__init__.py'
910--- bzrlib/tests/doc_generate/writers/__init__.py 1970-01-01 00:00:00 +0000
911+++ bzrlib/tests/doc_generate/writers/__init__.py 2010-07-21 08:08:51 +0000
912@@ -0,0 +1,36 @@
913+# Copyright (C) 2010 Canonical Ltd
914+#
915+# This program is free software; you can redistribute it and/or modify
916+# it under the terms of the GNU General Public License as published by
917+# the Free Software Foundation; either version 2 of the License, or
918+# (at your option) any later version.
919+#
920+# This program is distributed in the hope that it will be useful,
921+# but WITHOUT ANY WARRANTY; without even the implied warranty of
922+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
923+# GNU General Public License for more details.
924+#
925+# You should have received a copy of the GNU General Public License
926+# along with this program; if not, write to the Free Software
927+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
928+
929+
930+"""Sphinx writers tests."""
931+
932+from bzrlib.tests import features
933+
934+def load_tests(basic_tests, module, loader):
935+ suite = loader.suiteClass()
936+ # add the tests for this module
937+ suite.addTests(basic_tests)
938+
939+ if features.sphinx.available():
940+ testmod_names = [
941+ 'texinfo',
942+ ]
943+ # add the tests for the sub modules
944+ suite.addTests(loader.loadTestsFromModuleNames(
945+ ['bzrlib.tests.doc_generate.writers.test_' + name
946+ for name in testmod_names]))
947+
948+ return suite
949
950=== added file 'bzrlib/tests/doc_generate/writers/test_texinfo.py'
951--- bzrlib/tests/doc_generate/writers/test_texinfo.py 1970-01-01 00:00:00 +0000
952+++ bzrlib/tests/doc_generate/writers/test_texinfo.py 2010-07-21 08:08:51 +0000
953@@ -0,0 +1,335 @@
954+# Copyright (C) 2010 Canonical Ltd
955+#
956+# This program is free software; you can redistribute it and/or modify
957+# it under the terms of the GNU General Public License as published by
958+# the Free Software Foundation; either version 2 of the License, or
959+# (at your option) any later version.
960+#
961+# This program is distributed in the hope that it will be useful,
962+# but WITHOUT ANY WARRANTY; without even the implied warranty of
963+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
964+# GNU General Public License for more details.
965+#
966+# You should have received a copy of the GNU General Public License
967+# along with this program; if not, write to the Free Software
968+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
969+
970+"""sphinx texinfo writer tests."""
971+
972+from bzrlib.tests import (
973+ doc_generate as test_dg, # Avoid clash with from bzrlib import doc_generate
974+ )
975+
976+
977+class TestTextGeneration(test_dg.TestSphinx):
978+
979+ def test_special_chars(self):
980+ self.create_content("A '@' a '{' and a '}'")
981+ self.assertContent("A '@@' a '@{' and a '@}'")
982+
983+ def test_emphasis(self):
984+ self.create_content('*important*')
985+ self.assertContent('@emph{important}')
986+
987+ def test_strong(self):
988+ self.create_content('**very important**')
989+ self.assertContent('@strong{very important}')
990+
991+ def test_literal(self):
992+ self.create_content('the command is ``foo``')
993+ self.assertContent('the command is @code{foo}')
994+
995+ def test_paragraphs(self):
996+ self.create_content('''\
997+This is a paragraph.
998+
999+This is another one.
1000+''')
1001+ self.assertContent('''\
1002+This is a paragraph.
1003+
1004+This is another one.''')
1005+
1006+ def test_literal_block(self):
1007+ self.create_content('''\
1008+Do this::
1009+
1010+ bzr xxx
1011+ bzr yyy
1012+''')
1013+ self.assertContent('''\
1014+Do this:
1015+
1016+@samp{bzr xxx
1017+bzr yyy}
1018+
1019+''',
1020+ end='')
1021+
1022+ def test_block_quote(self):
1023+ self.create_content('''\
1024+This is an ordinary paragraph, introducing a block quote.
1025+
1026+ "It is my business to know things. That is my trade."
1027+
1028+This is another ordinary paragraph.
1029+''')
1030+ self.assertContent('''\
1031+This is an ordinary paragraph, introducing a block quote.
1032+
1033+@example
1034+"It is my business to know things. That is my trade."
1035+
1036+@end example
1037+
1038+This is another ordinary paragraph.
1039+
1040+''',
1041+ # examples are not followed by an empty line
1042+ end='')
1043+
1044+
1045+class TestDocumentAttributesGeneration(test_dg.TestSphinx):
1046+
1047+ def test_title(self):
1048+ self.create_content('''\
1049+####################
1050+Bazaar Release Notes
1051+####################
1052+''')
1053+ self.assertContent('''\
1054+@node bazaar-release-notes
1055+@chapter Bazaar Release Notes
1056+''',
1057+ end='')
1058+
1059+
1060+class TestListGeneration(test_dg.TestSphinx):
1061+
1062+ def test_bullet_list(self):
1063+ self.create_content('''\
1064+* This is a bulleted list.
1065+* It has two items, the second
1066+ item uses two lines.
1067+''')
1068+ self.assertContent('''\
1069+@itemize @bullet
1070+@item
1071+This is a bulleted list.
1072+
1073+@item
1074+It has two items, the second
1075+item uses two lines.
1076+
1077+@end itemize
1078+''',
1079+ end='')
1080+
1081+ def test_enumerated_list(self):
1082+ self.create_content('''\
1083+#. This is a numbered list.
1084+#. It has two items, the second
1085+ item uses two lines.
1086+''')
1087+ self.assertContent('''\
1088+@enumerate
1089+@item
1090+This is a numbered list.
1091+
1092+@item
1093+It has two items, the second
1094+item uses two lines.
1095+
1096+@end enumerate
1097+''',
1098+ end='')
1099+
1100+
1101+class TestTableGeneration(test_dg.TestSphinx):
1102+
1103+ def test_table(self):
1104+ self.create_content('''\
1105+ =========== ================
1106+ Prefix Description
1107+ =========== ================
1108+ first The first
1109+ second The second
1110+ last The last
1111+ =========== ================
1112+''')
1113+ # FIXME: Sphinx bug ? Why are tables enclosed in a block_quote
1114+ # (translated as an @example).
1115+ self.assertContent('''\
1116+@example
1117+@multitable {xxxxxxxxxxx}{xxxxxxxxxxxxxxxx}
1118+@headitem Prefix @tab Description
1119+@item first
1120+@tab The first
1121+@item second
1122+@tab The second
1123+@item last
1124+@tab The last
1125+@end multitable
1126+@end example''')
1127+
1128+
1129+class TestTocTreeGeneration(test_dg.TestSphinx):
1130+
1131+ def test_toctree(self):
1132+ self.build_tree_contents(
1133+ [('index.txt', """
1134+Table of Contents
1135+=================
1136+
1137+.. toctree::
1138+ :maxdepth: 1
1139+
1140+ bzr 0.0.8 <bzr-0.0.8>
1141+"""),
1142+ ('bzr-0.0.8.txt', """
1143+
1144+bzr 0.0.8
1145+*********
1146+
1147+Improvements
1148+============
1149+
1150+* Adding a file whose parent directory is not versioned will
1151+ implicitly add the parent, and so on up to the root.
1152+"""),
1153+ ])
1154+ app, out, err = self.make_sphinx()
1155+ self.build(app)
1156+ self.assertFileEqual("""\
1157+This file has been converted using a beta rst->texinfo converter.
1158+Most of the info links are currently bogus, don't report bugs about them,
1159+this is currently worked on.
1160+@node Top
1161+@top Placeholder
1162+@node table-of-contents
1163+@chapter Table of Contents
1164+@menu
1165+* bzr 0.0.8: (bzr-0.0.8.info)bzr 0.0.8.
1166+@end menu
1167+""",
1168+ 'index.texi')
1169+ self.assertFileEqual("""\
1170+This file has been converted using a beta rst->texinfo converter.
1171+Most of the info links are currently bogus, don't report bugs about them,
1172+this is currently worked on.
1173+@node Top
1174+@top Placeholder
1175+@node bzr-0-0-8
1176+@chapter bzr 0.0.8
1177+@node improvements
1178+@section Improvements
1179+@itemize @bullet
1180+@item
1181+Adding a file whose parent directory is not versioned will
1182+implicitly add the parent, and so on up to the root.
1183+
1184+@end itemize
1185+""",
1186+ 'bzr-0.0.8.texi')
1187+
1188+class TestSections(test_dg.TestSphinx):
1189+
1190+ def test_sections(self):
1191+ self.create_content('''\
1192+###########
1193+Chapter one
1194+###########
1195+
1196+Chapter introduction.
1197+
1198+***********
1199+section one
1200+***********
1201+
1202+The first section.
1203+
1204+
1205+subsection one
1206+==============
1207+
1208+The first subsection.
1209+
1210+subsection two
1211+==============
1212+
1213+The second subsection.
1214+
1215+subsubsection one
1216+-----------------
1217+
1218+Here is sus sub section one.
1219+
1220+blob one
1221+^^^^^^^^
1222+
1223+Far tooo deep to get a name
1224+
1225+thing one
1226+"""""""""
1227+
1228+No idea how to call that, but sphinx says it's a paragraph.
1229+''')
1230+ self.assertContent('''\
1231+@node chapter-one
1232+@chapter Chapter one
1233+Chapter introduction.
1234+
1235+@node section-one
1236+@section section one
1237+The first section.
1238+
1239+@node subsection-one
1240+@subsection subsection one
1241+The first subsection.
1242+
1243+@node subsection-two
1244+@subsection subsection two
1245+The second subsection.
1246+
1247+@node subsubsection-one
1248+@subsubsection subsubsection one
1249+Here is sus sub section one.
1250+
1251+@node blob-one
1252+@heading blob one
1253+Far tooo deep to get a name
1254+
1255+@node thing-one
1256+@heading thing one
1257+No idea how to call that, but sphinx says it's a paragraph.''')
1258+
1259+
1260+class TestReferences(test_dg.TestSphinx):
1261+
1262+ def test_external_reference(self):
1263+ self.create_content('''\
1264+The `example web site`_ is nice.
1265+
1266+.. _example web site: http://www.example.com/
1267+''')
1268+ self.assertContent('''\
1269+The @uref{http://www.example.com/,example web site} is nice.''')
1270+
1271+
1272+ def test_internal_reference(self):
1273+ self.create_content('''\
1274+The `example web site`_ contains more examples.
1275+
1276+Example web site
1277+----------------
1278+
1279+Here we have a lot of nice examples.
1280+
1281+''')
1282+ self.assertContent('''\
1283+The example web site (@pxref{example-web-site}) contains more examples.
1284+
1285+@node example-web-site
1286+@chapter Example web site
1287+Here we have a lot of nice examples.''')
1288+
1289
1290=== modified file 'bzrlib/tests/features.py'
1291--- bzrlib/tests/features.py 2010-05-25 21:30:52 +0000
1292+++ bzrlib/tests/features.py 2010-07-21 08:08:51 +0000
1293@@ -28,6 +28,7 @@
1294 pycurl = tests.ModuleAvailableFeature('pycurl')
1295 pywintypes = tests.ModuleAvailableFeature('pywintypes')
1296 subunit = tests.ModuleAvailableFeature('subunit')
1297+sphinx = tests.ModuleAvailableFeature('sphinx')
1298
1299
1300 class _BackslashDirSeparatorFeature(tests.Feature):
1301
1302=== modified file 'bzrlib/tests/test_source.py'
1303--- bzrlib/tests/test_source.py 2010-02-17 17:11:16 +0000
1304+++ bzrlib/tests/test_source.py 2010-07-21 08:08:51 +0000
1305@@ -40,11 +40,17 @@
1306
1307 # Files which are listed here will be skipped when testing for Copyright (or
1308 # GPL) statements.
1309-COPYRIGHT_EXCEPTIONS = ['bzrlib/lsprof.py', 'bzrlib/_bencode_py.py',
1310- 'bzrlib/doc_generate/sphinx_conf.py']
1311+COPYRIGHT_EXCEPTIONS = [
1312+ 'bzrlib/_bencode_py.py',
1313+ 'bzrlib/doc_generate/conf.py',
1314+ 'bzrlib/lsprof.py',
1315+ ]
1316
1317-LICENSE_EXCEPTIONS = ['bzrlib/lsprof.py', 'bzrlib/_bencode_py.py',
1318- 'bzrlib/doc_generate/sphinx_conf.py']
1319+LICENSE_EXCEPTIONS = [
1320+ 'bzrlib/_bencode_py.py',
1321+ 'bzrlib/doc_generate/conf.py',
1322+ 'bzrlib/lsprof.py',
1323+ ]
1324 # Technically, 'bzrlib/lsprof.py' should be 'bzrlib/util/lsprof.py',
1325 # (we do not check bzrlib/util/, since that is code bundled from elsewhere)
1326 # but for compatibility with previous releases, we don't want to move it.
1327
1328=== modified file 'doc/developers/conf.py'
1329--- doc/developers/conf.py 2009-09-09 15:51:18 +0000
1330+++ doc/developers/conf.py 2010-07-21 08:08:51 +0000
1331@@ -13,7 +13,7 @@
1332 sys.path = [os.path.abspath('../..')] + sys.path
1333
1334 # Most of the configuration for Bazaar docs is defined here ...
1335-from bzrlib.doc_generate.sphinx_conf import *
1336+from bzrlib.doc_generate.conf import *
1337
1338
1339 ## Configuration specific to this site ##
1340
1341=== modified file 'doc/en/Makefile'
1342--- doc/en/Makefile 2010-03-25 10:09:32 +0000
1343+++ doc/en/Makefile 2010-07-21 08:08:51 +0000
1344@@ -112,3 +112,7 @@
1345 $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) _build/doctest
1346 @echo "Testing of doctests in the sources finished, look at the " \
1347 "results in _build/doctest/output.txt."
1348+
1349+texinfo:
1350+ $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) _build/texinfo
1351+
1352
1353=== modified file 'doc/en/conf.py'
1354--- doc/en/conf.py 2010-05-26 04:26:59 +0000
1355+++ doc/en/conf.py 2010-07-21 08:08:51 +0000
1356@@ -13,8 +13,7 @@
1357 sys.path = [os.path.abspath('../..')] + sys.path
1358
1359 # Most of the configuration for Bazaar docs is defined here ...
1360-from bzrlib.doc_generate.sphinx_conf import *
1361-
1362+from bzrlib.doc_generate.conf import *
1363
1364 ## Configuration specific to this site ##
1365
1366
1367=== modified file 'doc/es/conf.py'
1368--- doc/es/conf.py 2009-09-09 15:51:18 +0000
1369+++ doc/es/conf.py 2010-07-21 08:08:51 +0000
1370@@ -13,7 +13,7 @@
1371 sys.path = [os.path.abspath('../..')] + sys.path
1372
1373 # Most of the configuration for Bazaar docs is defined here ...
1374-from bzrlib.doc_generate.sphinx_conf import *
1375+from bzrlib.doc_generate.conf import *
1376
1377
1378 ## Configuration specific to this site ##
1379
1380=== modified file 'doc/ja/conf.py'
1381--- doc/ja/conf.py 2009-11-18 06:18:14 +0000
1382+++ doc/ja/conf.py 2010-07-21 08:08:51 +0000
1383@@ -13,7 +13,7 @@
1384 sys.path = [os.path.abspath('../..')] + sys.path
1385
1386 # Most of the configuration for Bazaar docs is defined here ...
1387-from bzrlib.doc_generate.sphinx_conf import *
1388+from bzrlib.doc_generate.conf import *
1389
1390
1391 ## Configuration specific to this site ##
1392
1393=== modified file 'doc/ru/conf.py'
1394--- doc/ru/conf.py 2009-09-09 15:51:18 +0000
1395+++ doc/ru/conf.py 2010-07-21 08:08:51 +0000
1396@@ -13,7 +13,7 @@
1397 sys.path = [os.path.abspath('../..')] + sys.path
1398
1399 # Most of the configuration for Bazaar docs is defined here ...
1400-from bzrlib.doc_generate.sphinx_conf import *
1401+from bzrlib.doc_generate.conf import *
1402
1403
1404 ## Configuration specific to this site ##
1405
1406=== modified file 'tools/generate_release_notes.py'
1407--- tools/generate_release_notes.py 2009-09-03 05:09:22 +0000
1408+++ tools/generate_release_notes.py 2010-07-21 08:08:51 +0000
1409@@ -20,8 +20,6 @@
1410 import sys
1411 from optparse import OptionParser
1412
1413-sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
1414-
1415
1416 def split_into_topics(lines, out_file, out_dir):
1417 """Split a large NEWS file into topics, one per release.
1418@@ -32,7 +30,7 @@
1419 """
1420 topic_file = None
1421 for index, line in enumerate(lines):
1422- maybe_new_topic = line[:4] in ['bzr ', 'bzr-0',]
1423+ maybe_new_topic = line[:4] in ('bzr ', 'bzr-',)
1424 if maybe_new_topic and lines[index + 1].startswith('####'):
1425 release = line.strip()
1426 if topic_file is None:
1427@@ -45,13 +43,22 @@
1428 elif topic_file:
1429 topic_file.write(line)
1430 else:
1431+ # FIXME: the 'content' directive is used for rst2html (and
1432+ # conflicts with the 'toctree' we insert), we should get rid of
1433+ # that once we fully switch to sphinx -- vila 20100505
1434+ if (line.startswith('.. contents::')
1435+ or line.startswith(' :depth:')):
1436+ continue
1437 # Still in the header - dump content straight to output
1438 out_file.write(line)
1439+ if topic_file is not None:
1440+ # Close the last topic_file opened
1441+ topic_file.close()
1442
1443
1444 def open_topic_file(out_file, out_dir, release):
1445 topic_name = release.replace(' ', '-')
1446- out_file.write(" %s\n" % (topic_name,))
1447+ out_file.write(" %s <%s>\n" % (release, topic_name,))
1448 topic_path = os.path.join(out_dir, "%s.txt" % (topic_name,))
1449 result = open(topic_path, 'w')
1450 result.write("%s\n" % (release,))