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