Patchwork [2,of,2] formatter: add overview of API and example as doctest

login
register
mail settings
Submitter Yuya Nishihara
Date Nov. 27, 2016, 1:45 p.m.
Message ID <4ee6e3d759bb847648dc.1480254316@mimosa>
Download mbox | patch
Permalink /patch/17763/
State Accepted
Headers show

Comments

Yuya Nishihara - Nov. 27, 2016, 1:45 p.m.
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1477116131 -32400
#      Sat Oct 22 15:02:11 2016 +0900
# Node ID 4ee6e3d759bb847648dcab8c6af9a80a6cd56334
# Parent  a8627780e15c1382e11f1d5df0f33843497dbac7
formatter: add overview of API and example as doctest
Kostia Balytskyi - Nov. 27, 2016, 2:26 p.m.
I like this and previous patch, but if the purpose of this one is to provide examlpes to developers, should we maybe also include debugformatter?

On 11/27/16, 1:45 PM, "Mercurial-devel on behalf of Yuya Nishihara" <mercurial-devel-bounces@mercurial-scm.org on behalf of yuya@tcha.org> wrote:

    # HG changeset patch
    # User Yuya Nishihara <yuya@tcha.org>
    # Date 1477116131 -32400
    #      Sat Oct 22 15:02:11 2016 +0900
    # Node ID 4ee6e3d759bb847648dcab8c6af9a80a6cd56334
    # Parent  a8627780e15c1382e11f1d5df0f33843497dbac7
    formatter: add overview of API and example as doctest
    
    diff --git a/mercurial/formatter.py b/mercurial/formatter.py
    --- a/mercurial/formatter.py
    +++ b/mercurial/formatter.py
    @@ -5,6 +5,101 @@
     # This software may be used and distributed according to the terms of the
     # GNU General Public License version 2 or any later version.
     
    +"""Generic output formatting for Mercurial
    +
    +The formatter provides API to show data in various ways. The following
    +functions should be used in place of ui.write():
    +
    +- fm.write() for unconditional output
    +- fm.condwrite() to show some extra data conditionally in plain output
    +- fm.data() to provide extra data to JSON or template output
    +- fm.plain() to show raw text that isn't provided to JSON or template output
    +
    +To show structured data (e.g. date tuples, dicts, lists), apply fm.format*()
    +beforehand so the data is converted to the appropriate data type. Use
    +fm.isplain() if you need to convert or format data conditionally which isn't
    +supported by the formatter API.
    +
    +To build nested structure (i.e. a list of dicts), use fm.nested().
    +
    +See also https://urldefense.proofpoint.com/v2/url?u=https-3A__www.mercurial-2Dscm.org_wiki_GenericTemplatingPlan&d=DgIGaQ&c=5VD0RTtNlTh3ycd41b3MUw&r=Pp-gQYFgs4tKlSFPF5kfCw&m=v1vFs5tNeWq6k6_Ofepm4RPrO70LtbAVADCkfqBEKzI&s=509gZDjWRL6U3ASP0G6CyKtiYJUxvBvvTXDsBB55TPo&e= 
    +
    +fm.condwrite() vs 'if cond:':
    +
    +In most cases, use fm.condwrite() so users can selectively show the data
    +in template output. If it's costly to build data, use plain 'if cond:' with
    +fm.write().
    +
    +fm.nested() vs fm.formatdict() (or fm.formatlist()):
    +
    +fm.nested() should be used to form a tree structure (a list of dicts of
    +lists of dicts...) which can be accessed through template keywords, e.g.
    +"{foo % "{bar % {...}} {baz % {...}}"}". On the other hand, fm.formatdict()
    +exports a dict-type object to template, which can be accessed by e.g.
    +"{get(foo, key)}" function.
    +
    +Doctest helper:
    +
    +>>> def show(fn, verbose=False, **opts):
    +...     import sys
    +...     from . import ui as uimod
    +...     ui = uimod.ui()
    +...     ui.fout = sys.stdout  # redirect to doctest
    +...     ui.verbose = verbose
    +...     return fn(ui, ui.formatter(fn.__name__, opts))
    +
    +Basic example:
    +
    +>>> def files(ui, fm):
    +...     files = [('foo', 123, (0, 0)), ('bar', 456, (1, 0))]
    +...     for f in files:
    +...         fm.startitem()
    +...         fm.write('path', '%s', f[0])
    +...         fm.condwrite(ui.verbose, 'date', '  %s',
    +...                      fm.formatdate(f[2], '%Y-%m-%d %H:%M:%S'))
    +...         fm.data(size=f[1])
    +...         fm.plain('\\n')
    +...     fm.end()
    +>>> show(files)
    +foo
    +bar
    +>>> show(files, verbose=True)
    +foo  1970-01-01 00:00:00
    +bar  1970-01-01 00:00:01
    +>>> show(files, template='json')
    +[
    + {
    +  "date": [0, 0],
    +  "path": "foo",
    +  "size": 123
    + },
    + {
    +  "date": [1, 0],
    +  "path": "bar",
    +  "size": 456
    + }
    +]
    +>>> show(files, template='path: {path}\\ndate: {date|rfc3339date}\\n')
    +path: foo
    +date: 1970-01-01T00:00:00+00:00
    +path: bar
    +date: 1970-01-01T00:00:01+00:00
    +
    +Nested example:
    +
    +>>> def subrepos(ui, fm):
    +...     fm.startitem()
    +...     fm.write('repo', '[%s]\\n', 'baz')
    +...     files(ui, fm.nested('files'))
    +...     fm.end()
    +>>> show(subrepos)
    +[baz]
    +foo
    +bar
    +>>> show(subrepos, template='{repo}: {join(files % "{path}", ", ")}\\n')
    +baz: foo, bar
    +"""
    +
     from __future__ import absolute_import
     
     import os
    diff --git a/tests/test-doctest.py b/tests/test-doctest.py
    --- a/tests/test-doctest.py
    +++ b/tests/test-doctest.py
    @@ -20,6 +20,7 @@ testmod('mercurial.changelog')
     testmod('mercurial.dagparser', optionflags=doctest.NORMALIZE_WHITESPACE)
     testmod('mercurial.dispatch')
     testmod('mercurial.encoding')
    +testmod('mercurial.formatter')
     testmod('mercurial.hg')
     testmod('mercurial.hgweb.hgwebdir_mod')
     testmod('mercurial.match')
    _______________________________________________
    Mercurial-devel mailing list
    Mercurial-devel@mercurial-scm.org
    https://urldefense.proofpoint.com/v2/url?u=https-3A__www.mercurial-2Dscm.org_mailman_listinfo_mercurial-2Ddevel&d=DgIGaQ&c=5VD0RTtNlTh3ycd41b3MUw&r=Pp-gQYFgs4tKlSFPF5kfCw&m=v1vFs5tNeWq6k6_Ofepm4RPrO70LtbAVADCkfqBEKzI&s=In4BUFvxEUnUBJJGxeQa7R5HXMPM6hKOGMkehNkRnLg&e=
Yuya Nishihara - Nov. 28, 2016, 1:46 p.m.
On Sun, 27 Nov 2016 14:26:03 +0000, Kostia Balytskyi wrote:
> I like this and previous patch, but if the purpose of this one is to provide
> examlpes to developers, should we maybe also include debugformatter?

Good point, but debugformatter uses repr() so the output order is unstable.
I think jsonformatter serves well for debugging output.
Augie Fackler - Dec. 2, 2016, 8:58 p.m.
On Mon, Nov 28, 2016 at 10:46:04PM +0900, Yuya Nishihara wrote:
> On Sun, 27 Nov 2016 14:26:03 +0000, Kostia Balytskyi wrote:
> > I like this and previous patch, but if the purpose of this one is to provide
> > examlpes to developers, should we maybe also include debugformatter?
>
> Good point, but debugformatter uses repr() so the output order is unstable.
> I think jsonformatter serves well for debugging output.

Queued, thanks! Maybe I'll get some formatter love on some things now
that I've got some examples :D

> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel@mercurial-scm.org
> https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel

Patch

diff --git a/mercurial/formatter.py b/mercurial/formatter.py
--- a/mercurial/formatter.py
+++ b/mercurial/formatter.py
@@ -5,6 +5,101 @@ 
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
+"""Generic output formatting for Mercurial
+
+The formatter provides API to show data in various ways. The following
+functions should be used in place of ui.write():
+
+- fm.write() for unconditional output
+- fm.condwrite() to show some extra data conditionally in plain output
+- fm.data() to provide extra data to JSON or template output
+- fm.plain() to show raw text that isn't provided to JSON or template output
+
+To show structured data (e.g. date tuples, dicts, lists), apply fm.format*()
+beforehand so the data is converted to the appropriate data type. Use
+fm.isplain() if you need to convert or format data conditionally which isn't
+supported by the formatter API.
+
+To build nested structure (i.e. a list of dicts), use fm.nested().
+
+See also https://www.mercurial-scm.org/wiki/GenericTemplatingPlan
+
+fm.condwrite() vs 'if cond:':
+
+In most cases, use fm.condwrite() so users can selectively show the data
+in template output. If it's costly to build data, use plain 'if cond:' with
+fm.write().
+
+fm.nested() vs fm.formatdict() (or fm.formatlist()):
+
+fm.nested() should be used to form a tree structure (a list of dicts of
+lists of dicts...) which can be accessed through template keywords, e.g.
+"{foo % "{bar % {...}} {baz % {...}}"}". On the other hand, fm.formatdict()
+exports a dict-type object to template, which can be accessed by e.g.
+"{get(foo, key)}" function.
+
+Doctest helper:
+
+>>> def show(fn, verbose=False, **opts):
+...     import sys
+...     from . import ui as uimod
+...     ui = uimod.ui()
+...     ui.fout = sys.stdout  # redirect to doctest
+...     ui.verbose = verbose
+...     return fn(ui, ui.formatter(fn.__name__, opts))
+
+Basic example:
+
+>>> def files(ui, fm):
+...     files = [('foo', 123, (0, 0)), ('bar', 456, (1, 0))]
+...     for f in files:
+...         fm.startitem()
+...         fm.write('path', '%s', f[0])
+...         fm.condwrite(ui.verbose, 'date', '  %s',
+...                      fm.formatdate(f[2], '%Y-%m-%d %H:%M:%S'))
+...         fm.data(size=f[1])
+...         fm.plain('\\n')
+...     fm.end()
+>>> show(files)
+foo
+bar
+>>> show(files, verbose=True)
+foo  1970-01-01 00:00:00
+bar  1970-01-01 00:00:01
+>>> show(files, template='json')
+[
+ {
+  "date": [0, 0],
+  "path": "foo",
+  "size": 123
+ },
+ {
+  "date": [1, 0],
+  "path": "bar",
+  "size": 456
+ }
+]
+>>> show(files, template='path: {path}\\ndate: {date|rfc3339date}\\n')
+path: foo
+date: 1970-01-01T00:00:00+00:00
+path: bar
+date: 1970-01-01T00:00:01+00:00
+
+Nested example:
+
+>>> def subrepos(ui, fm):
+...     fm.startitem()
+...     fm.write('repo', '[%s]\\n', 'baz')
+...     files(ui, fm.nested('files'))
+...     fm.end()
+>>> show(subrepos)
+[baz]
+foo
+bar
+>>> show(subrepos, template='{repo}: {join(files % "{path}", ", ")}\\n')
+baz: foo, bar
+"""
+
 from __future__ import absolute_import
 
 import os
diff --git a/tests/test-doctest.py b/tests/test-doctest.py
--- a/tests/test-doctest.py
+++ b/tests/test-doctest.py
@@ -20,6 +20,7 @@  testmod('mercurial.changelog')
 testmod('mercurial.dagparser', optionflags=doctest.NORMALIZE_WHITESPACE)
 testmod('mercurial.dispatch')
 testmod('mercurial.encoding')
+testmod('mercurial.formatter')
 testmod('mercurial.hg')
 testmod('mercurial.hgweb.hgwebdir_mod')
 testmod('mercurial.match')