##// END OF EJS Templates
templatefuncs: declare resource requirements for future use
Yuya Nishihara -
r38447:aa98392e default
parent child Browse files
Show More
@@ -1,439 +1,444
1 # registrar.py - utilities to register function for specific purpose
1 # registrar.py - utilities to register function for specific purpose
2 #
2 #
3 # Copyright FUJIWARA Katsunori <foozy@lares.dti.ne.jp> and others
3 # Copyright FUJIWARA Katsunori <foozy@lares.dti.ne.jp> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 from . import (
10 from . import (
11 configitems,
11 configitems,
12 error,
12 error,
13 pycompat,
13 pycompat,
14 util,
14 util,
15 )
15 )
16
16
17 # unlike the other registered items, config options are neither functions or
17 # unlike the other registered items, config options are neither functions or
18 # classes. Registering the option is just small function call.
18 # classes. Registering the option is just small function call.
19 #
19 #
20 # We still add the official API to the registrar module for consistency with
20 # We still add the official API to the registrar module for consistency with
21 # the other items extensions want might to register.
21 # the other items extensions want might to register.
22 configitem = configitems.getitemregister
22 configitem = configitems.getitemregister
23
23
24 class _funcregistrarbase(object):
24 class _funcregistrarbase(object):
25 """Base of decorator to register a function for specific purpose
25 """Base of decorator to register a function for specific purpose
26
26
27 This decorator stores decorated functions into own dict 'table'.
27 This decorator stores decorated functions into own dict 'table'.
28
28
29 The least derived class can be defined by overriding 'formatdoc',
29 The least derived class can be defined by overriding 'formatdoc',
30 for example::
30 for example::
31
31
32 class keyword(_funcregistrarbase):
32 class keyword(_funcregistrarbase):
33 _docformat = ":%s: %s"
33 _docformat = ":%s: %s"
34
34
35 This should be used as below:
35 This should be used as below:
36
36
37 keyword = registrar.keyword()
37 keyword = registrar.keyword()
38
38
39 @keyword('bar')
39 @keyword('bar')
40 def barfunc(*args, **kwargs):
40 def barfunc(*args, **kwargs):
41 '''Explanation of bar keyword ....
41 '''Explanation of bar keyword ....
42 '''
42 '''
43 pass
43 pass
44
44
45 In this case:
45 In this case:
46
46
47 - 'barfunc' is stored as 'bar' in '_table' of an instance 'keyword' above
47 - 'barfunc' is stored as 'bar' in '_table' of an instance 'keyword' above
48 - 'barfunc.__doc__' becomes ":bar: Explanation of bar keyword"
48 - 'barfunc.__doc__' becomes ":bar: Explanation of bar keyword"
49 """
49 """
50 def __init__(self, table=None):
50 def __init__(self, table=None):
51 if table is None:
51 if table is None:
52 self._table = {}
52 self._table = {}
53 else:
53 else:
54 self._table = table
54 self._table = table
55
55
56 def __call__(self, decl, *args, **kwargs):
56 def __call__(self, decl, *args, **kwargs):
57 return lambda func: self._doregister(func, decl, *args, **kwargs)
57 return lambda func: self._doregister(func, decl, *args, **kwargs)
58
58
59 def _doregister(self, func, decl, *args, **kwargs):
59 def _doregister(self, func, decl, *args, **kwargs):
60 name = self._getname(decl)
60 name = self._getname(decl)
61
61
62 if name in self._table:
62 if name in self._table:
63 msg = 'duplicate registration for name: "%s"' % name
63 msg = 'duplicate registration for name: "%s"' % name
64 raise error.ProgrammingError(msg)
64 raise error.ProgrammingError(msg)
65
65
66 if func.__doc__ and not util.safehasattr(func, '_origdoc'):
66 if func.__doc__ and not util.safehasattr(func, '_origdoc'):
67 doc = pycompat.sysbytes(func.__doc__).strip()
67 doc = pycompat.sysbytes(func.__doc__).strip()
68 func._origdoc = doc
68 func._origdoc = doc
69 func.__doc__ = pycompat.sysstr(self._formatdoc(decl, doc))
69 func.__doc__ = pycompat.sysstr(self._formatdoc(decl, doc))
70
70
71 self._table[name] = func
71 self._table[name] = func
72 self._extrasetup(name, func, *args, **kwargs)
72 self._extrasetup(name, func, *args, **kwargs)
73
73
74 return func
74 return func
75
75
76 def _parsefuncdecl(self, decl):
76 def _parsefuncdecl(self, decl):
77 """Parse function declaration and return the name of function in it
77 """Parse function declaration and return the name of function in it
78 """
78 """
79 i = decl.find('(')
79 i = decl.find('(')
80 if i >= 0:
80 if i >= 0:
81 return decl[:i]
81 return decl[:i]
82 else:
82 else:
83 return decl
83 return decl
84
84
85 def _getname(self, decl):
85 def _getname(self, decl):
86 """Return the name of the registered function from decl
86 """Return the name of the registered function from decl
87
87
88 Derived class should override this, if it allows more
88 Derived class should override this, if it allows more
89 descriptive 'decl' string than just a name.
89 descriptive 'decl' string than just a name.
90 """
90 """
91 return decl
91 return decl
92
92
93 _docformat = None
93 _docformat = None
94
94
95 def _formatdoc(self, decl, doc):
95 def _formatdoc(self, decl, doc):
96 """Return formatted document of the registered function for help
96 """Return formatted document of the registered function for help
97
97
98 'doc' is '__doc__.strip()' of the registered function.
98 'doc' is '__doc__.strip()' of the registered function.
99 """
99 """
100 return self._docformat % (decl, doc)
100 return self._docformat % (decl, doc)
101
101
102 def _extrasetup(self, name, func):
102 def _extrasetup(self, name, func):
103 """Execute exra setup for registered function, if needed
103 """Execute exra setup for registered function, if needed
104 """
104 """
105
105
106 class command(_funcregistrarbase):
106 class command(_funcregistrarbase):
107 """Decorator to register a command function to table
107 """Decorator to register a command function to table
108
108
109 This class receives a command table as its argument. The table should
109 This class receives a command table as its argument. The table should
110 be a dict.
110 be a dict.
111
111
112 The created object can be used as a decorator for adding commands to
112 The created object can be used as a decorator for adding commands to
113 that command table. This accepts multiple arguments to define a command.
113 that command table. This accepts multiple arguments to define a command.
114
114
115 The first argument is the command name (as bytes).
115 The first argument is the command name (as bytes).
116
116
117 The `options` keyword argument is an iterable of tuples defining command
117 The `options` keyword argument is an iterable of tuples defining command
118 arguments. See ``mercurial.fancyopts.fancyopts()`` for the format of each
118 arguments. See ``mercurial.fancyopts.fancyopts()`` for the format of each
119 tuple.
119 tuple.
120
120
121 The `synopsis` argument defines a short, one line summary of how to use the
121 The `synopsis` argument defines a short, one line summary of how to use the
122 command. This shows up in the help output.
122 command. This shows up in the help output.
123
123
124 There are three arguments that control what repository (if any) is found
124 There are three arguments that control what repository (if any) is found
125 and passed to the decorated function: `norepo`, `optionalrepo`, and
125 and passed to the decorated function: `norepo`, `optionalrepo`, and
126 `inferrepo`.
126 `inferrepo`.
127
127
128 The `norepo` argument defines whether the command does not require a
128 The `norepo` argument defines whether the command does not require a
129 local repository. Most commands operate against a repository, thus the
129 local repository. Most commands operate against a repository, thus the
130 default is False. When True, no repository will be passed.
130 default is False. When True, no repository will be passed.
131
131
132 The `optionalrepo` argument defines whether the command optionally requires
132 The `optionalrepo` argument defines whether the command optionally requires
133 a local repository. If no repository can be found, None will be passed
133 a local repository. If no repository can be found, None will be passed
134 to the decorated function.
134 to the decorated function.
135
135
136 The `inferrepo` argument defines whether to try to find a repository from
136 The `inferrepo` argument defines whether to try to find a repository from
137 the command line arguments. If True, arguments will be examined for
137 the command line arguments. If True, arguments will be examined for
138 potential repository locations. See ``findrepo()``. If a repository is
138 potential repository locations. See ``findrepo()``. If a repository is
139 found, it will be used and passed to the decorated function.
139 found, it will be used and passed to the decorated function.
140
140
141 The `intents` argument defines a set of intended actions or capabilities
141 The `intents` argument defines a set of intended actions or capabilities
142 the command is taking. These intents can be used to affect the construction
142 the command is taking. These intents can be used to affect the construction
143 of the repository object passed to the command. For example, commands
143 of the repository object passed to the command. For example, commands
144 declaring that they are read-only could receive a repository that doesn't
144 declaring that they are read-only could receive a repository that doesn't
145 have any methods allowing repository mutation. Other intents could be used
145 have any methods allowing repository mutation. Other intents could be used
146 to prevent the command from running if the requested intent could not be
146 to prevent the command from running if the requested intent could not be
147 fulfilled.
147 fulfilled.
148
148
149 The following intents are defined:
149 The following intents are defined:
150
150
151 readonly
151 readonly
152 The command is read-only
152 The command is read-only
153
153
154 The signature of the decorated function looks like this:
154 The signature of the decorated function looks like this:
155 def cmd(ui[, repo] [, <args>] [, <options>])
155 def cmd(ui[, repo] [, <args>] [, <options>])
156
156
157 `repo` is required if `norepo` is False.
157 `repo` is required if `norepo` is False.
158 `<args>` are positional args (or `*args`) arguments, of non-option
158 `<args>` are positional args (or `*args`) arguments, of non-option
159 arguments from the command line.
159 arguments from the command line.
160 `<options>` are keyword arguments (or `**options`) of option arguments
160 `<options>` are keyword arguments (or `**options`) of option arguments
161 from the command line.
161 from the command line.
162
162
163 See the WritingExtensions and MercurialApi documentation for more exhaustive
163 See the WritingExtensions and MercurialApi documentation for more exhaustive
164 descriptions and examples.
164 descriptions and examples.
165 """
165 """
166
166
167 def _doregister(self, func, name, options=(), synopsis=None,
167 def _doregister(self, func, name, options=(), synopsis=None,
168 norepo=False, optionalrepo=False, inferrepo=False,
168 norepo=False, optionalrepo=False, inferrepo=False,
169 intents=None):
169 intents=None):
170
170
171 func.norepo = norepo
171 func.norepo = norepo
172 func.optionalrepo = optionalrepo
172 func.optionalrepo = optionalrepo
173 func.inferrepo = inferrepo
173 func.inferrepo = inferrepo
174 func.intents = intents or set()
174 func.intents = intents or set()
175 if synopsis:
175 if synopsis:
176 self._table[name] = func, list(options), synopsis
176 self._table[name] = func, list(options), synopsis
177 else:
177 else:
178 self._table[name] = func, list(options)
178 self._table[name] = func, list(options)
179 return func
179 return func
180
180
181 INTENT_READONLY = b'readonly'
181 INTENT_READONLY = b'readonly'
182
182
183 class revsetpredicate(_funcregistrarbase):
183 class revsetpredicate(_funcregistrarbase):
184 """Decorator to register revset predicate
184 """Decorator to register revset predicate
185
185
186 Usage::
186 Usage::
187
187
188 revsetpredicate = registrar.revsetpredicate()
188 revsetpredicate = registrar.revsetpredicate()
189
189
190 @revsetpredicate('mypredicate(arg1, arg2[, arg3])')
190 @revsetpredicate('mypredicate(arg1, arg2[, arg3])')
191 def mypredicatefunc(repo, subset, x):
191 def mypredicatefunc(repo, subset, x):
192 '''Explanation of this revset predicate ....
192 '''Explanation of this revset predicate ....
193 '''
193 '''
194 pass
194 pass
195
195
196 The first string argument is used also in online help.
196 The first string argument is used also in online help.
197
197
198 Optional argument 'safe' indicates whether a predicate is safe for
198 Optional argument 'safe' indicates whether a predicate is safe for
199 DoS attack (False by default).
199 DoS attack (False by default).
200
200
201 Optional argument 'takeorder' indicates whether a predicate function
201 Optional argument 'takeorder' indicates whether a predicate function
202 takes ordering policy as the last argument.
202 takes ordering policy as the last argument.
203
203
204 Optional argument 'weight' indicates the estimated run-time cost, useful
204 Optional argument 'weight' indicates the estimated run-time cost, useful
205 for static optimization, default is 1. Higher weight means more expensive.
205 for static optimization, default is 1. Higher weight means more expensive.
206 Usually, revsets that are fast and return only one revision has a weight of
206 Usually, revsets that are fast and return only one revision has a weight of
207 0.5 (ex. a symbol); revsets with O(changelog) complexity and read only the
207 0.5 (ex. a symbol); revsets with O(changelog) complexity and read only the
208 changelog have weight 10 (ex. author); revsets reading manifest deltas have
208 changelog have weight 10 (ex. author); revsets reading manifest deltas have
209 weight 30 (ex. adds); revset reading manifest contents have weight 100
209 weight 30 (ex. adds); revset reading manifest contents have weight 100
210 (ex. contains). Note: those values are flexible. If the revset has a
210 (ex. contains). Note: those values are flexible. If the revset has a
211 same big-O time complexity as 'contains', but with a smaller constant, it
211 same big-O time complexity as 'contains', but with a smaller constant, it
212 might have a weight of 90.
212 might have a weight of 90.
213
213
214 'revsetpredicate' instance in example above can be used to
214 'revsetpredicate' instance in example above can be used to
215 decorate multiple functions.
215 decorate multiple functions.
216
216
217 Decorated functions are registered automatically at loading
217 Decorated functions are registered automatically at loading
218 extension, if an instance named as 'revsetpredicate' is used for
218 extension, if an instance named as 'revsetpredicate' is used for
219 decorating in extension.
219 decorating in extension.
220
220
221 Otherwise, explicit 'revset.loadpredicate()' is needed.
221 Otherwise, explicit 'revset.loadpredicate()' is needed.
222 """
222 """
223 _getname = _funcregistrarbase._parsefuncdecl
223 _getname = _funcregistrarbase._parsefuncdecl
224 _docformat = "``%s``\n %s"
224 _docformat = "``%s``\n %s"
225
225
226 def _extrasetup(self, name, func, safe=False, takeorder=False, weight=1):
226 def _extrasetup(self, name, func, safe=False, takeorder=False, weight=1):
227 func._safe = safe
227 func._safe = safe
228 func._takeorder = takeorder
228 func._takeorder = takeorder
229 func._weight = weight
229 func._weight = weight
230
230
231 class filesetpredicate(_funcregistrarbase):
231 class filesetpredicate(_funcregistrarbase):
232 """Decorator to register fileset predicate
232 """Decorator to register fileset predicate
233
233
234 Usage::
234 Usage::
235
235
236 filesetpredicate = registrar.filesetpredicate()
236 filesetpredicate = registrar.filesetpredicate()
237
237
238 @filesetpredicate('mypredicate()')
238 @filesetpredicate('mypredicate()')
239 def mypredicatefunc(mctx, x):
239 def mypredicatefunc(mctx, x):
240 '''Explanation of this fileset predicate ....
240 '''Explanation of this fileset predicate ....
241 '''
241 '''
242 pass
242 pass
243
243
244 The first string argument is used also in online help.
244 The first string argument is used also in online help.
245
245
246 Optional argument 'callstatus' indicates whether a predicate
246 Optional argument 'callstatus' indicates whether a predicate
247 implies 'matchctx.status()' at runtime or not (False, by
247 implies 'matchctx.status()' at runtime or not (False, by
248 default).
248 default).
249
249
250 Optional argument 'callexisting' indicates whether a predicate
250 Optional argument 'callexisting' indicates whether a predicate
251 implies 'matchctx.existing()' at runtime or not (False, by
251 implies 'matchctx.existing()' at runtime or not (False, by
252 default).
252 default).
253
253
254 'filesetpredicate' instance in example above can be used to
254 'filesetpredicate' instance in example above can be used to
255 decorate multiple functions.
255 decorate multiple functions.
256
256
257 Decorated functions are registered automatically at loading
257 Decorated functions are registered automatically at loading
258 extension, if an instance named as 'filesetpredicate' is used for
258 extension, if an instance named as 'filesetpredicate' is used for
259 decorating in extension.
259 decorating in extension.
260
260
261 Otherwise, explicit 'fileset.loadpredicate()' is needed.
261 Otherwise, explicit 'fileset.loadpredicate()' is needed.
262 """
262 """
263 _getname = _funcregistrarbase._parsefuncdecl
263 _getname = _funcregistrarbase._parsefuncdecl
264 _docformat = "``%s``\n %s"
264 _docformat = "``%s``\n %s"
265
265
266 def _extrasetup(self, name, func, callstatus=False, callexisting=False):
266 def _extrasetup(self, name, func, callstatus=False, callexisting=False):
267 func._callstatus = callstatus
267 func._callstatus = callstatus
268 func._callexisting = callexisting
268 func._callexisting = callexisting
269
269
270 class _templateregistrarbase(_funcregistrarbase):
270 class _templateregistrarbase(_funcregistrarbase):
271 """Base of decorator to register functions as template specific one
271 """Base of decorator to register functions as template specific one
272 """
272 """
273 _docformat = ":%s: %s"
273 _docformat = ":%s: %s"
274
274
275 class templatekeyword(_templateregistrarbase):
275 class templatekeyword(_templateregistrarbase):
276 """Decorator to register template keyword
276 """Decorator to register template keyword
277
277
278 Usage::
278 Usage::
279
279
280 templatekeyword = registrar.templatekeyword()
280 templatekeyword = registrar.templatekeyword()
281
281
282 # new API (since Mercurial 4.6)
282 # new API (since Mercurial 4.6)
283 @templatekeyword('mykeyword', requires={'repo', 'ctx'})
283 @templatekeyword('mykeyword', requires={'repo', 'ctx'})
284 def mykeywordfunc(context, mapping):
284 def mykeywordfunc(context, mapping):
285 '''Explanation of this template keyword ....
285 '''Explanation of this template keyword ....
286 '''
286 '''
287 pass
287 pass
288
288
289 # old API
289 # old API
290 @templatekeyword('mykeyword')
290 @templatekeyword('mykeyword')
291 def mykeywordfunc(repo, ctx, templ, cache, revcache, **args):
291 def mykeywordfunc(repo, ctx, templ, cache, revcache, **args):
292 '''Explanation of this template keyword ....
292 '''Explanation of this template keyword ....
293 '''
293 '''
294 pass
294 pass
295
295
296 The first string argument is used also in online help.
296 The first string argument is used also in online help.
297
297
298 Optional argument 'requires' should be a collection of resource names
298 Optional argument 'requires' should be a collection of resource names
299 which the template keyword depends on. This also serves as a flag to
299 which the template keyword depends on. This also serves as a flag to
300 switch to the new API. If 'requires' is unspecified, all template
300 switch to the new API. If 'requires' is unspecified, all template
301 keywords and resources are expanded to the function arguments.
301 keywords and resources are expanded to the function arguments.
302
302
303 'templatekeyword' instance in example above can be used to
303 'templatekeyword' instance in example above can be used to
304 decorate multiple functions.
304 decorate multiple functions.
305
305
306 Decorated functions are registered automatically at loading
306 Decorated functions are registered automatically at loading
307 extension, if an instance named as 'templatekeyword' is used for
307 extension, if an instance named as 'templatekeyword' is used for
308 decorating in extension.
308 decorating in extension.
309
309
310 Otherwise, explicit 'templatekw.loadkeyword()' is needed.
310 Otherwise, explicit 'templatekw.loadkeyword()' is needed.
311 """
311 """
312
312
313 def _extrasetup(self, name, func, requires=None):
313 def _extrasetup(self, name, func, requires=None):
314 func._requires = requires
314 func._requires = requires
315
315
316 class templatefilter(_templateregistrarbase):
316 class templatefilter(_templateregistrarbase):
317 """Decorator to register template filer
317 """Decorator to register template filer
318
318
319 Usage::
319 Usage::
320
320
321 templatefilter = registrar.templatefilter()
321 templatefilter = registrar.templatefilter()
322
322
323 @templatefilter('myfilter', intype=bytes)
323 @templatefilter('myfilter', intype=bytes)
324 def myfilterfunc(text):
324 def myfilterfunc(text):
325 '''Explanation of this template filter ....
325 '''Explanation of this template filter ....
326 '''
326 '''
327 pass
327 pass
328
328
329 The first string argument is used also in online help.
329 The first string argument is used also in online help.
330
330
331 Optional argument 'intype' defines the type of the input argument,
331 Optional argument 'intype' defines the type of the input argument,
332 which should be (bytes, int, templateutil.date, or None for any.)
332 which should be (bytes, int, templateutil.date, or None for any.)
333
333
334 'templatefilter' instance in example above can be used to
334 'templatefilter' instance in example above can be used to
335 decorate multiple functions.
335 decorate multiple functions.
336
336
337 Decorated functions are registered automatically at loading
337 Decorated functions are registered automatically at loading
338 extension, if an instance named as 'templatefilter' is used for
338 extension, if an instance named as 'templatefilter' is used for
339 decorating in extension.
339 decorating in extension.
340
340
341 Otherwise, explicit 'templatefilters.loadkeyword()' is needed.
341 Otherwise, explicit 'templatefilters.loadkeyword()' is needed.
342 """
342 """
343
343
344 def _extrasetup(self, name, func, intype=None):
344 def _extrasetup(self, name, func, intype=None):
345 func._intype = intype
345 func._intype = intype
346
346
347 class templatefunc(_templateregistrarbase):
347 class templatefunc(_templateregistrarbase):
348 """Decorator to register template function
348 """Decorator to register template function
349
349
350 Usage::
350 Usage::
351
351
352 templatefunc = registrar.templatefunc()
352 templatefunc = registrar.templatefunc()
353
353
354 @templatefunc('myfunc(arg1, arg2[, arg3])', argspec='arg1 arg2 arg3')
354 @templatefunc('myfunc(arg1, arg2[, arg3])', argspec='arg1 arg2 arg3',
355 requires={'ctx'})
355 def myfuncfunc(context, mapping, args):
356 def myfuncfunc(context, mapping, args):
356 '''Explanation of this template function ....
357 '''Explanation of this template function ....
357 '''
358 '''
358 pass
359 pass
359
360
360 The first string argument is used also in online help.
361 The first string argument is used also in online help.
361
362
362 If optional 'argspec' is defined, the function will receive 'args' as
363 If optional 'argspec' is defined, the function will receive 'args' as
363 a dict of named arguments. Otherwise 'args' is a list of positional
364 a dict of named arguments. Otherwise 'args' is a list of positional
364 arguments.
365 arguments.
365
366
367 Optional argument 'requires' should be a collection of resource names
368 which the template function depends on.
369
366 'templatefunc' instance in example above can be used to
370 'templatefunc' instance in example above can be used to
367 decorate multiple functions.
371 decorate multiple functions.
368
372
369 Decorated functions are registered automatically at loading
373 Decorated functions are registered automatically at loading
370 extension, if an instance named as 'templatefunc' is used for
374 extension, if an instance named as 'templatefunc' is used for
371 decorating in extension.
375 decorating in extension.
372
376
373 Otherwise, explicit 'templatefuncs.loadfunction()' is needed.
377 Otherwise, explicit 'templatefuncs.loadfunction()' is needed.
374 """
378 """
375 _getname = _funcregistrarbase._parsefuncdecl
379 _getname = _funcregistrarbase._parsefuncdecl
376
380
377 def _extrasetup(self, name, func, argspec=None):
381 def _extrasetup(self, name, func, argspec=None, requires=()):
378 func._argspec = argspec
382 func._argspec = argspec
383 func._requires = requires
379
384
380 class internalmerge(_funcregistrarbase):
385 class internalmerge(_funcregistrarbase):
381 """Decorator to register in-process merge tool
386 """Decorator to register in-process merge tool
382
387
383 Usage::
388 Usage::
384
389
385 internalmerge = registrar.internalmerge()
390 internalmerge = registrar.internalmerge()
386
391
387 @internalmerge('mymerge', internalmerge.mergeonly,
392 @internalmerge('mymerge', internalmerge.mergeonly,
388 onfailure=None, precheck=None):
393 onfailure=None, precheck=None):
389 def mymergefunc(repo, mynode, orig, fcd, fco, fca,
394 def mymergefunc(repo, mynode, orig, fcd, fco, fca,
390 toolconf, files, labels=None):
395 toolconf, files, labels=None):
391 '''Explanation of this internal merge tool ....
396 '''Explanation of this internal merge tool ....
392 '''
397 '''
393 return 1, False # means "conflicted", "no deletion needed"
398 return 1, False # means "conflicted", "no deletion needed"
394
399
395 The first string argument is used to compose actual merge tool name,
400 The first string argument is used to compose actual merge tool name,
396 ":name" and "internal:name" (the latter is historical one).
401 ":name" and "internal:name" (the latter is historical one).
397
402
398 The second argument is one of merge types below:
403 The second argument is one of merge types below:
399
404
400 ========== ======== ======== =========
405 ========== ======== ======== =========
401 merge type precheck premerge fullmerge
406 merge type precheck premerge fullmerge
402 ========== ======== ======== =========
407 ========== ======== ======== =========
403 nomerge x x x
408 nomerge x x x
404 mergeonly o x o
409 mergeonly o x o
405 fullmerge o o o
410 fullmerge o o o
406 ========== ======== ======== =========
411 ========== ======== ======== =========
407
412
408 Optional argument 'onfailure' is the format of warning message
413 Optional argument 'onfailure' is the format of warning message
409 to be used at failure of merging (target filename is specified
414 to be used at failure of merging (target filename is specified
410 at formatting). Or, None or so, if warning message should be
415 at formatting). Or, None or so, if warning message should be
411 suppressed.
416 suppressed.
412
417
413 Optional argument 'precheck' is the function to be used
418 Optional argument 'precheck' is the function to be used
414 before actual invocation of internal merge tool itself.
419 before actual invocation of internal merge tool itself.
415 It takes as same arguments as internal merge tool does, other than
420 It takes as same arguments as internal merge tool does, other than
416 'files' and 'labels'. If it returns false value, merging is aborted
421 'files' and 'labels'. If it returns false value, merging is aborted
417 immediately (and file is marked as "unresolved").
422 immediately (and file is marked as "unresolved").
418
423
419 'internalmerge' instance in example above can be used to
424 'internalmerge' instance in example above can be used to
420 decorate multiple functions.
425 decorate multiple functions.
421
426
422 Decorated functions are registered automatically at loading
427 Decorated functions are registered automatically at loading
423 extension, if an instance named as 'internalmerge' is used for
428 extension, if an instance named as 'internalmerge' is used for
424 decorating in extension.
429 decorating in extension.
425
430
426 Otherwise, explicit 'filemerge.loadinternalmerge()' is needed.
431 Otherwise, explicit 'filemerge.loadinternalmerge()' is needed.
427 """
432 """
428 _docformat = "``:%s``\n %s"
433 _docformat = "``:%s``\n %s"
429
434
430 # merge type definitions:
435 # merge type definitions:
431 nomerge = None
436 nomerge = None
432 mergeonly = 'mergeonly' # just the full merge, no premerge
437 mergeonly = 'mergeonly' # just the full merge, no premerge
433 fullmerge = 'fullmerge' # both premerge and merge
438 fullmerge = 'fullmerge' # both premerge and merge
434
439
435 def _extrasetup(self, name, func, mergetype,
440 def _extrasetup(self, name, func, mergetype,
436 onfailure=None, precheck=None):
441 onfailure=None, precheck=None):
437 func.mergetype = mergetype
442 func.mergetype = mergetype
438 func.onfailure = onfailure
443 func.onfailure = onfailure
439 func.precheck = precheck
444 func.precheck = precheck
@@ -1,698 +1,698
1 # templatefuncs.py - common template functions
1 # templatefuncs.py - common template functions
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import re
10 import re
11
11
12 from .i18n import _
12 from .i18n import _
13 from .node import (
13 from .node import (
14 bin,
14 bin,
15 wdirid,
15 wdirid,
16 )
16 )
17 from . import (
17 from . import (
18 color,
18 color,
19 encoding,
19 encoding,
20 error,
20 error,
21 minirst,
21 minirst,
22 obsutil,
22 obsutil,
23 registrar,
23 registrar,
24 revset as revsetmod,
24 revset as revsetmod,
25 revsetlang,
25 revsetlang,
26 scmutil,
26 scmutil,
27 templatefilters,
27 templatefilters,
28 templatekw,
28 templatekw,
29 templateutil,
29 templateutil,
30 util,
30 util,
31 )
31 )
32 from .utils import (
32 from .utils import (
33 dateutil,
33 dateutil,
34 stringutil,
34 stringutil,
35 )
35 )
36
36
37 evalrawexp = templateutil.evalrawexp
37 evalrawexp = templateutil.evalrawexp
38 evalwrapped = templateutil.evalwrapped
38 evalwrapped = templateutil.evalwrapped
39 evalfuncarg = templateutil.evalfuncarg
39 evalfuncarg = templateutil.evalfuncarg
40 evalboolean = templateutil.evalboolean
40 evalboolean = templateutil.evalboolean
41 evaldate = templateutil.evaldate
41 evaldate = templateutil.evaldate
42 evalinteger = templateutil.evalinteger
42 evalinteger = templateutil.evalinteger
43 evalstring = templateutil.evalstring
43 evalstring = templateutil.evalstring
44 evalstringliteral = templateutil.evalstringliteral
44 evalstringliteral = templateutil.evalstringliteral
45
45
46 # dict of template built-in functions
46 # dict of template built-in functions
47 funcs = {}
47 funcs = {}
48 templatefunc = registrar.templatefunc(funcs)
48 templatefunc = registrar.templatefunc(funcs)
49
49
50 @templatefunc('date(date[, fmt])')
50 @templatefunc('date(date[, fmt])')
51 def date(context, mapping, args):
51 def date(context, mapping, args):
52 """Format a date. See :hg:`help dates` for formatting
52 """Format a date. See :hg:`help dates` for formatting
53 strings. The default is a Unix date format, including the timezone:
53 strings. The default is a Unix date format, including the timezone:
54 "Mon Sep 04 15:13:13 2006 0700"."""
54 "Mon Sep 04 15:13:13 2006 0700"."""
55 if not (1 <= len(args) <= 2):
55 if not (1 <= len(args) <= 2):
56 # i18n: "date" is a keyword
56 # i18n: "date" is a keyword
57 raise error.ParseError(_("date expects one or two arguments"))
57 raise error.ParseError(_("date expects one or two arguments"))
58
58
59 date = evaldate(context, mapping, args[0],
59 date = evaldate(context, mapping, args[0],
60 # i18n: "date" is a keyword
60 # i18n: "date" is a keyword
61 _("date expects a date information"))
61 _("date expects a date information"))
62 fmt = None
62 fmt = None
63 if len(args) == 2:
63 if len(args) == 2:
64 fmt = evalstring(context, mapping, args[1])
64 fmt = evalstring(context, mapping, args[1])
65 if fmt is None:
65 if fmt is None:
66 return dateutil.datestr(date)
66 return dateutil.datestr(date)
67 else:
67 else:
68 return dateutil.datestr(date, fmt)
68 return dateutil.datestr(date, fmt)
69
69
70 @templatefunc('dict([[key=]value...])', argspec='*args **kwargs')
70 @templatefunc('dict([[key=]value...])', argspec='*args **kwargs')
71 def dict_(context, mapping, args):
71 def dict_(context, mapping, args):
72 """Construct a dict from key-value pairs. A key may be omitted if
72 """Construct a dict from key-value pairs. A key may be omitted if
73 a value expression can provide an unambiguous name."""
73 a value expression can provide an unambiguous name."""
74 data = util.sortdict()
74 data = util.sortdict()
75
75
76 for v in args['args']:
76 for v in args['args']:
77 k = templateutil.findsymbolicname(v)
77 k = templateutil.findsymbolicname(v)
78 if not k:
78 if not k:
79 raise error.ParseError(_('dict key cannot be inferred'))
79 raise error.ParseError(_('dict key cannot be inferred'))
80 if k in data or k in args['kwargs']:
80 if k in data or k in args['kwargs']:
81 raise error.ParseError(_("duplicated dict key '%s' inferred") % k)
81 raise error.ParseError(_("duplicated dict key '%s' inferred") % k)
82 data[k] = evalfuncarg(context, mapping, v)
82 data[k] = evalfuncarg(context, mapping, v)
83
83
84 data.update((k, evalfuncarg(context, mapping, v))
84 data.update((k, evalfuncarg(context, mapping, v))
85 for k, v in args['kwargs'].iteritems())
85 for k, v in args['kwargs'].iteritems())
86 return templateutil.hybriddict(data)
86 return templateutil.hybriddict(data)
87
87
88 @templatefunc('diff([includepattern [, excludepattern]])')
88 @templatefunc('diff([includepattern [, excludepattern]])', requires={'ctx'})
89 def diff(context, mapping, args):
89 def diff(context, mapping, args):
90 """Show a diff, optionally
90 """Show a diff, optionally
91 specifying files to include or exclude."""
91 specifying files to include or exclude."""
92 if len(args) > 2:
92 if len(args) > 2:
93 # i18n: "diff" is a keyword
93 # i18n: "diff" is a keyword
94 raise error.ParseError(_("diff expects zero, one, or two arguments"))
94 raise error.ParseError(_("diff expects zero, one, or two arguments"))
95
95
96 def getpatterns(i):
96 def getpatterns(i):
97 if i < len(args):
97 if i < len(args):
98 s = evalstring(context, mapping, args[i]).strip()
98 s = evalstring(context, mapping, args[i]).strip()
99 if s:
99 if s:
100 return [s]
100 return [s]
101 return []
101 return []
102
102
103 ctx = context.resource(mapping, 'ctx')
103 ctx = context.resource(mapping, 'ctx')
104 chunks = ctx.diff(match=ctx.match([], getpatterns(0), getpatterns(1)))
104 chunks = ctx.diff(match=ctx.match([], getpatterns(0), getpatterns(1)))
105
105
106 return ''.join(chunks)
106 return ''.join(chunks)
107
107
108 @templatefunc('extdata(source)', argspec='source')
108 @templatefunc('extdata(source)', argspec='source', requires={'ctx', 'cache'})
109 def extdata(context, mapping, args):
109 def extdata(context, mapping, args):
110 """Show a text read from the specified extdata source. (EXPERIMENTAL)"""
110 """Show a text read from the specified extdata source. (EXPERIMENTAL)"""
111 if 'source' not in args:
111 if 'source' not in args:
112 # i18n: "extdata" is a keyword
112 # i18n: "extdata" is a keyword
113 raise error.ParseError(_('extdata expects one argument'))
113 raise error.ParseError(_('extdata expects one argument'))
114
114
115 source = evalstring(context, mapping, args['source'])
115 source = evalstring(context, mapping, args['source'])
116 if not source:
116 if not source:
117 sym = templateutil.findsymbolicname(args['source'])
117 sym = templateutil.findsymbolicname(args['source'])
118 if sym:
118 if sym:
119 raise error.ParseError(_('empty data source specified'),
119 raise error.ParseError(_('empty data source specified'),
120 hint=_("did you mean extdata('%s')?") % sym)
120 hint=_("did you mean extdata('%s')?") % sym)
121 else:
121 else:
122 raise error.ParseError(_('empty data source specified'))
122 raise error.ParseError(_('empty data source specified'))
123 cache = context.resource(mapping, 'cache').setdefault('extdata', {})
123 cache = context.resource(mapping, 'cache').setdefault('extdata', {})
124 ctx = context.resource(mapping, 'ctx')
124 ctx = context.resource(mapping, 'ctx')
125 if source in cache:
125 if source in cache:
126 data = cache[source]
126 data = cache[source]
127 else:
127 else:
128 data = cache[source] = scmutil.extdatasource(ctx.repo(), source)
128 data = cache[source] = scmutil.extdatasource(ctx.repo(), source)
129 return data.get(ctx.rev(), '')
129 return data.get(ctx.rev(), '')
130
130
131 @templatefunc('files(pattern)')
131 @templatefunc('files(pattern)', requires={'ctx'})
132 def files(context, mapping, args):
132 def files(context, mapping, args):
133 """All files of the current changeset matching the pattern. See
133 """All files of the current changeset matching the pattern. See
134 :hg:`help patterns`."""
134 :hg:`help patterns`."""
135 if not len(args) == 1:
135 if not len(args) == 1:
136 # i18n: "files" is a keyword
136 # i18n: "files" is a keyword
137 raise error.ParseError(_("files expects one argument"))
137 raise error.ParseError(_("files expects one argument"))
138
138
139 raw = evalstring(context, mapping, args[0])
139 raw = evalstring(context, mapping, args[0])
140 ctx = context.resource(mapping, 'ctx')
140 ctx = context.resource(mapping, 'ctx')
141 m = ctx.match([raw])
141 m = ctx.match([raw])
142 files = list(ctx.matches(m))
142 files = list(ctx.matches(m))
143 return templateutil.compatlist(context, mapping, "file", files)
143 return templateutil.compatlist(context, mapping, "file", files)
144
144
145 @templatefunc('fill(text[, width[, initialident[, hangindent]]])')
145 @templatefunc('fill(text[, width[, initialident[, hangindent]]])')
146 def fill(context, mapping, args):
146 def fill(context, mapping, args):
147 """Fill many
147 """Fill many
148 paragraphs with optional indentation. See the "fill" filter."""
148 paragraphs with optional indentation. See the "fill" filter."""
149 if not (1 <= len(args) <= 4):
149 if not (1 <= len(args) <= 4):
150 # i18n: "fill" is a keyword
150 # i18n: "fill" is a keyword
151 raise error.ParseError(_("fill expects one to four arguments"))
151 raise error.ParseError(_("fill expects one to four arguments"))
152
152
153 text = evalstring(context, mapping, args[0])
153 text = evalstring(context, mapping, args[0])
154 width = 76
154 width = 76
155 initindent = ''
155 initindent = ''
156 hangindent = ''
156 hangindent = ''
157 if 2 <= len(args) <= 4:
157 if 2 <= len(args) <= 4:
158 width = evalinteger(context, mapping, args[1],
158 width = evalinteger(context, mapping, args[1],
159 # i18n: "fill" is a keyword
159 # i18n: "fill" is a keyword
160 _("fill expects an integer width"))
160 _("fill expects an integer width"))
161 try:
161 try:
162 initindent = evalstring(context, mapping, args[2])
162 initindent = evalstring(context, mapping, args[2])
163 hangindent = evalstring(context, mapping, args[3])
163 hangindent = evalstring(context, mapping, args[3])
164 except IndexError:
164 except IndexError:
165 pass
165 pass
166
166
167 return templatefilters.fill(text, width, initindent, hangindent)
167 return templatefilters.fill(text, width, initindent, hangindent)
168
168
169 @templatefunc('formatnode(node)')
169 @templatefunc('formatnode(node)', requires={'ui'})
170 def formatnode(context, mapping, args):
170 def formatnode(context, mapping, args):
171 """Obtain the preferred form of a changeset hash. (DEPRECATED)"""
171 """Obtain the preferred form of a changeset hash. (DEPRECATED)"""
172 if len(args) != 1:
172 if len(args) != 1:
173 # i18n: "formatnode" is a keyword
173 # i18n: "formatnode" is a keyword
174 raise error.ParseError(_("formatnode expects one argument"))
174 raise error.ParseError(_("formatnode expects one argument"))
175
175
176 ui = context.resource(mapping, 'ui')
176 ui = context.resource(mapping, 'ui')
177 node = evalstring(context, mapping, args[0])
177 node = evalstring(context, mapping, args[0])
178 if ui.debugflag:
178 if ui.debugflag:
179 return node
179 return node
180 return templatefilters.short(node)
180 return templatefilters.short(node)
181
181
182 @templatefunc('mailmap(author)')
182 @templatefunc('mailmap(author)', requires={'repo', 'cache'})
183 def mailmap(context, mapping, args):
183 def mailmap(context, mapping, args):
184 """Return the author, updated according to the value
184 """Return the author, updated according to the value
185 set in the .mailmap file"""
185 set in the .mailmap file"""
186 if len(args) != 1:
186 if len(args) != 1:
187 raise error.ParseError(_("mailmap expects one argument"))
187 raise error.ParseError(_("mailmap expects one argument"))
188
188
189 author = evalstring(context, mapping, args[0])
189 author = evalstring(context, mapping, args[0])
190
190
191 cache = context.resource(mapping, 'cache')
191 cache = context.resource(mapping, 'cache')
192 repo = context.resource(mapping, 'repo')
192 repo = context.resource(mapping, 'repo')
193
193
194 if 'mailmap' not in cache:
194 if 'mailmap' not in cache:
195 data = repo.wvfs.tryread('.mailmap')
195 data = repo.wvfs.tryread('.mailmap')
196 cache['mailmap'] = stringutil.parsemailmap(data)
196 cache['mailmap'] = stringutil.parsemailmap(data)
197
197
198 return stringutil.mapname(cache['mailmap'], author)
198 return stringutil.mapname(cache['mailmap'], author)
199
199
200 @templatefunc('pad(text, width[, fillchar=\' \'[, left=False]])',
200 @templatefunc('pad(text, width[, fillchar=\' \'[, left=False]])',
201 argspec='text width fillchar left')
201 argspec='text width fillchar left')
202 def pad(context, mapping, args):
202 def pad(context, mapping, args):
203 """Pad text with a
203 """Pad text with a
204 fill character."""
204 fill character."""
205 if 'text' not in args or 'width' not in args:
205 if 'text' not in args or 'width' not in args:
206 # i18n: "pad" is a keyword
206 # i18n: "pad" is a keyword
207 raise error.ParseError(_("pad() expects two to four arguments"))
207 raise error.ParseError(_("pad() expects two to four arguments"))
208
208
209 width = evalinteger(context, mapping, args['width'],
209 width = evalinteger(context, mapping, args['width'],
210 # i18n: "pad" is a keyword
210 # i18n: "pad" is a keyword
211 _("pad() expects an integer width"))
211 _("pad() expects an integer width"))
212
212
213 text = evalstring(context, mapping, args['text'])
213 text = evalstring(context, mapping, args['text'])
214
214
215 left = False
215 left = False
216 fillchar = ' '
216 fillchar = ' '
217 if 'fillchar' in args:
217 if 'fillchar' in args:
218 fillchar = evalstring(context, mapping, args['fillchar'])
218 fillchar = evalstring(context, mapping, args['fillchar'])
219 if len(color.stripeffects(fillchar)) != 1:
219 if len(color.stripeffects(fillchar)) != 1:
220 # i18n: "pad" is a keyword
220 # i18n: "pad" is a keyword
221 raise error.ParseError(_("pad() expects a single fill character"))
221 raise error.ParseError(_("pad() expects a single fill character"))
222 if 'left' in args:
222 if 'left' in args:
223 left = evalboolean(context, mapping, args['left'])
223 left = evalboolean(context, mapping, args['left'])
224
224
225 fillwidth = width - encoding.colwidth(color.stripeffects(text))
225 fillwidth = width - encoding.colwidth(color.stripeffects(text))
226 if fillwidth <= 0:
226 if fillwidth <= 0:
227 return text
227 return text
228 if left:
228 if left:
229 return fillchar * fillwidth + text
229 return fillchar * fillwidth + text
230 else:
230 else:
231 return text + fillchar * fillwidth
231 return text + fillchar * fillwidth
232
232
233 @templatefunc('indent(text, indentchars[, firstline])')
233 @templatefunc('indent(text, indentchars[, firstline])')
234 def indent(context, mapping, args):
234 def indent(context, mapping, args):
235 """Indents all non-empty lines
235 """Indents all non-empty lines
236 with the characters given in the indentchars string. An optional
236 with the characters given in the indentchars string. An optional
237 third parameter will override the indent for the first line only
237 third parameter will override the indent for the first line only
238 if present."""
238 if present."""
239 if not (2 <= len(args) <= 3):
239 if not (2 <= len(args) <= 3):
240 # i18n: "indent" is a keyword
240 # i18n: "indent" is a keyword
241 raise error.ParseError(_("indent() expects two or three arguments"))
241 raise error.ParseError(_("indent() expects two or three arguments"))
242
242
243 text = evalstring(context, mapping, args[0])
243 text = evalstring(context, mapping, args[0])
244 indent = evalstring(context, mapping, args[1])
244 indent = evalstring(context, mapping, args[1])
245
245
246 if len(args) == 3:
246 if len(args) == 3:
247 firstline = evalstring(context, mapping, args[2])
247 firstline = evalstring(context, mapping, args[2])
248 else:
248 else:
249 firstline = indent
249 firstline = indent
250
250
251 # the indent function doesn't indent the first line, so we do it here
251 # the indent function doesn't indent the first line, so we do it here
252 return templatefilters.indent(firstline + text, indent)
252 return templatefilters.indent(firstline + text, indent)
253
253
254 @templatefunc('get(dict, key)')
254 @templatefunc('get(dict, key)')
255 def get(context, mapping, args):
255 def get(context, mapping, args):
256 """Get an attribute/key from an object. Some keywords
256 """Get an attribute/key from an object. Some keywords
257 are complex types. This function allows you to obtain the value of an
257 are complex types. This function allows you to obtain the value of an
258 attribute on these types."""
258 attribute on these types."""
259 if len(args) != 2:
259 if len(args) != 2:
260 # i18n: "get" is a keyword
260 # i18n: "get" is a keyword
261 raise error.ParseError(_("get() expects two arguments"))
261 raise error.ParseError(_("get() expects two arguments"))
262
262
263 dictarg = evalwrapped(context, mapping, args[0])
263 dictarg = evalwrapped(context, mapping, args[0])
264 key = evalrawexp(context, mapping, args[1])
264 key = evalrawexp(context, mapping, args[1])
265 try:
265 try:
266 return dictarg.getmember(context, mapping, key)
266 return dictarg.getmember(context, mapping, key)
267 except error.ParseError as err:
267 except error.ParseError as err:
268 # i18n: "get" is a keyword
268 # i18n: "get" is a keyword
269 hint = _("get() expects a dict as first argument")
269 hint = _("get() expects a dict as first argument")
270 raise error.ParseError(bytes(err), hint=hint)
270 raise error.ParseError(bytes(err), hint=hint)
271
271
272 @templatefunc('if(expr, then[, else])')
272 @templatefunc('if(expr, then[, else])')
273 def if_(context, mapping, args):
273 def if_(context, mapping, args):
274 """Conditionally execute based on the result of
274 """Conditionally execute based on the result of
275 an expression."""
275 an expression."""
276 if not (2 <= len(args) <= 3):
276 if not (2 <= len(args) <= 3):
277 # i18n: "if" is a keyword
277 # i18n: "if" is a keyword
278 raise error.ParseError(_("if expects two or three arguments"))
278 raise error.ParseError(_("if expects two or three arguments"))
279
279
280 test = evalboolean(context, mapping, args[0])
280 test = evalboolean(context, mapping, args[0])
281 if test:
281 if test:
282 return evalrawexp(context, mapping, args[1])
282 return evalrawexp(context, mapping, args[1])
283 elif len(args) == 3:
283 elif len(args) == 3:
284 return evalrawexp(context, mapping, args[2])
284 return evalrawexp(context, mapping, args[2])
285
285
286 @templatefunc('ifcontains(needle, haystack, then[, else])')
286 @templatefunc('ifcontains(needle, haystack, then[, else])')
287 def ifcontains(context, mapping, args):
287 def ifcontains(context, mapping, args):
288 """Conditionally execute based
288 """Conditionally execute based
289 on whether the item "needle" is in "haystack"."""
289 on whether the item "needle" is in "haystack"."""
290 if not (3 <= len(args) <= 4):
290 if not (3 <= len(args) <= 4):
291 # i18n: "ifcontains" is a keyword
291 # i18n: "ifcontains" is a keyword
292 raise error.ParseError(_("ifcontains expects three or four arguments"))
292 raise error.ParseError(_("ifcontains expects three or four arguments"))
293
293
294 haystack = evalwrapped(context, mapping, args[1])
294 haystack = evalwrapped(context, mapping, args[1])
295 try:
295 try:
296 needle = evalrawexp(context, mapping, args[0])
296 needle = evalrawexp(context, mapping, args[0])
297 found = haystack.contains(context, mapping, needle)
297 found = haystack.contains(context, mapping, needle)
298 except error.ParseError:
298 except error.ParseError:
299 found = False
299 found = False
300
300
301 if found:
301 if found:
302 return evalrawexp(context, mapping, args[2])
302 return evalrawexp(context, mapping, args[2])
303 elif len(args) == 4:
303 elif len(args) == 4:
304 return evalrawexp(context, mapping, args[3])
304 return evalrawexp(context, mapping, args[3])
305
305
306 @templatefunc('ifeq(expr1, expr2, then[, else])')
306 @templatefunc('ifeq(expr1, expr2, then[, else])')
307 def ifeq(context, mapping, args):
307 def ifeq(context, mapping, args):
308 """Conditionally execute based on
308 """Conditionally execute based on
309 whether 2 items are equivalent."""
309 whether 2 items are equivalent."""
310 if not (3 <= len(args) <= 4):
310 if not (3 <= len(args) <= 4):
311 # i18n: "ifeq" is a keyword
311 # i18n: "ifeq" is a keyword
312 raise error.ParseError(_("ifeq expects three or four arguments"))
312 raise error.ParseError(_("ifeq expects three or four arguments"))
313
313
314 test = evalstring(context, mapping, args[0])
314 test = evalstring(context, mapping, args[0])
315 match = evalstring(context, mapping, args[1])
315 match = evalstring(context, mapping, args[1])
316 if test == match:
316 if test == match:
317 return evalrawexp(context, mapping, args[2])
317 return evalrawexp(context, mapping, args[2])
318 elif len(args) == 4:
318 elif len(args) == 4:
319 return evalrawexp(context, mapping, args[3])
319 return evalrawexp(context, mapping, args[3])
320
320
321 @templatefunc('join(list, sep)')
321 @templatefunc('join(list, sep)')
322 def join(context, mapping, args):
322 def join(context, mapping, args):
323 """Join items in a list with a delimiter."""
323 """Join items in a list with a delimiter."""
324 if not (1 <= len(args) <= 2):
324 if not (1 <= len(args) <= 2):
325 # i18n: "join" is a keyword
325 # i18n: "join" is a keyword
326 raise error.ParseError(_("join expects one or two arguments"))
326 raise error.ParseError(_("join expects one or two arguments"))
327
327
328 joinset = evalwrapped(context, mapping, args[0])
328 joinset = evalwrapped(context, mapping, args[0])
329 joiner = " "
329 joiner = " "
330 if len(args) > 1:
330 if len(args) > 1:
331 joiner = evalstring(context, mapping, args[1])
331 joiner = evalstring(context, mapping, args[1])
332 return joinset.join(context, mapping, joiner)
332 return joinset.join(context, mapping, joiner)
333
333
334 @templatefunc('label(label, expr)')
334 @templatefunc('label(label, expr)', requires={'ui'})
335 def label(context, mapping, args):
335 def label(context, mapping, args):
336 """Apply a label to generated content. Content with
336 """Apply a label to generated content. Content with
337 a label applied can result in additional post-processing, such as
337 a label applied can result in additional post-processing, such as
338 automatic colorization."""
338 automatic colorization."""
339 if len(args) != 2:
339 if len(args) != 2:
340 # i18n: "label" is a keyword
340 # i18n: "label" is a keyword
341 raise error.ParseError(_("label expects two arguments"))
341 raise error.ParseError(_("label expects two arguments"))
342
342
343 ui = context.resource(mapping, 'ui')
343 ui = context.resource(mapping, 'ui')
344 thing = evalstring(context, mapping, args[1])
344 thing = evalstring(context, mapping, args[1])
345 # preserve unknown symbol as literal so effects like 'red', 'bold',
345 # preserve unknown symbol as literal so effects like 'red', 'bold',
346 # etc. don't need to be quoted
346 # etc. don't need to be quoted
347 label = evalstringliteral(context, mapping, args[0])
347 label = evalstringliteral(context, mapping, args[0])
348
348
349 return ui.label(thing, label)
349 return ui.label(thing, label)
350
350
351 @templatefunc('latesttag([pattern])')
351 @templatefunc('latesttag([pattern])')
352 def latesttag(context, mapping, args):
352 def latesttag(context, mapping, args):
353 """The global tags matching the given pattern on the
353 """The global tags matching the given pattern on the
354 most recent globally tagged ancestor of this changeset.
354 most recent globally tagged ancestor of this changeset.
355 If no such tags exist, the "{tag}" template resolves to
355 If no such tags exist, the "{tag}" template resolves to
356 the string "null". See :hg:`help revisions.patterns` for the pattern
356 the string "null". See :hg:`help revisions.patterns` for the pattern
357 syntax.
357 syntax.
358 """
358 """
359 if len(args) > 1:
359 if len(args) > 1:
360 # i18n: "latesttag" is a keyword
360 # i18n: "latesttag" is a keyword
361 raise error.ParseError(_("latesttag expects at most one argument"))
361 raise error.ParseError(_("latesttag expects at most one argument"))
362
362
363 pattern = None
363 pattern = None
364 if len(args) == 1:
364 if len(args) == 1:
365 pattern = evalstring(context, mapping, args[0])
365 pattern = evalstring(context, mapping, args[0])
366 return templatekw.showlatesttags(context, mapping, pattern)
366 return templatekw.showlatesttags(context, mapping, pattern)
367
367
368 @templatefunc('localdate(date[, tz])')
368 @templatefunc('localdate(date[, tz])')
369 def localdate(context, mapping, args):
369 def localdate(context, mapping, args):
370 """Converts a date to the specified timezone.
370 """Converts a date to the specified timezone.
371 The default is local date."""
371 The default is local date."""
372 if not (1 <= len(args) <= 2):
372 if not (1 <= len(args) <= 2):
373 # i18n: "localdate" is a keyword
373 # i18n: "localdate" is a keyword
374 raise error.ParseError(_("localdate expects one or two arguments"))
374 raise error.ParseError(_("localdate expects one or two arguments"))
375
375
376 date = evaldate(context, mapping, args[0],
376 date = evaldate(context, mapping, args[0],
377 # i18n: "localdate" is a keyword
377 # i18n: "localdate" is a keyword
378 _("localdate expects a date information"))
378 _("localdate expects a date information"))
379 if len(args) >= 2:
379 if len(args) >= 2:
380 tzoffset = None
380 tzoffset = None
381 tz = evalfuncarg(context, mapping, args[1])
381 tz = evalfuncarg(context, mapping, args[1])
382 if isinstance(tz, bytes):
382 if isinstance(tz, bytes):
383 tzoffset, remainder = dateutil.parsetimezone(tz)
383 tzoffset, remainder = dateutil.parsetimezone(tz)
384 if remainder:
384 if remainder:
385 tzoffset = None
385 tzoffset = None
386 if tzoffset is None:
386 if tzoffset is None:
387 try:
387 try:
388 tzoffset = int(tz)
388 tzoffset = int(tz)
389 except (TypeError, ValueError):
389 except (TypeError, ValueError):
390 # i18n: "localdate" is a keyword
390 # i18n: "localdate" is a keyword
391 raise error.ParseError(_("localdate expects a timezone"))
391 raise error.ParseError(_("localdate expects a timezone"))
392 else:
392 else:
393 tzoffset = dateutil.makedate()[1]
393 tzoffset = dateutil.makedate()[1]
394 return templateutil.date((date[0], tzoffset))
394 return templateutil.date((date[0], tzoffset))
395
395
396 @templatefunc('max(iterable)')
396 @templatefunc('max(iterable)')
397 def max_(context, mapping, args, **kwargs):
397 def max_(context, mapping, args, **kwargs):
398 """Return the max of an iterable"""
398 """Return the max of an iterable"""
399 if len(args) != 1:
399 if len(args) != 1:
400 # i18n: "max" is a keyword
400 # i18n: "max" is a keyword
401 raise error.ParseError(_("max expects one argument"))
401 raise error.ParseError(_("max expects one argument"))
402
402
403 iterable = evalwrapped(context, mapping, args[0])
403 iterable = evalwrapped(context, mapping, args[0])
404 try:
404 try:
405 return iterable.getmax(context, mapping)
405 return iterable.getmax(context, mapping)
406 except error.ParseError as err:
406 except error.ParseError as err:
407 # i18n: "max" is a keyword
407 # i18n: "max" is a keyword
408 hint = _("max first argument should be an iterable")
408 hint = _("max first argument should be an iterable")
409 raise error.ParseError(bytes(err), hint=hint)
409 raise error.ParseError(bytes(err), hint=hint)
410
410
411 @templatefunc('min(iterable)')
411 @templatefunc('min(iterable)')
412 def min_(context, mapping, args, **kwargs):
412 def min_(context, mapping, args, **kwargs):
413 """Return the min of an iterable"""
413 """Return the min of an iterable"""
414 if len(args) != 1:
414 if len(args) != 1:
415 # i18n: "min" is a keyword
415 # i18n: "min" is a keyword
416 raise error.ParseError(_("min expects one argument"))
416 raise error.ParseError(_("min expects one argument"))
417
417
418 iterable = evalwrapped(context, mapping, args[0])
418 iterable = evalwrapped(context, mapping, args[0])
419 try:
419 try:
420 return iterable.getmin(context, mapping)
420 return iterable.getmin(context, mapping)
421 except error.ParseError as err:
421 except error.ParseError as err:
422 # i18n: "min" is a keyword
422 # i18n: "min" is a keyword
423 hint = _("min first argument should be an iterable")
423 hint = _("min first argument should be an iterable")
424 raise error.ParseError(bytes(err), hint=hint)
424 raise error.ParseError(bytes(err), hint=hint)
425
425
426 @templatefunc('mod(a, b)')
426 @templatefunc('mod(a, b)')
427 def mod(context, mapping, args):
427 def mod(context, mapping, args):
428 """Calculate a mod b such that a / b + a mod b == a"""
428 """Calculate a mod b such that a / b + a mod b == a"""
429 if not len(args) == 2:
429 if not len(args) == 2:
430 # i18n: "mod" is a keyword
430 # i18n: "mod" is a keyword
431 raise error.ParseError(_("mod expects two arguments"))
431 raise error.ParseError(_("mod expects two arguments"))
432
432
433 func = lambda a, b: a % b
433 func = lambda a, b: a % b
434 return templateutil.runarithmetic(context, mapping,
434 return templateutil.runarithmetic(context, mapping,
435 (func, args[0], args[1]))
435 (func, args[0], args[1]))
436
436
437 @templatefunc('obsfateoperations(markers)')
437 @templatefunc('obsfateoperations(markers)')
438 def obsfateoperations(context, mapping, args):
438 def obsfateoperations(context, mapping, args):
439 """Compute obsfate related information based on markers (EXPERIMENTAL)"""
439 """Compute obsfate related information based on markers (EXPERIMENTAL)"""
440 if len(args) != 1:
440 if len(args) != 1:
441 # i18n: "obsfateoperations" is a keyword
441 # i18n: "obsfateoperations" is a keyword
442 raise error.ParseError(_("obsfateoperations expects one argument"))
442 raise error.ParseError(_("obsfateoperations expects one argument"))
443
443
444 markers = evalfuncarg(context, mapping, args[0])
444 markers = evalfuncarg(context, mapping, args[0])
445
445
446 try:
446 try:
447 data = obsutil.markersoperations(markers)
447 data = obsutil.markersoperations(markers)
448 return templateutil.hybridlist(data, name='operation')
448 return templateutil.hybridlist(data, name='operation')
449 except (TypeError, KeyError):
449 except (TypeError, KeyError):
450 # i18n: "obsfateoperations" is a keyword
450 # i18n: "obsfateoperations" is a keyword
451 errmsg = _("obsfateoperations first argument should be an iterable")
451 errmsg = _("obsfateoperations first argument should be an iterable")
452 raise error.ParseError(errmsg)
452 raise error.ParseError(errmsg)
453
453
454 @templatefunc('obsfatedate(markers)')
454 @templatefunc('obsfatedate(markers)')
455 def obsfatedate(context, mapping, args):
455 def obsfatedate(context, mapping, args):
456 """Compute obsfate related information based on markers (EXPERIMENTAL)"""
456 """Compute obsfate related information based on markers (EXPERIMENTAL)"""
457 if len(args) != 1:
457 if len(args) != 1:
458 # i18n: "obsfatedate" is a keyword
458 # i18n: "obsfatedate" is a keyword
459 raise error.ParseError(_("obsfatedate expects one argument"))
459 raise error.ParseError(_("obsfatedate expects one argument"))
460
460
461 markers = evalfuncarg(context, mapping, args[0])
461 markers = evalfuncarg(context, mapping, args[0])
462
462
463 try:
463 try:
464 # TODO: maybe this has to be a wrapped list of date wrappers?
464 # TODO: maybe this has to be a wrapped list of date wrappers?
465 data = obsutil.markersdates(markers)
465 data = obsutil.markersdates(markers)
466 return templateutil.hybridlist(data, name='date', fmt='%d %d')
466 return templateutil.hybridlist(data, name='date', fmt='%d %d')
467 except (TypeError, KeyError):
467 except (TypeError, KeyError):
468 # i18n: "obsfatedate" is a keyword
468 # i18n: "obsfatedate" is a keyword
469 errmsg = _("obsfatedate first argument should be an iterable")
469 errmsg = _("obsfatedate first argument should be an iterable")
470 raise error.ParseError(errmsg)
470 raise error.ParseError(errmsg)
471
471
472 @templatefunc('obsfateusers(markers)')
472 @templatefunc('obsfateusers(markers)')
473 def obsfateusers(context, mapping, args):
473 def obsfateusers(context, mapping, args):
474 """Compute obsfate related information based on markers (EXPERIMENTAL)"""
474 """Compute obsfate related information based on markers (EXPERIMENTAL)"""
475 if len(args) != 1:
475 if len(args) != 1:
476 # i18n: "obsfateusers" is a keyword
476 # i18n: "obsfateusers" is a keyword
477 raise error.ParseError(_("obsfateusers expects one argument"))
477 raise error.ParseError(_("obsfateusers expects one argument"))
478
478
479 markers = evalfuncarg(context, mapping, args[0])
479 markers = evalfuncarg(context, mapping, args[0])
480
480
481 try:
481 try:
482 data = obsutil.markersusers(markers)
482 data = obsutil.markersusers(markers)
483 return templateutil.hybridlist(data, name='user')
483 return templateutil.hybridlist(data, name='user')
484 except (TypeError, KeyError, ValueError):
484 except (TypeError, KeyError, ValueError):
485 # i18n: "obsfateusers" is a keyword
485 # i18n: "obsfateusers" is a keyword
486 msg = _("obsfateusers first argument should be an iterable of "
486 msg = _("obsfateusers first argument should be an iterable of "
487 "obsmakers")
487 "obsmakers")
488 raise error.ParseError(msg)
488 raise error.ParseError(msg)
489
489
490 @templatefunc('obsfateverb(successors, markers)')
490 @templatefunc('obsfateverb(successors, markers)')
491 def obsfateverb(context, mapping, args):
491 def obsfateverb(context, mapping, args):
492 """Compute obsfate related information based on successors (EXPERIMENTAL)"""
492 """Compute obsfate related information based on successors (EXPERIMENTAL)"""
493 if len(args) != 2:
493 if len(args) != 2:
494 # i18n: "obsfateverb" is a keyword
494 # i18n: "obsfateverb" is a keyword
495 raise error.ParseError(_("obsfateverb expects two arguments"))
495 raise error.ParseError(_("obsfateverb expects two arguments"))
496
496
497 successors = evalfuncarg(context, mapping, args[0])
497 successors = evalfuncarg(context, mapping, args[0])
498 markers = evalfuncarg(context, mapping, args[1])
498 markers = evalfuncarg(context, mapping, args[1])
499
499
500 try:
500 try:
501 return obsutil.obsfateverb(successors, markers)
501 return obsutil.obsfateverb(successors, markers)
502 except TypeError:
502 except TypeError:
503 # i18n: "obsfateverb" is a keyword
503 # i18n: "obsfateverb" is a keyword
504 errmsg = _("obsfateverb first argument should be countable")
504 errmsg = _("obsfateverb first argument should be countable")
505 raise error.ParseError(errmsg)
505 raise error.ParseError(errmsg)
506
506
507 @templatefunc('relpath(path)')
507 @templatefunc('relpath(path)', requires={'repo'})
508 def relpath(context, mapping, args):
508 def relpath(context, mapping, args):
509 """Convert a repository-absolute path into a filesystem path relative to
509 """Convert a repository-absolute path into a filesystem path relative to
510 the current working directory."""
510 the current working directory."""
511 if len(args) != 1:
511 if len(args) != 1:
512 # i18n: "relpath" is a keyword
512 # i18n: "relpath" is a keyword
513 raise error.ParseError(_("relpath expects one argument"))
513 raise error.ParseError(_("relpath expects one argument"))
514
514
515 repo = context.resource(mapping, 'repo')
515 repo = context.resource(mapping, 'repo')
516 path = evalstring(context, mapping, args[0])
516 path = evalstring(context, mapping, args[0])
517 return repo.pathto(path)
517 return repo.pathto(path)
518
518
519 @templatefunc('revset(query[, formatargs...])')
519 @templatefunc('revset(query[, formatargs...])', requires={'repo', 'cache'})
520 def revset(context, mapping, args):
520 def revset(context, mapping, args):
521 """Execute a revision set query. See
521 """Execute a revision set query. See
522 :hg:`help revset`."""
522 :hg:`help revset`."""
523 if not len(args) > 0:
523 if not len(args) > 0:
524 # i18n: "revset" is a keyword
524 # i18n: "revset" is a keyword
525 raise error.ParseError(_("revset expects one or more arguments"))
525 raise error.ParseError(_("revset expects one or more arguments"))
526
526
527 raw = evalstring(context, mapping, args[0])
527 raw = evalstring(context, mapping, args[0])
528 repo = context.resource(mapping, 'repo')
528 repo = context.resource(mapping, 'repo')
529
529
530 def query(expr):
530 def query(expr):
531 m = revsetmod.match(repo.ui, expr, lookup=revsetmod.lookupfn(repo))
531 m = revsetmod.match(repo.ui, expr, lookup=revsetmod.lookupfn(repo))
532 return m(repo)
532 return m(repo)
533
533
534 if len(args) > 1:
534 if len(args) > 1:
535 formatargs = [evalfuncarg(context, mapping, a) for a in args[1:]]
535 formatargs = [evalfuncarg(context, mapping, a) for a in args[1:]]
536 revs = query(revsetlang.formatspec(raw, *formatargs))
536 revs = query(revsetlang.formatspec(raw, *formatargs))
537 revs = list(revs)
537 revs = list(revs)
538 else:
538 else:
539 cache = context.resource(mapping, 'cache')
539 cache = context.resource(mapping, 'cache')
540 revsetcache = cache.setdefault("revsetcache", {})
540 revsetcache = cache.setdefault("revsetcache", {})
541 if raw in revsetcache:
541 if raw in revsetcache:
542 revs = revsetcache[raw]
542 revs = revsetcache[raw]
543 else:
543 else:
544 revs = query(raw)
544 revs = query(raw)
545 revs = list(revs)
545 revs = list(revs)
546 revsetcache[raw] = revs
546 revsetcache[raw] = revs
547 return templatekw.showrevslist(context, mapping, "revision", revs)
547 return templatekw.showrevslist(context, mapping, "revision", revs)
548
548
549 @templatefunc('rstdoc(text, style)')
549 @templatefunc('rstdoc(text, style)')
550 def rstdoc(context, mapping, args):
550 def rstdoc(context, mapping, args):
551 """Format reStructuredText."""
551 """Format reStructuredText."""
552 if len(args) != 2:
552 if len(args) != 2:
553 # i18n: "rstdoc" is a keyword
553 # i18n: "rstdoc" is a keyword
554 raise error.ParseError(_("rstdoc expects two arguments"))
554 raise error.ParseError(_("rstdoc expects two arguments"))
555
555
556 text = evalstring(context, mapping, args[0])
556 text = evalstring(context, mapping, args[0])
557 style = evalstring(context, mapping, args[1])
557 style = evalstring(context, mapping, args[1])
558
558
559 return minirst.format(text, style=style, keep=['verbose'])[0]
559 return minirst.format(text, style=style, keep=['verbose'])[0]
560
560
561 @templatefunc('separate(sep, args...)', argspec='sep *args')
561 @templatefunc('separate(sep, args...)', argspec='sep *args')
562 def separate(context, mapping, args):
562 def separate(context, mapping, args):
563 """Add a separator between non-empty arguments."""
563 """Add a separator between non-empty arguments."""
564 if 'sep' not in args:
564 if 'sep' not in args:
565 # i18n: "separate" is a keyword
565 # i18n: "separate" is a keyword
566 raise error.ParseError(_("separate expects at least one argument"))
566 raise error.ParseError(_("separate expects at least one argument"))
567
567
568 sep = evalstring(context, mapping, args['sep'])
568 sep = evalstring(context, mapping, args['sep'])
569 first = True
569 first = True
570 for arg in args['args']:
570 for arg in args['args']:
571 argstr = evalstring(context, mapping, arg)
571 argstr = evalstring(context, mapping, arg)
572 if not argstr:
572 if not argstr:
573 continue
573 continue
574 if first:
574 if first:
575 first = False
575 first = False
576 else:
576 else:
577 yield sep
577 yield sep
578 yield argstr
578 yield argstr
579
579
580 @templatefunc('shortest(node, minlength=4)')
580 @templatefunc('shortest(node, minlength=4)', requires={'repo'})
581 def shortest(context, mapping, args):
581 def shortest(context, mapping, args):
582 """Obtain the shortest representation of
582 """Obtain the shortest representation of
583 a node."""
583 a node."""
584 if not (1 <= len(args) <= 2):
584 if not (1 <= len(args) <= 2):
585 # i18n: "shortest" is a keyword
585 # i18n: "shortest" is a keyword
586 raise error.ParseError(_("shortest() expects one or two arguments"))
586 raise error.ParseError(_("shortest() expects one or two arguments"))
587
587
588 hexnode = evalstring(context, mapping, args[0])
588 hexnode = evalstring(context, mapping, args[0])
589
589
590 minlength = 4
590 minlength = 4
591 if len(args) > 1:
591 if len(args) > 1:
592 minlength = evalinteger(context, mapping, args[1],
592 minlength = evalinteger(context, mapping, args[1],
593 # i18n: "shortest" is a keyword
593 # i18n: "shortest" is a keyword
594 _("shortest() expects an integer minlength"))
594 _("shortest() expects an integer minlength"))
595
595
596 repo = context.resource(mapping, 'repo')
596 repo = context.resource(mapping, 'repo')
597 if len(hexnode) > 40:
597 if len(hexnode) > 40:
598 return hexnode
598 return hexnode
599 elif len(hexnode) == 40:
599 elif len(hexnode) == 40:
600 try:
600 try:
601 node = bin(hexnode)
601 node = bin(hexnode)
602 except TypeError:
602 except TypeError:
603 return hexnode
603 return hexnode
604 else:
604 else:
605 try:
605 try:
606 node = scmutil.resolvehexnodeidprefix(repo, hexnode)
606 node = scmutil.resolvehexnodeidprefix(repo, hexnode)
607 except error.WdirUnsupported:
607 except error.WdirUnsupported:
608 node = wdirid
608 node = wdirid
609 except error.LookupError:
609 except error.LookupError:
610 return hexnode
610 return hexnode
611 if not node:
611 if not node:
612 return hexnode
612 return hexnode
613 try:
613 try:
614 return scmutil.shortesthexnodeidprefix(repo, node, minlength)
614 return scmutil.shortesthexnodeidprefix(repo, node, minlength)
615 except error.RepoLookupError:
615 except error.RepoLookupError:
616 return hexnode
616 return hexnode
617
617
618 @templatefunc('strip(text[, chars])')
618 @templatefunc('strip(text[, chars])')
619 def strip(context, mapping, args):
619 def strip(context, mapping, args):
620 """Strip characters from a string. By default,
620 """Strip characters from a string. By default,
621 strips all leading and trailing whitespace."""
621 strips all leading and trailing whitespace."""
622 if not (1 <= len(args) <= 2):
622 if not (1 <= len(args) <= 2):
623 # i18n: "strip" is a keyword
623 # i18n: "strip" is a keyword
624 raise error.ParseError(_("strip expects one or two arguments"))
624 raise error.ParseError(_("strip expects one or two arguments"))
625
625
626 text = evalstring(context, mapping, args[0])
626 text = evalstring(context, mapping, args[0])
627 if len(args) == 2:
627 if len(args) == 2:
628 chars = evalstring(context, mapping, args[1])
628 chars = evalstring(context, mapping, args[1])
629 return text.strip(chars)
629 return text.strip(chars)
630 return text.strip()
630 return text.strip()
631
631
632 @templatefunc('sub(pattern, replacement, expression)')
632 @templatefunc('sub(pattern, replacement, expression)')
633 def sub(context, mapping, args):
633 def sub(context, mapping, args):
634 """Perform text substitution
634 """Perform text substitution
635 using regular expressions."""
635 using regular expressions."""
636 if len(args) != 3:
636 if len(args) != 3:
637 # i18n: "sub" is a keyword
637 # i18n: "sub" is a keyword
638 raise error.ParseError(_("sub expects three arguments"))
638 raise error.ParseError(_("sub expects three arguments"))
639
639
640 pat = evalstring(context, mapping, args[0])
640 pat = evalstring(context, mapping, args[0])
641 rpl = evalstring(context, mapping, args[1])
641 rpl = evalstring(context, mapping, args[1])
642 src = evalstring(context, mapping, args[2])
642 src = evalstring(context, mapping, args[2])
643 try:
643 try:
644 patre = re.compile(pat)
644 patre = re.compile(pat)
645 except re.error:
645 except re.error:
646 # i18n: "sub" is a keyword
646 # i18n: "sub" is a keyword
647 raise error.ParseError(_("sub got an invalid pattern: %s") % pat)
647 raise error.ParseError(_("sub got an invalid pattern: %s") % pat)
648 try:
648 try:
649 yield patre.sub(rpl, src)
649 yield patre.sub(rpl, src)
650 except re.error:
650 except re.error:
651 # i18n: "sub" is a keyword
651 # i18n: "sub" is a keyword
652 raise error.ParseError(_("sub got an invalid replacement: %s") % rpl)
652 raise error.ParseError(_("sub got an invalid replacement: %s") % rpl)
653
653
654 @templatefunc('startswith(pattern, text)')
654 @templatefunc('startswith(pattern, text)')
655 def startswith(context, mapping, args):
655 def startswith(context, mapping, args):
656 """Returns the value from the "text" argument
656 """Returns the value from the "text" argument
657 if it begins with the content from the "pattern" argument."""
657 if it begins with the content from the "pattern" argument."""
658 if len(args) != 2:
658 if len(args) != 2:
659 # i18n: "startswith" is a keyword
659 # i18n: "startswith" is a keyword
660 raise error.ParseError(_("startswith expects two arguments"))
660 raise error.ParseError(_("startswith expects two arguments"))
661
661
662 patn = evalstring(context, mapping, args[0])
662 patn = evalstring(context, mapping, args[0])
663 text = evalstring(context, mapping, args[1])
663 text = evalstring(context, mapping, args[1])
664 if text.startswith(patn):
664 if text.startswith(patn):
665 return text
665 return text
666 return ''
666 return ''
667
667
668 @templatefunc('word(number, text[, separator])')
668 @templatefunc('word(number, text[, separator])')
669 def word(context, mapping, args):
669 def word(context, mapping, args):
670 """Return the nth word from a string."""
670 """Return the nth word from a string."""
671 if not (2 <= len(args) <= 3):
671 if not (2 <= len(args) <= 3):
672 # i18n: "word" is a keyword
672 # i18n: "word" is a keyword
673 raise error.ParseError(_("word expects two or three arguments, got %d")
673 raise error.ParseError(_("word expects two or three arguments, got %d")
674 % len(args))
674 % len(args))
675
675
676 num = evalinteger(context, mapping, args[0],
676 num = evalinteger(context, mapping, args[0],
677 # i18n: "word" is a keyword
677 # i18n: "word" is a keyword
678 _("word expects an integer index"))
678 _("word expects an integer index"))
679 text = evalstring(context, mapping, args[1])
679 text = evalstring(context, mapping, args[1])
680 if len(args) == 3:
680 if len(args) == 3:
681 splitter = evalstring(context, mapping, args[2])
681 splitter = evalstring(context, mapping, args[2])
682 else:
682 else:
683 splitter = None
683 splitter = None
684
684
685 tokens = text.split(splitter)
685 tokens = text.split(splitter)
686 if num >= len(tokens) or num < -len(tokens):
686 if num >= len(tokens) or num < -len(tokens):
687 return ''
687 return ''
688 else:
688 else:
689 return tokens[num]
689 return tokens[num]
690
690
691 def loadfunction(ui, extname, registrarobj):
691 def loadfunction(ui, extname, registrarobj):
692 """Load template function from specified registrarobj
692 """Load template function from specified registrarobj
693 """
693 """
694 for name, func in registrarobj._table.iteritems():
694 for name, func in registrarobj._table.iteritems():
695 funcs[name] = func
695 funcs[name] = func
696
696
697 # tell hggettext to extract docstrings from these functions:
697 # tell hggettext to extract docstrings from these functions:
698 i18nfunctions = funcs.values()
698 i18nfunctions = funcs.values()
General Comments 0
You need to be logged in to leave comments. Login now