fastannotate: add a JSON formatter

Summary: The vanilla annotate command supports `-Tjson`. Let's implement it as well.

Test Plan: Added a new test

Reviewers: #mercurial, simonfar

Reviewed By: simonfar

Subscribers: mjpieters

Differential Revision: https://phabricator.intern.facebook.com/D4059577

Signature: t1:4059577:1477074802:09b0b3ea0769d480eb3a2e42308636ff2b8d40d2
This commit is contained in:
Jun Wu 2016-10-21 19:28:30 +01:00
parent 6318d6eb2a
commit a91f5d53ac
3 changed files with 128 additions and 30 deletions

View File

@ -76,7 +76,7 @@ fastannotatecommandargs = {
('', 'long-hash', None, _('show long changeset hash (EXPERIMENTAL)')),
('', 'rebuild', None, _('rebuild cache even if it exists '
'(EXPERIMENTAL)')),
] + commands.diffwsopts + commands.walkopts,
] + commands.diffwsopts + commands.walkopts + commands.formatteropts,
'synopsis': _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
'inferrepo': True,
}
@ -124,7 +124,11 @@ def fastannotate(ui, repo, *pats, **opts):
for name in ui.configlist('fastannotate', 'defaultformat', ['number']):
opts[name] = True
formatter = faformatter.defaultformatter(ui, repo, opts)
template = opts.get('template')
if template == 'json':
formatter = faformatter.jsonformatter(ui, repo, opts)
else:
formatter = faformatter.defaultformatter(ui, repo, opts)
showdeleted = opts.get('deleted', False)
showlines = not bool(opts.get('no_content'))
showpath = opts.get('file', False)
@ -156,6 +160,7 @@ def fastannotate(ui, repo, *pats, **opts):
result, lines = result
formatter.write(result, lines, existinglines=existinglines)
formatter.end()
_newopts = set([])
_knownopts = set([opt[1].replace('-', '_') for opt in
@ -165,6 +170,9 @@ def _annotatewrapper(orig, ui, repo, *pats, **opts):
"""use this in extensions.wrapcommand"""
nonemptyopts = set(k for k, v in opts.iteritems() if v)
unknownopts = nonemptyopts.difference(_knownopts)
if opts.get('template', '') not in ['json', '']:
# if -T is used, fastannotate only supports -Tjson
unknownopts.add('template')
if unknownopts:
ui.debug('fastannotate: option %r is not supported, falling back '
'to the original annotate\n' % list(unknownopts))

View File

@ -7,11 +7,13 @@
from mercurial import (
encoding,
formatter,
node,
util,
)
# extracted from mercurial.commands.annotate
# 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"""
@ -20,25 +22,26 @@ class defaultformatter(object):
datefunc = util.shortdate
else:
datefunc = util.datestr
if ui.debugflag or opts.get('long_hash'):
hexfunc = node.hex
else:
hexfunc = node.short
datefunc = util.cachefunc(datefunc)
getctx = util.cachefunc(lambda x: repo[x[0]])
opmap = [('user', ' ', lambda x: ui.shortuser(getctx(x).user())),
('number', ' ', lambda x: str(getctx(x).rev())),
('changeset', ' ', lambda x: hexfunc(x[0])),
('date', ' ', lambda x: datefunc(getctx(x).date())),
('file', ' ', lambda x: x[2]),
('line_number', ':', lambda x: str(x[1] + 1))]
funcmap = [(get, sep) for op, sep, get in opmap
# opt name, separator, raw value (for json/plain), encoder (for plain)
opmap = [('user', ' ', lambda x: getctx(x).user(), ui.shortuser),
('number', ' ', lambda x: getctx(x).rev(), str),
('changeset', ' ', lambda x: self._hexfunc(x[0]), str),
('date', ' ', lambda x: getctx(x).date(), datefunc),
('file', ' ', lambda x: x[2], str),
('line_number', ':', lambda x: x[1] + 1, str)]
fieldnamemap = {'number': 'rev', 'changeset': 'node'}
funcmap = [(get, sep, fieldnamemap.get(op, op), enc)
for op, sep, get, enc in opmap
if opts.get(op)]
funcmap[0] = (funcmap[0][0], '') # no separator for first column
# no separator for first column
funcmap[0] = list(funcmap[0])
funcmap[0][1] = ''
self.ui = ui
self.opts = opts
self.funcmap = funcmap
def write(self, annotatedresult, lines=None, existinglines=None):
@ -48,32 +51,87 @@ class defaultformatter(object):
pieces = [] # [[str]]
maxwidths = [] # [int]
# calculate
for f, sep in self.funcmap:
l = map(f, annotatedresult)
# calculate padding
for f, sep, name, enc in self.funcmap:
l = [enc(f(x)) for x in annotatedresult]
pieces.append(l)
widths = map(encoding.colwidth, l)
widths = map(encoding.colwidth, set(l))
maxwidth = (max(widths) if widths else 0)
maxwidths.append(maxwidth)
# output
# buffered output
result = ''
for i in xrange(len(annotatedresult)):
msg = ''
for j, p in enumerate(pieces):
sep = self.funcmap[j][1]
padding = ' ' * (maxwidths[j] - len(p[i]))
msg += sep + padding + p[i]
result += sep + padding + p[i]
if lines:
if existinglines is None:
msg += ': ' + lines[i]
result += ': ' + lines[i]
else: # extra formatting showing whether a line exists
key = (annotatedresult[i][0], annotatedresult[i][1])
if key in existinglines:
msg += ': ' + lines[i]
result += ': ' + lines[i]
else:
msg += ': ' + self.ui.label('-' + lines[i],
'diff.deleted')
result += ': ' + self.ui.label('-' + lines[i],
'diff.deleted')
if msg[-1] != '\n':
msg += '\n'
self.ui.write(msg)
if result[-1] != '\n':
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
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()
pieces = [(name, map(f, annotatedresult))
for f, sep, name, enc in self.funcmap]
if lines is not None:
pieces.append(('line', lines))
pieces.sort()
seps = [','] * len(pieces[:-1]) + ['']
result = ''
lasti = len(annotatedresult) - 1
for i in xrange(len(annotatedresult)):
result += '\n {\n'
for j, p in enumerate(pieces):
k, vs = p
result += (' "%s": %s%s\n'
% (k, formatter._jsonifyobj(vs[i]), seps[j]))
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')

View File

@ -196,3 +196,35 @@ empty file
$ touch empty
$ hg commit -A empty -m empty
$ hg fastannotate empty
json format
$ hg fastannotate -Tjson -cludn b a empty
[
{
"date": [0.0, 0],
"line": "a\n",
"line_number": 1,
"node": "1fd620b16252aecb54c6aa530dff5ed6e6ec3d21",
"rev": 0,
"user": "test"
},
{
"date": [0.0, 0],
"line": "b\n",
"line_number": 1,
"node": "1fd620b16252aecb54c6aa530dff5ed6e6ec3d21",
"rev": 0,
"user": "test"
}
]
$ hg fastannotate -Tjson -cludn empty
[
]
$ hg fastannotate -Tjson --no-content -n a
[
{
"rev": 0
}
]