diff options
Diffstat (limited to 'mercurial/minirst.py')
-rw-r--r-- | mercurial/minirst.py | 331 |
1 files changed, 68 insertions, 263 deletions
diff --git a/mercurial/minirst.py b/mercurial/minirst.py index 0586213..01e6528 100644 --- a/mercurial/minirst.py +++ b/mercurial/minirst.py @@ -18,33 +18,18 @@ Remember to update http://mercurial.selenic.com/wiki/HelpStyleGuide when adding support for new constructs. """ -import re +import re, sys import util, encoding from i18n import _ + def replace(text, substs): - ''' - Apply a list of (find, replace) pairs to a text. - - >>> replace("foo bar", [('f', 'F'), ('b', 'B')]) - 'Foo Bar' - >>> encoding.encoding = 'latin1' - >>> replace('\\x81\\\\', [('\\\\', '/')]) - '\\x81/' - >>> encoding.encoding = 'shiftjis' - >>> replace('\\x81\\\\', [('\\\\', '/')]) - '\\x81\\\\' - ''' - - # some character encodings (cp932 for Japanese, at least) use - # ASCII characters other than control/alphabet/digit as a part of - # multi-bytes characters, so direct replacing with such characters - # on strings in local encoding causes invalid byte sequences. utext = text.decode(encoding.encoding) for f, t in substs: utext = utext.replace(f, t) return utext.encode(encoding.encoding) + _blockre = re.compile(r"\n(?:\s*\n)+") def findblocks(text): @@ -54,14 +39,14 @@ def findblocks(text): has an 'indent' field and a 'lines' field. """ blocks = [] - for b in _blockre.split(text.lstrip('\n').rstrip()): + for b in _blockre.split(text.strip()): lines = b.splitlines() - if lines: - indent = min((len(l) - len(l.lstrip())) for l in lines) - lines = [l[indent:] for l in lines] - blocks.append(dict(indent=indent, lines=lines)) + indent = min((len(l) - len(l.lstrip())) for l in lines) + lines = [l[indent:] for l in lines] + blocks.append(dict(indent=indent, lines=lines)) return blocks + def findliteralblocks(blocks): """Finds literal blocks and adds a 'type' field to the blocks. @@ -118,7 +103,6 @@ _optionre = re.compile(r'^(-([a-zA-Z0-9]), )?(--[a-z0-9-]+)' r'((.*) +)(.*)$') _fieldre = re.compile(r':(?![: ])([^:]*)(?<! ):[ ]+(.*)') _definitionre = re.compile(r'[^ ]') -_tablere = re.compile(r'(=+\s+)*=+') def splitparagraphs(blocks): """Split paragraphs into lists.""" @@ -162,28 +146,34 @@ def splitparagraphs(blocks): i += 1 return blocks -_fieldwidth = 14 + +_fieldwidth = 12 def updatefieldlists(blocks): - """Find key for field lists.""" + """Find key and maximum key width for field lists.""" i = 0 while i < len(blocks): if blocks[i]['type'] != 'field': i += 1 continue + keywidth = 0 j = i while j < len(blocks) and blocks[j]['type'] == 'field': m = _fieldre.match(blocks[j]['lines'][0]) key, rest = m.groups() blocks[j]['lines'][0] = rest blocks[j]['key'] = key + keywidth = max(keywidth, len(key)) j += 1 + for block in blocks[i:j]: + block['keywidth'] = keywidth i = j + 1 return blocks + def updateoptionlists(blocks): i = 0 while i < len(blocks): @@ -248,67 +238,18 @@ def prunecontainers(blocks, keep): # Always delete "..container:: type" block del blocks[i] j = i - i -= 1 while j < len(blocks) and blocks[j]['indent'] > indent: if prune: del blocks[j] + i -= 1 # adjust outer index else: blocks[j]['indent'] -= adjustment j += 1 i += 1 return blocks, pruned -_sectionre = re.compile(r"""^([-=`:.'"~^_*+#])\1+$""") - -def findtables(blocks): - '''Find simple tables - - Only simple one-line table elements are supported - ''' - for block in blocks: - # Searching for a block that looks like this: - # - # === ==== === - # A B C - # === ==== === <- optional - # 1 2 3 - # x y z - # === ==== === - if (block['type'] == 'paragraph' and - len(block['lines']) > 2 and - _tablere.match(block['lines'][0]) and - block['lines'][0] == block['lines'][-1]): - block['type'] = 'table' - block['header'] = False - div = block['lines'][0] - - # column markers are ASCII so we can calculate column - # position in bytes - columns = [x for x in xrange(len(div)) - if div[x] == '=' and (x == 0 or div[x - 1] == ' ')] - rows = [] - for l in block['lines'][1:-1]: - if l == div: - block['header'] = True - continue - row = [] - # we measure columns not in bytes or characters but in - # colwidth which makes things tricky - pos = columns[0] # leading whitespace is bytes - for n, start in enumerate(columns): - if n + 1 < len(columns): - width = columns[n + 1] - start - v = encoding.getcols(l, pos, width) # gather columns - pos += len(v) # calculate byte position of end - row.append(v.strip()) - else: - row.append(l[pos:].strip()) - rows.append(row) - - block['table'] = rows - - return blocks +_sectionre = re.compile(r"""^([-=`:.'"~^_*+#])\1+$""") def findsections(blocks): """Finds sections. @@ -332,6 +273,7 @@ def findsections(blocks): del block['lines'][1] return blocks + def inlineliterals(blocks): substs = [('``', '"')] for b in blocks: @@ -339,6 +281,7 @@ def inlineliterals(blocks): b['lines'] = [replace(l, substs) for l in b['lines']] return blocks + def hgrole(blocks): substs = [(':hg:`', '"hg '), ('`', '"')] for b in blocks: @@ -350,6 +293,7 @@ def hgrole(blocks): b['lines'] = [replace(l, substs) for l in b['lines']] return blocks + def addmargins(blocks): """Adds empty blocks for vertical spacing. @@ -422,7 +366,7 @@ def formatoption(block, width): hanging = block['optstrwidth'] initindent = '%s%s ' % (block['optstr'], ' ' * ((hanging - colwidth))) hangindent = ' ' * (encoding.colwidth(initindent) + 1) - return ' %s\n' % (util.wrap(desc, usablewidth, + return ' %s' % (util.wrap(desc, usablewidth, initindent=initindent, hangindent=hangindent)) @@ -437,47 +381,25 @@ def formatblock(block, width): defindent = indent + hang * ' ' text = ' '.join(map(str.strip, block['lines'])) - return '%s\n%s\n' % (indent + admonition, - util.wrap(text, width=width, - initindent=defindent, - hangindent=defindent)) + return '%s\n%s' % (indent + admonition, util.wrap(text, width=width, + initindent=defindent, + hangindent=defindent)) if block['type'] == 'margin': - return '\n' + return '' if block['type'] == 'literal': indent += ' ' - return indent + ('\n' + indent).join(block['lines']) + '\n' + return indent + ('\n' + indent).join(block['lines']) if block['type'] == 'section': underline = encoding.colwidth(block['lines'][0]) * block['underline'] - return "%s%s\n%s%s\n" % (indent, block['lines'][0],indent, underline) - if block['type'] == 'table': - table = block['table'] - # compute column widths - widths = [max([encoding.colwidth(e) for e in c]) for c in zip(*table)] - text = '' - span = sum(widths) + len(widths) - 1 - indent = ' ' * block['indent'] - hang = ' ' * (len(indent) + span - widths[-1]) - - for row in table: - l = [] - for w, v in zip(widths, row): - pad = ' ' * (w - encoding.colwidth(v)) - l.append(v + pad) - l = ' '.join(l) - l = util.wrap(l, width=width, initindent=indent, hangindent=hang) - if not text and block['header']: - text = l + '\n' + indent + '-' * (min(width, span)) + '\n' - else: - text += l + "\n" - return text + return "%s%s\n%s%s" % (indent, block['lines'][0],indent, underline) if block['type'] == 'definition': term = indent + block['lines'][0] hang = len(block['lines'][-1]) - len(block['lines'][-1].lstrip()) defindent = indent + hang * ' ' text = ' '.join(map(str.strip, block['lines'][1:])) - return '%s\n%s\n' % (term, util.wrap(text, width=width, - initindent=defindent, - hangindent=defindent)) + return '%s\n%s' % (term, util.wrap(text, width=width, + initindent=defindent, + hangindent=defindent)) subindent = indent if block['type'] == 'bullet': if block['lines'][0].startswith('| '): @@ -488,13 +410,19 @@ def formatblock(block, width): m = _bulletre.match(block['lines'][0]) subindent = indent + m.end() * ' ' elif block['type'] == 'field': + keywidth = block['keywidth'] key = block['key'] + subindent = indent + _fieldwidth * ' ' if len(key) + 2 > _fieldwidth: # key too large, use full line width key = key.ljust(width) + elif keywidth + 2 < _fieldwidth: + # all keys are small, add only two spaces + key = key.ljust(keywidth + 2) + subindent = indent + (keywidth + 2) * ' ' else: - # key fits within field width + # mixed sizes, use fieldwidth for this one key = key.ljust(_fieldwidth) block['lines'][0] = key + block['lines'][0] elif block['type'] == 'option': @@ -503,103 +431,15 @@ def formatblock(block, width): text = ' '.join(map(str.strip, block['lines'])) return util.wrap(text, width=width, initindent=indent, - hangindent=subindent) + '\n' - -def formathtml(blocks): - """Format RST blocks as HTML""" - - out = [] - headernest = '' - listnest = [] - - def openlist(start, level): - if not listnest or listnest[-1][0] != start: - listnest.append((start, level)) - out.append('<%s>\n' % start) - - blocks = [b for b in blocks if b['type'] != 'margin'] - - for pos, b in enumerate(blocks): - btype = b['type'] - level = b['indent'] - lines = b['lines'] - - if btype == 'admonition': - admonition = _admonitiontitles[b['admonitiontitle']] - text = ' '.join(map(str.strip, lines)) - out.append('<p>\n<b>%s</b> %s\n</p>\n' % (admonition, text)) - elif btype == 'paragraph': - out.append('<p>\n%s\n</p>\n' % '\n'.join(lines)) - elif btype == 'margin': - pass - elif btype == 'literal': - out.append('<pre>\n%s\n</pre>\n' % '\n'.join(lines)) - elif btype == 'section': - i = b['underline'] - if i not in headernest: - headernest += i - level = headernest.index(i) + 1 - out.append('<h%d>%s</h%d>\n' % (level, lines[0], level)) - elif btype == 'table': - table = b['table'] - t = [] - for row in table: - l = [] - for v in zip(row): - if not t: - l.append('<th>%s</th>' % v) - else: - l.append('<td>%s</td>' % v) - t.append(' <tr>%s</tr>\n' % ''.join(l)) - out.append('<table>\n%s</table>\n' % ''.join(t)) - elif btype == 'definition': - openlist('dl', level) - term = lines[0] - text = ' '.join(map(str.strip, lines[1:])) - out.append(' <dt>%s\n <dd>%s\n' % (term, text)) - elif btype == 'bullet': - bullet, head = lines[0].split(' ', 1) - if bullet == '-': - openlist('ul', level) - else: - openlist('ol', level) - out.append(' <li> %s\n' % ' '.join([head] + lines[1:])) - elif btype == 'field': - openlist('dl', level) - key = b['key'] - text = ' '.join(map(str.strip, lines)) - out.append(' <dt>%s\n <dd>%s\n' % (key, text)) - elif btype == 'option': - openlist('dl', level) - opt = b['optstr'] - desc = ' '.join(map(str.strip, lines)) - out.append(' <dt>%s\n <dd>%s\n' % (opt, desc)) - - # close lists if indent level of next block is lower - if listnest: - start, level = listnest[-1] - if pos == len(blocks) - 1: - out.append('</%s>\n' % start) - listnest.pop() - else: - nb = blocks[pos + 1] - ni = nb['indent'] - if (ni < level or - (ni == level and - nb['type'] not in 'definition bullet field option')): - out.append('</%s>\n' % start) - listnest.pop() - - return ''.join(out) - -def parse(text, indent=0, keep=None): - """Parse text into a list of blocks""" - pruned = [] + hangindent=subindent) + + +def format(text, width, indent=0, keep=None): + """Parse and format the text according to width.""" blocks = findblocks(text) for b in blocks: b['indent'] += indent blocks = findliteralblocks(blocks) - blocks = findtables(blocks) blocks, pruned = prunecontainers(blocks, keep or []) blocks = findsections(blocks) blocks = inlineliterals(blocks) @@ -610,68 +450,33 @@ def parse(text, indent=0, keep=None): blocks = addmargins(blocks) blocks = prunecomments(blocks) blocks = findadmonitions(blocks) - return blocks, pruned - -def formatblocks(blocks, width): - text = ''.join(formatblock(b, width) for b in blocks) - return text - -def format(text, width=80, indent=0, keep=None, style='plain'): - """Parse and format the text according to width.""" - blocks, pruned = parse(text, indent, keep or []) - if style == 'html': - text = formathtml(blocks) - else: - text = ''.join(formatblock(b, width) for b in blocks) + text = '\n'.join(formatblock(b, width) for b in blocks) if keep is None: return text else: return text, pruned -def getsections(blocks): - '''return a list of (section name, nesting level, blocks) tuples''' - nest = "" - level = 0 - secs = [] - for b in blocks: - if b['type'] == 'section': - i = b['underline'] - if i not in nest: - nest += i - level = nest.index(i) + 1 - nest = nest[:level] - secs.append((b['lines'][0], level, [b])) - else: - if not secs: - # add an initial empty section - secs = [('', 0, [])] - secs[-1][2].append(b) - return secs - -def decorateblocks(blocks, width): - '''generate a list of (section name, line text) pairs for search''' - lines = [] - for s in getsections(blocks): - section = s[0] - text = formatblocks(s[2], width) - lines.append([(section, l) for l in text.splitlines(True)]) - return lines - -def maketable(data, indent=0, header=False): - '''Generate an RST table for the given table data as a list of lines''' - - widths = [max(encoding.colwidth(e) for e in c) for c in zip(*data)] - indent = ' ' * indent - div = indent + ' '.join('=' * w for w in widths) + '\n' - - out = [div] - for row in data: - l = [] - for w, v in zip(widths, row): - pad = ' ' * (w - encoding.colwidth(v)) - l.append(v + pad) - out.append(indent + ' '.join(l) + "\n") - if header and len(data) > 1: - out.insert(2, div) - out.append(div) - return out + +if __name__ == "__main__": + from pprint import pprint + + def debug(func, *args): + blocks = func(*args) + print "*** after %s:" % func.__name__ + pprint(blocks) + print + return blocks + + text = sys.stdin.read() + blocks = debug(findblocks, text) + blocks = debug(findliteralblocks, blocks) + blocks, pruned = debug(prunecontainers, blocks, sys.argv[1:]) + blocks = debug(inlineliterals, blocks) + blocks = debug(splitparagraphs, blocks) + blocks = debug(updatefieldlists, blocks) + blocks = debug(updateoptionlists, blocks) + blocks = debug(findsections, blocks) + blocks = debug(addmargins, blocks) + blocks = debug(prunecomments, blocks) + blocks = debug(findadmonitions, blocks) + print '\n'.join(formatblock(b, 30) for b in blocks) |