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