Patchwork [4,of,5] formatter: map -Tjson(...) and -Tcbor(...) to templater

login
register
mail settings
Submitter Yuya Nishihara
Date Oct. 6, 2019, 8 p.m.
Message ID <7260fcfed7308f15554b.1570392015@mimosa>
Download mbox | patch
Permalink /patch/42044/
State Accepted
Headers show

Comments

Yuya Nishihara - Oct. 6, 2019, 8 p.m.
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1570332609 14400
#      Sat Oct 05 23:30:09 2019 -0400
# Node ID 7260fcfed7308f15554bbb0ed86e5e36f1a420fd
# Parent  efda5c5a4fe1a783ad40da15d4e52ccfb09554da
formatter: map -Tjson(...) and -Tcbor(...) to templater

Even though custom JSON output could be generated by using
--config template.json="{dict(...)|json}" ..., doing that is tedious because
of the trailing comma handling.

This patch introduces special syntax for JSON/CBOR formats. -Tjson(...) is
translated to template as if function-style template definition were
supported:

  [templates]
  json(...) = "{dict(...)|json}"
  json(...):docheader = "[\n "
  json(...):docfooter = "\n]\n"
  json(...):separator = ",\n "

Patch

diff --git a/mercurial/formatter.py b/mercurial/formatter.py
--- a/mercurial/formatter.py
+++ b/mercurial/formatter.py
@@ -475,7 +475,7 @@  class _templateconverter(object):
 
 
 class templateformatter(baseformatter):
-    def __init__(self, ui, out, topic, opts, spec):
+    def __init__(self, ui, out, topic, opts, spec, overridetemplates=None):
         baseformatter.__init__(self, ui, topic, opts, _templateconverter)
         self._out = out
         self._tref = spec.ref
@@ -486,6 +486,8 @@  class templateformatter(baseformatter):
             resources=templateresources(ui),
             cache=templatekw.defaulttempl,
         )
+        if overridetemplates:
+            self._t.cache.update(overridetemplates)
         self._parts = templatepartsmap(
             spec, self._t, [b'docheader', b'docfooter', b'separator']
         )
@@ -523,6 +525,7 @@  class templatespec(object):
     ref = attr.ib()
     tmpl = attr.ib()
     mapfile = attr.ib()
+    refargs = attr.ib(default=None)
 
 
 def lookuptemplate(ui, topic, tmpl):
@@ -556,6 +559,12 @@  def lookuptemplate(ui, topic, tmpl):
     if tmpl in {b'cbor', b'json', b'pickle', b'debug'}:
         return templatespec(tmpl, None, None)
 
+    # a function-style reference to built-in template
+    func, fsep, ftail = tmpl.partition(b'(')
+    if func in {b'cbor', b'json'} and fsep and ftail.endswith(b')'):
+        templater.parseexpr(tmpl)  # make sure syntax errors are confined
+        return templatespec(func, None, None, refargs=ftail[:-1])
+
     # perhaps a stock style?
     if not os.path.split(tmpl)[0]:
         mapname = templater.templatepath(
@@ -719,17 +728,68 @@  class templateresources(templater.resour
     }
 
 
+def _internaltemplateformatter(
+    ui,
+    out,
+    topic,
+    opts,
+    spec,
+    tmpl,
+    docheader=b'',
+    docfooter=b'',
+    separator=b'',
+):
+    """Build template formatter that handles customizable built-in templates
+    such as -Tjson(...)"""
+    templates = {spec.ref: tmpl}
+    if docheader:
+        templates[b'%s:docheader' % spec.ref] = docheader
+    if docfooter:
+        templates[b'%s:docfooter' % spec.ref] = docfooter
+    if separator:
+        templates[b'%s:separator' % spec.ref] = separator
+    return templateformatter(
+        ui, out, topic, opts, spec, overridetemplates=templates
+    )
+
+
 def formatter(ui, out, topic, opts):
     spec = lookuptemplate(ui, topic, opts.get(b'template', b''))
-    if spec.ref == b"cbor":
+    if spec.ref == b"cbor" and spec.refargs is not None:
+        return _internaltemplateformatter(
+            ui,
+            out,
+            topic,
+            opts,
+            spec,
+            tmpl=b'{dict(%s)|cbor}' % spec.refargs,
+            docheader=cborutil.BEGIN_INDEFINITE_ARRAY,
+            docfooter=cborutil.BREAK,
+        )
+    elif spec.ref == b"cbor":
         return cborformatter(ui, out, topic, opts)
+    elif spec.ref == b"json" and spec.refargs is not None:
+        return _internaltemplateformatter(
+            ui,
+            out,
+            topic,
+            opts,
+            spec,
+            tmpl=b'{dict(%s)|json}' % spec.refargs,
+            docheader=b'[\n ',
+            docfooter=b'\n]\n',
+            separator=b',\n ',
+        )
     elif spec.ref == b"json":
         return jsonformatter(ui, out, topic, opts)
     elif spec.ref == b"pickle":
+        assert spec.refargs is None, r'function-style not supported'
         return pickleformatter(ui, out, topic, opts)
     elif spec.ref == b"debug":
+        assert spec.refargs is None, r'function-style not supported'
         return debugformatter(ui, out, topic, opts)
     elif spec.ref or spec.tmpl or spec.mapfile:
+        assert spec.refargs is None, r'function-style not supported'
         return templateformatter(ui, out, topic, opts, spec)
     # developer config: ui.formatdebug
     elif ui.configbool(b'ui', b'formatdebug'):
diff --git a/tests/test-template-map.t b/tests/test-template-map.t
--- a/tests/test-template-map.t
+++ b/tests/test-template-map.t
@@ -736,6 +736,18 @@  test CBOR style:
    }
   ]
 
+  $ hg log -r . -T'cbor(rev, node|short)' | "$PYTHON" "$TESTTMP/decodecborarray.py"
+  [
+   {
+    'node': '95c24699272e',
+    'rev': 8
+   }
+  ]
+
+  $ hg log -r . -T'cbor()' | "$PYTHON" "$TESTTMP/decodecborarray.py"
+  [
+   {}
+  ]
 
 Test JSON style:
 
@@ -1101,6 +1113,17 @@  honor --git but not format-breaking diff
    }
   ]
 
+  $ hg log -l2 -T'json(rev, parents)'
+  [
+   {"parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"], "rev": 8},
+   {"parents": ["0000000000000000000000000000000000000000"], "rev": 7}
+  ]
+
+  $ hg log -r. -T'json()'
+  [
+   {}
+  ]
+
 Other unsupported formatter styles:
 
   $ hg log -qr . -Tpickle
@@ -1110,6 +1133,24 @@  Other unsupported formatter styles:
   abort: "debug" not in template map
   [255]
 
+Unparsable function-style references:
+
+  $ hg log -qr . -T'json(-)'
+  hg: parse error at 6: not a prefix: )
+  (json(-)
+         ^ here)
+  [255]
+
+For backward compatibility, the following examples are not parsed as
+function-style references:
+
+  $ hg log -qr . -T'cbor(rev'
+  cbor(rev (no-eol)
+  $ hg log -qr . -T'json (rev)'
+  json (rev) (no-eol)
+  $ hg log -qr . -T'json(x="{rev}")'
+  json(x="8") (no-eol)
+
 Error if style not readable:
 
 #if unix-permissions no-root