diff -r 5ff1fc726848 -r c6bca38c1cbf eggs/mercurial-1.7.3-py2.6-linux-x86_64.egg/mercurial/minirst.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/eggs/mercurial-1.7.3-py2.6-linux-x86_64.egg/mercurial/minirst.py Sat Jan 08 11:20:57 2011 +0530 @@ -0,0 +1,450 @@ +# minirst.py - minimal reStructuredText parser +# +# Copyright 2009, 2010 Matt Mackall and others +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +"""simplified reStructuredText parser. + +This parser knows just enough about reStructuredText to parse the +Mercurial docstrings. + +It cheats in a major way: nested blocks are not really nested. They +are just indented blocks that look like they are nested. This relies +on the user to keep the right indentation for the blocks. + +It only supports a small subset of reStructuredText: + +- sections + +- paragraphs + +- literal blocks + +- definition lists + +- specific admonitions + +- bullet lists (items must start with '-') + +- enumerated lists (no autonumbering) + +- field lists (colons cannot be escaped) + +- option lists (supports only long options without arguments) + +- inline literals (no other inline markup is not recognized) +""" + +import re, sys +import util, encoding +from i18n import _ + + +def replace(text, substs): + 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): + """Find continuous blocks of lines in text. + + Returns a list of dictionaries representing the blocks. Each block + has an 'indent' field and a 'lines' field. + """ + blocks = [] + for b in _blockre.split(text.strip()): + lines = b.splitlines() + 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. + + Literal blocks are given the type 'literal', all other blocks are + given type the 'paragraph'. + """ + i = 0 + while i < len(blocks): + # Searching for a block that looks like this: + # + # +------------------------------+ + # | paragraph | + # | (ends with "::") | + # +------------------------------+ + # +---------------------------+ + # | indented literal block | + # +---------------------------+ + blocks[i]['type'] = 'paragraph' + if blocks[i]['lines'][-1].endswith('::') and i + 1 < len(blocks): + indent = blocks[i]['indent'] + adjustment = blocks[i + 1]['indent'] - indent + + if blocks[i]['lines'] == ['::']: + # Expanded form: remove block + del blocks[i] + i -= 1 + elif blocks[i]['lines'][-1].endswith(' ::'): + # Partially minimized form: remove space and both + # colons. + blocks[i]['lines'][-1] = blocks[i]['lines'][-1][:-3] + else: + # Fully minimized form: remove just one colon. + blocks[i]['lines'][-1] = blocks[i]['lines'][-1][:-1] + + # List items are formatted with a hanging indent. We must + # correct for this here while we still have the original + # information on the indentation of the subsequent literal + # blocks available. + m = _bulletre.match(blocks[i]['lines'][0]) + if m: + indent += m.end() + adjustment -= m.end() + + # Mark the following indented blocks. + while i + 1 < len(blocks) and blocks[i + 1]['indent'] > indent: + blocks[i + 1]['type'] = 'literal' + blocks[i + 1]['indent'] -= adjustment + i += 1 + i += 1 + return blocks + +_bulletre = re.compile(r'(-|[0-9A-Za-z]+\.|\(?[0-9A-Za-z]+\)|\|) ') +_optionre = re.compile(r'^(--[a-z-]+)((?:[ =][a-zA-Z][\w-]*)? +)(.*)$') +_fieldre = re.compile(r':(?![: ])([^:]*)(? 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 findsections(blocks): + """Finds sections. + + The blocks must have a 'type' field, i.e., they should have been + run through findliteralblocks first. + """ + for block in blocks: + # Searching for a block that looks like this: + # + # +------------------------------+ + # | Section title | + # | ------------- | + # +------------------------------+ + if (block['type'] == 'paragraph' and + len(block['lines']) == 2 and + encoding.colwidth(block['lines'][0]) == len(block['lines'][1]) and + _sectionre.match(block['lines'][1])): + block['underline'] = block['lines'][1][0] + block['type'] = 'section' + del block['lines'][1] + return blocks + + +def inlineliterals(blocks): + substs = [('``', '"')] + for b in blocks: + if b['type'] in ('paragraph', 'section'): + b['lines'] = [replace(l, substs) for l in b['lines']] + return blocks + + +def hgrole(blocks): + substs = [(':hg:`', '"hg '), ('`', '"')] + for b in blocks: + if b['type'] in ('paragraph', 'section'): + # Turn :hg:`command` into "hg command". This also works + # when there is a line break in the command and relies on + # the fact that we have no stray back-quotes in the input + # (run the blocks through inlineliterals first). + b['lines'] = [replace(l, substs) for l in b['lines']] + return blocks + + +def addmargins(blocks): + """Adds empty blocks for vertical spacing. + + This groups bullets, options, and definitions together with no vertical + space between them, and adds an empty block between all other blocks. + """ + i = 1 + while i < len(blocks): + if (blocks[i]['type'] == blocks[i - 1]['type'] and + blocks[i]['type'] in ('bullet', 'option', 'field')): + i += 1 + else: + blocks.insert(i, dict(lines=[''], indent=0, type='margin')) + i += 2 + return blocks + +def prunecomments(blocks): + """Remove comments.""" + i = 0 + while i < len(blocks): + b = blocks[i] + if b['type'] == 'paragraph' and b['lines'][0].startswith('.. '): + del blocks[i] + else: + i += 1 + return blocks + +_admonitionre = re.compile(r"\.\. (admonition|attention|caution|danger|" + r"error|hint|important|note|tip|warning)::", + flags=re.IGNORECASE) + +def findadmonitions(blocks): + """ + Makes the type of the block an admonition block if + the first line is an admonition directive + """ + i = 0 + while i < len(blocks): + m = _admonitionre.match(blocks[i]['lines'][0]) + if m: + blocks[i]['type'] = 'admonition' + admonitiontitle = blocks[i]['lines'][0][3:m.end() - 2].lower() + + firstline = blocks[i]['lines'][0][m.end() + 1:] + if firstline: + blocks[i]['lines'].insert(1, ' ' + firstline) + + blocks[i]['admonitiontitle'] = admonitiontitle + del blocks[i]['lines'][0] + i = i + 1 + return blocks + +_admonitiontitles = {'attention': _('Attention:'), + 'caution': _('Caution:'), + 'danger': _('!Danger!') , + 'error': _('Error:'), + 'hint': _('Hint:'), + 'important': _('Important:'), + 'note': _('Note:'), + 'tip': _('Tip:'), + 'warning': _('Warning!')} + +def formatblock(block, width): + """Format a block according to width.""" + if width <= 0: + width = 78 + indent = ' ' * block['indent'] + if block['type'] == 'admonition': + admonition = _admonitiontitles[block['admonitiontitle']] + hang = len(block['lines'][-1]) - len(block['lines'][-1].lstrip()) + + defindent = indent + hang * ' ' + text = ' '.join(map(str.strip, block['lines'])) + return '%s\n%s' % (indent + admonition, util.wrap(text, width=width, + initindent=defindent, + hangindent=defindent)) + if block['type'] == 'margin': + return '' + if block['type'] == 'literal': + indent += ' ' + 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" % (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' % (term, util.wrap(text, width=width, + initindent=defindent, + hangindent=defindent)) + subindent = indent + if block['type'] == 'bullet': + if block['lines'][0].startswith('| '): + # Remove bullet for line blocks and add no extra + # indention. + block['lines'][0] = block['lines'][0][2:] + else: + 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: + # mixed sizes, use fieldwidth for this one + key = key.ljust(_fieldwidth) + block['lines'][0] = key + block['lines'][0] + elif block['type'] == 'option': + m = _optionre.match(block['lines'][0]) + option, arg, rest = m.groups() + subindent = indent + (len(option) + len(arg)) * ' ' + + text = ' '.join(map(str.strip, block['lines'])) + return util.wrap(text, width=width, + initindent=indent, + 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, pruned = prunecontainers(blocks, keep or []) + blocks = findsections(blocks) + blocks = inlineliterals(blocks) + blocks = hgrole(blocks) + blocks = splitparagraphs(blocks) + blocks = updatefieldlists(blocks) + blocks = prunecomments(blocks) + blocks = addmargins(blocks) + blocks = findadmonitions(blocks) + text = '\n'.join(formatblock(b, width) for b in blocks) + if keep is None: + return text + else: + return text, pruned + + +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 = open(sys.argv[1]).read() + blocks = debug(findblocks, text) + blocks = debug(findliteralblocks, blocks) + blocks, pruned = debug(prunecontainers, blocks, sys.argv[2:]) + blocks = debug(inlineliterals, blocks) + blocks = debug(splitparagraphs, blocks) + blocks = debug(updatefieldlists, blocks) + blocks = debug(findsections, blocks) + blocks = debug(prunecomments, blocks) + blocks = debug(addmargins, blocks) + blocks = debug(findadmonitions, blocks) + print '\n'.join(formatblock(b, 30) for b in blocks)