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