Patchwork [v2] templater: add separate() template function

login
register
mail settings
Submitter via Mercurial-devel
Date May 4, 2016, 4:45 a.m.
Message ID <9be8673e90a26c27276b.1462337117@martinvonz.mtv.corp.google.com>
Download mbox | patch
Permalink /patch/14877/
State Superseded
Commit df838803c1d487e4601f96c6cfd85e6ad4f6291f
Headers show

Comments

via Mercurial-devel - May 4, 2016, 4:45 a.m.
# HG changeset patch
# User Martin von Zweigbergk <martinvonz@google.com>
# Date 1462294194 25200
#      Tue May 03 09:49:54 2016 -0700
# Node ID 9be8673e90a26c27276b8c8963b86b95a8f184e6
# Parent  8eba4cdcfd810d80785c94d770a442c5bd1f9d0f
templater: add separate() template function

A pretty common pattern in templates is adding conditional separators
like so:

  {node}{if(bookmarks, " {bookmarks}")}{if(tags, " {tags}")}

With this patch, the above can be simplified to:

  {separate(" ", node, bookmarks, tags)}

The function is similar to the already existing join(), but with a few
differences:

 * separate() takes the separator first in order to allow a variable
   number of arguments after it

 * separate() skips empty arguments

 * join() expects a single list argument, while separate() accepts
   either a single list or separate arguments
Yuya Nishihara - May 4, 2016, 12:06 p.m.
On Tue, 03 May 2016 21:45:17 -0700, Martin von Zweigbergk via Mercurial-devel wrote:
> # HG changeset patch
> # User Martin von Zweigbergk <martinvonz@google.com>
> # Date 1462294194 25200
> #      Tue May 03 09:49:54 2016 -0700
> # Node ID 9be8673e90a26c27276b8c8963b86b95a8f184e6
> # Parent  8eba4cdcfd810d80785c94d770a442c5bd1f9d0f
> templater: add separate() template function
> 
> A pretty common pattern in templates is adding conditional separators
> like so:
> 
>   {node}{if(bookmarks, " {bookmarks}")}{if(tags, " {tags}")}
> 
> With this patch, the above can be simplified to:
> 
>   {separate(" ", node, bookmarks, tags)}
> 
> The function is similar to the already existing join(), but with a few
> differences:
> 
>  * separate() takes the separator first in order to allow a variable
>    number of arguments after it
> 
>  * separate() skips empty arguments
> 
>  * join() expects a single list argument, while separate() accepts
>    either a single list or separate arguments
> 
> diff -r 8eba4cdcfd81 -r 9be8673e90a2 mercurial/help/templates.txt
> --- a/mercurial/help/templates.txt	Sun Apr 17 12:31:06 2016 +0900
> +++ b/mercurial/help/templates.txt	Tue May 03 09:49:54 2016 -0700
> @@ -81,6 +81,10 @@
>  
>     $ hg log -r 0 --template "files: {join(files, ', ')}\n"
>  
> +- Separate non-empty arguments by a " "::
> +
> +   $ hg log -r 0 --template "{separate(' ', node, bookmarks, tags}\n"
> +
>  - Modify each line of a commit description::
>  
>     $ hg log --template "{splitlines(desc) % '**** {line}\n'}"
> diff -r 8eba4cdcfd81 -r 9be8673e90a2 mercurial/templater.py
> --- a/mercurial/templater.py	Sun Apr 17 12:31:06 2016 +0900
> +++ b/mercurial/templater.py	Tue May 03 09:49:54 2016 -0700
> @@ -621,6 +621,28 @@
>              yield joiner
>          yield x
>  
> +@templatefunc('separate(sep, args)')
> +def separate(context, mapping, args):
> +    """Add a separator between non-empty arguments."""
> +    if not args:
> +        # i18n: "separate" is a keyword
> +        raise error.ParseError(_("separate expects at least one argument"))
> +
> +    sep = evalstring(context, mapping, args[0])
> +    things = [evalfuncarg(context, mapping, arg) for arg in args[1:]]
> +    if len(things) == 1 and util.safehasattr(things[0], 'values'):
> +        things = things[0].values

Perhaps we can assume things[0] a list if len(args[1:]) == 1.
separate(' ', nonlist) is useless just like built-in min(x) function.
via Mercurial-devel - May 4, 2016, 3:48 p.m.
On Wed, May 4, 2016 at 5:06 AM, Yuya Nishihara <yuya@tcha.org> wrote:
> On Tue, 03 May 2016 21:45:17 -0700, Martin von Zweigbergk via Mercurial-devel wrote:
>> # HG changeset patch
>> # User Martin von Zweigbergk <martinvonz@google.com>
>> # Date 1462294194 25200
>> #      Tue May 03 09:49:54 2016 -0700
>> # Node ID 9be8673e90a26c27276b8c8963b86b95a8f184e6
>> # Parent  8eba4cdcfd810d80785c94d770a442c5bd1f9d0f
>> templater: add separate() template function
>>
>> A pretty common pattern in templates is adding conditional separators
>> like so:
>>
>>   {node}{if(bookmarks, " {bookmarks}")}{if(tags, " {tags}")}
>>
>> With this patch, the above can be simplified to:
>>
>>   {separate(" ", node, bookmarks, tags)}
>>
>> The function is similar to the already existing join(), but with a few
>> differences:
>>
>>  * separate() takes the separator first in order to allow a variable
>>    number of arguments after it
>>
>>  * separate() skips empty arguments
>>
>>  * join() expects a single list argument, while separate() accepts
>>    either a single list or separate arguments
>>
>> diff -r 8eba4cdcfd81 -r 9be8673e90a2 mercurial/help/templates.txt
>> --- a/mercurial/help/templates.txt    Sun Apr 17 12:31:06 2016 +0900
>> +++ b/mercurial/help/templates.txt    Tue May 03 09:49:54 2016 -0700
>> @@ -81,6 +81,10 @@
>>
>>     $ hg log -r 0 --template "files: {join(files, ', ')}\n"
>>
>> +- Separate non-empty arguments by a " "::
>> +
>> +   $ hg log -r 0 --template "{separate(' ', node, bookmarks, tags}\n"
>> +
>>  - Modify each line of a commit description::
>>
>>     $ hg log --template "{splitlines(desc) % '**** {line}\n'}"
>> diff -r 8eba4cdcfd81 -r 9be8673e90a2 mercurial/templater.py
>> --- a/mercurial/templater.py  Sun Apr 17 12:31:06 2016 +0900
>> +++ b/mercurial/templater.py  Tue May 03 09:49:54 2016 -0700
>> @@ -621,6 +621,28 @@
>>              yield joiner
>>          yield x
>>
>> +@templatefunc('separate(sep, args)')
>> +def separate(context, mapping, args):
>> +    """Add a separator between non-empty arguments."""
>> +    if not args:
>> +        # i18n: "separate" is a keyword
>> +        raise error.ParseError(_("separate expects at least one argument"))
>> +
>> +    sep = evalstring(context, mapping, args[0])
>> +    things = [evalfuncarg(context, mapping, arg) for arg in args[1:]]
>> +    if len(things) == 1 and util.safehasattr(things[0], 'values'):
>> +        things = things[0].values
>
> Perhaps we can assume things[0] a list if len(args[1:]) == 1.
> separate(' ', nonlist) is useless just like built-in min(x) function.

I considered that. I don't know which is less surprising, having
behavior depending on number of arguments or type of arguments (and
number of them). I could go either way. Any votes in either direction?

Btw:

$ hg log -T '{join(1)}'
[stack trace]
TypeError: 'int' object is not iterable

Stupid use case, but we should eliminate the stack trace.
Sean Farley - May 4, 2016, 11:14 p.m.
Martin von Zweigbergk via Mercurial-devel <mercurial-devel@mercurial-scm.org> writes:

> On Wed, May 4, 2016 at 5:06 AM, Yuya Nishihara <yuya@tcha.org> wrote:
>> On Tue, 03 May 2016 21:45:17 -0700, Martin von Zweigbergk via Mercurial-devel wrote:
>>> # HG changeset patch
>>> # User Martin von Zweigbergk <martinvonz@google.com>
>>> # Date 1462294194 25200
>>> #      Tue May 03 09:49:54 2016 -0700
>>> # Node ID 9be8673e90a26c27276b8c8963b86b95a8f184e6
>>> # Parent  8eba4cdcfd810d80785c94d770a442c5bd1f9d0f
>>> templater: add separate() template function
>>>
>>> A pretty common pattern in templates is adding conditional separators
>>> like so:
>>>
>>>   {node}{if(bookmarks, " {bookmarks}")}{if(tags, " {tags}")}
>>>
>>> With this patch, the above can be simplified to:
>>>
>>>   {separate(" ", node, bookmarks, tags)}
>>>
>>> The function is similar to the already existing join(), but with a few
>>> differences:
>>>
>>>  * separate() takes the separator first in order to allow a variable
>>>    number of arguments after it
>>>
>>>  * separate() skips empty arguments
>>>
>>>  * join() expects a single list argument, while separate() accepts
>>>    either a single list or separate arguments
>>>
>>> diff -r 8eba4cdcfd81 -r 9be8673e90a2 mercurial/help/templates.txt
>>> --- a/mercurial/help/templates.txt    Sun Apr 17 12:31:06 2016 +0900
>>> +++ b/mercurial/help/templates.txt    Tue May 03 09:49:54 2016 -0700
>>> @@ -81,6 +81,10 @@
>>>
>>>     $ hg log -r 0 --template "files: {join(files, ', ')}\n"
>>>
>>> +- Separate non-empty arguments by a " "::
>>> +
>>> +   $ hg log -r 0 --template "{separate(' ', node, bookmarks, tags}\n"
>>> +
>>>  - Modify each line of a commit description::
>>>
>>>     $ hg log --template "{splitlines(desc) % '**** {line}\n'}"
>>> diff -r 8eba4cdcfd81 -r 9be8673e90a2 mercurial/templater.py
>>> --- a/mercurial/templater.py  Sun Apr 17 12:31:06 2016 +0900
>>> +++ b/mercurial/templater.py  Tue May 03 09:49:54 2016 -0700
>>> @@ -621,6 +621,28 @@
>>>              yield joiner
>>>          yield x
>>>
>>> +@templatefunc('separate(sep, args)')
>>> +def separate(context, mapping, args):
>>> +    """Add a separator between non-empty arguments."""
>>> +    if not args:
>>> +        # i18n: "separate" is a keyword
>>> +        raise error.ParseError(_("separate expects at least one argument"))
>>> +
>>> +    sep = evalstring(context, mapping, args[0])
>>> +    things = [evalfuncarg(context, mapping, arg) for arg in args[1:]]
>>> +    if len(things) == 1 and util.safehasattr(things[0], 'values'):
>>> +        things = things[0].values
>>
>> Perhaps we can assume things[0] a list if len(args[1:]) == 1.
>> separate(' ', nonlist) is useless just like built-in min(x) function.
>
> I considered that. I don't know which is less surprising, having
> behavior depending on number of arguments or type of arguments (and
> number of them). I could go either way. Any votes in either direction?

At first glance, I'd rather have 'join' become smarter (and it seems
more elegant).

> Btw:
>
> $ hg log -T '{join(1)}'
> [stack trace]
> TypeError: 'int' object is not iterable
>
> Stupid use case, but we should eliminate the stack trace.

Ah, nice catch. I'll leave it to Yuya, hehe.
Yuya Nishihara - May 5, 2016, 3:02 a.m.
On Wed, 04 May 2016 16:14:55 -0700, Sean Farley wrote:
> Martin von Zweigbergk via Mercurial-devel <mercurial-devel@mercurial-scm.org> writes:
> 
> > On Wed, May 4, 2016 at 5:06 AM, Yuya Nishihara <yuya@tcha.org> wrote:  
> >> On Tue, 03 May 2016 21:45:17 -0700, Martin von Zweigbergk via Mercurial-devel wrote:  
> >>> # HG changeset patch
> >>> # User Martin von Zweigbergk <martinvonz@google.com>
> >>> # Date 1462294194 25200
> >>> #      Tue May 03 09:49:54 2016 -0700
> >>> # Node ID 9be8673e90a26c27276b8c8963b86b95a8f184e6
> >>> # Parent  8eba4cdcfd810d80785c94d770a442c5bd1f9d0f
> >>> templater: add separate() template function
> >>>
> >>> A pretty common pattern in templates is adding conditional separators
> >>> like so:
> >>>
> >>>   {node}{if(bookmarks, " {bookmarks}")}{if(tags, " {tags}")}
> >>>
> >>> With this patch, the above can be simplified to:
> >>>
> >>>   {separate(" ", node, bookmarks, tags)}
> >>>
> >>> The function is similar to the already existing join(), but with a few
> >>> differences:
> >>>
> >>>  * separate() takes the separator first in order to allow a variable
> >>>    number of arguments after it
> >>>
> >>>  * separate() skips empty arguments
> >>>
> >>>  * join() expects a single list argument, while separate() accepts
> >>>    either a single list or separate arguments
> >>>
> >>> diff -r 8eba4cdcfd81 -r 9be8673e90a2 mercurial/help/templates.txt
> >>> --- a/mercurial/help/templates.txt    Sun Apr 17 12:31:06 2016 +0900
> >>> +++ b/mercurial/help/templates.txt    Tue May 03 09:49:54 2016 -0700
> >>> @@ -81,6 +81,10 @@
> >>>
> >>>     $ hg log -r 0 --template "files: {join(files, ', ')}\n"
> >>>
> >>> +- Separate non-empty arguments by a " "::
> >>> +
> >>> +   $ hg log -r 0 --template "{separate(' ', node, bookmarks, tags}\n"
> >>> +
> >>>  - Modify each line of a commit description::
> >>>
> >>>     $ hg log --template "{splitlines(desc) % '**** {line}\n'}"
> >>> diff -r 8eba4cdcfd81 -r 9be8673e90a2 mercurial/templater.py
> >>> --- a/mercurial/templater.py  Sun Apr 17 12:31:06 2016 +0900
> >>> +++ b/mercurial/templater.py  Tue May 03 09:49:54 2016 -0700
> >>> @@ -621,6 +621,28 @@
> >>>              yield joiner
> >>>          yield x
> >>>
> >>> +@templatefunc('separate(sep, args)')
> >>> +def separate(context, mapping, args):
> >>> +    """Add a separator between non-empty arguments."""
> >>> +    if not args:
> >>> +        # i18n: "separate" is a keyword
> >>> +        raise error.ParseError(_("separate expects at least one argument"))
> >>> +
> >>> +    sep = evalstring(context, mapping, args[0])
> >>> +    things = [evalfuncarg(context, mapping, arg) for arg in args[1:]]
> >>> +    if len(things) == 1 and util.safehasattr(things[0], 'values'):
> >>> +        things = things[0].values  
> >>
> >> Perhaps we can assume things[0] a list if len(args[1:]) == 1.
> >> separate(' ', nonlist) is useless just like built-in min(x) function.  
> >
> > I considered that. I don't know which is less surprising, having
> > behavior depending on number of arguments or type of arguments (and
> > number of them). I could go either way. Any votes in either direction?

+1 for "by number of arguments". We have "latesttag" that doesn't sound like
a list, but it returns a list.

> At first glance, I'd rather have 'join' become smarter (and it seems
> more elegant).
> 
> > Btw:
> >
> > $ hg log -T '{join(1)}'
> > [stack trace]
> > TypeError: 'int' object is not iterable
> >
> > Stupid use case, but we should eliminate the stack trace.  
> 
> Ah, nice catch. I'll leave it to Yuya, hehe.

Sigh, another example: '{ifcontains("", rev, "")}'

Should we allow '{join(":", node)}' ?
I know ifcontains() supports partial matching in string, but join(str, sep)
seems useless.
via Mercurial-devel - May 5, 2016, 3:19 a.m.
Perhaps we can extend join() after all. When given at least 3 arguments,
the last one can still be the separator and the ones before it are the
items to separate. It's a little weird to have the separator last, but it
does avoid the need for a new name. Thoughts?

On Wed, May 4, 2016, 16:52 Sean Farley <sean@farley.io> wrote:

>
> Martin von Zweigbergk via Mercurial-devel <
> mercurial-devel@mercurial-scm.org> writes:
>
> > On Wed, May 4, 2016 at 5:06 AM, Yuya Nishihara <yuya@tcha.org> wrote:
> >> On Tue, 03 May 2016 21:45:17 -0700, Martin von Zweigbergk via
> Mercurial-devel wrote:
> >>> # HG changeset patch
> >>> # User Martin von Zweigbergk <martinvonz@google.com>
> >>> # Date 1462294194 25200
> >>> #      Tue May 03 09:49:54 2016 -0700
> >>> # Node ID 9be8673e90a26c27276b8c8963b86b95a8f184e6
> >>> # Parent  8eba4cdcfd810d80785c94d770a442c5bd1f9d0f
> >>> templater: add separate() template function
> >>>
> >>> A pretty common pattern in templates is adding conditional separators
> >>> like so:
> >>>
> >>>   {node}{if(bookmarks, " {bookmarks}")}{if(tags, " {tags}")}
> >>>
> >>> With this patch, the above can be simplified to:
> >>>
> >>>   {separate(" ", node, bookmarks, tags)}
> >>>
> >>> The function is similar to the already existing join(), but with a few
> >>> differences:
> >>>
> >>>  * separate() takes the separator first in order to allow a variable
> >>>    number of arguments after it
> >>>
> >>>  * separate() skips empty arguments
> >>>
> >>>  * join() expects a single list argument, while separate() accepts
> >>>    either a single list or separate arguments
> >>>
> >>> diff -r 8eba4cdcfd81 -r 9be8673e90a2 mercurial/help/templates.txt
> >>> --- a/mercurial/help/templates.txt    Sun Apr 17 12:31:06 2016 +0900
> >>> +++ b/mercurial/help/templates.txt    Tue May 03 09:49:54 2016 -0700
> >>> @@ -81,6 +81,10 @@
> >>>
> >>>     $ hg log -r 0 --template "files: {join(files, ', ')}\n"
> >>>
> >>> +- Separate non-empty arguments by a " "::
> >>> +
> >>> +   $ hg log -r 0 --template "{separate(' ', node, bookmarks, tags}\n"
> >>> +
> >>>  - Modify each line of a commit description::
> >>>
> >>>     $ hg log --template "{splitlines(desc) % '**** {line}\n'}"
> >>> diff -r 8eba4cdcfd81 -r 9be8673e90a2 mercurial/templater.py
> >>> --- a/mercurial/templater.py  Sun Apr 17 12:31:06 2016 +0900
> >>> +++ b/mercurial/templater.py  Tue May 03 09:49:54 2016 -0700
> >>> @@ -621,6 +621,28 @@
> >>>              yield joiner
> >>>          yield x
> >>>
> >>> +@templatefunc('separate(sep, args)')
> >>> +def separate(context, mapping, args):
> >>> +    """Add a separator between non-empty arguments."""
> >>> +    if not args:
> >>> +        # i18n: "separate" is a keyword
> >>> +        raise error.ParseError(_("separate expects at least one
> argument"))
> >>> +
> >>> +    sep = evalstring(context, mapping, args[0])
> >>> +    things = [evalfuncarg(context, mapping, arg) for arg in args[1:]]
> >>> +    if len(things) == 1 and util.safehasattr(things[0], 'values'):
> >>> +        things = things[0].values
> >>
> >> Perhaps we can assume things[0] a list if len(args[1:]) == 1.
> >> separate(' ', nonlist) is useless just like built-in min(x) function.
> >
> > I considered that. I don't know which is less surprising, having
> > behavior depending on number of arguments or type of arguments (and
> > number of them). I could go either way. Any votes in either direction?
>
> At first glance, I'd rather have 'join' become smarter (and it seems
> more elegant).
>
> > Btw:
> >
> > $ hg log -T '{join(1)}'
> > [stack trace]
> > TypeError: 'int' object is not iterable
> >
> > Stupid use case, but we should eliminate the stack trace.
>
> Ah, nice catch. I'll leave it to Yuya, hehe.
>
Yuya Nishihara - May 5, 2016, 3:49 a.m.
On Thu, 05 May 2016 03:19:35 +0000, Martin von Zweigbergk wrote:
> Perhaps we can extend join() after all. When given at least 3 arguments,
> the last one can still be the separator and the ones before it are the
> items to separate. It's a little weird to have the separator last, but it
> does avoid the need for a new name. Thoughts?

or introduce a list constructor?

  join(items(node, bookmarks, tags), " ")
  items(node, bookmarks, tags) % "{item} "

No idea if it should implicitly drop empty items.
via Mercurial-devel - May 5, 2016, 4:36 a.m.
On Wed, May 4, 2016 at 8:51 PM Yuya Nishihara <yuya@tcha.org> wrote:

> On Thu, 05 May 2016 03:19:35 +0000, Martin von Zweigbergk wrote:
> > Perhaps we can extend join() after all. When given at least 3 arguments,
> > the last one can still be the separator and the ones before it are the
> > items to separate. It's a little weird to have the separator last, but it
> > does avoid the need for a new name. Thoughts?
>
> or introduce a list constructor?
>
>   join(items(node, bookmarks, tags), " ")
>   items(node, bookmarks, tags) % "{item} "
>
> No idea if it should implicitly drop empty items.
>

Right, I just remembered that point too. So a new function is probably best.

So the difference between your proposal and Matt's is when two arguments
are given and the second one is not a list, for example:

separate(" ", rev)
separate(" ", branch)

With Matt's proposal, they would be allowed (and pointless) and with your
proposal they would be disallowed. Although pointless, I think it's a small
win that separate(" ", rev, branch) can be simplified into one of the above
and still work without forcing the user to clean it up by dropping the call
to separate. Perhaps the user has a group of similar calls and wants to
keep the separate() for consistency.

Also note that, with either proposal, these two behave quite differently:

separate(":", node, branches)
separate(":", branches)
Yuya Nishihara - May 5, 2016, 7:22 a.m.
On Thu, 05 May 2016 04:36:14 +0000, Martin von Zweigbergk wrote:
> On Wed, May 4, 2016 at 8:51 PM Yuya Nishihara <yuya@tcha.org> wrote:
> > On Thu, 05 May 2016 03:19:35 +0000, Martin von Zweigbergk wrote:  
> > > Perhaps we can extend join() after all. When given at least 3 arguments,
> > > the last one can still be the separator and the ones before it are the
> > > items to separate. It's a little weird to have the separator last, but it
> > > does avoid the need for a new name. Thoughts?  
> >
> > or introduce a list constructor?
> >
> >   join(items(node, bookmarks, tags), " ")
> >   items(node, bookmarks, tags) % "{item} "
> >
> > No idea if it should implicitly drop empty items.
> 
> Right, I just remembered that point too. So a new function is probably best.
> 
> So the difference between your proposal and Matt's is when two arguments
> are given and the second one is not a list, for example:
> 
> separate(" ", rev)
> separate(" ", branch)
> 
> With Matt's proposal, they would be allowed (and pointless) and with your
> proposal they would be disallowed. Although pointless, I think it's a small
> win that separate(" ", rev, branch) can be simplified into one of the above
> and still work without forcing the user to clean it up by dropping the call
> to separate. Perhaps the user has a group of similar calls and wants to
> keep the separate() for consistency.
> 
> Also note that, with either proposal, these two behave quite differently:
> 
> separate(":", node, branches)
> separate(":", branches)

Yeah, that's one reason I insist that separate(":", rev) should be disallowed.
If both separate(":", rev) and separate(":", branches) are allowed, I would
expect that separate() is the function to flatten lists.

  separate(":", rev)             # {rev}
  separate(":", branches)        # {branch0}:{branch1}
  separate(":", branches, tags)  # {branch0}:{branch1}:{tag0}:{tag1}
via Mercurial-devel - May 5, 2016, 2:22 p.m.
I'm tempted to just do support for list arguments and let the user use
join() for those. What do others think? Matt?

On Thu, May 5, 2016, 00:24 Yuya Nishihara <yuya@tcha.org> wrote:

> On Thu, 05 May 2016 04:36:14 +0000, Martin von Zweigbergk wrote:
> > On Wed, May 4, 2016 at 8:51 PM Yuya Nishihara <yuya@tcha.org> wrote:
> > > On Thu, 05 May 2016 03:19:35 +0000, Martin von Zweigbergk wrote:
> > > > Perhaps we can extend join() after all. When given at least 3
> arguments,
> > > > the last one can still be the separator and the ones before it are
> the
> > > > items to separate. It's a little weird to have the separator last,
> but it
> > > > does avoid the need for a new name. Thoughts?
> > >
> > > or introduce a list constructor?
> > >
> > >   join(items(node, bookmarks, tags), " ")
> > >   items(node, bookmarks, tags) % "{item} "
> > >
> > > No idea if it should implicitly drop empty items.
> >
> > Right, I just remembered that point too. So a new function is probably
> best.
> >
> > So the difference between your proposal and Matt's is when two arguments
> > are given and the second one is not a list, for example:
> >
> > separate(" ", rev)
> > separate(" ", branch)
> >
> > With Matt's proposal, they would be allowed (and pointless) and with your
> > proposal they would be disallowed. Although pointless, I think it's a
> small
> > win that separate(" ", rev, branch) can be simplified into one of the
> above
> > and still work without forcing the user to clean it up by dropping the
> call
> > to separate. Perhaps the user has a group of similar calls and wants to
> > keep the separate() for consistency.
> >
> > Also note that, with either proposal, these two behave quite differently:
> >
> > separate(":", node, branches)
> > separate(":", branches)
>
> Yeah, that's one reason I insist that separate(":", rev) should be
> disallowed.
> If both separate(":", rev) and separate(":", branches) are allowed, I would
> expect that separate() is the function to flatten lists.
>
>   separate(":", rev)             # {rev}
>   separate(":", branches)        # {branch0}:{branch1}
>   separate(":", branches, tags)  # {branch0}:{branch1}:{tag0}:{tag1}
>
via Mercurial-devel - May 5, 2016, 2:23 p.m.
Sorry, s/do support/drop support/

On Thu, May 5, 2016, 07:22 Martin von Zweigbergk <martinvonz@google.com>
wrote:

> I'm tempted to just do support for list arguments and let the user use
> join() for those. What do others think? Matt?
>
> On Thu, May 5, 2016, 00:24 Yuya Nishihara <yuya@tcha.org> wrote:
>
>> On Thu, 05 May 2016 04:36:14 +0000, Martin von Zweigbergk wrote:
>> > On Wed, May 4, 2016 at 8:51 PM Yuya Nishihara <yuya@tcha.org> wrote:
>> > > On Thu, 05 May 2016 03:19:35 +0000, Martin von Zweigbergk wrote:
>> > > > Perhaps we can extend join() after all. When given at least 3
>> arguments,
>> > > > the last one can still be the separator and the ones before it are
>> the
>> > > > items to separate. It's a little weird to have the separator last,
>> but it
>> > > > does avoid the need for a new name. Thoughts?
>> > >
>> > > or introduce a list constructor?
>> > >
>> > >   join(items(node, bookmarks, tags), " ")
>> > >   items(node, bookmarks, tags) % "{item} "
>> > >
>> > > No idea if it should implicitly drop empty items.
>> >
>> > Right, I just remembered that point too. So a new function is probably
>> best.
>> >
>> > So the difference between your proposal and Matt's is when two arguments
>> > are given and the second one is not a list, for example:
>> >
>> > separate(" ", rev)
>> > separate(" ", branch)
>> >
>> > With Matt's proposal, they would be allowed (and pointless) and with
>> your
>> > proposal they would be disallowed. Although pointless, I think it's a
>> small
>> > win that separate(" ", rev, branch) can be simplified into one of the
>> above
>> > and still work without forcing the user to clean it up by dropping the
>> call
>> > to separate. Perhaps the user has a group of similar calls and wants to
>> > keep the separate() for consistency.
>> >
>> > Also note that, with either proposal, these two behave quite
>> differently:
>> >
>> > separate(":", node, branches)
>> > separate(":", branches)
>>
>> Yeah, that's one reason I insist that separate(":", rev) should be
>> disallowed.
>> If both separate(":", rev) and separate(":", branches) are allowed, I
>> would
>> expect that separate() is the function to flatten lists.
>>
>>   separate(":", rev)             # {rev}
>>   separate(":", branches)        # {branch0}:{branch1}
>>   separate(":", branches, tags)  # {branch0}:{branch1}:{tag0}:{tag1}
>>
>
via Mercurial-devel - May 5, 2016, 8:50 p.m.
I'll send a V3 with no special treatment of lists. That seems safest
to me. We can add support for lists before the next release, or after
if we can accept the minor BC breakage.

On Thu, May 5, 2016 at 7:23 AM, Martin von Zweigbergk
<martinvonz@google.com> wrote:
> Sorry, s/do support/drop support/
>
>
> On Thu, May 5, 2016, 07:22 Martin von Zweigbergk <martinvonz@google.com>
> wrote:
>>
>> I'm tempted to just do support for list arguments and let the user use
>> join() for those. What do others think? Matt?
>>
>> On Thu, May 5, 2016, 00:24 Yuya Nishihara <yuya@tcha.org> wrote:
>>>
>>> On Thu, 05 May 2016 04:36:14 +0000, Martin von Zweigbergk wrote:
>>> > On Wed, May 4, 2016 at 8:51 PM Yuya Nishihara <yuya@tcha.org> wrote:
>>> > > On Thu, 05 May 2016 03:19:35 +0000, Martin von Zweigbergk wrote:
>>> > > > Perhaps we can extend join() after all. When given at least 3
>>> > > > arguments,
>>> > > > the last one can still be the separator and the ones before it are
>>> > > > the
>>> > > > items to separate. It's a little weird to have the separator last,
>>> > > > but it
>>> > > > does avoid the need for a new name. Thoughts?
>>> > >
>>> > > or introduce a list constructor?
>>> > >
>>> > >   join(items(node, bookmarks, tags), " ")
>>> > >   items(node, bookmarks, tags) % "{item} "
>>> > >
>>> > > No idea if it should implicitly drop empty items.
>>> >
>>> > Right, I just remembered that point too. So a new function is probably
>>> > best.
>>> >
>>> > So the difference between your proposal and Matt's is when two
>>> > arguments
>>> > are given and the second one is not a list, for example:
>>> >
>>> > separate(" ", rev)
>>> > separate(" ", branch)
>>> >
>>> > With Matt's proposal, they would be allowed (and pointless) and with
>>> > your
>>> > proposal they would be disallowed. Although pointless, I think it's a
>>> > small
>>> > win that separate(" ", rev, branch) can be simplified into one of the
>>> > above
>>> > and still work without forcing the user to clean it up by dropping the
>>> > call
>>> > to separate. Perhaps the user has a group of similar calls and wants to
>>> > keep the separate() for consistency.
>>> >
>>> > Also note that, with either proposal, these two behave quite
>>> > differently:
>>> >
>>> > separate(":", node, branches)
>>> > separate(":", branches)
>>>
>>> Yeah, that's one reason I insist that separate(":", rev) should be
>>> disallowed.
>>> If both separate(":", rev) and separate(":", branches) are allowed, I
>>> would
>>> expect that separate() is the function to flatten lists.
>>>
>>>   separate(":", rev)             # {rev}
>>>   separate(":", branches)        # {branch0}:{branch1}
>>>   separate(":", branches, tags)  # {branch0}:{branch1}:{tag0}:{tag1}

Patch

diff -r 8eba4cdcfd81 -r 9be8673e90a2 mercurial/help/templates.txt
--- a/mercurial/help/templates.txt	Sun Apr 17 12:31:06 2016 +0900
+++ b/mercurial/help/templates.txt	Tue May 03 09:49:54 2016 -0700
@@ -81,6 +81,10 @@ 
 
    $ hg log -r 0 --template "files: {join(files, ', ')}\n"
 
+- Separate non-empty arguments by a " "::
+
+   $ hg log -r 0 --template "{separate(' ', node, bookmarks, tags}\n"
+
 - Modify each line of a commit description::
 
    $ hg log --template "{splitlines(desc) % '**** {line}\n'}"
diff -r 8eba4cdcfd81 -r 9be8673e90a2 mercurial/templater.py
--- a/mercurial/templater.py	Sun Apr 17 12:31:06 2016 +0900
+++ b/mercurial/templater.py	Tue May 03 09:49:54 2016 -0700
@@ -621,6 +621,28 @@ 
             yield joiner
         yield x
 
+@templatefunc('separate(sep, args)')
+def separate(context, mapping, args):
+    """Add a separator between non-empty arguments."""
+    if not args:
+        # i18n: "separate" is a keyword
+        raise error.ParseError(_("separate expects at least one argument"))
+
+    sep = evalstring(context, mapping, args[0])
+    things = [evalfuncarg(context, mapping, arg) for arg in args[1:]]
+    if len(things) == 1 and util.safehasattr(things[0], 'values'):
+        things = things[0].values
+    first = True
+    for thing in things:
+        argstr = stringify(thing)
+        if not argstr:
+            continue
+        if first:
+            first = False
+        else:
+            yield sep
+        yield argstr
+
 @templatefunc('label(label, expr)')
 def label(context, mapping, args):
     """Apply a label to generated content. Content with
diff -r 8eba4cdcfd81 -r 9be8673e90a2 tests/test-command-template.t
--- a/tests/test-command-template.t	Sun Apr 17 12:31:06 2016 +0900
+++ b/tests/test-command-template.t	Tue May 03 09:49:54 2016 -0700
@@ -3320,6 +3320,19 @@ 
   hg: parse error: pad() expects an integer width
   [255]
 
+Test separate function
+
+  $ hg log -r 0 -T '{separate("-", "", "a", "b", "", "", "c", "")}\n'
+  a-b-c
+  $ hg log -r 0 -T '{separate(" ", "{rev}:{node|short}", author|user, branch)}\n'
+  0:f7769ec2ab97 test default
+  $ hg log -r 0 --color=always -T '{separate(" ", "a", label(red, "b"), "c", label(red, ""), "d")}\n'
+  a \x1b[0;31mb\x1b[0m c d (esc)
+  $ hg log -T '{rev} *{separate(":", files)}*\n'
+  2 *aa:b*
+  1 **
+  0 *a*
+
 Test ifcontains function
 
   $ hg log --template '{rev} {ifcontains(rev, "2 two 0", "is in the string", "is not")}\n'