# HG changeset patch # User Yuya Nishihara # Date 1471500836 -32400 # Node ID 33461139c31ca59ad98e5aabc4ee39716758846f # Parent 0418cdf67efb3c2face162a91eaa5d1def7193f9 grep: add formatter support Several fields are renamed to be consistent with the annotate command, which doesn't mean the last call for the name unification [1]. Actually, I'd rather rename line_number to linenumber, linenum, lineno or line, but I want to port the grep command to formatter first. [1]: https://www.mercurial-scm.org/wiki/GenericTemplatingPlan#Dictionary I don't have any better name for the list of matched/unmatched texts, so they are just called as "texts". diff -r 0418cdf67efb -r 33461139c31c mercurial/commands.py --- a/mercurial/commands.py Thu Aug 18 14:52:06 2016 +0900 +++ b/mercurial/commands.py Thu Aug 18 15:13:56 2016 +0900 @@ -4283,7 +4283,7 @@ _('only search files changed within revision range'), _('REV')), ('u', 'user', None, _('list the author (long with -v)')), ('d', 'date', None, _('list the date (short with -q)')), - ] + walkopts, + ] + formatteropts + walkopts, _('[OPTION]... PATTERN [FILE]...'), inferrepo=True) def grep(ui, repo, pattern, *pats, **opts): @@ -4380,59 +4380,75 @@ for i in xrange(blo, bhi): yield ('+', b[i]) - def display(fn, ctx, pstates, states): + def display(fm, fn, ctx, pstates, states): rev = ctx.rev() + if fm: + formatuser = str + else: + formatuser = ui.shortuser if ui.quiet: - datefunc = util.shortdate + datefmt = '%Y-%m-%d' else: - datefunc = util.datestr + datefmt = '%a %b %d %H:%M:%S %Y %1%2' found = False @util.cachefunc def binary(): flog = getfile(fn) return util.binary(flog.read(ctx.filenode(fn))) + fieldnamemap = {'filename': 'file', 'linenumber': 'line_number'} if opts.get('all'): iter = difflinestates(pstates, states) else: iter = [('', l) for l in states] for change, l in iter: + fm.startitem() + fm.data(node=fm.hexfunc(ctx.node())) cols = [ ('filename', fn, True), - ('rev', str(rev), True), - ('linenumber', str(l.linenum), opts.get('line_number')), + ('rev', rev, True), + ('linenumber', l.linenum, opts.get('line_number')), ] if opts.get('all'): cols.append(('change', change, True)) cols.extend([ - ('user', ui.shortuser(ctx.user()), opts.get('user')), - ('date', datefunc(ctx.date()), opts.get('date')), + ('user', formatuser(ctx.user()), opts.get('user')), + ('date', fm.formatdate(ctx.date(), datefmt), opts.get('date')), ]) lastcol = next(name for name, data, cond in reversed(cols) if cond) for name, data, cond in cols: - if cond: - ui.write(data, label='grep.%s' % name) + field = fieldnamemap.get(name, name) + fm.condwrite(cond, field, '%s', data, label='grep.%s' % name) if cond and name != lastcol: - ui.write(sep, label='grep.sep') + fm.plain(sep, label='grep.sep') if not opts.get('files_with_matches'): - ui.write(sep, label='grep.sep') + fm.plain(sep, label='grep.sep') if not opts.get('text') and binary(): - ui.write(_(" Binary file matches")) + fm.plain(_(" Binary file matches")) else: - displaymatches(l) - ui.write(eol) + displaymatches(fm.nested('texts'), l) + fm.plain(eol) found = True if opts.get('files_with_matches'): break return found - def displaymatches(l): + def displaymatches(fm, l): p = 0 for s, e in l.findpos(): - ui.write(l.line[p:s]) - ui.write(l.line[s:e], label='grep.match') + if p < s: + fm.startitem() + fm.write('text', '%s', l.line[p:s]) + fm.data(matched=False) + fm.startitem() + fm.write('text', '%s', l.line[s:e], label='grep.match') + fm.data(matched=True) p = e - ui.write(l.line[p:]) + if p < len(l.line): + fm.startitem() + fm.write('text', '%s', l.line[p:]) + fm.data(matched=False) + fm.end() skip = {} revfiles = {} @@ -4475,6 +4491,7 @@ except error.LookupError: pass + fm = ui.formatter('grep', opts) for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep): rev = ctx.rev() parent = ctx.p1().rev() @@ -4487,7 +4504,7 @@ continue pstates = matches.get(parent, {}).get(copy or fn, []) if pstates or states: - r = display(fn, ctx, pstates, states) + r = display(fm, fn, ctx, pstates, states) found = found or r if r and not opts.get('all'): skip[fn] = True @@ -4495,6 +4512,7 @@ skip[copy] = True del matches[rev] del revfiles[rev] + fm.end() return not found diff -r 0418cdf67efb -r 33461139c31c tests/test-completion.t --- a/tests/test-completion.t Thu Aug 18 14:52:06 2016 +0900 +++ b/tests/test-completion.t Thu Aug 18 15:13:56 2016 +0900 @@ -278,7 +278,7 @@ debugwireargs: three, four, five, ssh, remotecmd, insecure files: rev, print0, include, exclude, template, subrepos graft: rev, continue, edit, log, force, currentdate, currentuser, date, user, tool, dry-run - grep: print0, all, text, follow, ignore-case, files-with-matches, line-number, rev, user, date, include, exclude + grep: print0, all, text, follow, ignore-case, files-with-matches, line-number, rev, user, date, template, include, exclude heads: rev, topo, active, closed, style, template help: extension, command, keyword, system identify: rev, num, id, branch, tags, bookmarks, ssh, remotecmd, insecure diff -r 0418cdf67efb -r 33461139c31c tests/test-grep.t --- a/tests/test-grep.t Thu Aug 18 14:52:06 2016 +0900 +++ b/tests/test-grep.t Thu Aug 18 15:13:56 2016 +0900 @@ -40,6 +40,61 @@ \x1b[0;35mport\x1b[0m\x1b[0;36m:\x1b[0m\x1b[0;32m4\x1b[0m\x1b[0;36m:\x1b[0mva\x1b[0;31;1mport\x1b[0might (esc) \x1b[0;35mport\x1b[0m\x1b[0;36m:\x1b[0m\x1b[0;32m4\x1b[0m\x1b[0;36m:\x1b[0mim\x1b[0;31;1mport\x1b[0m/ex\x1b[0;31;1mport\x1b[0m (esc) +simple templated + + $ hg grep port \ + > -T '{file}:{rev}:{node|short}:{texts % "{if(matched, text|upper, text)}"}\n' + port:4:914fa752cdea:exPORT + port:4:914fa752cdea:vaPORTight + port:4:914fa752cdea:imPORT/exPORT + +simple JSON (no "change" field) + + $ hg grep -Tjson port + [ + { + "date": [4.0, 0], + "file": "port", + "line_number": 1, + "node": "914fa752cdea87777ac1a8d5c858b0c736218f6c", + "rev": 4, + "texts": [{"matched": false, "text": "ex"}, {"matched": true, "text": "port"}], + "user": "spam" + }, + { + "date": [4.0, 0], + "file": "port", + "line_number": 2, + "node": "914fa752cdea87777ac1a8d5c858b0c736218f6c", + "rev": 4, + "texts": [{"matched": false, "text": "va"}, {"matched": true, "text": "port"}, {"matched": false, "text": "ight"}], + "user": "spam" + }, + { + "date": [4.0, 0], + "file": "port", + "line_number": 3, + "node": "914fa752cdea87777ac1a8d5c858b0c736218f6c", + "rev": 4, + "texts": [{"matched": false, "text": "im"}, {"matched": true, "text": "port"}, {"matched": false, "text": "/ex"}, {"matched": true, "text": "port"}], + "user": "spam" + } + ] + +simple JSON without matching lines + + $ hg grep -Tjson -l port + [ + { + "date": [4.0, 0], + "file": "port", + "line_number": 1, + "node": "914fa752cdea87777ac1a8d5c858b0c736218f6c", + "rev": 4, + "user": "spam" + } + ] + all $ hg grep --traceback --all -nu port port @@ -53,6 +108,102 @@ port:1:2:+:eggs:export port:0:1:+:spam:import +all JSON + + $ hg grep --all -Tjson port port + [ + { + "change": "-", + "date": [4.0, 0], + "file": "port", + "line_number": 4, + "node": "914fa752cdea87777ac1a8d5c858b0c736218f6c", + "rev": 4, + "texts": [{"matched": false, "text": "im"}, {"matched": true, "text": "port"}, {"matched": false, "text": "/ex"}, {"matched": true, "text": "port"}], + "user": "spam" + }, + { + "change": "+", + "date": [3.0, 0], + "file": "port", + "line_number": 4, + "node": "95040cfd017d658c536071c6290230a613c4c2a6", + "rev": 3, + "texts": [{"matched": false, "text": "im"}, {"matched": true, "text": "port"}, {"matched": false, "text": "/ex"}, {"matched": true, "text": "port"}], + "user": "eggs" + }, + { + "change": "-", + "date": [2.0, 0], + "file": "port", + "line_number": 1, + "node": "3b325e3481a1f07435d81dfdbfa434d9a0245b47", + "rev": 2, + "texts": [{"matched": false, "text": "im"}, {"matched": true, "text": "port"}], + "user": "spam" + }, + { + "change": "-", + "date": [2.0, 0], + "file": "port", + "line_number": 2, + "node": "3b325e3481a1f07435d81dfdbfa434d9a0245b47", + "rev": 2, + "texts": [{"matched": false, "text": "ex"}, {"matched": true, "text": "port"}], + "user": "spam" + }, + { + "change": "+", + "date": [2.0, 0], + "file": "port", + "line_number": 1, + "node": "3b325e3481a1f07435d81dfdbfa434d9a0245b47", + "rev": 2, + "texts": [{"matched": false, "text": "ex"}, {"matched": true, "text": "port"}], + "user": "spam" + }, + { + "change": "+", + "date": [2.0, 0], + "file": "port", + "line_number": 2, + "node": "3b325e3481a1f07435d81dfdbfa434d9a0245b47", + "rev": 2, + "texts": [{"matched": false, "text": "va"}, {"matched": true, "text": "port"}, {"matched": false, "text": "ight"}], + "user": "spam" + }, + { + "change": "+", + "date": [2.0, 0], + "file": "port", + "line_number": 3, + "node": "3b325e3481a1f07435d81dfdbfa434d9a0245b47", + "rev": 2, + "texts": [{"matched": false, "text": "im"}, {"matched": true, "text": "port"}, {"matched": false, "text": "/ex"}, {"matched": true, "text": "port"}], + "user": "spam" + }, + { + "change": "+", + "date": [1.0, 0], + "file": "port", + "line_number": 2, + "node": "8b20f75c158513ff5ac80bd0e5219bfb6f0eb587", + "rev": 1, + "texts": [{"matched": false, "text": "ex"}, {"matched": true, "text": "port"}], + "user": "eggs" + }, + { + "change": "+", + "date": [0.0, 0], + "file": "port", + "line_number": 1, + "node": "f31323c9217050ba245ee8b537c713ec2e8ab226", + "rev": 0, + "texts": [{"matched": false, "text": "im"}, {"matched": true, "text": "port"}], + "user": "spam" + } + ] + other $ hg grep -l port port