--- a/mercurial/minirst.py Sat Oct 05 10:29:34 2019 -0400
+++ b/mercurial/minirst.py Sun Oct 06 09:45:02 2019 -0400
@@ -28,25 +28,29 @@
pycompat,
url,
)
-from .utils import (
- stringutil,
-)
+from .utils import stringutil
+
def section(s):
return "%s\n%s\n\n" % (s, "\"" * encoding.colwidth(s))
+
def subsection(s):
return "%s\n%s\n\n" % (s, '=' * encoding.colwidth(s))
+
def subsubsection(s):
return "%s\n%s\n\n" % (s, "-" * encoding.colwidth(s))
+
def subsubsubsection(s):
return "%s\n%s\n\n" % (s, "." * encoding.colwidth(s))
+
def subsubsubsubsection(s):
return "%s\n%s\n\n" % (s, "'" * encoding.colwidth(s))
+
def replace(text, substs):
'''
Apply a list of (find, replace) pairs to a text.
@@ -70,8 +74,10 @@
utext = utext.replace(f.decode("ascii"), t.decode("ascii"))
return utext.encode(pycompat.sysstr(encoding.encoding))
+
_blockre = re.compile(br"\n(?:\s*\n)+")
+
def findblocks(text):
"""Find continuous blocks of lines in text.
@@ -87,6 +93,7 @@
blocks.append({'indent': indent, 'lines': lines})
return blocks
+
def findliteralblocks(blocks):
"""Finds literal blocks and adds a 'type' field to the blocks.
@@ -117,9 +124,11 @@
# Partially minimized form: remove space and both
# colons.
blocks[i]['lines'][-1] = blocks[i]['lines'][-1][:-3]
- elif (len(blocks[i]['lines']) == 1 and
- blocks[i]['lines'][0].lstrip(' ').startswith('.. ') and
- blocks[i]['lines'][0].find(' ', 3) == -1):
+ elif (
+ len(blocks[i]['lines']) == 1
+ and blocks[i]['lines'][0].lstrip(' ').startswith('.. ')
+ and blocks[i]['lines'][0].find(' ', 3) == -1
+ ):
# directive on its own line, not a literal block
i += 1
continue
@@ -144,22 +153,27 @@
i += 1
return blocks
+
_bulletre = re.compile(br'(\*|-|[0-9A-Za-z]+\.|\(?[0-9A-Za-z]+\)|\|) ')
-_optionre = re.compile(br'^(-([a-zA-Z0-9]), )?(--[a-z0-9-]+)'
- br'((.*) +)(.*)$')
+_optionre = re.compile(
+ br'^(-([a-zA-Z0-9]), )?(--[a-z0-9-]+)' br'((.*) +)(.*)$'
+)
_fieldre = re.compile(br':(?![: ])([^:]*)(?<! ):[ ]+(.*)')
_definitionre = re.compile(br'[^ ]')
_tablere = re.compile(br'(=+\s+)*=+')
+
def splitparagraphs(blocks):
"""Split paragraphs into lists."""
# Tuples with (list type, item regexp, single line items?). Order
# matters: definition lists has the least specific regexp and must
# come last.
- listtypes = [('bullet', _bulletre, True),
- ('option', _optionre, True),
- ('field', _fieldre, True),
- ('definition', _definitionre, False)]
+ listtypes = [
+ ('bullet', _bulletre, True),
+ ('option', _optionre, True),
+ ('field', _fieldre, True),
+ ('definition', _definitionre, False),
+ ]
def match(lines, i, itemre, singleline):
"""Does itemre match an item at line i?
@@ -185,16 +199,23 @@
items = []
for j, line in enumerate(lines):
if match(lines, j, itemre, singleline):
- items.append({'type': type, 'lines': [],
- 'indent': blocks[i]['indent']})
+ items.append(
+ {
+ 'type': type,
+ 'lines': [],
+ 'indent': blocks[i]['indent'],
+ }
+ )
items[-1]['lines'].append(line)
- blocks[i:i + 1] = items
+ blocks[i : i + 1] = items
break
i += 1
return blocks
+
_fieldwidth = 14
+
def updatefieldlists(blocks):
"""Find key for field lists."""
i = 0
@@ -215,6 +236,7 @@
return blocks
+
def updateoptionlists(blocks):
i = 0
while i < len(blocks):
@@ -238,9 +260,10 @@
if not shortoption:
noshortop = ' '
- opt = "%s%s" % (shortoption and "-%s " % shortoption or '',
- ("%s--%s %s") % (noshortop, longoption,
- longoptionarg))
+ opt = "%s%s" % (
+ shortoption and "-%s " % shortoption or '',
+ "%s--%s %s" % (noshortop, longoption, longoptionarg),
+ )
opt = opt.rstrip()
blocks[j]['optstr'] = opt
optstrwidth = max(optstrwidth, encoding.colwidth(opt))
@@ -251,6 +274,7 @@
i = j + 1
return blocks
+
def prunecontainers(blocks, keep):
"""Prune unwanted containers.
@@ -267,8 +291,9 @@
# +---+ |
# | blocks |
# +-------------------------------+
- if (blocks[i]['type'] == 'paragraph' and
- blocks[i]['lines'][0].startswith('.. container::')):
+ if blocks[i]['type'] == 'paragraph' and blocks[i]['lines'][
+ 0
+ ].startswith('.. container::'):
indent = blocks[i]['indent']
adjustment = blocks[i + 1]['indent'] - indent
containertype = blocks[i]['lines'][0][15:]
@@ -292,8 +317,10 @@
i += 1
return blocks, pruned
+
_sectionre = re.compile(br"""^([-=`:.'"~^_*+#])\1+$""")
+
def findtables(blocks):
'''Find simple tables
@@ -309,19 +336,23 @@
# 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]):
+ 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 pycompat.xrange(len(div))
- if div[x:x + 1] == '=' and (x == 0 or
- div[x - 1:x] == ' ')]
+ columns = [
+ x
+ for x in pycompat.xrange(len(div))
+ if div[x : x + 1] == '=' and (x == 0 or div[x - 1 : x] == ' ')
+ ]
rows = []
for l in block['lines'][1:-1]:
if l == div:
@@ -330,12 +361,12 @@
row = []
# we measure columns not in bytes or characters but in
# colwidth which makes things tricky
- pos = columns[0] # leading whitespace is bytes
+ 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
+ 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())
@@ -345,6 +376,7 @@
return blocks
+
def findsections(blocks):
"""Finds sections.
@@ -358,15 +390,18 @@
# | 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])):
+ 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:1]
block['type'] = 'section'
del block['lines'][1]
return blocks
+
def inlineliterals(blocks):
substs = [('``', '"')]
for b in blocks:
@@ -374,6 +409,7 @@
b['lines'] = [replace(l, substs) for l in b['lines']]
return blocks
+
def hgrole(blocks):
substs = [(':hg:`', "'hg "), ('`', "'")]
for b in blocks:
@@ -385,6 +421,7 @@
b['lines'] = [replace(l, substs) for l in b['lines']]
return blocks
+
def addmargins(blocks):
"""Adds empty blocks for vertical spacing.
@@ -393,8 +430,11 @@
"""
i = 1
while i < len(blocks):
- if (blocks[i]['type'] == blocks[i - 1]['type'] and
- blocks[i]['type'] in ('bullet', 'option', 'field')):
+ if blocks[i]['type'] == blocks[i - 1]['type'] and blocks[i]['type'] in (
+ 'bullet',
+ 'option',
+ 'field',
+ ):
i += 1
elif not blocks[i - 1]['lines']:
# no lines in previous block, do not separate
@@ -404,13 +444,15 @@
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('.. ') or
- b['lines'] == ['..']):
+ if b['type'] == 'paragraph' and (
+ b['lines'][0].startswith('.. ') or b['lines'] == ['..']
+ ):
del blocks[i]
if i < len(blocks) and blocks[i]['type'] == 'margin':
del blocks[i]
@@ -426,17 +468,18 @@
"""
admonitions = admonitions or _admonitiontitles.keys()
- admonitionre = re.compile(br'\.\. (%s)::' % '|'.join(sorted(admonitions)),
- flags=re.IGNORECASE)
+ admonitionre = re.compile(
+ br'\.\. (%s)::' % '|'.join(sorted(admonitions)), flags=re.IGNORECASE
+ )
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()
+ admonitiontitle = blocks[i]['lines'][0][3 : m.end() - 2].lower()
- firstline = blocks[i]['lines'][0][m.end() + 1:]
+ firstline = blocks[i]['lines'][0][m.end() + 1 :]
if firstline:
blocks[i]['lines'].insert(1, ' ' + firstline)
@@ -445,6 +488,7 @@
i = i + 1
return blocks
+
_admonitiontitles = {
'attention': _('Attention:'),
'caution': _('Caution:'),
@@ -457,6 +501,7 @@
'warning': _('Warning!'),
}
+
def formatoption(block, width):
desc = ' '.join(map(bytes.strip, block['lines']))
colwidth = encoding.colwidth(block['optstr'])
@@ -464,9 +509,12 @@
hanging = block['optstrwidth']
initindent = '%s%s ' % (block['optstr'], ' ' * ((hanging - colwidth)))
hangindent = ' ' * (encoding.colwidth(initindent) + 1)
- return ' %s\n' % (stringutil.wrap(desc, usablewidth,
- initindent=initindent,
- hangindent=hangindent))
+ return ' %s\n' % (
+ stringutil.wrap(
+ desc, usablewidth, initindent=initindent, hangindent=hangindent
+ )
+ )
+
def formatblock(block, width):
"""Format a block according to width."""
@@ -481,10 +529,12 @@
defindent = indent + hang * ' '
text = ' '.join(map(bytes.strip, block['lines']))
- return '%s\n%s\n' % (indent + admonition,
- stringutil.wrap(text, width=width,
- initindent=defindent,
- hangindent=defindent))
+ return '%s\n%s\n' % (
+ indent + admonition,
+ stringutil.wrap(
+ text, width=width, initindent=defindent, hangindent=defindent
+ ),
+ )
if block['type'] == 'margin':
return '\n'
if block['type'] == 'literal':
@@ -492,7 +542,7 @@
return indent + ('\n' + indent).join(block['lines']) + '\n'
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)
+ return "%s%s\n%s%s\n" % (indent, block['lines'][0], indent, underline)
if block['type'] == 'table':
table = block['table']
# compute column widths
@@ -508,9 +558,9 @@
pad = ' ' * (w - encoding.colwidth(v))
l.append(v + pad)
l = ' '.join(l)
- l = stringutil.wrap(l, width=width,
- initindent=indent,
- hangindent=hang)
+ l = stringutil.wrap(
+ l, width=width, initindent=indent, hangindent=hang
+ )
if not text and block['header']:
text = l + '\n' + indent + '-' * (min(width, span)) + '\n'
else:
@@ -521,9 +571,12 @@
hang = len(block['lines'][-1]) - len(block['lines'][-1].lstrip())
defindent = indent + hang * ' '
text = ' '.join(map(bytes.strip, block['lines'][1:]))
- return '%s\n%s\n' % (term, stringutil.wrap(text, width=width,
- initindent=defindent,
- hangindent=defindent))
+ return '%s\n%s\n' % (
+ term,
+ stringutil.wrap(
+ text, width=width, initindent=defindent, hangindent=defindent
+ ),
+ )
subindent = indent
if block['type'] == 'bullet':
if block['lines'][0].startswith('| '):
@@ -547,9 +600,13 @@
return formatoption(block, width)
text = ' '.join(map(bytes.strip, block['lines']))
- return stringutil.wrap(text, width=width,
- initindent=indent,
- hangindent=subindent) + '\n'
+ return (
+ stringutil.wrap(
+ text, width=width, initindent=indent, hangindent=subindent
+ )
+ + '\n'
+ )
+
def formathtml(blocks):
"""Format RST blocks as HTML"""
@@ -634,14 +691,16 @@
else:
nb = blocks[pos + 1]
ni = nb['indent']
- if (ni < level or
- (ni == level and
- nb['type'] not in 'definition bullet field option')):
+ 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, admonitions=None):
"""Parse text into a list of blocks"""
blocks = findblocks(text)
@@ -661,14 +720,17 @@
blocks = prunecomments(blocks)
return blocks, pruned
+
def formatblocks(blocks, width):
text = ''.join(formatblock(b, width) for b in blocks)
return text
+
def formatplain(blocks, width):
"""Format parsed blocks as plain text"""
return ''.join(formatblock(b, width) for b in blocks)
+
def format(text, width=80, indent=0, keep=None, style='plain', section=None):
"""Parse and format the text according to width."""
blocks, pruned = parse(text, indent, keep or [])
@@ -679,6 +741,7 @@
else:
return formatplain(blocks, width=width)
+
def filtersections(blocks, section):
"""Select parsed blocks under the specified section
@@ -705,8 +768,7 @@
s = []
for j in pycompat.xrange(3, plen - 1):
parent = parents[j]
- if (j >= llen or
- lastparents[j] != parent):
+ if j >= llen or lastparents[j] != parent:
s.append(len(blocks))
sec = sections[parent][2]
blocks.append(sec[0])
@@ -728,12 +790,14 @@
path = [blocks[syn]['lines'][0] for syn in s]
real = s[-1] + 2
realline = blocks[real]['lines']
- realline[0] = ('"%s"' %
- '.'.join(path + [realline[0]]).replace('"', ''))
- del blocks[s[0]:real]
+ realline[0] = '"%s"' % '.'.join(path + [realline[0]]).replace(
+ '"', ''
+ )
+ del blocks[s[0] : real]
return blocks
+
def _getsections(blocks):
'''return a list of (section path, nesting level, blocks) tuples'''
nest = ""
@@ -793,8 +857,9 @@
if section['type'] != 'margin':
sindent = section['indent']
if len(section['lines']) > 1:
- sindent += (len(section['lines'][1]) -
- len(section['lines'][1].lstrip(' ')))
+ sindent += len(section['lines'][1]) - len(
+ section['lines'][1].lstrip(' ')
+ )
if bindent >= sindent:
break
pointer += 1
@@ -806,6 +871,7 @@
secs[-1][2].append(b)
return secs
+
def maketable(data, indent=0, header=False):
'''Generate an RST table for the given table data as a list of lines'''