Patchwork [2,of,5] formatter: add fm.nested(field) to either write or build sub items

login
register
mail settings
Submitter Yuya Nishihara
Date Aug. 22, 2016, 2:44 p.m.
Message ID <22b19113435d7e2d0dd0.1471877074@mimosa>
Download mbox | patch
Permalink /patch/16380/
State Accepted
Headers show

Comments

Yuya Nishihara - Aug. 22, 2016, 2:44 p.m.
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1457866779 -32400
#      Sun Mar 13 19:59:39 2016 +0900
# Node ID 22b19113435d7e2d0dd0eb8a515d131f2552b8ac
# Parent  194ac3287b843cad7bf3d21f4f17ac901b070dea
formatter: add fm.nested(field) to either write or build sub items

We sometimes need to build nested items by formatter, but there was no
convenient way other than building and putting them manually by fm.data():

  exts = []
  for n, v in extensions:
      fm.plain('%s %s\n' % (n, v))
      exts.append({'name': n, 'ver': v})
  fm.data(extensions=exts)

This should work for simple cases, but doing this would make it harder to
change the underlying data type for better templating support.

So this patch provides fm.nested(field), which returns new nested formatter
(or self if items aren't structured and just written to ui.) A nested formatter
stores items which will later be rendered by the parent formatter.

  fn = fm.nested('extensions')
  for n, v in extensions:
      fn.startitem()
      fn.write('name ver', '%s %s\n', n, v)
  fn.end()

Nested items are directly exported to a template for now:

  {extensions % "{name} {ver}\n"}

There's no {extensions} nor {join(extensions, sep)} yet. I have a plan for
them by extending fm.nested() API, but I want to revisit it after trying
out this API in the real world.
Gregory Szorc - Aug. 22, 2016, 3:54 p.m.
> On Aug 22, 2016, at 07:44, Yuya Nishihara <yuya@tcha.org> wrote:
> 
> # HG changeset patch
> # User Yuya Nishihara <yuya@tcha.org>
> # Date 1457866779 -32400
> #      Sun Mar 13 19:59:39 2016 +0900
> # Node ID 22b19113435d7e2d0dd0eb8a515d131f2552b8ac
> # Parent  194ac3287b843cad7bf3d21f4f17ac901b070dea
> formatter: add fm.nested(field) to either write or build sub items
> 
> We sometimes need to build nested items by formatter, but there was no
> convenient way other than building and putting them manually by fm.data():

If there is always a begin and end call, should we be using a context manager?

> 
>  exts = []
>  for n, v in extensions:
>      fm.plain('%s %s\n' % (n, v))
>      exts.append({'name': n, 'ver': v})
>  fm.data(extensions=exts)
> 
> This should work for simple cases, but doing this would make it harder to
> change the underlying data type for better templating support.
> 
> So this patch provides fm.nested(field), which returns new nested formatter
> (or self if items aren't structured and just written to ui.) A nested formatter
> stores items which will later be rendered by the parent formatter.
> 
>  fn = fm.nested('extensions')
>  for n, v in extensions:
>      fn.startitem()
>      fn.write('name ver', '%s %s\n', n, v)
>  fn.end()
> 
> Nested items are directly exported to a template for now:
> 
>  {extensions % "{name} {ver}\n"}
> 
> There's no {extensions} nor {join(extensions, sep)} yet. I have a plan for
> them by extending fm.nested() API, but I want to revisit it after trying
> out this API in the real world.
> 
> diff --git a/mercurial/formatter.py b/mercurial/formatter.py
> --- a/mercurial/formatter.py
> +++ b/mercurial/formatter.py
> @@ -91,11 +91,23 @@ class baseformatter(object):
>     def plain(self, text, **opts):
>         '''show raw text for non-templated mode'''
>         pass
> +    def nested(self, field):
> +        '''sub formatter to store nested data in the specified field'''
> +        self._item[field] = data = []
> +        return _nestedformatter(self._ui, self._converter, data)
>     def end(self):
>         '''end output for the formatter'''
>         if self._item is not None:
>             self._showitem()
> 
> +class _nestedformatter(baseformatter):
> +    '''build sub items and store them in the parent formatter'''
> +    def __init__(self, ui, converter, data):
> +        baseformatter.__init__(self, ui, topic='', opts={}, converter=converter)
> +        self._data = data
> +    def _showitem(self):
> +        self._data.append(self._item)
> +
> def _iteritems(data):
>     '''iterate key-value pairs in stable order'''
>     if isinstance(data, dict):
> @@ -139,6 +151,9 @@ class plainformatter(baseformatter):
>             self._ui.write(deftext % fielddata, **opts)
>     def plain(self, text, **opts):
>         self._ui.write(text, **opts)
> +    def nested(self, field):
> +        # nested data will be directly written to ui
> +        return self
>     def end(self):
>         pass
> 
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel@mercurial-scm.org
> https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
Yuya Nishihara - Aug. 23, 2016, 1:36 p.m.
On Mon, 22 Aug 2016 08:54:02 -0700, Gregory Szorc wrote:
> > On Aug 22, 2016, at 07:44, Yuya Nishihara <yuya@tcha.org> wrote:
> > # HG changeset patch
> > # User Yuya Nishihara <yuya@tcha.org>
> > # Date 1457866779 -32400
> > #      Sun Mar 13 19:59:39 2016 +0900
> > # Node ID 22b19113435d7e2d0dd0eb8a515d131f2552b8ac
> > # Parent  194ac3287b843cad7bf3d21f4f17ac901b070dea
> > formatter: add fm.nested(field) to either write or build sub items
> > 
> > We sometimes need to build nested items by formatter, but there was no
> > convenient way other than building and putting them manually by fm.data():
> 
> If there is always a begin and end call, should we be using a context manager?

baseformatter could have __enter__ and __exit__, but I'm not tempted to use
"with" statement there because doing that would add extra indent.

Patch

diff --git a/mercurial/formatter.py b/mercurial/formatter.py
--- a/mercurial/formatter.py
+++ b/mercurial/formatter.py
@@ -91,11 +91,23 @@  class baseformatter(object):
     def plain(self, text, **opts):
         '''show raw text for non-templated mode'''
         pass
+    def nested(self, field):
+        '''sub formatter to store nested data in the specified field'''
+        self._item[field] = data = []
+        return _nestedformatter(self._ui, self._converter, data)
     def end(self):
         '''end output for the formatter'''
         if self._item is not None:
             self._showitem()
 
+class _nestedformatter(baseformatter):
+    '''build sub items and store them in the parent formatter'''
+    def __init__(self, ui, converter, data):
+        baseformatter.__init__(self, ui, topic='', opts={}, converter=converter)
+        self._data = data
+    def _showitem(self):
+        self._data.append(self._item)
+
 def _iteritems(data):
     '''iterate key-value pairs in stable order'''
     if isinstance(data, dict):
@@ -139,6 +151,9 @@  class plainformatter(baseformatter):
             self._ui.write(deftext % fielddata, **opts)
     def plain(self, text, **opts):
         self._ui.write(text, **opts)
+    def nested(self, field):
+        # nested data will be directly written to ui
+        return self
     def end(self):
         pass