Commit Graph

285 Commits

Author SHA1 Message Date
Martin von Zweigbergk
f553986d8e 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() skips empty arguments

 * join() expects a single list argument, while separate() expects
   each item as a separate argument

 * separate() takes the separator first in order to allow a variable
   number of arguments after it
2016-05-03 09:49:54 -07:00
Yuya Nishihara
ec53346d72 templater: load and expand aliases by template engine (API) (issue4842)
Now template aliases are fully supported in log and formatter templates.

As I said before, aliases are not expanded in map files. This avoids possible
corruption of our stock styles and web templates. This behavior is undocumented
since no map file nor [templates] section are documented at all. Later on,
we might want to add [aliases] section to map files if it appears to be useful.
2016-03-27 20:59:36 +09:00
Yuya Nishihara
97c46852fc templater: inline compiletemplate() function into engine
This allows the template engine to modify parsed tree.
2016-04-03 13:23:40 +09:00
Yuya Nishihara
3f981af86b templater: separate function to create templater from map file (API)
New frommapfile() function will make it clear when template aliases will be
loaded. They should be applied to command arguments and templates in hgrc,
but not to map files. Otherwise, our stock styles and web templates
(i.e map-file templates) could be modified unintentionally.

Future patches will add "aliases" argument to __init__(), but not to
frommapfile().
2016-04-03 23:26:48 +09:00
Yuya Nishihara
7c3e18d9fe templater: extract function that loads template map file
Prepares for API change. See the next patch for details.

'map' variable is renamed to avoid shadowing map() function.
2016-04-03 23:18:30 +09:00
Yuya Nishihara
dda7a93bdf templater: demote "base" directory of map file to local variable
It isn't referenced from other places.
2016-04-03 23:22:43 +09:00
Yuya Nishihara
d00a0ddedf templater: add parsing and expansion rules to process "templatealias" section
The debugtemplate command is updated to show expanded tree, but still the
template engine doesn't support alias expansion. That's why the test says
"parse error" for now.
2016-03-27 20:31:56 +09:00
Yuya Nishihara
b9d70a8ce1 templater: add function to parse whole string as template expression
This will be a parser of template aliases, and it can also be used for
processing quoted string templates in map files. That's why this function
isn't defined in the upcoming _aliasrules class.
2016-03-27 20:29:03 +09:00
Yuya Nishihara
72e05b7594 templater: drop deprecated handling of KeyError from changeset_templater
It's been superseded by 50f9d20d4c27 and the previous patch. templater.mapfile
is no longer used and removed.
2016-04-03 11:23:31 +09:00
Yuya Nishihara
d6788adafc templater: give better error message for invalid engine type
Before, KeyError was caught at changeset_templater._show(), which said "no
key named '%s'" as it was intended to catch the KeyError of unknown map key.
Instead, we should catch KeyError explicitly for better error indication.

For those who don't know what the template engine is (read "everyone"), it is
hidden extension feature that allows switching template syntax in map file.
See b901d7e82888 for details.
2016-04-03 11:20:50 +09:00
FUJIWARA Katsunori
4f1a0e917c templater: use templatefunc to mark a function as template function
Using decorator can localize changes for adding (or removing) a
template function in source code.

This patch also removes leading ":FUNC(ARG...):" part in help document
of each function, because using templatefunc makes it useless.

This patch uses not 'func' but 'templatefunc' as a decorator name,
because the former is too generic one, even though the latter is a
little redundant in 'templater.py'.
2016-03-30 02:10:44 +09:00
FUJIWARA Katsunori
7a6d96d902 registrar: add templatefunc to mark a function as template function (API)
This patch also adds loadfunction() to templater, because this
combination helps to figure out how they cooperate with each other.

Listing up loadfunction() in dispatch.extraloaders causes implicit
loading template function at loading (3rd party) extension.

This patch explicitly tests whether templatefunc decorator works as
expected, because there is no bundled extension, which defines
template function.

This change requires that "templatefunc" attribute of (3rd party)
extension is registrar.templatefunc or so.
2016-03-30 02:10:44 +09:00
Yuya Nishihara
a32c974037 templater: do not strip non-quote characters from template config
Before this patch, the first and last characters were stripped from
ui.logtemplate and template.* if they were the same. It could lead to a
strange result as quotes are optional. See the test for example.
2016-03-27 17:42:19 +09:00
Yuya Nishihara
634500f64e templater: relax unquotestring() to fall back to bare string
This is convenient for our use case where quotes are optional except in
a map file.
2016-03-26 18:12:12 +09:00
Yuya Nishihara
f0125a308f templater: do not abuse SyntaxError to report errors in template map file
SyntaxError is the class representing syntax errors in Python code. We should
use a dedicated exception class for our needs. With this change, unnecessary
re-wrapping of SyntaxError can be eliminated.
2016-03-26 18:01:04 +09:00
Yuya Nishihara
e489dfcaf7 templater: expand list of parsed templates to template node
This patch eliminates a nested data structure other than the parsed tree.

  ('template', [(op, data), ..]) -> ('template', (op, data), ..)

New expanded tree can be processed by common parser functions. This change
will help implementing template aliases.

Because a (template ..) node should have at least one child node, an empty
template (template []) is mapped to (string ''). Also a trivial string
(template [(string ..)]) node is unwrapped to (string ..) at parsing phase,
instead of compiling phase.
2016-02-13 23:20:47 +09:00
Yuya Nishihara
633de11fa7 templater: relax type of mapped template
Now compiled template fragments are packed into a generic type, (func, data),
a string can be a valid template. This change allows us to unwrap a trivial
string node. See the next patch for details.
2016-02-14 15:42:49 +09:00
Yuya Nishihara
402c2c88bb templater: lift parsed and compiled templates to generic data types
Before this patch, parsed and compiled templates were kept as lists. That
was inconvenient for applying transformation such as alias expansion.

This patch changes the types of the outermost objects as follows:

  stage     old             new
  --------  --------------  ------------------------------
  parsed    [(op, ..)]      ('template', [(op, ..)])
  compiled  [(func, data)]  (runtemplate, [(func, data)])

New templater.parse() function has the same signature as revset.parse()
and fileset.parse().
2016-02-13 23:54:24 +09:00
Yuya Nishihara
823c900008 templater: make label() just fail if ui object isn't available
Silent failure hides bugs and makes it harder to track down the issue. It's
worse than raising exception.

In future patches, I plan to sort out template functions that require 'ui',
'ctx', 'fctx', etc. so that incompatible functions are excluded and the doc can
say in which context these functions are usable.

  @templatefunc('label', requires=('ui',))
  def label(context, mapping, args):
      ...
2016-03-09 23:59:26 +09:00
timeless
4ff3d8a36b templater: ignore orig/rej files
If your mercurial/templates/ directory is dirty, then the template system would
otherwise import duplicate templates from the .orig files and potentially try to
parse .rej files.

Since editing/reverting these templates isn't an unexpected action, and since
they're in .hgignore, it's best that the template system know to skip them."
2016-03-02 05:27:07 +00:00
Kostia Balytskyi
e31d7d20ae formatter: make labels work with templated output
To describe the bug this fix is addressing, one can do
   ``$ hg status -T "{label('red', path)}\n" --color=debug``
and observe that the label is not applied before my fix and applied with it.
2016-03-08 04:08:33 -08:00
Yuya Nishihara
b20edef8a1 templater: move label() function from color extension
ui.label() is no-op by default, so we can just call ui.label() by label()
template function no matter if the color is enabled or not.
2015-06-11 23:04:14 +09:00
Yuya Nishihara
e7ec9f2344 templater: make label() take unknown symbol as color literal
Instead of the mapping hack introduced by d4686e0c15c9, this patch changes the
way how a label symbol is evaluated. This is still hackish, but should be more
predictable in that it doesn't depend on the known color effects.

This change is intended to eliminate the reference to color._effects so that
color.templatelabel() can be merged with templater.label().
2015-06-11 22:58:27 +09:00
Yuya Nishihara
e7f2a2e46a templater: handle exception when applying map operator to non-iterable object
Before this, "{noniterable % template}" raised an exception. This tries to
provide a better indication for the common case, where a left-hand-side
expression is a keyword.
2016-02-14 13:58:46 +09:00
Yuya Nishihara
a0a8a0401c templater: factor out thin helper that evaluates argument as string
This function is trivial, but it can make it clear that args[i] is a
(func, data) pair.
2016-02-14 13:30:32 +09:00
Yuya Nishihara
40ff3175fd templater: fix shortest() to evaluate int argument and handle error 2016-02-14 13:09:17 +09:00
Yuya Nishihara
7bcf850662 templater: fix pad() to evaluate int argument and handle error 2016-02-14 13:05:09 +09:00
Yuya Nishihara
608ccd9ef6 templater: drop redundant type conversion when evaluating integer argument
A function argument may be an integer. In this case, it isn't necessary to
convert a value to string and back to integer.

Because an argument may be an arbitrary object (e.g. date tuple), TypeError
should be caught as well.
2016-02-14 12:48:14 +09:00
Yuya Nishihara
077f7d6378 templater: factor out function that evaluates argument as integer
We have more bare int()s that may raise ValueError or TypeError. This function
will be used to fix them.
2016-02-14 12:42:25 +09:00
Yuya Nishihara
bbfa1c1944 templater: make date() use helper function to evaluate argument
A date argument should never be a generator, but using evalfuncarg() should
be good for consistency.
2016-02-14 13:36:50 +09:00
Yuya Nishihara
946f6161fa templater: fix revset() to evaluate format arguments eagerly
See the previous patch for why. This patch also removes redundant list()
construction from a list.
2016-02-14 00:27:09 +09:00
Yuya Nishihara
f96cf1c058 templater: fix ifcontains() to evaluate items argument eagerly
See the previous patch for why. An "items" argument may be a string,
a generator, or an arbitrary container object.
2016-02-14 00:18:12 +09:00
Yuya Nishihara
85d9fe1400 templater: fix get() to evaluate arguments eagerly
If a key is constructed from a template expression, it may be a generator.
In that case, a key have to be stringified.

A dictarg should never be a generator, but this patch also changes it to
call evalfuncarg() for consistency.
2016-02-14 00:05:58 +09:00
Kostia Balytskyi
27393a4469 templater: fix list templating bug
High-level use case: printing a list of objects with formatter
when each object in turn contains a list of properties (like
when % template symbol is used in {things % '{thing}'}

Let the top-level list contain one thing with two properties:
objs = [{
   'props': [
     { 'value': 1, 'show': 1 },
     { 'value': 2 }]
}]

(please note that second property does not have 'show' key)

If a templateformatter is used to print this with template
  "{props % '{if(show, value)}'}"
current implementation will print value for both properties,
which is a bug. This happens because in `templater.runmap`
function we only rewrite mapping values with existing new
values for each item. If some mapping value is missing in
the item, it will not be removed.
2016-02-24 19:31:55 +00:00
Yuya Nishihara
e51bed3d10 templater: factor out type conversion of revset() result
This makes it clear why we have to do repo[int(x)].
2016-02-12 18:39:48 +09:00
Yuya Nishihara
1f60c51fa5 templater: abort if infinite recursion detected while compiling
In this case, a template is parsed recursively with no thunk for lazy
evaluation. This patch prevents recursion by putting a dummy of the same name
into a cache that will be referenced while parsing if there's a recursion.

  changeset = {files % changeset}\n
                       ~~~~~~~~~
                        = [(_runrecursivesymbol, 'changeset')]
2015-07-23 23:41:29 +09:00
Yuya Nishihara
554208ae8f templater: abort if infinite recursion detected while evaluation (issue4758)
It would be nice if we could detect recursion at the parsing phase, but we
can't because a template can refer to a keyword of the same name. For example,
"rev = {rev}" is valid if rev is a keyword, and we don't know if rev is a
keyword or a template while parsing.
2015-07-22 23:29:41 +09:00
Yuya Nishihara
e2a7f69128 templater: make get(dict, key) return a single value
This is necessary to obtain a _hybrid object from a dict. If get() yields
a value, it would be stringified.

I see no benefit to make get() lazy, so this patch just changes "yield" to
"return".
2016-01-16 13:53:32 +09:00
Yuya Nishihara
20aed11e76 templater: make _hybrid not callable to avoid conflicting semantics
In templater, a callable symbol exists for lazy evaluation, which should have
f(**mapping) signature. On the other hand, _hybrid.__call__(), which was
introduced by 4e182fb53989, generates mapping for each element.

This patch renames _hybrid.__call__() to _hybrid.itermaps() so that a _hybrid
object can be a value of a mapping dict.

  {namespaces % "{namespace}: {names % "{name }"}\n"}
                               ~~~~~
                               a _hybrid object
2016-01-16 13:42:37 +09:00
timeless
ebb1d48658 cleanup: remove superfluous space after space after equals (python) 2015-12-31 08:16:59 +00:00
timeless
7451a5cbc6 grammar: favor zero, one, two over ... or no 2015-11-30 19:30:16 +00:00
Pierre-Yves David
30913031d4 error: get Abort from 'error' instead of 'util'
The home of 'Abort' is 'error' not 'util' however, a lot of code seems to be
confused about that and gives all the credit to 'util' instead of the
hardworking 'error'. In a spirit of equity, we break the cycle of injustice and
give back to 'error' the respect it deserves. And screw that 'util' poser.

For great justice.
2015-10-08 12:55:45 -07:00
Matt Mackall
3302d0eab4 merge with stable 2015-10-08 17:44:22 -05:00
Yuya Nishihara
3bbeb95ce8 templater: do not pre-evaluate generator keyword at runsymbol (issue4868)
It was introduced by 236440938a03, but the important code was removed by
fcf2407610f4. So there was no positive effect other than exhausting memory.

The problem spotted by 236440938a03 is that you can't use a generator keyword
more than once. For example, in hgweb template, "{child} {child}" doesn't work
because the first "{child}" consumes the generator. But as fcf2407610f4 says,
the fix was wrong because it could overwrite a callable keyword that returns
a generator. Also the fix didn't work for a generator of generator such as
"{diff}" keyword. So, the proper fix for that problem would be to not put
a generator in a keyword table. Instead, it should be a factory of a generator.

Note that this should fix the memory issue in hgweb, but my firefox killed by
OOM in place. Be careful to not use a modern web browser to test the issue4868.
2015-10-08 23:24:38 +09:00
Matt Harbison
16d4ee35c6 templater: protect word() from crashing on out of range negative value
The function isn't documented to work with negative values at all, but it does,
which can be useful.  However, the range check didn't account for this.
2015-10-05 12:37:26 -04:00
Matt Mackall
86f1841884 merge with stable 2015-10-07 13:44:48 -05:00
Matt Harbison
99689724c6 templater: introduce {latesttag()} function to match a pattern (issue4184)
This allows the latest class of tag to be found, such as a release candidate or
final build, instead of just the absolute latest.

It doesn't appear that the existing keyword can be given an optional argument.
There is a keyword, function and filter for 'date', so it doesn't seem harmful
to introduce a new function with the same name as an existing keyword.  Most
functions are pretty Mercurial agnostic, but there is {revset()} as precedent.

Even though templatekw.getlatesttags() returns a single tuple, one entry of
which is a list, it is simplest to present this as a list of tags instead of a
single item, with each tag having a distance and change count attribute.  It is
also closer to how {latesttag} returns a list of tags, and how this function
works when not given a '%' operator.
2015-08-23 23:44:58 -04:00
Pierre-Yves David
6988077eb7 templater: remove a mutable default argument
Mutable default arguments are know to the state of California to cause bugs.
2015-09-24 01:07:09 -07:00
Pierre-Yves David
031cb65d89 templater: remove a mutable default argument
Mutable default arguments are know to the state of California to cause bugs.
2015-09-24 01:06:41 -07:00
Pierre-Yves David
3ea92affd9 templater: remove a mutable default argument
Mutable default arguments are know to the state of California to cause bugs.
2015-09-24 01:06:10 -07:00