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