Merge lp:~vila/bzr/219334-texinfo into lp:bzr
- 219334-texinfo
- Merge into bzr.dev
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 | ||||
Related bugs: |
|
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.
Vincent Ladeuil (vila) wrote : | # |
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://
iEYEARECAAYFAkw
9mkAn1kfOn8i4Oi
=Ty94
-----END PGP SIGNATURE-----
Vincent Ladeuil (vila) wrote : | # |
s/proper rest ref/proper sphinx ref/ sorry:
http://
But this requires a full switch to sphinx as mentioned.
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://
>
> 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
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://
>>
>> 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://
iEYEARECAAYFAkw
QsAAoMVxBiWCO9f
=2FbL
-----END PGP SIGNATURE-----
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://
>>>
>>> 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).
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.
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
Vincent Ladeuil (vila) wrote : | # |
sent to pqm by email
Preview Diff
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,)) |
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.