formatter.py
174 lines
| 5.7 KiB
| text/x-python
|
PythonLexer
|
r39243 | # Copyright 2016-present Facebook. All Rights Reserved. | ||
# | ||||
# format: defines the format used to output annotate result | ||||
# | ||||
# This software may be used and distributed according to the terms of the | ||||
# GNU General Public License version 2 or any later version. | ||||
from __future__ import absolute_import | ||||
from mercurial import ( | ||||
encoding, | ||||
node, | ||||
pycompat, | ||||
templatefilters, | ||||
util, | ||||
) | ||||
|
r43346 | from mercurial.utils import dateutil | ||
|
r39243 | |||
# imitating mercurial.commands.annotate, not using the vanilla formatter since | ||||
# the data structures are a bit different, and we have some fast paths. | ||||
class defaultformatter(object): | ||||
"""the default formatter that does leftpad and support some common flags""" | ||||
def __init__(self, ui, repo, opts): | ||||
self.ui = ui | ||||
self.opts = opts | ||||
if ui.quiet: | ||||
datefunc = dateutil.shortdate | ||||
else: | ||||
datefunc = dateutil.datestr | ||||
datefunc = util.cachefunc(datefunc) | ||||
getctx = util.cachefunc(lambda x: repo[x[0]]) | ||||
hexfunc = self._hexfunc | ||||
# special handling working copy "changeset" and "rev" functions | ||||
if self.opts.get('rev') == 'wdir()': | ||||
orig = hexfunc | ||||
hexfunc = lambda x: None if x is None else orig(x) | ||||
|
r41443 | wnode = hexfunc(repo['.'].node()) + '+' | ||
wrev = '%d' % repo['.'].rev() | ||||
|
r39243 | wrevpad = '' | ||
|
r43346 | if not opts.get('changeset'): # only show + if changeset is hidden | ||
|
r39243 | wrev += '+' | ||
wrevpad = ' ' | ||||
|
r40763 | revenc = lambda x: wrev if x is None else ('%d' % x) + wrevpad | ||
|
r43346 | |||
|
r40763 | def csetenc(x): | ||
if x is None: | ||||
return wnode | ||||
return pycompat.bytestr(x) + ' ' | ||||
|
r43346 | |||
|
r39243 | else: | ||
|
r40763 | revenc = csetenc = pycompat.bytestr | ||
|
r39243 | |||
# opt name, separator, raw value (for json/plain), encoder (for plain) | ||||
|
r43346 | opmap = [ | ||
('user', ' ', lambda x: getctx(x).user(), ui.shortuser), | ||||
('number', ' ', lambda x: getctx(x).rev(), revenc), | ||||
('changeset', ' ', lambda x: hexfunc(x[0]), csetenc), | ||||
('date', ' ', lambda x: getctx(x).date(), datefunc), | ||||
('file', ' ', lambda x: x[2], pycompat.bytestr), | ||||
('line_number', ':', lambda x: x[1] + 1, pycompat.bytestr), | ||||
] | ||||
|
r39243 | fieldnamemap = {'number': 'rev', 'changeset': 'node'} | ||
|
r43346 | funcmap = [ | ||
(get, sep, fieldnamemap.get(op, op), enc) | ||||
for op, sep, get, enc in opmap | ||||
if opts.get(op) | ||||
] | ||||
|
r39243 | # no separator for first column | ||
funcmap[0] = list(funcmap[0]) | ||||
funcmap[0][1] = '' | ||||
self.funcmap = funcmap | ||||
def write(self, annotatedresult, lines=None, existinglines=None): | ||||
"""(annotateresult, [str], set([rev, linenum])) -> None. write output. | ||||
annotateresult can be [(node, linenum, path)], or [(node, linenum)] | ||||
""" | ||||
|
r43346 | pieces = [] # [[str]] | ||
maxwidths = [] # [int] | ||||
|
r39243 | |||
# calculate padding | ||||
for f, sep, name, enc in self.funcmap: | ||||
l = [enc(f(x)) for x in annotatedresult] | ||||
pieces.append(l) | ||||
|
r43346 | if name in ['node', 'date']: # node and date has fixed size | ||
|
r39243 | l = l[:1] | ||
|
r39762 | widths = pycompat.maplist(encoding.colwidth, set(l)) | ||
|
r43346 | maxwidth = max(widths) if widths else 0 | ||
|
r39243 | maxwidths.append(maxwidth) | ||
# buffered output | ||||
result = '' | ||||
for i in pycompat.xrange(len(annotatedresult)): | ||||
for j, p in enumerate(pieces): | ||||
sep = self.funcmap[j][1] | ||||
padding = ' ' * (maxwidths[j] - len(p[i])) | ||||
result += sep + padding + p[i] | ||||
if lines: | ||||
if existinglines is None: | ||||
result += ': ' + lines[i] | ||||
|
r43346 | else: # extra formatting showing whether a line exists | ||
|
r39243 | key = (annotatedresult[i][0], annotatedresult[i][1]) | ||
if key in existinglines: | ||||
result += ': ' + lines[i] | ||||
else: | ||||
|
r43346 | result += ': ' + self.ui.label( | ||
'-' + lines[i], 'diff.deleted' | ||||
) | ||||
|
r39243 | |||
|
r41298 | if result[-1:] != '\n': | ||
|
r39243 | result += '\n' | ||
self.ui.write(result) | ||||
@util.propertycache | ||||
def _hexfunc(self): | ||||
if self.ui.debugflag or self.opts.get('long_hash'): | ||||
return node.hex | ||||
else: | ||||
return node.short | ||||
def end(self): | ||||
pass | ||||
|
r43346 | |||
|
r39243 | class jsonformatter(defaultformatter): | ||
def __init__(self, ui, repo, opts): | ||||
super(jsonformatter, self).__init__(ui, repo, opts) | ||||
self.ui.write('[') | ||||
self.needcomma = False | ||||
def write(self, annotatedresult, lines=None, existinglines=None): | ||||
if annotatedresult: | ||||
self._writecomma() | ||||
|
r43346 | pieces = [ | ||
(name, pycompat.maplist(f, annotatedresult)) | ||||
for f, sep, name, enc in self.funcmap | ||||
] | ||||
|
r39243 | if lines is not None: | ||
pieces.append(('line', lines)) | ||||
pieces.sort() | ||||
seps = [','] * len(pieces[:-1]) + [''] | ||||
result = '' | ||||
lasti = len(annotatedresult) - 1 | ||||
for i in pycompat.xrange(len(annotatedresult)): | ||||
result += '\n {\n' | ||||
for j, p in enumerate(pieces): | ||||
k, vs = p | ||||
|
r43346 | result += ' "%s": %s%s\n' % ( | ||
k, | ||||
templatefilters.json(vs[i], paranoid=False), | ||||
seps[j], | ||||
) | ||||
|
r39243 | result += ' }%s' % ('' if i == lasti else ',') | ||
if lasti >= 0: | ||||
self.needcomma = True | ||||
self.ui.write(result) | ||||
def _writecomma(self): | ||||
if self.needcomma: | ||||
self.ui.write(',') | ||||
self.needcomma = False | ||||
@util.propertycache | ||||
def _hexfunc(self): | ||||
return node.hex | ||||
def end(self): | ||||
self.ui.write('\n]\n') | ||||