summaryrefslogtreecommitdiff
path: root/mercurial/minirst.py
diff options
context:
space:
mode:
Diffstat (limited to 'mercurial/minirst.py')
-rw-r--r--mercurial/minirst.py331
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)