##// END OF EJS Templates
minirst: link to HelpStyleGuide in docstring
Martin Geisler -
r12958:8957c398 default
parent child Browse files
Show More
@@ -1,450 +1,431 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 It only supports a small subset of reStructuredText:
17 Remember to update http://mercurial.selenic.com/wiki/HelpStyleGuide
18
18 when adding support for new constructs.
19 - sections
20
21 - paragraphs
22
23 - literal blocks
24
25 - definition lists
26
27 - specific admonitions
28
29 - bullet lists (items must start with '-')
30
31 - enumerated lists (no autonumbering)
32
33 - field lists (colons cannot be escaped)
34
35 - option lists (supports only long options without arguments)
36
37 - inline literals (no other inline markup is not recognized)
38 """
19 """
39
20
40 import re, sys
21 import re, sys
41 import util, encoding
22 import util, encoding
42 from i18n import _
23 from i18n import _
43
24
44
25
45 def replace(text, substs):
26 def replace(text, substs):
46 utext = text.decode(encoding.encoding)
27 utext = text.decode(encoding.encoding)
47 for f, t in substs:
28 for f, t in substs:
48 utext = utext.replace(f, t)
29 utext = utext.replace(f, t)
49 return utext.encode(encoding.encoding)
30 return utext.encode(encoding.encoding)
50
31
51
32
52 _blockre = re.compile(r"\n(?:\s*\n)+")
33 _blockre = re.compile(r"\n(?:\s*\n)+")
53
34
54 def findblocks(text):
35 def findblocks(text):
55 """Find continuous blocks of lines in text.
36 """Find continuous blocks of lines in text.
56
37
57 Returns a list of dictionaries representing the blocks. Each block
38 Returns a list of dictionaries representing the blocks. Each block
58 has an 'indent' field and a 'lines' field.
39 has an 'indent' field and a 'lines' field.
59 """
40 """
60 blocks = []
41 blocks = []
61 for b in _blockre.split(text.strip()):
42 for b in _blockre.split(text.strip()):
62 lines = b.splitlines()
43 lines = b.splitlines()
63 indent = min((len(l) - len(l.lstrip())) for l in lines)
44 indent = min((len(l) - len(l.lstrip())) for l in lines)
64 lines = [l[indent:] for l in lines]
45 lines = [l[indent:] for l in lines]
65 blocks.append(dict(indent=indent, lines=lines))
46 blocks.append(dict(indent=indent, lines=lines))
66 return blocks
47 return blocks
67
48
68
49
69 def findliteralblocks(blocks):
50 def findliteralblocks(blocks):
70 """Finds literal blocks and adds a 'type' field to the blocks.
51 """Finds literal blocks and adds a 'type' field to the blocks.
71
52
72 Literal blocks are given the type 'literal', all other blocks are
53 Literal blocks are given the type 'literal', all other blocks are
73 given type the 'paragraph'.
54 given type the 'paragraph'.
74 """
55 """
75 i = 0
56 i = 0
76 while i < len(blocks):
57 while i < len(blocks):
77 # Searching for a block that looks like this:
58 # Searching for a block that looks like this:
78 #
59 #
79 # +------------------------------+
60 # +------------------------------+
80 # | paragraph |
61 # | paragraph |
81 # | (ends with "::") |
62 # | (ends with "::") |
82 # +------------------------------+
63 # +------------------------------+
83 # +---------------------------+
64 # +---------------------------+
84 # | indented literal block |
65 # | indented literal block |
85 # +---------------------------+
66 # +---------------------------+
86 blocks[i]['type'] = 'paragraph'
67 blocks[i]['type'] = 'paragraph'
87 if blocks[i]['lines'][-1].endswith('::') and i + 1 < len(blocks):
68 if blocks[i]['lines'][-1].endswith('::') and i + 1 < len(blocks):
88 indent = blocks[i]['indent']
69 indent = blocks[i]['indent']
89 adjustment = blocks[i + 1]['indent'] - indent
70 adjustment = blocks[i + 1]['indent'] - indent
90
71
91 if blocks[i]['lines'] == ['::']:
72 if blocks[i]['lines'] == ['::']:
92 # Expanded form: remove block
73 # Expanded form: remove block
93 del blocks[i]
74 del blocks[i]
94 i -= 1
75 i -= 1
95 elif blocks[i]['lines'][-1].endswith(' ::'):
76 elif blocks[i]['lines'][-1].endswith(' ::'):
96 # Partially minimized form: remove space and both
77 # Partially minimized form: remove space and both
97 # colons.
78 # colons.
98 blocks[i]['lines'][-1] = blocks[i]['lines'][-1][:-3]
79 blocks[i]['lines'][-1] = blocks[i]['lines'][-1][:-3]
99 else:
80 else:
100 # Fully minimized form: remove just one colon.
81 # Fully minimized form: remove just one colon.
101 blocks[i]['lines'][-1] = blocks[i]['lines'][-1][:-1]
82 blocks[i]['lines'][-1] = blocks[i]['lines'][-1][:-1]
102
83
103 # List items are formatted with a hanging indent. We must
84 # List items are formatted with a hanging indent. We must
104 # correct for this here while we still have the original
85 # correct for this here while we still have the original
105 # information on the indentation of the subsequent literal
86 # information on the indentation of the subsequent literal
106 # blocks available.
87 # blocks available.
107 m = _bulletre.match(blocks[i]['lines'][0])
88 m = _bulletre.match(blocks[i]['lines'][0])
108 if m:
89 if m:
109 indent += m.end()
90 indent += m.end()
110 adjustment -= m.end()
91 adjustment -= m.end()
111
92
112 # Mark the following indented blocks.
93 # Mark the following indented blocks.
113 while i + 1 < len(blocks) and blocks[i + 1]['indent'] > indent:
94 while i + 1 < len(blocks) and blocks[i + 1]['indent'] > indent:
114 blocks[i + 1]['type'] = 'literal'
95 blocks[i + 1]['type'] = 'literal'
115 blocks[i + 1]['indent'] -= adjustment
96 blocks[i + 1]['indent'] -= adjustment
116 i += 1
97 i += 1
117 i += 1
98 i += 1
118 return blocks
99 return blocks
119
100
120 _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]+\)|\|) ')
121 _optionre = re.compile(r'^(--[a-z-]+)((?:[ =][a-zA-Z][\w-]*)? +)(.*)$')
102 _optionre = re.compile(r'^(--[a-z-]+)((?:[ =][a-zA-Z][\w-]*)? +)(.*)$')
122 _fieldre = re.compile(r':(?![: ])([^:]*)(?<! ):[ ]+(.*)')
103 _fieldre = re.compile(r':(?![: ])([^:]*)(?<! ):[ ]+(.*)')
123 _definitionre = re.compile(r'[^ ]')
104 _definitionre = re.compile(r'[^ ]')
124
105
125 def splitparagraphs(blocks):
106 def splitparagraphs(blocks):
126 """Split paragraphs into lists."""
107 """Split paragraphs into lists."""
127 # Tuples with (list type, item regexp, single line items?). Order
108 # Tuples with (list type, item regexp, single line items?). Order
128 # matters: definition lists has the least specific regexp and must
109 # matters: definition lists has the least specific regexp and must
129 # come last.
110 # come last.
130 listtypes = [('bullet', _bulletre, True),
111 listtypes = [('bullet', _bulletre, True),
131 ('option', _optionre, True),
112 ('option', _optionre, True),
132 ('field', _fieldre, True),
113 ('field', _fieldre, True),
133 ('definition', _definitionre, False)]
114 ('definition', _definitionre, False)]
134
115
135 def match(lines, i, itemre, singleline):
116 def match(lines, i, itemre, singleline):
136 """Does itemre match an item at line i?
117 """Does itemre match an item at line i?
137
118
138 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
139 item (but only if singleline is True).
120 item (but only if singleline is True).
140 """
121 """
141 line1 = lines[i]
122 line1 = lines[i]
142 line2 = i + 1 < len(lines) and lines[i + 1] or ''
123 line2 = i + 1 < len(lines) and lines[i + 1] or ''
143 if not itemre.match(line1):
124 if not itemre.match(line1):
144 return False
125 return False
145 if singleline:
126 if singleline:
146 return line2 == '' or line2[0] == ' ' or itemre.match(line2)
127 return line2 == '' or line2[0] == ' ' or itemre.match(line2)
147 else:
128 else:
148 return line2.startswith(' ')
129 return line2.startswith(' ')
149
130
150 i = 0
131 i = 0
151 while i < len(blocks):
132 while i < len(blocks):
152 if blocks[i]['type'] == 'paragraph':
133 if blocks[i]['type'] == 'paragraph':
153 lines = blocks[i]['lines']
134 lines = blocks[i]['lines']
154 for type, itemre, singleline in listtypes:
135 for type, itemre, singleline in listtypes:
155 if match(lines, 0, itemre, singleline):
136 if match(lines, 0, itemre, singleline):
156 items = []
137 items = []
157 for j, line in enumerate(lines):
138 for j, line in enumerate(lines):
158 if match(lines, j, itemre, singleline):
139 if match(lines, j, itemre, singleline):
159 items.append(dict(type=type, lines=[],
140 items.append(dict(type=type, lines=[],
160 indent=blocks[i]['indent']))
141 indent=blocks[i]['indent']))
161 items[-1]['lines'].append(line)
142 items[-1]['lines'].append(line)
162 blocks[i:i + 1] = items
143 blocks[i:i + 1] = items
163 break
144 break
164 i += 1
145 i += 1
165 return blocks
146 return blocks
166
147
167
148
168 _fieldwidth = 12
149 _fieldwidth = 12
169
150
170 def updatefieldlists(blocks):
151 def updatefieldlists(blocks):
171 """Find key and maximum key width for field lists."""
152 """Find key and maximum key width for field lists."""
172 i = 0
153 i = 0
173 while i < len(blocks):
154 while i < len(blocks):
174 if blocks[i]['type'] != 'field':
155 if blocks[i]['type'] != 'field':
175 i += 1
156 i += 1
176 continue
157 continue
177
158
178 keywidth = 0
159 keywidth = 0
179 j = i
160 j = i
180 while j < len(blocks) and blocks[j]['type'] == 'field':
161 while j < len(blocks) and blocks[j]['type'] == 'field':
181 m = _fieldre.match(blocks[j]['lines'][0])
162 m = _fieldre.match(blocks[j]['lines'][0])
182 key, rest = m.groups()
163 key, rest = m.groups()
183 blocks[j]['lines'][0] = rest
164 blocks[j]['lines'][0] = rest
184 blocks[j]['key'] = key
165 blocks[j]['key'] = key
185 keywidth = max(keywidth, len(key))
166 keywidth = max(keywidth, len(key))
186 j += 1
167 j += 1
187
168
188 for block in blocks[i:j]:
169 for block in blocks[i:j]:
189 block['keywidth'] = keywidth
170 block['keywidth'] = keywidth
190 i = j + 1
171 i = j + 1
191
172
192 return blocks
173 return blocks
193
174
194
175
195 def prunecontainers(blocks, keep):
176 def prunecontainers(blocks, keep):
196 """Prune unwanted containers.
177 """Prune unwanted containers.
197
178
198 The blocks must have a 'type' field, i.e., they should have been
179 The blocks must have a 'type' field, i.e., they should have been
199 run through findliteralblocks first.
180 run through findliteralblocks first.
200 """
181 """
201 pruned = []
182 pruned = []
202 i = 0
183 i = 0
203 while i + 1 < len(blocks):
184 while i + 1 < len(blocks):
204 # Searching for a block that looks like this:
185 # Searching for a block that looks like this:
205 #
186 #
206 # +-------+---------------------------+
187 # +-------+---------------------------+
207 # | ".. container ::" type |
188 # | ".. container ::" type |
208 # +---+ |
189 # +---+ |
209 # | blocks |
190 # | blocks |
210 # +-------------------------------+
191 # +-------------------------------+
211 if (blocks[i]['type'] == 'paragraph' and
192 if (blocks[i]['type'] == 'paragraph' and
212 blocks[i]['lines'][0].startswith('.. container::')):
193 blocks[i]['lines'][0].startswith('.. container::')):
213 indent = blocks[i]['indent']
194 indent = blocks[i]['indent']
214 adjustment = blocks[i + 1]['indent'] - indent
195 adjustment = blocks[i + 1]['indent'] - indent
215 containertype = blocks[i]['lines'][0][15:]
196 containertype = blocks[i]['lines'][0][15:]
216 prune = containertype not in keep
197 prune = containertype not in keep
217 if prune:
198 if prune:
218 pruned.append(containertype)
199 pruned.append(containertype)
219
200
220 # Always delete "..container:: type" block
201 # Always delete "..container:: type" block
221 del blocks[i]
202 del blocks[i]
222 j = i
203 j = i
223 while j < len(blocks) and blocks[j]['indent'] > indent:
204 while j < len(blocks) and blocks[j]['indent'] > indent:
224 if prune:
205 if prune:
225 del blocks[j]
206 del blocks[j]
226 i -= 1 # adjust outer index
207 i -= 1 # adjust outer index
227 else:
208 else:
228 blocks[j]['indent'] -= adjustment
209 blocks[j]['indent'] -= adjustment
229 j += 1
210 j += 1
230 i += 1
211 i += 1
231 return blocks, pruned
212 return blocks, pruned
232
213
233
214
234 _sectionre = re.compile(r"""^([-=`:.'"~^_*+#])\1+$""")
215 _sectionre = re.compile(r"""^([-=`:.'"~^_*+#])\1+$""")
235
216
236 def findsections(blocks):
217 def findsections(blocks):
237 """Finds sections.
218 """Finds sections.
238
219
239 The blocks must have a 'type' field, i.e., they should have been
220 The blocks must have a 'type' field, i.e., they should have been
240 run through findliteralblocks first.
221 run through findliteralblocks first.
241 """
222 """
242 for block in blocks:
223 for block in blocks:
243 # Searching for a block that looks like this:
224 # Searching for a block that looks like this:
244 #
225 #
245 # +------------------------------+
226 # +------------------------------+
246 # | Section title |
227 # | Section title |
247 # | ------------- |
228 # | ------------- |
248 # +------------------------------+
229 # +------------------------------+
249 if (block['type'] == 'paragraph' and
230 if (block['type'] == 'paragraph' and
250 len(block['lines']) == 2 and
231 len(block['lines']) == 2 and
251 encoding.colwidth(block['lines'][0]) == len(block['lines'][1]) and
232 encoding.colwidth(block['lines'][0]) == len(block['lines'][1]) and
252 _sectionre.match(block['lines'][1])):
233 _sectionre.match(block['lines'][1])):
253 block['underline'] = block['lines'][1][0]
234 block['underline'] = block['lines'][1][0]
254 block['type'] = 'section'
235 block['type'] = 'section'
255 del block['lines'][1]
236 del block['lines'][1]
256 return blocks
237 return blocks
257
238
258
239
259 def inlineliterals(blocks):
240 def inlineliterals(blocks):
260 substs = [('``', '"')]
241 substs = [('``', '"')]
261 for b in blocks:
242 for b in blocks:
262 if b['type'] in ('paragraph', 'section'):
243 if b['type'] in ('paragraph', 'section'):
263 b['lines'] = [replace(l, substs) for l in b['lines']]
244 b['lines'] = [replace(l, substs) for l in b['lines']]
264 return blocks
245 return blocks
265
246
266
247
267 def hgrole(blocks):
248 def hgrole(blocks):
268 substs = [(':hg:`', '"hg '), ('`', '"')]
249 substs = [(':hg:`', '"hg '), ('`', '"')]
269 for b in blocks:
250 for b in blocks:
270 if b['type'] in ('paragraph', 'section'):
251 if b['type'] in ('paragraph', 'section'):
271 # Turn :hg:`command` into "hg command". This also works
252 # Turn :hg:`command` into "hg command". This also works
272 # when there is a line break in the command and relies on
253 # when there is a line break in the command and relies on
273 # the fact that we have no stray back-quotes in the input
254 # the fact that we have no stray back-quotes in the input
274 # (run the blocks through inlineliterals first).
255 # (run the blocks through inlineliterals first).
275 b['lines'] = [replace(l, substs) for l in b['lines']]
256 b['lines'] = [replace(l, substs) for l in b['lines']]
276 return blocks
257 return blocks
277
258
278
259
279 def addmargins(blocks):
260 def addmargins(blocks):
280 """Adds empty blocks for vertical spacing.
261 """Adds empty blocks for vertical spacing.
281
262
282 This groups bullets, options, and definitions together with no vertical
263 This groups bullets, options, and definitions together with no vertical
283 space between them, and adds an empty block between all other blocks.
264 space between them, and adds an empty block between all other blocks.
284 """
265 """
285 i = 1
266 i = 1
286 while i < len(blocks):
267 while i < len(blocks):
287 if (blocks[i]['type'] == blocks[i - 1]['type'] and
268 if (blocks[i]['type'] == blocks[i - 1]['type'] and
288 blocks[i]['type'] in ('bullet', 'option', 'field')):
269 blocks[i]['type'] in ('bullet', 'option', 'field')):
289 i += 1
270 i += 1
290 else:
271 else:
291 blocks.insert(i, dict(lines=[''], indent=0, type='margin'))
272 blocks.insert(i, dict(lines=[''], indent=0, type='margin'))
292 i += 2
273 i += 2
293 return blocks
274 return blocks
294
275
295 def prunecomments(blocks):
276 def prunecomments(blocks):
296 """Remove comments."""
277 """Remove comments."""
297 i = 0
278 i = 0
298 while i < len(blocks):
279 while i < len(blocks):
299 b = blocks[i]
280 b = blocks[i]
300 if b['type'] == 'paragraph' and b['lines'][0].startswith('.. '):
281 if b['type'] == 'paragraph' and b['lines'][0].startswith('.. '):
301 del blocks[i]
282 del blocks[i]
302 else:
283 else:
303 i += 1
284 i += 1
304 return blocks
285 return blocks
305
286
306 _admonitionre = re.compile(r"\.\. (admonition|attention|caution|danger|"
287 _admonitionre = re.compile(r"\.\. (admonition|attention|caution|danger|"
307 r"error|hint|important|note|tip|warning)::",
288 r"error|hint|important|note|tip|warning)::",
308 flags=re.IGNORECASE)
289 flags=re.IGNORECASE)
309
290
310 def findadmonitions(blocks):
291 def findadmonitions(blocks):
311 """
292 """
312 Makes the type of the block an admonition block if
293 Makes the type of the block an admonition block if
313 the first line is an admonition directive
294 the first line is an admonition directive
314 """
295 """
315 i = 0
296 i = 0
316 while i < len(blocks):
297 while i < len(blocks):
317 m = _admonitionre.match(blocks[i]['lines'][0])
298 m = _admonitionre.match(blocks[i]['lines'][0])
318 if m:
299 if m:
319 blocks[i]['type'] = 'admonition'
300 blocks[i]['type'] = 'admonition'
320 admonitiontitle = blocks[i]['lines'][0][3:m.end() - 2].lower()
301 admonitiontitle = blocks[i]['lines'][0][3:m.end() - 2].lower()
321
302
322 firstline = blocks[i]['lines'][0][m.end() + 1:]
303 firstline = blocks[i]['lines'][0][m.end() + 1:]
323 if firstline:
304 if firstline:
324 blocks[i]['lines'].insert(1, ' ' + firstline)
305 blocks[i]['lines'].insert(1, ' ' + firstline)
325
306
326 blocks[i]['admonitiontitle'] = admonitiontitle
307 blocks[i]['admonitiontitle'] = admonitiontitle
327 del blocks[i]['lines'][0]
308 del blocks[i]['lines'][0]
328 i = i + 1
309 i = i + 1
329 return blocks
310 return blocks
330
311
331 _admonitiontitles = {'attention': _('Attention:'),
312 _admonitiontitles = {'attention': _('Attention:'),
332 'caution': _('Caution:'),
313 'caution': _('Caution:'),
333 'danger': _('!Danger!') ,
314 'danger': _('!Danger!') ,
334 'error': _('Error:'),
315 'error': _('Error:'),
335 'hint': _('Hint:'),
316 'hint': _('Hint:'),
336 'important': _('Important:'),
317 'important': _('Important:'),
337 'note': _('Note:'),
318 'note': _('Note:'),
338 'tip': _('Tip:'),
319 'tip': _('Tip:'),
339 'warning': _('Warning!')}
320 'warning': _('Warning!')}
340
321
341 def formatblock(block, width):
322 def formatblock(block, width):
342 """Format a block according to width."""
323 """Format a block according to width."""
343 if width <= 0:
324 if width <= 0:
344 width = 78
325 width = 78
345 indent = ' ' * block['indent']
326 indent = ' ' * block['indent']
346 if block['type'] == 'admonition':
327 if block['type'] == 'admonition':
347 admonition = _admonitiontitles[block['admonitiontitle']]
328 admonition = _admonitiontitles[block['admonitiontitle']]
348 hang = len(block['lines'][-1]) - len(block['lines'][-1].lstrip())
329 hang = len(block['lines'][-1]) - len(block['lines'][-1].lstrip())
349
330
350 defindent = indent + hang * ' '
331 defindent = indent + hang * ' '
351 text = ' '.join(map(str.strip, block['lines']))
332 text = ' '.join(map(str.strip, block['lines']))
352 return '%s\n%s' % (indent + admonition, util.wrap(text, width=width,
333 return '%s\n%s' % (indent + admonition, util.wrap(text, width=width,
353 initindent=defindent,
334 initindent=defindent,
354 hangindent=defindent))
335 hangindent=defindent))
355 if block['type'] == 'margin':
336 if block['type'] == 'margin':
356 return ''
337 return ''
357 if block['type'] == 'literal':
338 if block['type'] == 'literal':
358 indent += ' '
339 indent += ' '
359 return indent + ('\n' + indent).join(block['lines'])
340 return indent + ('\n' + indent).join(block['lines'])
360 if block['type'] == 'section':
341 if block['type'] == 'section':
361 underline = encoding.colwidth(block['lines'][0]) * block['underline']
342 underline = encoding.colwidth(block['lines'][0]) * block['underline']
362 return "%s%s\n%s%s" % (indent, block['lines'][0],indent, underline)
343 return "%s%s\n%s%s" % (indent, block['lines'][0],indent, underline)
363 if block['type'] == 'definition':
344 if block['type'] == 'definition':
364 term = indent + block['lines'][0]
345 term = indent + block['lines'][0]
365 hang = len(block['lines'][-1]) - len(block['lines'][-1].lstrip())
346 hang = len(block['lines'][-1]) - len(block['lines'][-1].lstrip())
366 defindent = indent + hang * ' '
347 defindent = indent + hang * ' '
367 text = ' '.join(map(str.strip, block['lines'][1:]))
348 text = ' '.join(map(str.strip, block['lines'][1:]))
368 return '%s\n%s' % (term, util.wrap(text, width=width,
349 return '%s\n%s' % (term, util.wrap(text, width=width,
369 initindent=defindent,
350 initindent=defindent,
370 hangindent=defindent))
351 hangindent=defindent))
371 subindent = indent
352 subindent = indent
372 if block['type'] == 'bullet':
353 if block['type'] == 'bullet':
373 if block['lines'][0].startswith('| '):
354 if block['lines'][0].startswith('| '):
374 # Remove bullet for line blocks and add no extra
355 # Remove bullet for line blocks and add no extra
375 # indention.
356 # indention.
376 block['lines'][0] = block['lines'][0][2:]
357 block['lines'][0] = block['lines'][0][2:]
377 else:
358 else:
378 m = _bulletre.match(block['lines'][0])
359 m = _bulletre.match(block['lines'][0])
379 subindent = indent + m.end() * ' '
360 subindent = indent + m.end() * ' '
380 elif block['type'] == 'field':
361 elif block['type'] == 'field':
381 keywidth = block['keywidth']
362 keywidth = block['keywidth']
382 key = block['key']
363 key = block['key']
383
364
384 subindent = indent + _fieldwidth * ' '
365 subindent = indent + _fieldwidth * ' '
385 if len(key) + 2 > _fieldwidth:
366 if len(key) + 2 > _fieldwidth:
386 # key too large, use full line width
367 # key too large, use full line width
387 key = key.ljust(width)
368 key = key.ljust(width)
388 elif keywidth + 2 < _fieldwidth:
369 elif keywidth + 2 < _fieldwidth:
389 # all keys are small, add only two spaces
370 # all keys are small, add only two spaces
390 key = key.ljust(keywidth + 2)
371 key = key.ljust(keywidth + 2)
391 subindent = indent + (keywidth + 2) * ' '
372 subindent = indent + (keywidth + 2) * ' '
392 else:
373 else:
393 # mixed sizes, use fieldwidth for this one
374 # mixed sizes, use fieldwidth for this one
394 key = key.ljust(_fieldwidth)
375 key = key.ljust(_fieldwidth)
395 block['lines'][0] = key + block['lines'][0]
376 block['lines'][0] = key + block['lines'][0]
396 elif block['type'] == 'option':
377 elif block['type'] == 'option':
397 m = _optionre.match(block['lines'][0])
378 m = _optionre.match(block['lines'][0])
398 option, arg, rest = m.groups()
379 option, arg, rest = m.groups()
399 subindent = indent + (len(option) + len(arg)) * ' '
380 subindent = indent + (len(option) + len(arg)) * ' '
400
381
401 text = ' '.join(map(str.strip, block['lines']))
382 text = ' '.join(map(str.strip, block['lines']))
402 return util.wrap(text, width=width,
383 return util.wrap(text, width=width,
403 initindent=indent,
384 initindent=indent,
404 hangindent=subindent)
385 hangindent=subindent)
405
386
406
387
407 def format(text, width, indent=0, keep=None):
388 def format(text, width, indent=0, keep=None):
408 """Parse and format the text according to width."""
389 """Parse and format the text according to width."""
409 blocks = findblocks(text)
390 blocks = findblocks(text)
410 for b in blocks:
391 for b in blocks:
411 b['indent'] += indent
392 b['indent'] += indent
412 blocks = findliteralblocks(blocks)
393 blocks = findliteralblocks(blocks)
413 blocks, pruned = prunecontainers(blocks, keep or [])
394 blocks, pruned = prunecontainers(blocks, keep or [])
414 blocks = findsections(blocks)
395 blocks = findsections(blocks)
415 blocks = inlineliterals(blocks)
396 blocks = inlineliterals(blocks)
416 blocks = hgrole(blocks)
397 blocks = hgrole(blocks)
417 blocks = splitparagraphs(blocks)
398 blocks = splitparagraphs(blocks)
418 blocks = updatefieldlists(blocks)
399 blocks = updatefieldlists(blocks)
419 blocks = prunecomments(blocks)
400 blocks = prunecomments(blocks)
420 blocks = addmargins(blocks)
401 blocks = addmargins(blocks)
421 blocks = findadmonitions(blocks)
402 blocks = findadmonitions(blocks)
422 text = '\n'.join(formatblock(b, width) for b in blocks)
403 text = '\n'.join(formatblock(b, width) for b in blocks)
423 if keep is None:
404 if keep is None:
424 return text
405 return text
425 else:
406 else:
426 return text, pruned
407 return text, pruned
427
408
428
409
429 if __name__ == "__main__":
410 if __name__ == "__main__":
430 from pprint import pprint
411 from pprint import pprint
431
412
432 def debug(func, *args):
413 def debug(func, *args):
433 blocks = func(*args)
414 blocks = func(*args)
434 print "*** after %s:" % func.__name__
415 print "*** after %s:" % func.__name__
435 pprint(blocks)
416 pprint(blocks)
436 print
417 print
437 return blocks
418 return blocks
438
419
439 text = open(sys.argv[1]).read()
420 text = open(sys.argv[1]).read()
440 blocks = debug(findblocks, text)
421 blocks = debug(findblocks, text)
441 blocks = debug(findliteralblocks, blocks)
422 blocks = debug(findliteralblocks, blocks)
442 blocks, pruned = debug(prunecontainers, blocks, sys.argv[2:])
423 blocks, pruned = debug(prunecontainers, blocks, sys.argv[2:])
443 blocks = debug(inlineliterals, blocks)
424 blocks = debug(inlineliterals, blocks)
444 blocks = debug(splitparagraphs, blocks)
425 blocks = debug(splitparagraphs, blocks)
445 blocks = debug(updatefieldlists, blocks)
426 blocks = debug(updatefieldlists, blocks)
446 blocks = debug(findsections, blocks)
427 blocks = debug(findsections, blocks)
447 blocks = debug(prunecomments, blocks)
428 blocks = debug(prunecomments, blocks)
448 blocks = debug(addmargins, blocks)
429 blocks = debug(addmargins, blocks)
449 blocks = debug(findadmonitions, blocks)
430 blocks = debug(findadmonitions, blocks)
450 print '\n'.join(formatblock(b, 30) for b in blocks)
431 print '\n'.join(formatblock(b, 30) for b in blocks)
General Comments 0
You need to be logged in to leave comments. Login now