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