##// END OF EJS Templates
minirst: fix container stripping logic
Matt Mackall -
r15102:a7e375d0 default
parent child Browse files
Show More
@@ -1,571 +1,571 b''
1 # minirst.py - minimal reStructuredText parser
1 # minirst.py - minimal reStructuredText parser
2 #
2 #
3 # Copyright 2009, 2010 Matt Mackall <mpm@selenic.com> and others
3 # Copyright 2009, 2010 Matt Mackall <mpm@selenic.com> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 """simplified reStructuredText parser.
8 """simplified reStructuredText parser.
9
9
10 This parser knows just enough about reStructuredText to parse the
10 This parser knows just enough about reStructuredText to parse the
11 Mercurial docstrings.
11 Mercurial docstrings.
12
12
13 It cheats in a major way: nested blocks are not really nested. They
13 It cheats in a major way: nested blocks are not really nested. They
14 are just indented blocks that look like they are nested. This relies
14 are just indented blocks that look like they are nested. This relies
15 on the user to keep the right indentation for the blocks.
15 on the user to keep the right indentation for the blocks.
16
16
17 Remember to update http://mercurial.selenic.com/wiki/HelpStyleGuide
17 Remember to update http://mercurial.selenic.com/wiki/HelpStyleGuide
18 when adding support for new constructs.
18 when adding support for new constructs.
19 """
19 """
20
20
21 import re
21 import re
22 import util, encoding
22 import util, encoding
23 from i18n import _
23 from i18n import _
24
24
25
25
26 def replace(text, substs):
26 def replace(text, substs):
27 utext = text.decode(encoding.encoding)
27 utext = text.decode(encoding.encoding)
28 for f, t in substs:
28 for f, t in substs:
29 utext = utext.replace(f, t)
29 utext = utext.replace(f, t)
30 return utext.encode(encoding.encoding)
30 return utext.encode(encoding.encoding)
31
31
32
32
33 _blockre = re.compile(r"\n(?:\s*\n)+")
33 _blockre = re.compile(r"\n(?:\s*\n)+")
34
34
35 def findblocks(text):
35 def findblocks(text):
36 """Find continuous blocks of lines in text.
36 """Find continuous blocks of lines in text.
37
37
38 Returns a list of dictionaries representing the blocks. Each block
38 Returns a list of dictionaries representing the blocks. Each block
39 has an 'indent' field and a 'lines' field.
39 has an 'indent' field and a 'lines' field.
40 """
40 """
41 blocks = []
41 blocks = []
42 for b in _blockre.split(text.lstrip('\n').rstrip()):
42 for b in _blockre.split(text.lstrip('\n').rstrip()):
43 lines = b.splitlines()
43 lines = b.splitlines()
44 indent = min((len(l) - len(l.lstrip())) for l in lines)
44 indent = min((len(l) - len(l.lstrip())) for l in lines)
45 lines = [l[indent:] for l in lines]
45 lines = [l[indent:] for l in lines]
46 blocks.append(dict(indent=indent, lines=lines))
46 blocks.append(dict(indent=indent, lines=lines))
47 return blocks
47 return blocks
48
48
49
49
50 def findliteralblocks(blocks):
50 def findliteralblocks(blocks):
51 """Finds literal blocks and adds a 'type' field to the blocks.
51 """Finds literal blocks and adds a 'type' field to the blocks.
52
52
53 Literal blocks are given the type 'literal', all other blocks are
53 Literal blocks are given the type 'literal', all other blocks are
54 given type the 'paragraph'.
54 given type the 'paragraph'.
55 """
55 """
56 i = 0
56 i = 0
57 while i < len(blocks):
57 while i < len(blocks):
58 # Searching for a block that looks like this:
58 # Searching for a block that looks like this:
59 #
59 #
60 # +------------------------------+
60 # +------------------------------+
61 # | paragraph |
61 # | paragraph |
62 # | (ends with "::") |
62 # | (ends with "::") |
63 # +------------------------------+
63 # +------------------------------+
64 # +---------------------------+
64 # +---------------------------+
65 # | indented literal block |
65 # | indented literal block |
66 # +---------------------------+
66 # +---------------------------+
67 blocks[i]['type'] = 'paragraph'
67 blocks[i]['type'] = 'paragraph'
68 if blocks[i]['lines'][-1].endswith('::') and i + 1 < len(blocks):
68 if blocks[i]['lines'][-1].endswith('::') and i + 1 < len(blocks):
69 indent = blocks[i]['indent']
69 indent = blocks[i]['indent']
70 adjustment = blocks[i + 1]['indent'] - indent
70 adjustment = blocks[i + 1]['indent'] - indent
71
71
72 if blocks[i]['lines'] == ['::']:
72 if blocks[i]['lines'] == ['::']:
73 # Expanded form: remove block
73 # Expanded form: remove block
74 del blocks[i]
74 del blocks[i]
75 i -= 1
75 i -= 1
76 elif blocks[i]['lines'][-1].endswith(' ::'):
76 elif blocks[i]['lines'][-1].endswith(' ::'):
77 # Partially minimized form: remove space and both
77 # Partially minimized form: remove space and both
78 # colons.
78 # colons.
79 blocks[i]['lines'][-1] = blocks[i]['lines'][-1][:-3]
79 blocks[i]['lines'][-1] = blocks[i]['lines'][-1][:-3]
80 else:
80 else:
81 # Fully minimized form: remove just one colon.
81 # Fully minimized form: remove just one colon.
82 blocks[i]['lines'][-1] = blocks[i]['lines'][-1][:-1]
82 blocks[i]['lines'][-1] = blocks[i]['lines'][-1][:-1]
83
83
84 # List items are formatted with a hanging indent. We must
84 # List items are formatted with a hanging indent. We must
85 # correct for this here while we still have the original
85 # correct for this here while we still have the original
86 # information on the indentation of the subsequent literal
86 # information on the indentation of the subsequent literal
87 # blocks available.
87 # blocks available.
88 m = _bulletre.match(blocks[i]['lines'][0])
88 m = _bulletre.match(blocks[i]['lines'][0])
89 if m:
89 if m:
90 indent += m.end()
90 indent += m.end()
91 adjustment -= m.end()
91 adjustment -= m.end()
92
92
93 # Mark the following indented blocks.
93 # Mark the following indented blocks.
94 while i + 1 < len(blocks) and blocks[i + 1]['indent'] > indent:
94 while i + 1 < len(blocks) and blocks[i + 1]['indent'] > indent:
95 blocks[i + 1]['type'] = 'literal'
95 blocks[i + 1]['type'] = 'literal'
96 blocks[i + 1]['indent'] -= adjustment
96 blocks[i + 1]['indent'] -= adjustment
97 i += 1
97 i += 1
98 i += 1
98 i += 1
99 return blocks
99 return blocks
100
100
101 _bulletre = re.compile(r'(-|[0-9A-Za-z]+\.|\(?[0-9A-Za-z]+\)|\|) ')
101 _bulletre = re.compile(r'(-|[0-9A-Za-z]+\.|\(?[0-9A-Za-z]+\)|\|) ')
102 _optionre = re.compile(r'^(-([a-zA-Z0-9]), )?(--[a-z0-9-]+)'
102 _optionre = re.compile(r'^(-([a-zA-Z0-9]), )?(--[a-z0-9-]+)'
103 r'((.*) +)(.*)$')
103 r'((.*) +)(.*)$')
104 _fieldre = re.compile(r':(?![: ])([^:]*)(?<! ):[ ]+(.*)')
104 _fieldre = re.compile(r':(?![: ])([^:]*)(?<! ):[ ]+(.*)')
105 _definitionre = re.compile(r'[^ ]')
105 _definitionre = re.compile(r'[^ ]')
106 _tablere = re.compile(r'(=+\s+)*=+')
106 _tablere = re.compile(r'(=+\s+)*=+')
107
107
108 def splitparagraphs(blocks):
108 def splitparagraphs(blocks):
109 """Split paragraphs into lists."""
109 """Split paragraphs into lists."""
110 # Tuples with (list type, item regexp, single line items?). Order
110 # Tuples with (list type, item regexp, single line items?). Order
111 # matters: definition lists has the least specific regexp and must
111 # matters: definition lists has the least specific regexp and must
112 # come last.
112 # come last.
113 listtypes = [('bullet', _bulletre, True),
113 listtypes = [('bullet', _bulletre, True),
114 ('option', _optionre, True),
114 ('option', _optionre, True),
115 ('field', _fieldre, True),
115 ('field', _fieldre, True),
116 ('definition', _definitionre, False)]
116 ('definition', _definitionre, False)]
117
117
118 def match(lines, i, itemre, singleline):
118 def match(lines, i, itemre, singleline):
119 """Does itemre match an item at line i?
119 """Does itemre match an item at line i?
120
120
121 A list item can be followed by an idented line or another list
121 A list item can be followed by an idented line or another list
122 item (but only if singleline is True).
122 item (but only if singleline is True).
123 """
123 """
124 line1 = lines[i]
124 line1 = lines[i]
125 line2 = i + 1 < len(lines) and lines[i + 1] or ''
125 line2 = i + 1 < len(lines) and lines[i + 1] or ''
126 if not itemre.match(line1):
126 if not itemre.match(line1):
127 return False
127 return False
128 if singleline:
128 if singleline:
129 return line2 == '' or line2[0] == ' ' or itemre.match(line2)
129 return line2 == '' or line2[0] == ' ' or itemre.match(line2)
130 else:
130 else:
131 return line2.startswith(' ')
131 return line2.startswith(' ')
132
132
133 i = 0
133 i = 0
134 while i < len(blocks):
134 while i < len(blocks):
135 if blocks[i]['type'] == 'paragraph':
135 if blocks[i]['type'] == 'paragraph':
136 lines = blocks[i]['lines']
136 lines = blocks[i]['lines']
137 for type, itemre, singleline in listtypes:
137 for type, itemre, singleline in listtypes:
138 if match(lines, 0, itemre, singleline):
138 if match(lines, 0, itemre, singleline):
139 items = []
139 items = []
140 for j, line in enumerate(lines):
140 for j, line in enumerate(lines):
141 if match(lines, j, itemre, singleline):
141 if match(lines, j, itemre, singleline):
142 items.append(dict(type=type, lines=[],
142 items.append(dict(type=type, lines=[],
143 indent=blocks[i]['indent']))
143 indent=blocks[i]['indent']))
144 items[-1]['lines'].append(line)
144 items[-1]['lines'].append(line)
145 blocks[i:i + 1] = items
145 blocks[i:i + 1] = items
146 break
146 break
147 i += 1
147 i += 1
148 return blocks
148 return blocks
149
149
150
150
151 _fieldwidth = 12
151 _fieldwidth = 12
152
152
153 def updatefieldlists(blocks):
153 def updatefieldlists(blocks):
154 """Find key and maximum key width for field lists."""
154 """Find key and maximum key width for field lists."""
155 i = 0
155 i = 0
156 while i < len(blocks):
156 while i < len(blocks):
157 if blocks[i]['type'] != 'field':
157 if blocks[i]['type'] != 'field':
158 i += 1
158 i += 1
159 continue
159 continue
160
160
161 keywidth = 0
161 keywidth = 0
162 j = i
162 j = i
163 while j < len(blocks) and blocks[j]['type'] == 'field':
163 while j < len(blocks) and blocks[j]['type'] == 'field':
164 m = _fieldre.match(blocks[j]['lines'][0])
164 m = _fieldre.match(blocks[j]['lines'][0])
165 key, rest = m.groups()
165 key, rest = m.groups()
166 blocks[j]['lines'][0] = rest
166 blocks[j]['lines'][0] = rest
167 blocks[j]['key'] = key
167 blocks[j]['key'] = key
168 keywidth = max(keywidth, len(key))
168 keywidth = max(keywidth, len(key))
169 j += 1
169 j += 1
170
170
171 for block in blocks[i:j]:
171 for block in blocks[i:j]:
172 block['keywidth'] = keywidth
172 block['keywidth'] = keywidth
173 i = j + 1
173 i = j + 1
174
174
175 return blocks
175 return blocks
176
176
177
177
178 def updateoptionlists(blocks):
178 def updateoptionlists(blocks):
179 i = 0
179 i = 0
180 while i < len(blocks):
180 while i < len(blocks):
181 if blocks[i]['type'] != 'option':
181 if blocks[i]['type'] != 'option':
182 i += 1
182 i += 1
183 continue
183 continue
184
184
185 optstrwidth = 0
185 optstrwidth = 0
186 j = i
186 j = i
187 while j < len(blocks) and blocks[j]['type'] == 'option':
187 while j < len(blocks) and blocks[j]['type'] == 'option':
188 m = _optionre.match(blocks[j]['lines'][0])
188 m = _optionre.match(blocks[j]['lines'][0])
189
189
190 shortoption = m.group(2)
190 shortoption = m.group(2)
191 group3 = m.group(3)
191 group3 = m.group(3)
192 longoption = group3[2:].strip()
192 longoption = group3[2:].strip()
193 desc = m.group(6).strip()
193 desc = m.group(6).strip()
194 longoptionarg = m.group(5).strip()
194 longoptionarg = m.group(5).strip()
195 blocks[j]['lines'][0] = desc
195 blocks[j]['lines'][0] = desc
196
196
197 noshortop = ''
197 noshortop = ''
198 if not shortoption:
198 if not shortoption:
199 noshortop = ' '
199 noshortop = ' '
200
200
201 opt = "%s%s" % (shortoption and "-%s " % shortoption or '',
201 opt = "%s%s" % (shortoption and "-%s " % shortoption or '',
202 ("%s--%s %s") % (noshortop, longoption,
202 ("%s--%s %s") % (noshortop, longoption,
203 longoptionarg))
203 longoptionarg))
204 opt = opt.rstrip()
204 opt = opt.rstrip()
205 blocks[j]['optstr'] = opt
205 blocks[j]['optstr'] = opt
206 optstrwidth = max(optstrwidth, encoding.colwidth(opt))
206 optstrwidth = max(optstrwidth, encoding.colwidth(opt))
207 j += 1
207 j += 1
208
208
209 for block in blocks[i:j]:
209 for block in blocks[i:j]:
210 block['optstrwidth'] = optstrwidth
210 block['optstrwidth'] = optstrwidth
211 i = j + 1
211 i = j + 1
212 return blocks
212 return blocks
213
213
214 def prunecontainers(blocks, keep):
214 def prunecontainers(blocks, keep):
215 """Prune unwanted containers.
215 """Prune unwanted containers.
216
216
217 The blocks must have a 'type' field, i.e., they should have been
217 The blocks must have a 'type' field, i.e., they should have been
218 run through findliteralblocks first.
218 run through findliteralblocks first.
219 """
219 """
220 pruned = []
220 pruned = []
221 i = 0
221 i = 0
222 while i + 1 < len(blocks):
222 while i + 1 < len(blocks):
223 # Searching for a block that looks like this:
223 # Searching for a block that looks like this:
224 #
224 #
225 # +-------+---------------------------+
225 # +-------+---------------------------+
226 # | ".. container ::" type |
226 # | ".. container ::" type |
227 # +---+ |
227 # +---+ |
228 # | blocks |
228 # | blocks |
229 # +-------------------------------+
229 # +-------------------------------+
230 if (blocks[i]['type'] == 'paragraph' and
230 if (blocks[i]['type'] == 'paragraph' and
231 blocks[i]['lines'][0].startswith('.. container::')):
231 blocks[i]['lines'][0].startswith('.. container::')):
232 indent = blocks[i]['indent']
232 indent = blocks[i]['indent']
233 adjustment = blocks[i + 1]['indent'] - indent
233 adjustment = blocks[i + 1]['indent'] - indent
234 containertype = blocks[i]['lines'][0][15:]
234 containertype = blocks[i]['lines'][0][15:]
235 prune = containertype not in keep
235 prune = containertype not in keep
236 if prune:
236 if prune:
237 pruned.append(containertype)
237 pruned.append(containertype)
238
238
239 # Always delete "..container:: type" block
239 # Always delete "..container:: type" block
240 del blocks[i]
240 del blocks[i]
241 j = i
241 j = i
242 i -= 1
242 while j < len(blocks) and blocks[j]['indent'] > indent:
243 while j < len(blocks) and blocks[j]['indent'] > indent:
243 if prune:
244 if prune:
244 del blocks[j]
245 del blocks[j]
245 i -= 1 # adjust outer index
246 else:
246 else:
247 blocks[j]['indent'] -= adjustment
247 blocks[j]['indent'] -= adjustment
248 j += 1
248 j += 1
249 i += 1
249 i += 1
250 return blocks, pruned
250 return blocks, pruned
251
251
252
252
253 _sectionre = re.compile(r"""^([-=`:.'"~^_*+#])\1+$""")
253 _sectionre = re.compile(r"""^([-=`:.'"~^_*+#])\1+$""")
254
254
255 def findtables(blocks):
255 def findtables(blocks):
256 '''Find simple tables
256 '''Find simple tables
257
257
258 Only simple one-line table elements are supported
258 Only simple one-line table elements are supported
259 '''
259 '''
260
260
261 for block in blocks:
261 for block in blocks:
262 # Searching for a block that looks like this:
262 # Searching for a block that looks like this:
263 #
263 #
264 # === ==== ===
264 # === ==== ===
265 # A B C
265 # A B C
266 # === ==== === <- optional
266 # === ==== === <- optional
267 # 1 2 3
267 # 1 2 3
268 # x y z
268 # x y z
269 # === ==== ===
269 # === ==== ===
270 if (block['type'] == 'paragraph' and
270 if (block['type'] == 'paragraph' and
271 len(block['lines']) > 4 and
271 len(block['lines']) > 4 and
272 _tablere.match(block['lines'][0]) and
272 _tablere.match(block['lines'][0]) and
273 block['lines'][0] == block['lines'][-1]):
273 block['lines'][0] == block['lines'][-1]):
274 block['type'] = 'table'
274 block['type'] = 'table'
275 block['header'] = False
275 block['header'] = False
276 div = block['lines'][0]
276 div = block['lines'][0]
277 columns = [x for x in xrange(len(div))
277 columns = [x for x in xrange(len(div))
278 if div[x] == '=' and (x == 0 or div[x - 1] == ' ')]
278 if div[x] == '=' and (x == 0 or div[x - 1] == ' ')]
279 rows = []
279 rows = []
280 for l in block['lines'][1:-1]:
280 for l in block['lines'][1:-1]:
281 if l == div:
281 if l == div:
282 block['header'] = True
282 block['header'] = True
283 continue
283 continue
284 row = []
284 row = []
285 for n, start in enumerate(columns):
285 for n, start in enumerate(columns):
286 if n + 1 < len(columns):
286 if n + 1 < len(columns):
287 row.append(l[start:columns[n + 1]].strip())
287 row.append(l[start:columns[n + 1]].strip())
288 else:
288 else:
289 row.append(l[start:].strip())
289 row.append(l[start:].strip())
290 rows.append(row)
290 rows.append(row)
291 block['table'] = rows
291 block['table'] = rows
292
292
293 return blocks
293 return blocks
294
294
295 def findsections(blocks):
295 def findsections(blocks):
296 """Finds sections.
296 """Finds sections.
297
297
298 The blocks must have a 'type' field, i.e., they should have been
298 The blocks must have a 'type' field, i.e., they should have been
299 run through findliteralblocks first.
299 run through findliteralblocks first.
300 """
300 """
301 for block in blocks:
301 for block in blocks:
302 # Searching for a block that looks like this:
302 # Searching for a block that looks like this:
303 #
303 #
304 # +------------------------------+
304 # +------------------------------+
305 # | Section title |
305 # | Section title |
306 # | ------------- |
306 # | ------------- |
307 # +------------------------------+
307 # +------------------------------+
308 if (block['type'] == 'paragraph' and
308 if (block['type'] == 'paragraph' and
309 len(block['lines']) == 2 and
309 len(block['lines']) == 2 and
310 encoding.colwidth(block['lines'][0]) == len(block['lines'][1]) and
310 encoding.colwidth(block['lines'][0]) == len(block['lines'][1]) and
311 _sectionre.match(block['lines'][1])):
311 _sectionre.match(block['lines'][1])):
312 block['underline'] = block['lines'][1][0]
312 block['underline'] = block['lines'][1][0]
313 block['type'] = 'section'
313 block['type'] = 'section'
314 del block['lines'][1]
314 del block['lines'][1]
315 return blocks
315 return blocks
316
316
317
317
318 def inlineliterals(blocks):
318 def inlineliterals(blocks):
319 substs = [('``', '"')]
319 substs = [('``', '"')]
320 for b in blocks:
320 for b in blocks:
321 if b['type'] in ('paragraph', 'section'):
321 if b['type'] in ('paragraph', 'section'):
322 b['lines'] = [replace(l, substs) for l in b['lines']]
322 b['lines'] = [replace(l, substs) for l in b['lines']]
323 return blocks
323 return blocks
324
324
325
325
326 def hgrole(blocks):
326 def hgrole(blocks):
327 substs = [(':hg:`', '"hg '), ('`', '"')]
327 substs = [(':hg:`', '"hg '), ('`', '"')]
328 for b in blocks:
328 for b in blocks:
329 if b['type'] in ('paragraph', 'section'):
329 if b['type'] in ('paragraph', 'section'):
330 # Turn :hg:`command` into "hg command". This also works
330 # Turn :hg:`command` into "hg command". This also works
331 # when there is a line break in the command and relies on
331 # when there is a line break in the command and relies on
332 # the fact that we have no stray back-quotes in the input
332 # the fact that we have no stray back-quotes in the input
333 # (run the blocks through inlineliterals first).
333 # (run the blocks through inlineliterals first).
334 b['lines'] = [replace(l, substs) for l in b['lines']]
334 b['lines'] = [replace(l, substs) for l in b['lines']]
335 return blocks
335 return blocks
336
336
337
337
338 def addmargins(blocks):
338 def addmargins(blocks):
339 """Adds empty blocks for vertical spacing.
339 """Adds empty blocks for vertical spacing.
340
340
341 This groups bullets, options, and definitions together with no vertical
341 This groups bullets, options, and definitions together with no vertical
342 space between them, and adds an empty block between all other blocks.
342 space between them, and adds an empty block between all other blocks.
343 """
343 """
344 i = 1
344 i = 1
345 while i < len(blocks):
345 while i < len(blocks):
346 if (blocks[i]['type'] == blocks[i - 1]['type'] and
346 if (blocks[i]['type'] == blocks[i - 1]['type'] and
347 blocks[i]['type'] in ('bullet', 'option', 'field')):
347 blocks[i]['type'] in ('bullet', 'option', 'field')):
348 i += 1
348 i += 1
349 else:
349 else:
350 blocks.insert(i, dict(lines=[''], indent=0, type='margin'))
350 blocks.insert(i, dict(lines=[''], indent=0, type='margin'))
351 i += 2
351 i += 2
352 return blocks
352 return blocks
353
353
354 def prunecomments(blocks):
354 def prunecomments(blocks):
355 """Remove comments."""
355 """Remove comments."""
356 i = 0
356 i = 0
357 while i < len(blocks):
357 while i < len(blocks):
358 b = blocks[i]
358 b = blocks[i]
359 if b['type'] == 'paragraph' and (b['lines'][0].startswith('.. ') or
359 if b['type'] == 'paragraph' and (b['lines'][0].startswith('.. ') or
360 b['lines'] == ['..']):
360 b['lines'] == ['..']):
361 del blocks[i]
361 del blocks[i]
362 if i < len(blocks) and blocks[i]['type'] == 'margin':
362 if i < len(blocks) and blocks[i]['type'] == 'margin':
363 del blocks[i]
363 del blocks[i]
364 else:
364 else:
365 i += 1
365 i += 1
366 return blocks
366 return blocks
367
367
368 _admonitionre = re.compile(r"\.\. (admonition|attention|caution|danger|"
368 _admonitionre = re.compile(r"\.\. (admonition|attention|caution|danger|"
369 r"error|hint|important|note|tip|warning)::",
369 r"error|hint|important|note|tip|warning)::",
370 flags=re.IGNORECASE)
370 flags=re.IGNORECASE)
371
371
372 def findadmonitions(blocks):
372 def findadmonitions(blocks):
373 """
373 """
374 Makes the type of the block an admonition block if
374 Makes the type of the block an admonition block if
375 the first line is an admonition directive
375 the first line is an admonition directive
376 """
376 """
377 i = 0
377 i = 0
378 while i < len(blocks):
378 while i < len(blocks):
379 m = _admonitionre.match(blocks[i]['lines'][0])
379 m = _admonitionre.match(blocks[i]['lines'][0])
380 if m:
380 if m:
381 blocks[i]['type'] = 'admonition'
381 blocks[i]['type'] = 'admonition'
382 admonitiontitle = blocks[i]['lines'][0][3:m.end() - 2].lower()
382 admonitiontitle = blocks[i]['lines'][0][3:m.end() - 2].lower()
383
383
384 firstline = blocks[i]['lines'][0][m.end() + 1:]
384 firstline = blocks[i]['lines'][0][m.end() + 1:]
385 if firstline:
385 if firstline:
386 blocks[i]['lines'].insert(1, ' ' + firstline)
386 blocks[i]['lines'].insert(1, ' ' + firstline)
387
387
388 blocks[i]['admonitiontitle'] = admonitiontitle
388 blocks[i]['admonitiontitle'] = admonitiontitle
389 del blocks[i]['lines'][0]
389 del blocks[i]['lines'][0]
390 i = i + 1
390 i = i + 1
391 return blocks
391 return blocks
392
392
393 _admonitiontitles = {'attention': _('Attention:'),
393 _admonitiontitles = {'attention': _('Attention:'),
394 'caution': _('Caution:'),
394 'caution': _('Caution:'),
395 'danger': _('!Danger!') ,
395 'danger': _('!Danger!') ,
396 'error': _('Error:'),
396 'error': _('Error:'),
397 'hint': _('Hint:'),
397 'hint': _('Hint:'),
398 'important': _('Important:'),
398 'important': _('Important:'),
399 'note': _('Note:'),
399 'note': _('Note:'),
400 'tip': _('Tip:'),
400 'tip': _('Tip:'),
401 'warning': _('Warning!')}
401 'warning': _('Warning!')}
402
402
403 def formatoption(block, width):
403 def formatoption(block, width):
404 desc = ' '.join(map(str.strip, block['lines']))
404 desc = ' '.join(map(str.strip, block['lines']))
405 colwidth = encoding.colwidth(block['optstr'])
405 colwidth = encoding.colwidth(block['optstr'])
406 usablewidth = width - 1
406 usablewidth = width - 1
407 hanging = block['optstrwidth']
407 hanging = block['optstrwidth']
408 initindent = '%s%s ' % (block['optstr'], ' ' * ((hanging - colwidth)))
408 initindent = '%s%s ' % (block['optstr'], ' ' * ((hanging - colwidth)))
409 hangindent = ' ' * (encoding.colwidth(initindent) + 1)
409 hangindent = ' ' * (encoding.colwidth(initindent) + 1)
410 return ' %s' % (util.wrap(desc, usablewidth,
410 return ' %s' % (util.wrap(desc, usablewidth,
411 initindent=initindent,
411 initindent=initindent,
412 hangindent=hangindent))
412 hangindent=hangindent))
413
413
414 def formatblock(block, width):
414 def formatblock(block, width):
415 """Format a block according to width."""
415 """Format a block according to width."""
416 if width <= 0:
416 if width <= 0:
417 width = 78
417 width = 78
418 indent = ' ' * block['indent']
418 indent = ' ' * block['indent']
419 if block['type'] == 'admonition':
419 if block['type'] == 'admonition':
420 admonition = _admonitiontitles[block['admonitiontitle']]
420 admonition = _admonitiontitles[block['admonitiontitle']]
421 hang = len(block['lines'][-1]) - len(block['lines'][-1].lstrip())
421 hang = len(block['lines'][-1]) - len(block['lines'][-1].lstrip())
422
422
423 defindent = indent + hang * ' '
423 defindent = indent + hang * ' '
424 text = ' '.join(map(str.strip, block['lines']))
424 text = ' '.join(map(str.strip, block['lines']))
425 return '%s\n%s' % (indent + admonition, util.wrap(text, width=width,
425 return '%s\n%s' % (indent + admonition, util.wrap(text, width=width,
426 initindent=defindent,
426 initindent=defindent,
427 hangindent=defindent))
427 hangindent=defindent))
428 if block['type'] == 'margin':
428 if block['type'] == 'margin':
429 return ''
429 return ''
430 if block['type'] == 'literal':
430 if block['type'] == 'literal':
431 indent += ' '
431 indent += ' '
432 return indent + ('\n' + indent).join(block['lines'])
432 return indent + ('\n' + indent).join(block['lines'])
433 if block['type'] == 'section':
433 if block['type'] == 'section':
434 underline = encoding.colwidth(block['lines'][0]) * block['underline']
434 underline = encoding.colwidth(block['lines'][0]) * block['underline']
435 return "%s%s\n%s%s" % (indent, block['lines'][0],indent, underline)
435 return "%s%s\n%s%s" % (indent, block['lines'][0],indent, underline)
436 if block['type'] == 'table':
436 if block['type'] == 'table':
437 table = block['table']
437 table = block['table']
438 # compute column widths
438 # compute column widths
439 widths = [max([encoding.colwidth(e) for e in c]) for c in zip(*table)]
439 widths = [max([encoding.colwidth(e) for e in c]) for c in zip(*table)]
440 text = ''
440 text = ''
441 span = sum(widths) + len(widths) - 1
441 span = sum(widths) + len(widths) - 1
442 indent = ' ' * block['indent']
442 indent = ' ' * block['indent']
443 hang = ' ' * (len(indent) + span - widths[-1])
443 hang = ' ' * (len(indent) + span - widths[-1])
444 f = ' '.join('%%-%ds' % n for n in widths)
444 f = ' '.join('%%-%ds' % n for n in widths)
445
445
446 for row in table:
446 for row in table:
447 l = f % tuple(row)
447 l = f % tuple(row)
448 l = util.wrap(l, width=width, initindent=indent, hangindent=hang)
448 l = util.wrap(l, width=width, initindent=indent, hangindent=hang)
449 if not text and block['header']:
449 if not text and block['header']:
450 text = l + '\n' + indent + '-' * (min(width, span)) + '\n'
450 text = l + '\n' + indent + '-' * (min(width, span)) + '\n'
451 else:
451 else:
452 text += l + "\n"
452 text += l + "\n"
453 return text
453 return text
454 if block['type'] == 'definition':
454 if block['type'] == 'definition':
455 term = indent + block['lines'][0]
455 term = indent + block['lines'][0]
456 hang = len(block['lines'][-1]) - len(block['lines'][-1].lstrip())
456 hang = len(block['lines'][-1]) - len(block['lines'][-1].lstrip())
457 defindent = indent + hang * ' '
457 defindent = indent + hang * ' '
458 text = ' '.join(map(str.strip, block['lines'][1:]))
458 text = ' '.join(map(str.strip, block['lines'][1:]))
459 return '%s\n%s' % (term, util.wrap(text, width=width,
459 return '%s\n%s' % (term, util.wrap(text, width=width,
460 initindent=defindent,
460 initindent=defindent,
461 hangindent=defindent))
461 hangindent=defindent))
462 subindent = indent
462 subindent = indent
463 if block['type'] == 'bullet':
463 if block['type'] == 'bullet':
464 if block['lines'][0].startswith('| '):
464 if block['lines'][0].startswith('| '):
465 # Remove bullet for line blocks and add no extra
465 # Remove bullet for line blocks and add no extra
466 # indention.
466 # indention.
467 block['lines'][0] = block['lines'][0][2:]
467 block['lines'][0] = block['lines'][0][2:]
468 else:
468 else:
469 m = _bulletre.match(block['lines'][0])
469 m = _bulletre.match(block['lines'][0])
470 subindent = indent + m.end() * ' '
470 subindent = indent + m.end() * ' '
471 elif block['type'] == 'field':
471 elif block['type'] == 'field':
472 keywidth = block['keywidth']
472 keywidth = block['keywidth']
473 key = block['key']
473 key = block['key']
474
474
475 subindent = indent + _fieldwidth * ' '
475 subindent = indent + _fieldwidth * ' '
476 if len(key) + 2 > _fieldwidth:
476 if len(key) + 2 > _fieldwidth:
477 # key too large, use full line width
477 # key too large, use full line width
478 key = key.ljust(width)
478 key = key.ljust(width)
479 elif keywidth + 2 < _fieldwidth:
479 elif keywidth + 2 < _fieldwidth:
480 # all keys are small, add only two spaces
480 # all keys are small, add only two spaces
481 key = key.ljust(keywidth + 2)
481 key = key.ljust(keywidth + 2)
482 subindent = indent + (keywidth + 2) * ' '
482 subindent = indent + (keywidth + 2) * ' '
483 else:
483 else:
484 # mixed sizes, use fieldwidth for this one
484 # mixed sizes, use fieldwidth for this one
485 key = key.ljust(_fieldwidth)
485 key = key.ljust(_fieldwidth)
486 block['lines'][0] = key + block['lines'][0]
486 block['lines'][0] = key + block['lines'][0]
487 elif block['type'] == 'option':
487 elif block['type'] == 'option':
488 return formatoption(block, width)
488 return formatoption(block, width)
489
489
490 text = ' '.join(map(str.strip, block['lines']))
490 text = ' '.join(map(str.strip, block['lines']))
491 return util.wrap(text, width=width,
491 return util.wrap(text, width=width,
492 initindent=indent,
492 initindent=indent,
493 hangindent=subindent)
493 hangindent=subindent)
494
494
495 def parse(text, indent=0, keep=None):
495 def parse(text, indent=0, keep=None):
496 """Parse text into a list of blocks"""
496 """Parse text into a list of blocks"""
497 pruned = []
497 pruned = []
498 blocks = findblocks(text)
498 blocks = findblocks(text)
499 for b in blocks:
499 for b in blocks:
500 b['indent'] += indent
500 b['indent'] += indent
501 blocks = findliteralblocks(blocks)
501 blocks = findliteralblocks(blocks)
502 blocks = findtables(blocks)
502 blocks = findtables(blocks)
503 blocks, pruned = prunecontainers(blocks, keep or [])
503 blocks, pruned = prunecontainers(blocks, keep or [])
504 blocks = findsections(blocks)
504 blocks = findsections(blocks)
505 blocks = inlineliterals(blocks)
505 blocks = inlineliterals(blocks)
506 blocks = hgrole(blocks)
506 blocks = hgrole(blocks)
507 blocks = splitparagraphs(blocks)
507 blocks = splitparagraphs(blocks)
508 blocks = updatefieldlists(blocks)
508 blocks = updatefieldlists(blocks)
509 blocks = updateoptionlists(blocks)
509 blocks = updateoptionlists(blocks)
510 blocks = addmargins(blocks)
510 blocks = addmargins(blocks)
511 blocks = prunecomments(blocks)
511 blocks = prunecomments(blocks)
512 blocks = findadmonitions(blocks)
512 blocks = findadmonitions(blocks)
513 return blocks, pruned
513 return blocks, pruned
514
514
515 def formatblocks(blocks, width):
515 def formatblocks(blocks, width):
516 text = '\n'.join(formatblock(b, width) for b in blocks)
516 text = '\n'.join(formatblock(b, width) for b in blocks)
517 return text
517 return text
518
518
519 def format(text, width, indent=0, keep=None):
519 def format(text, width, indent=0, keep=None):
520 """Parse and format the text according to width."""
520 """Parse and format the text according to width."""
521 blocks, pruned = parse(text, indent, keep or [])
521 blocks, pruned = parse(text, indent, keep or [])
522 text = '\n'.join(formatblock(b, width) for b in blocks)
522 text = '\n'.join(formatblock(b, width) for b in blocks)
523 if keep is None:
523 if keep is None:
524 return text
524 return text
525 else:
525 else:
526 return text, pruned
526 return text, pruned
527
527
528 def getsections(blocks):
528 def getsections(blocks):
529 '''return a list of (section name, nesting level, blocks) tuples'''
529 '''return a list of (section name, nesting level, blocks) tuples'''
530 nest = ""
530 nest = ""
531 level = 0
531 level = 0
532 secs = []
532 secs = []
533 for b in blocks:
533 for b in blocks:
534 if b['type'] == 'section':
534 if b['type'] == 'section':
535 i = b['underline']
535 i = b['underline']
536 if i not in nest:
536 if i not in nest:
537 nest += i
537 nest += i
538 level = nest.index(i) + 1
538 level = nest.index(i) + 1
539 nest = nest[:level]
539 nest = nest[:level]
540 secs.append((b['lines'][0], level, [b]))
540 secs.append((b['lines'][0], level, [b]))
541 else:
541 else:
542 if not secs:
542 if not secs:
543 # add an initial empty section
543 # add an initial empty section
544 secs = [('', 0, [])]
544 secs = [('', 0, [])]
545 secs[-1][2].append(b)
545 secs[-1][2].append(b)
546 return secs
546 return secs
547
547
548 def decorateblocks(blocks, width):
548 def decorateblocks(blocks, width):
549 '''generate a list of (section name, line text) pairs for search'''
549 '''generate a list of (section name, line text) pairs for search'''
550 lines = []
550 lines = []
551 for s in getsections(blocks):
551 for s in getsections(blocks):
552 section = s[0]
552 section = s[0]
553 text = formatblocks(s[2], width)
553 text = formatblocks(s[2], width)
554 lines.append([(section, l) for l in text.splitlines(True)])
554 lines.append([(section, l) for l in text.splitlines(True)])
555 return lines
555 return lines
556
556
557 def maketable(data, indent=0, header=False):
557 def maketable(data, indent=0, header=False):
558 '''Generate an RST table for the given table data'''
558 '''Generate an RST table for the given table data'''
559
559
560 widths = [max(encoding.colwidth(e) for e in c) for c in zip(*data)]
560 widths = [max(encoding.colwidth(e) for e in c) for c in zip(*data)]
561 indent = ' ' * indent
561 indent = ' ' * indent
562 f = indent + ' '.join('%%-%ds' % w for w in widths) + '\n'
562 f = indent + ' '.join('%%-%ds' % w for w in widths) + '\n'
563 div = indent + ' '.join('=' * w for w in widths) + '\n'
563 div = indent + ' '.join('=' * w for w in widths) + '\n'
564
564
565 out = [div]
565 out = [div]
566 for row in data:
566 for row in data:
567 out.append(f % tuple(row))
567 out.append(f % tuple(row))
568 if header and len(data) > 1:
568 if header and len(data) > 1:
569 out.insert(2, div)
569 out.insert(2, div)
570 out.append(div)
570 out.append(div)
571 return ''.join(out)
571 return ''.join(out)
General Comments 0
You need to be logged in to leave comments. Login now