##// END OF EJS Templates
doc: document that labels must have a dot in them to have an effect...
Jordi Gutiérrez Hermoso -
r52283:92c77659 default
parent child Browse files
Show More
@@ -1,149 +1,150 b''
1 Mercurial colorizes output from several commands.
1 Mercurial colorizes output from several commands.
2
2
3 For example, the diff command shows additions in green and deletions
3 For example, the diff command shows additions in green and deletions
4 in red, while the status command shows modified files in magenta. Many
4 in red, while the status command shows modified files in magenta. Many
5 other commands have analogous colors. It is possible to customize
5 other commands have analogous colors. It is possible to customize
6 these colors.
6 these colors.
7
7
8 To enable color (default) whenever possible use::
8 To enable color (default) whenever possible use::
9
9
10 [ui]
10 [ui]
11 color = yes
11 color = yes
12
12
13 To disable color use::
13 To disable color use::
14
14
15 [ui]
15 [ui]
16 color = no
16 color = no
17
17
18 See :hg:`help config.ui.color` for details.
18 See :hg:`help config.ui.color` for details.
19
19
20 .. container:: windows
20 .. container:: windows
21
21
22 The default pager on Windows does not support color, so enabling the pager
22 The default pager on Windows does not support color, so enabling the pager
23 will effectively disable color. See :hg:`help config.ui.paginate` to disable
23 will effectively disable color. See :hg:`help config.ui.paginate` to disable
24 the pager. Alternately, MSYS and Cygwin shells provide `less` as a pager,
24 the pager. Alternately, MSYS and Cygwin shells provide `less` as a pager,
25 which can be configured to support ANSI color mode. Windows 10 natively
25 which can be configured to support ANSI color mode. Windows 10 natively
26 supports ANSI color mode.
26 supports ANSI color mode.
27
27
28 Mode
28 Mode
29 ====
29 ====
30
30
31 Mercurial can use various systems to display color. The supported modes are
31 Mercurial can use various systems to display color. The supported modes are
32 ``ansi``, ``win32``, and ``terminfo``. See :hg:`help config.color` for details
32 ``ansi``, ``win32``, and ``terminfo``. See :hg:`help config.color` for details
33 about how to control the mode.
33 about how to control the mode.
34
34
35 Effects
35 Effects
36 =======
36 =======
37
37
38 Other effects in addition to color, like bold and underlined text, are
38 Other effects in addition to color, like bold and underlined text, are
39 also available. By default, the terminfo database is used to find the
39 also available. By default, the terminfo database is used to find the
40 terminal codes used to change color and effect. If terminfo is not
40 terminal codes used to change color and effect. If terminfo is not
41 available, then effects are rendered with the ECMA-48 SGR control
41 available, then effects are rendered with the ECMA-48 SGR control
42 function (aka ANSI escape codes).
42 function (aka ANSI escape codes).
43
43
44 The available effects in terminfo mode are 'blink', 'bold', 'dim',
44 The available effects in terminfo mode are 'blink', 'bold', 'dim',
45 'inverse', 'invisible', 'italic', 'standout', and 'underline'; in
45 'inverse', 'invisible', 'italic', 'standout', and 'underline'; in
46 ECMA-48 mode, the options are 'bold', 'inverse', 'italic', and
46 ECMA-48 mode, the options are 'bold', 'inverse', 'italic', and
47 'underline'. How each is rendered depends on the terminal emulator.
47 'underline'. How each is rendered depends on the terminal emulator.
48 Some may not be available for a given terminal type, and will be
48 Some may not be available for a given terminal type, and will be
49 silently ignored.
49 silently ignored.
50
50
51 If the terminfo entry for your terminal is missing codes for an effect
51 If the terminfo entry for your terminal is missing codes for an effect
52 or has the wrong codes, you can add or override those codes in your
52 or has the wrong codes, you can add or override those codes in your
53 configuration::
53 configuration::
54
54
55 [color]
55 [color]
56 terminfo.dim = \E[2m
56 terminfo.dim = \E[2m
57
57
58 where '\E' is substituted with an escape character.
58 where '\E' is substituted with an escape character.
59
59
60 Labels
60 Labels
61 ======
61 ======
62
62
63 Text receives color effects depending on the labels that it has. Many
63 Text receives color effects depending on the labels that it has. Many
64 default Mercurial commands emit labelled text. You can also define
64 default Mercurial commands emit labelled text. You can also define
65 your own labels in templates using the label function, see :hg:`help
65 your own labels in templates using the label function, see :hg:`help
66 templates`. A single portion of text may have more than one label. In
66 templates`. In order to receive effects, labels must have a dot, such
67 that case, effects given to the last label will override any other
67 as `log.secret` or `branch.active`. A single portion of text may have
68 effects. This includes the special "none" effect, which nullifies
68 more than one label. In that case, effects given to the last label
69 other effects.
69 will override any other effects. This includes the special "none"
70 effect, which nullifies other effects.
70
71
71 Labels are normally invisible. In order to see these labels and their
72 Labels are normally invisible. In order to see these labels and their
72 position in the text, use the global --color=debug option. The same
73 position in the text, use the global --color=debug option. The same
73 anchor text may be associated to multiple labels, e.g.
74 anchor text may be associated to multiple labels, e.g.
74
75
75 [log.changeset changeset.secret|changeset: 22611:6f0a53c8f587]
76 [log.changeset changeset.secret|changeset: 22611:6f0a53c8f587]
76
77
77 The following are the default effects for some default labels. Default
78 The following are the default effects for some default labels. Default
78 effects may be overridden from your configuration file::
79 effects may be overridden from your configuration file::
79
80
80 [color]
81 [color]
81 status.modified = blue bold underline red_background
82 status.modified = blue bold underline red_background
82 status.added = green bold
83 status.added = green bold
83 status.removed = red bold blue_background
84 status.removed = red bold blue_background
84 status.deleted = cyan bold underline
85 status.deleted = cyan bold underline
85 status.unknown = magenta bold underline
86 status.unknown = magenta bold underline
86 status.ignored = black bold
87 status.ignored = black bold
87
88
88 # 'none' turns off all effects
89 # 'none' turns off all effects
89 status.clean = none
90 status.clean = none
90 status.copied = none
91 status.copied = none
91
92
92 qseries.applied = blue bold underline
93 qseries.applied = blue bold underline
93 qseries.unapplied = black bold
94 qseries.unapplied = black bold
94 qseries.missing = red bold
95 qseries.missing = red bold
95
96
96 diff.diffline = bold
97 diff.diffline = bold
97 diff.extended = cyan bold
98 diff.extended = cyan bold
98 diff.file_a = red bold
99 diff.file_a = red bold
99 diff.file_b = green bold
100 diff.file_b = green bold
100 diff.hunk = magenta
101 diff.hunk = magenta
101 diff.deleted = red
102 diff.deleted = red
102 diff.inserted = green
103 diff.inserted = green
103 diff.changed = white
104 diff.changed = white
104 diff.tab =
105 diff.tab =
105 diff.trailingwhitespace = bold red_background
106 diff.trailingwhitespace = bold red_background
106
107
107 # Blank so it inherits the style of the surrounding label
108 # Blank so it inherits the style of the surrounding label
108 changeset.public =
109 changeset.public =
109 changeset.draft =
110 changeset.draft =
110 changeset.secret =
111 changeset.secret =
111
112
112 resolve.unresolved = red bold
113 resolve.unresolved = red bold
113 resolve.resolved = green bold
114 resolve.resolved = green bold
114
115
115 bookmarks.active = green
116 bookmarks.active = green
116
117
117 branches.active = none
118 branches.active = none
118 branches.closed = black bold
119 branches.closed = black bold
119 branches.current = green
120 branches.current = green
120 branches.inactive = none
121 branches.inactive = none
121
122
122 tags.normal = green
123 tags.normal = green
123 tags.local = black bold
124 tags.local = black bold
124
125
125 rebase.rebased = blue
126 rebase.rebased = blue
126 rebase.remaining = red bold
127 rebase.remaining = red bold
127
128
128 shelve.age = cyan
129 shelve.age = cyan
129 shelve.newest = green bold
130 shelve.newest = green bold
130 shelve.name = blue bold
131 shelve.name = blue bold
131
132
132 histedit.remaining = red bold
133 histedit.remaining = red bold
133
134
134 Custom colors
135 Custom colors
135 =============
136 =============
136
137
137 Because there are only eight standard colors, Mercurial allows you
138 Because there are only eight standard colors, Mercurial allows you
138 to define color names for other color slots which might be available
139 to define color names for other color slots which might be available
139 for your terminal type, assuming terminfo mode. For instance::
140 for your terminal type, assuming terminfo mode. For instance::
140
141
141 color.brightblue = 12
142 color.brightblue = 12
142 color.pink = 207
143 color.pink = 207
143 color.orange = 202
144 color.orange = 202
144
145
145 to set 'brightblue' to color slot 12 (useful for 16 color terminals
146 to set 'brightblue' to color slot 12 (useful for 16 color terminals
146 that have brighter colors defined in the upper eight) and, 'pink' and
147 that have brighter colors defined in the upper eight) and, 'pink' and
147 'orange' to colors in 256-color xterm's default color cube. These
148 'orange' to colors in 256-color xterm's default color cube. These
148 defined colors may then be used as any of the pre-defined eight,
149 defined colors may then be used as any of the pre-defined eight,
149 including appending '_background' to set the background to that color.
150 including appending '_background' to set the background to that color.
@@ -1,919 +1,920 b''
1 # templatefuncs.py - common template functions
1 # templatefuncs.py - common template functions
2 #
2 #
3 # Copyright 2005, 2006 Olivia Mackall <olivia@selenic.com>
3 # Copyright 2005, 2006 Olivia Mackall <olivia@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
8
9 import binascii
9 import binascii
10 import re
10 import re
11
11
12 from .i18n import _
12 from .i18n import _
13 from .node import bin
13 from .node import bin
14 from . import (
14 from . import (
15 color,
15 color,
16 dagop,
16 dagop,
17 diffutil,
17 diffutil,
18 encoding,
18 encoding,
19 error,
19 error,
20 minirst,
20 minirst,
21 obsutil,
21 obsutil,
22 pycompat,
22 pycompat,
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
50
51 @templatefunc(b'date(date[, fmt])')
51 @templatefunc(b'date(date[, fmt])')
52 def date(context, mapping, args):
52 def date(context, mapping, args):
53 """Format a date. The format string uses the Python strftime format.
53 """Format a date. The format string uses the Python strftime format.
54 The default is a Unix date format, including the timezone:
54 The default is a Unix date format, including the timezone:
55 "Mon Sep 04 15:13:13 2006 0700"."""
55 "Mon Sep 04 15:13:13 2006 0700"."""
56 if not (1 <= len(args) <= 2):
56 if not (1 <= len(args) <= 2):
57 # i18n: "date" is a keyword
57 # i18n: "date" is a keyword
58 raise error.ParseError(_(b"date expects one or two arguments"))
58 raise error.ParseError(_(b"date expects one or two arguments"))
59
59
60 date = evaldate(
60 date = evaldate(
61 context,
61 context,
62 mapping,
62 mapping,
63 args[0],
63 args[0],
64 # i18n: "date" is a keyword
64 # i18n: "date" is a keyword
65 _(b"date expects a date information"),
65 _(b"date expects a date information"),
66 )
66 )
67 fmt = None
67 fmt = None
68 if len(args) == 2:
68 if len(args) == 2:
69 fmt = evalstring(context, mapping, args[1])
69 fmt = evalstring(context, mapping, args[1])
70 if fmt is None:
70 if fmt is None:
71 return dateutil.datestr(date)
71 return dateutil.datestr(date)
72 else:
72 else:
73 return dateutil.datestr(date, fmt)
73 return dateutil.datestr(date, fmt)
74
74
75
75
76 @templatefunc(b'dict([[key=]value...])', argspec=b'*args **kwargs')
76 @templatefunc(b'dict([[key=]value...])', argspec=b'*args **kwargs')
77 def dict_(context, mapping, args):
77 def dict_(context, mapping, args):
78 """Construct a dict from key-value pairs. A key may be omitted if
78 """Construct a dict from key-value pairs. A key may be omitted if
79 a value expression can provide an unambiguous name."""
79 a value expression can provide an unambiguous name."""
80 data = util.sortdict()
80 data = util.sortdict()
81
81
82 for v in args[b'args']:
82 for v in args[b'args']:
83 k = templateutil.findsymbolicname(v)
83 k = templateutil.findsymbolicname(v)
84 if not k:
84 if not k:
85 raise error.ParseError(_(b'dict key cannot be inferred'))
85 raise error.ParseError(_(b'dict key cannot be inferred'))
86 if k in data or k in args[b'kwargs']:
86 if k in data or k in args[b'kwargs']:
87 raise error.ParseError(_(b"duplicated dict key '%s' inferred") % k)
87 raise error.ParseError(_(b"duplicated dict key '%s' inferred") % k)
88 data[k] = evalfuncarg(context, mapping, v)
88 data[k] = evalfuncarg(context, mapping, v)
89
89
90 data.update(
90 data.update(
91 (k, evalfuncarg(context, mapping, v))
91 (k, evalfuncarg(context, mapping, v))
92 for k, v in args[b'kwargs'].items()
92 for k, v in args[b'kwargs'].items()
93 )
93 )
94 return templateutil.hybriddict(data)
94 return templateutil.hybriddict(data)
95
95
96
96
97 @templatefunc(
97 @templatefunc(
98 b'diff([includepattern [, excludepattern]])', requires={b'ctx', b'ui'}
98 b'diff([includepattern [, excludepattern]])', requires={b'ctx', b'ui'}
99 )
99 )
100 def diff(context, mapping, args):
100 def diff(context, mapping, args):
101 """Show a diff, optionally
101 """Show a diff, optionally
102 specifying files to include or exclude."""
102 specifying files to include or exclude."""
103 if len(args) > 2:
103 if len(args) > 2:
104 # i18n: "diff" is a keyword
104 # i18n: "diff" is a keyword
105 raise error.ParseError(_(b"diff expects zero, one, or two arguments"))
105 raise error.ParseError(_(b"diff expects zero, one, or two arguments"))
106
106
107 def getpatterns(i):
107 def getpatterns(i):
108 if i < len(args):
108 if i < len(args):
109 s = evalstring(context, mapping, args[i]).strip()
109 s = evalstring(context, mapping, args[i]).strip()
110 if s:
110 if s:
111 return [s]
111 return [s]
112 return []
112 return []
113
113
114 ctx = context.resource(mapping, b'ctx')
114 ctx = context.resource(mapping, b'ctx')
115 ui = context.resource(mapping, b'ui')
115 ui = context.resource(mapping, b'ui')
116 diffopts = diffutil.diffallopts(ui)
116 diffopts = diffutil.diffallopts(ui)
117 chunks = ctx.diff(
117 chunks = ctx.diff(
118 match=ctx.match([], getpatterns(0), getpatterns(1)), opts=diffopts
118 match=ctx.match([], getpatterns(0), getpatterns(1)), opts=diffopts
119 )
119 )
120
120
121 return b''.join(chunks)
121 return b''.join(chunks)
122
122
123
123
124 @templatefunc(
124 @templatefunc(
125 b'extdata(source)', argspec=b'source', requires={b'ctx', b'cache'}
125 b'extdata(source)', argspec=b'source', requires={b'ctx', b'cache'}
126 )
126 )
127 def extdata(context, mapping, args):
127 def extdata(context, mapping, args):
128 """Show a text read from the specified extdata source. (EXPERIMENTAL)"""
128 """Show a text read from the specified extdata source. (EXPERIMENTAL)"""
129 if b'source' not in args:
129 if b'source' not in args:
130 # i18n: "extdata" is a keyword
130 # i18n: "extdata" is a keyword
131 raise error.ParseError(_(b'extdata expects one argument'))
131 raise error.ParseError(_(b'extdata expects one argument'))
132
132
133 source = evalstring(context, mapping, args[b'source'])
133 source = evalstring(context, mapping, args[b'source'])
134 if not source:
134 if not source:
135 sym = templateutil.findsymbolicname(args[b'source'])
135 sym = templateutil.findsymbolicname(args[b'source'])
136 if sym:
136 if sym:
137 raise error.ParseError(
137 raise error.ParseError(
138 _(b'empty data source specified'),
138 _(b'empty data source specified'),
139 hint=_(b"did you mean extdata('%s')?") % sym,
139 hint=_(b"did you mean extdata('%s')?") % sym,
140 )
140 )
141 else:
141 else:
142 raise error.ParseError(_(b'empty data source specified'))
142 raise error.ParseError(_(b'empty data source specified'))
143 cache = context.resource(mapping, b'cache').setdefault(b'extdata', {})
143 cache = context.resource(mapping, b'cache').setdefault(b'extdata', {})
144 ctx = context.resource(mapping, b'ctx')
144 ctx = context.resource(mapping, b'ctx')
145 if source in cache:
145 if source in cache:
146 data = cache[source]
146 data = cache[source]
147 else:
147 else:
148 data = cache[source] = scmutil.extdatasource(ctx.repo(), source)
148 data = cache[source] = scmutil.extdatasource(ctx.repo(), source)
149 return data.get(ctx.rev(), b'')
149 return data.get(ctx.rev(), b'')
150
150
151
151
152 @templatefunc(b'files(pattern)', requires={b'ctx'})
152 @templatefunc(b'files(pattern)', requires={b'ctx'})
153 def files(context, mapping, args):
153 def files(context, mapping, args):
154 """All files of the current changeset matching the pattern. See
154 """All files of the current changeset matching the pattern. See
155 :hg:`help patterns`."""
155 :hg:`help patterns`."""
156 if not len(args) == 1:
156 if not len(args) == 1:
157 # i18n: "files" is a keyword
157 # i18n: "files" is a keyword
158 raise error.ParseError(_(b"files expects one argument"))
158 raise error.ParseError(_(b"files expects one argument"))
159
159
160 raw = evalstring(context, mapping, args[0])
160 raw = evalstring(context, mapping, args[0])
161 ctx = context.resource(mapping, b'ctx')
161 ctx = context.resource(mapping, b'ctx')
162 m = ctx.match([raw])
162 m = ctx.match([raw])
163 files = list(ctx.matches(m))
163 files = list(ctx.matches(m))
164 return templateutil.compatfileslist(context, mapping, b"file", files)
164 return templateutil.compatfileslist(context, mapping, b"file", files)
165
165
166
166
167 @templatefunc(b'fill(text[, width[, initialident[, hangindent]]])')
167 @templatefunc(b'fill(text[, width[, initialident[, hangindent]]])')
168 def fill(context, mapping, args):
168 def fill(context, mapping, args):
169 """Fill many
169 """Fill many
170 paragraphs with optional indentation. See the "fill" filter."""
170 paragraphs with optional indentation. See the "fill" filter."""
171 if not (1 <= len(args) <= 4):
171 if not (1 <= len(args) <= 4):
172 # i18n: "fill" is a keyword
172 # i18n: "fill" is a keyword
173 raise error.ParseError(_(b"fill expects one to four arguments"))
173 raise error.ParseError(_(b"fill expects one to four arguments"))
174
174
175 text = evalstring(context, mapping, args[0])
175 text = evalstring(context, mapping, args[0])
176 width = 76
176 width = 76
177 initindent = b''
177 initindent = b''
178 hangindent = b''
178 hangindent = b''
179 if 2 <= len(args) <= 4:
179 if 2 <= len(args) <= 4:
180 width = evalinteger(
180 width = evalinteger(
181 context,
181 context,
182 mapping,
182 mapping,
183 args[1],
183 args[1],
184 # i18n: "fill" is a keyword
184 # i18n: "fill" is a keyword
185 _(b"fill expects an integer width"),
185 _(b"fill expects an integer width"),
186 )
186 )
187 try:
187 try:
188 initindent = evalstring(context, mapping, args[2])
188 initindent = evalstring(context, mapping, args[2])
189 hangindent = evalstring(context, mapping, args[3])
189 hangindent = evalstring(context, mapping, args[3])
190 except IndexError:
190 except IndexError:
191 pass
191 pass
192
192
193 return templatefilters.fill(text, width, initindent, hangindent)
193 return templatefilters.fill(text, width, initindent, hangindent)
194
194
195
195
196 @templatefunc(b'filter(iterable[, expr])')
196 @templatefunc(b'filter(iterable[, expr])')
197 def filter_(context, mapping, args):
197 def filter_(context, mapping, args):
198 """Remove empty elements from a list or a dict. If expr specified, it's
198 """Remove empty elements from a list or a dict. If expr specified, it's
199 applied to each element to test emptiness."""
199 applied to each element to test emptiness."""
200 if not (1 <= len(args) <= 2):
200 if not (1 <= len(args) <= 2):
201 # i18n: "filter" is a keyword
201 # i18n: "filter" is a keyword
202 raise error.ParseError(_(b"filter expects one or two arguments"))
202 raise error.ParseError(_(b"filter expects one or two arguments"))
203 iterable = evalwrapped(context, mapping, args[0])
203 iterable = evalwrapped(context, mapping, args[0])
204 if len(args) == 1:
204 if len(args) == 1:
205
205
206 def select(w):
206 def select(w):
207 return w.tobool(context, mapping)
207 return w.tobool(context, mapping)
208
208
209 else:
209 else:
210
210
211 def select(w):
211 def select(w):
212 if not isinstance(w, templateutil.mappable):
212 if not isinstance(w, templateutil.mappable):
213 raise error.ParseError(_(b"not filterable by expression"))
213 raise error.ParseError(_(b"not filterable by expression"))
214 lm = context.overlaymap(mapping, w.tomap(context))
214 lm = context.overlaymap(mapping, w.tomap(context))
215 return evalboolean(context, lm, args[1])
215 return evalboolean(context, lm, args[1])
216
216
217 return iterable.filter(context, mapping, select)
217 return iterable.filter(context, mapping, select)
218
218
219
219
220 @templatefunc(b'formatnode(node)', requires={b'ui'})
220 @templatefunc(b'formatnode(node)', requires={b'ui'})
221 def formatnode(context, mapping, args):
221 def formatnode(context, mapping, args):
222 """Obtain the preferred form of a changeset hash. (DEPRECATED)"""
222 """Obtain the preferred form of a changeset hash. (DEPRECATED)"""
223 if len(args) != 1:
223 if len(args) != 1:
224 # i18n: "formatnode" is a keyword
224 # i18n: "formatnode" is a keyword
225 raise error.ParseError(_(b"formatnode expects one argument"))
225 raise error.ParseError(_(b"formatnode expects one argument"))
226
226
227 ui = context.resource(mapping, b'ui')
227 ui = context.resource(mapping, b'ui')
228 node = evalstring(context, mapping, args[0])
228 node = evalstring(context, mapping, args[0])
229 if ui.debugflag:
229 if ui.debugflag:
230 return node
230 return node
231 return templatefilters.short(node)
231 return templatefilters.short(node)
232
232
233
233
234 @templatefunc(b'mailmap(author)', requires={b'repo', b'cache'})
234 @templatefunc(b'mailmap(author)', requires={b'repo', b'cache'})
235 def mailmap(context, mapping, args):
235 def mailmap(context, mapping, args):
236 """Return the author, updated according to the value
236 """Return the author, updated according to the value
237 set in the .mailmap file"""
237 set in the .mailmap file"""
238 if len(args) != 1:
238 if len(args) != 1:
239 raise error.ParseError(_(b"mailmap expects one argument"))
239 raise error.ParseError(_(b"mailmap expects one argument"))
240
240
241 author = evalstring(context, mapping, args[0])
241 author = evalstring(context, mapping, args[0])
242
242
243 cache = context.resource(mapping, b'cache')
243 cache = context.resource(mapping, b'cache')
244 repo = context.resource(mapping, b'repo')
244 repo = context.resource(mapping, b'repo')
245
245
246 if b'mailmap' not in cache:
246 if b'mailmap' not in cache:
247 data = repo.wvfs.tryread(b'.mailmap')
247 data = repo.wvfs.tryread(b'.mailmap')
248 cache[b'mailmap'] = stringutil.parsemailmap(data)
248 cache[b'mailmap'] = stringutil.parsemailmap(data)
249
249
250 return stringutil.mapname(cache[b'mailmap'], author)
250 return stringutil.mapname(cache[b'mailmap'], author)
251
251
252
252
253 @templatefunc(
253 @templatefunc(
254 b'pad(text, width[, fillchar=\' \'[, left=False[, truncate=False]]])',
254 b'pad(text, width[, fillchar=\' \'[, left=False[, truncate=False]]])',
255 argspec=b'text width fillchar left truncate',
255 argspec=b'text width fillchar left truncate',
256 )
256 )
257 def pad(context, mapping, args):
257 def pad(context, mapping, args):
258 """Pad text with a
258 """Pad text with a
259 fill character."""
259 fill character."""
260 if b'text' not in args or b'width' not in args:
260 if b'text' not in args or b'width' not in args:
261 # i18n: "pad" is a keyword
261 # i18n: "pad" is a keyword
262 raise error.ParseError(_(b"pad() expects two to four arguments"))
262 raise error.ParseError(_(b"pad() expects two to four arguments"))
263
263
264 width = evalinteger(
264 width = evalinteger(
265 context,
265 context,
266 mapping,
266 mapping,
267 args[b'width'],
267 args[b'width'],
268 # i18n: "pad" is a keyword
268 # i18n: "pad" is a keyword
269 _(b"pad() expects an integer width"),
269 _(b"pad() expects an integer width"),
270 )
270 )
271
271
272 text = evalstring(context, mapping, args[b'text'])
272 text = evalstring(context, mapping, args[b'text'])
273
273
274 truncate = False
274 truncate = False
275 left = False
275 left = False
276 fillchar = b' '
276 fillchar = b' '
277 if b'fillchar' in args:
277 if b'fillchar' in args:
278 fillchar = evalstring(context, mapping, args[b'fillchar'])
278 fillchar = evalstring(context, mapping, args[b'fillchar'])
279 if len(color.stripeffects(fillchar)) != 1:
279 if len(color.stripeffects(fillchar)) != 1:
280 # i18n: "pad" is a keyword
280 # i18n: "pad" is a keyword
281 raise error.ParseError(_(b"pad() expects a single fill character"))
281 raise error.ParseError(_(b"pad() expects a single fill character"))
282 if b'left' in args:
282 if b'left' in args:
283 left = evalboolean(context, mapping, args[b'left'])
283 left = evalboolean(context, mapping, args[b'left'])
284 if b'truncate' in args:
284 if b'truncate' in args:
285 truncate = evalboolean(context, mapping, args[b'truncate'])
285 truncate = evalboolean(context, mapping, args[b'truncate'])
286
286
287 fillwidth = width - encoding.colwidth(color.stripeffects(text))
287 fillwidth = width - encoding.colwidth(color.stripeffects(text))
288 if fillwidth < 0 and truncate:
288 if fillwidth < 0 and truncate:
289 return encoding.trim(color.stripeffects(text), width, leftside=left)
289 return encoding.trim(color.stripeffects(text), width, leftside=left)
290 if fillwidth <= 0:
290 if fillwidth <= 0:
291 return text
291 return text
292 if left:
292 if left:
293 return fillchar * fillwidth + text
293 return fillchar * fillwidth + text
294 else:
294 else:
295 return text + fillchar * fillwidth
295 return text + fillchar * fillwidth
296
296
297
297
298 @templatefunc(b'indent(text, indentchars[, firstline])')
298 @templatefunc(b'indent(text, indentchars[, firstline])')
299 def indent(context, mapping, args):
299 def indent(context, mapping, args):
300 """Indents all non-empty lines
300 """Indents all non-empty lines
301 with the characters given in the indentchars string. An optional
301 with the characters given in the indentchars string. An optional
302 third parameter will override the indent for the first line only
302 third parameter will override the indent for the first line only
303 if present."""
303 if present."""
304 if not (2 <= len(args) <= 3):
304 if not (2 <= len(args) <= 3):
305 # i18n: "indent" is a keyword
305 # i18n: "indent" is a keyword
306 raise error.ParseError(_(b"indent() expects two or three arguments"))
306 raise error.ParseError(_(b"indent() expects two or three arguments"))
307
307
308 text = evalstring(context, mapping, args[0])
308 text = evalstring(context, mapping, args[0])
309 indent = evalstring(context, mapping, args[1])
309 indent = evalstring(context, mapping, args[1])
310
310
311 firstline = indent
311 firstline = indent
312 if len(args) == 3:
312 if len(args) == 3:
313 firstline = evalstring(context, mapping, args[2])
313 firstline = evalstring(context, mapping, args[2])
314
314
315 return templatefilters.indent(text, indent, firstline=firstline)
315 return templatefilters.indent(text, indent, firstline=firstline)
316
316
317
317
318 @templatefunc(b'get(dict, key)')
318 @templatefunc(b'get(dict, key)')
319 def get(context, mapping, args):
319 def get(context, mapping, args):
320 """Get an attribute/key from an object. Some keywords
320 """Get an attribute/key from an object. Some keywords
321 are complex types. This function allows you to obtain the value of an
321 are complex types. This function allows you to obtain the value of an
322 attribute on these types."""
322 attribute on these types."""
323 if len(args) != 2:
323 if len(args) != 2:
324 # i18n: "get" is a keyword
324 # i18n: "get" is a keyword
325 raise error.ParseError(_(b"get() expects two arguments"))
325 raise error.ParseError(_(b"get() expects two arguments"))
326
326
327 dictarg = evalwrapped(context, mapping, args[0])
327 dictarg = evalwrapped(context, mapping, args[0])
328 key = evalrawexp(context, mapping, args[1])
328 key = evalrawexp(context, mapping, args[1])
329 try:
329 try:
330 return dictarg.getmember(context, mapping, key)
330 return dictarg.getmember(context, mapping, key)
331 except error.ParseError as err:
331 except error.ParseError as err:
332 # i18n: "get" is a keyword
332 # i18n: "get" is a keyword
333 hint = _(b"get() expects a dict as first argument")
333 hint = _(b"get() expects a dict as first argument")
334 raise error.ParseError(bytes(err), hint=hint)
334 raise error.ParseError(bytes(err), hint=hint)
335
335
336
336
337 @templatefunc(b'config(section, name[, default])', requires={b'ui'})
337 @templatefunc(b'config(section, name[, default])', requires={b'ui'})
338 def config(context, mapping, args):
338 def config(context, mapping, args):
339 """Returns the requested hgrc config option as a string."""
339 """Returns the requested hgrc config option as a string."""
340 fn = context.resource(mapping, b'ui').config
340 fn = context.resource(mapping, b'ui').config
341 return _config(context, mapping, args, fn, evalstring)
341 return _config(context, mapping, args, fn, evalstring)
342
342
343
343
344 @templatefunc(b'configbool(section, name[, default])', requires={b'ui'})
344 @templatefunc(b'configbool(section, name[, default])', requires={b'ui'})
345 def configbool(context, mapping, args):
345 def configbool(context, mapping, args):
346 """Returns the requested hgrc config option as a boolean."""
346 """Returns the requested hgrc config option as a boolean."""
347 fn = context.resource(mapping, b'ui').configbool
347 fn = context.resource(mapping, b'ui').configbool
348 return _config(context, mapping, args, fn, evalboolean)
348 return _config(context, mapping, args, fn, evalboolean)
349
349
350
350
351 @templatefunc(b'configint(section, name[, default])', requires={b'ui'})
351 @templatefunc(b'configint(section, name[, default])', requires={b'ui'})
352 def configint(context, mapping, args):
352 def configint(context, mapping, args):
353 """Returns the requested hgrc config option as an integer."""
353 """Returns the requested hgrc config option as an integer."""
354 fn = context.resource(mapping, b'ui').configint
354 fn = context.resource(mapping, b'ui').configint
355 return _config(context, mapping, args, fn, evalinteger)
355 return _config(context, mapping, args, fn, evalinteger)
356
356
357
357
358 def _config(context, mapping, args, configfn, defaultfn):
358 def _config(context, mapping, args, configfn, defaultfn):
359 if not (2 <= len(args) <= 3):
359 if not (2 <= len(args) <= 3):
360 raise error.ParseError(_(b"config expects two or three arguments"))
360 raise error.ParseError(_(b"config expects two or three arguments"))
361
361
362 # The config option can come from any section, though we specifically
362 # The config option can come from any section, though we specifically
363 # reserve the [templateconfig] section for dynamically defining options
363 # reserve the [templateconfig] section for dynamically defining options
364 # for this function without also requiring an extension.
364 # for this function without also requiring an extension.
365 section = evalstringliteral(context, mapping, args[0])
365 section = evalstringliteral(context, mapping, args[0])
366 name = evalstringliteral(context, mapping, args[1])
366 name = evalstringliteral(context, mapping, args[1])
367 if len(args) == 3:
367 if len(args) == 3:
368 default = defaultfn(context, mapping, args[2])
368 default = defaultfn(context, mapping, args[2])
369 return configfn(section, name, default)
369 return configfn(section, name, default)
370 else:
370 else:
371 return configfn(section, name)
371 return configfn(section, name)
372
372
373
373
374 @templatefunc(b'if(expr, then[, else])')
374 @templatefunc(b'if(expr, then[, else])')
375 def if_(context, mapping, args):
375 def if_(context, mapping, args):
376 """Conditionally execute based on the result of
376 """Conditionally execute based on the result of
377 an expression."""
377 an expression."""
378 if not (2 <= len(args) <= 3):
378 if not (2 <= len(args) <= 3):
379 # i18n: "if" is a keyword
379 # i18n: "if" is a keyword
380 raise error.ParseError(_(b"if expects two or three arguments"))
380 raise error.ParseError(_(b"if expects two or three arguments"))
381
381
382 test = evalboolean(context, mapping, args[0])
382 test = evalboolean(context, mapping, args[0])
383 if test:
383 if test:
384 return evalrawexp(context, mapping, args[1])
384 return evalrawexp(context, mapping, args[1])
385 elif len(args) == 3:
385 elif len(args) == 3:
386 return evalrawexp(context, mapping, args[2])
386 return evalrawexp(context, mapping, args[2])
387
387
388
388
389 @templatefunc(b'ifcontains(needle, haystack, then[, else])')
389 @templatefunc(b'ifcontains(needle, haystack, then[, else])')
390 def ifcontains(context, mapping, args):
390 def ifcontains(context, mapping, args):
391 """Conditionally execute based
391 """Conditionally execute based
392 on whether the item "needle" is in "haystack"."""
392 on whether the item "needle" is in "haystack"."""
393 if not (3 <= len(args) <= 4):
393 if not (3 <= len(args) <= 4):
394 # i18n: "ifcontains" is a keyword
394 # i18n: "ifcontains" is a keyword
395 raise error.ParseError(_(b"ifcontains expects three or four arguments"))
395 raise error.ParseError(_(b"ifcontains expects three or four arguments"))
396
396
397 haystack = evalwrapped(context, mapping, args[1])
397 haystack = evalwrapped(context, mapping, args[1])
398 try:
398 try:
399 needle = evalrawexp(context, mapping, args[0])
399 needle = evalrawexp(context, mapping, args[0])
400 found = haystack.contains(context, mapping, needle)
400 found = haystack.contains(context, mapping, needle)
401 except error.ParseError:
401 except error.ParseError:
402 found = False
402 found = False
403
403
404 if found:
404 if found:
405 return evalrawexp(context, mapping, args[2])
405 return evalrawexp(context, mapping, args[2])
406 elif len(args) == 4:
406 elif len(args) == 4:
407 return evalrawexp(context, mapping, args[3])
407 return evalrawexp(context, mapping, args[3])
408
408
409
409
410 @templatefunc(b'ifeq(expr1, expr2, then[, else])')
410 @templatefunc(b'ifeq(expr1, expr2, then[, else])')
411 def ifeq(context, mapping, args):
411 def ifeq(context, mapping, args):
412 """Conditionally execute based on
412 """Conditionally execute based on
413 whether 2 items are equivalent."""
413 whether 2 items are equivalent."""
414 if not (3 <= len(args) <= 4):
414 if not (3 <= len(args) <= 4):
415 # i18n: "ifeq" is a keyword
415 # i18n: "ifeq" is a keyword
416 raise error.ParseError(_(b"ifeq expects three or four arguments"))
416 raise error.ParseError(_(b"ifeq expects three or four arguments"))
417
417
418 test = evalstring(context, mapping, args[0])
418 test = evalstring(context, mapping, args[0])
419 match = evalstring(context, mapping, args[1])
419 match = evalstring(context, mapping, args[1])
420 if test == match:
420 if test == match:
421 return evalrawexp(context, mapping, args[2])
421 return evalrawexp(context, mapping, args[2])
422 elif len(args) == 4:
422 elif len(args) == 4:
423 return evalrawexp(context, mapping, args[3])
423 return evalrawexp(context, mapping, args[3])
424
424
425
425
426 @templatefunc(b'join(list, sep)')
426 @templatefunc(b'join(list, sep)')
427 def join(context, mapping, args):
427 def join(context, mapping, args):
428 """Join items in a list with a delimiter."""
428 """Join items in a list with a delimiter."""
429 if not (1 <= len(args) <= 2):
429 if not (1 <= len(args) <= 2):
430 # i18n: "join" is a keyword
430 # i18n: "join" is a keyword
431 raise error.ParseError(_(b"join expects one or two arguments"))
431 raise error.ParseError(_(b"join expects one or two arguments"))
432
432
433 joinset = evalwrapped(context, mapping, args[0])
433 joinset = evalwrapped(context, mapping, args[0])
434 joiner = b" "
434 joiner = b" "
435 if len(args) > 1:
435 if len(args) > 1:
436 joiner = evalstring(context, mapping, args[1])
436 joiner = evalstring(context, mapping, args[1])
437 return joinset.join(context, mapping, joiner)
437 return joinset.join(context, mapping, joiner)
438
438
439
439
440 @templatefunc(b'label(label, expr)', requires={b'ui'})
440 @templatefunc(b'label(label, expr)', requires={b'ui'})
441 def label(context, mapping, args):
441 def label(context, mapping, args):
442 """Apply a label to generated content. Content with
442 """Apply a label to generated content. Content with a label
443 a label applied can result in additional post-processing, such as
443 applied can result in additional post-processing, such as
444 automatic colorization."""
444 automatic colorization. In order to receive effects, labels must
445 have a dot, such as `log.secret` or `branch.active`."""
445 if len(args) != 2:
446 if len(args) != 2:
446 # i18n: "label" is a keyword
447 # i18n: "label" is a keyword
447 raise error.ParseError(_(b"label expects two arguments"))
448 raise error.ParseError(_(b"label expects two arguments"))
448
449
449 ui = context.resource(mapping, b'ui')
450 ui = context.resource(mapping, b'ui')
450 thing = evalstring(context, mapping, args[1])
451 thing = evalstring(context, mapping, args[1])
451 # preserve unknown symbol as literal so effects like 'red', 'bold',
452 # preserve unknown symbol as literal so effects like 'red', 'bold',
452 # etc. don't need to be quoted
453 # etc. don't need to be quoted
453 label = evalstringliteral(context, mapping, args[0])
454 label = evalstringliteral(context, mapping, args[0])
454
455
455 return ui.label(thing, label)
456 return ui.label(thing, label)
456
457
457
458
458 @templatefunc(b'latesttag([pattern])')
459 @templatefunc(b'latesttag([pattern])')
459 def latesttag(context, mapping, args):
460 def latesttag(context, mapping, args):
460 """The global tags matching the given pattern on the
461 """The global tags matching the given pattern on the
461 most recent globally tagged ancestor of this changeset.
462 most recent globally tagged ancestor of this changeset.
462 If no such tags exist, the "{tag}" template resolves to
463 If no such tags exist, the "{tag}" template resolves to
463 the string "null". See :hg:`help revisions.patterns` for the pattern
464 the string "null". See :hg:`help revisions.patterns` for the pattern
464 syntax.
465 syntax.
465 """
466 """
466 if len(args) > 1:
467 if len(args) > 1:
467 # i18n: "latesttag" is a keyword
468 # i18n: "latesttag" is a keyword
468 raise error.ParseError(_(b"latesttag expects at most one argument"))
469 raise error.ParseError(_(b"latesttag expects at most one argument"))
469
470
470 pattern = None
471 pattern = None
471 if len(args) == 1:
472 if len(args) == 1:
472 pattern = evalstring(context, mapping, args[0])
473 pattern = evalstring(context, mapping, args[0])
473 return templatekw.showlatesttags(context, mapping, pattern)
474 return templatekw.showlatesttags(context, mapping, pattern)
474
475
475
476
476 @templatefunc(b'localdate(date[, tz])')
477 @templatefunc(b'localdate(date[, tz])')
477 def localdate(context, mapping, args):
478 def localdate(context, mapping, args):
478 """Converts a date to the specified timezone.
479 """Converts a date to the specified timezone.
479 The default is local date."""
480 The default is local date."""
480 if not (1 <= len(args) <= 2):
481 if not (1 <= len(args) <= 2):
481 # i18n: "localdate" is a keyword
482 # i18n: "localdate" is a keyword
482 raise error.ParseError(_(b"localdate expects one or two arguments"))
483 raise error.ParseError(_(b"localdate expects one or two arguments"))
483
484
484 date = evaldate(
485 date = evaldate(
485 context,
486 context,
486 mapping,
487 mapping,
487 args[0],
488 args[0],
488 # i18n: "localdate" is a keyword
489 # i18n: "localdate" is a keyword
489 _(b"localdate expects a date information"),
490 _(b"localdate expects a date information"),
490 )
491 )
491 if len(args) >= 2:
492 if len(args) >= 2:
492 tzoffset = None
493 tzoffset = None
493 tz = evalfuncarg(context, mapping, args[1])
494 tz = evalfuncarg(context, mapping, args[1])
494 if isinstance(tz, bytes):
495 if isinstance(tz, bytes):
495 tzoffset, remainder = dateutil.parsetimezone(tz)
496 tzoffset, remainder = dateutil.parsetimezone(tz)
496 if remainder:
497 if remainder:
497 tzoffset = None
498 tzoffset = None
498 if tzoffset is None:
499 if tzoffset is None:
499 try:
500 try:
500 tzoffset = int(tz)
501 tzoffset = int(tz)
501 except (TypeError, ValueError):
502 except (TypeError, ValueError):
502 # i18n: "localdate" is a keyword
503 # i18n: "localdate" is a keyword
503 raise error.ParseError(_(b"localdate expects a timezone"))
504 raise error.ParseError(_(b"localdate expects a timezone"))
504 else:
505 else:
505 tzoffset = dateutil.makedate()[1]
506 tzoffset = dateutil.makedate()[1]
506 return templateutil.date((date[0], tzoffset))
507 return templateutil.date((date[0], tzoffset))
507
508
508
509
509 @templatefunc(b'max(iterable)')
510 @templatefunc(b'max(iterable)')
510 def max_(context, mapping, args, **kwargs):
511 def max_(context, mapping, args, **kwargs):
511 """Return the max of an iterable"""
512 """Return the max of an iterable"""
512 if len(args) != 1:
513 if len(args) != 1:
513 # i18n: "max" is a keyword
514 # i18n: "max" is a keyword
514 raise error.ParseError(_(b"max expects one argument"))
515 raise error.ParseError(_(b"max expects one argument"))
515
516
516 iterable = evalwrapped(context, mapping, args[0])
517 iterable = evalwrapped(context, mapping, args[0])
517 try:
518 try:
518 return iterable.getmax(context, mapping)
519 return iterable.getmax(context, mapping)
519 except error.ParseError as err:
520 except error.ParseError as err:
520 # i18n: "max" is a keyword
521 # i18n: "max" is a keyword
521 hint = _(b"max first argument should be an iterable")
522 hint = _(b"max first argument should be an iterable")
522 raise error.ParseError(bytes(err), hint=hint)
523 raise error.ParseError(bytes(err), hint=hint)
523
524
524
525
525 @templatefunc(b'min(iterable)')
526 @templatefunc(b'min(iterable)')
526 def min_(context, mapping, args, **kwargs):
527 def min_(context, mapping, args, **kwargs):
527 """Return the min of an iterable"""
528 """Return the min of an iterable"""
528 if len(args) != 1:
529 if len(args) != 1:
529 # i18n: "min" is a keyword
530 # i18n: "min" is a keyword
530 raise error.ParseError(_(b"min expects one argument"))
531 raise error.ParseError(_(b"min expects one argument"))
531
532
532 iterable = evalwrapped(context, mapping, args[0])
533 iterable = evalwrapped(context, mapping, args[0])
533 try:
534 try:
534 return iterable.getmin(context, mapping)
535 return iterable.getmin(context, mapping)
535 except error.ParseError as err:
536 except error.ParseError as err:
536 # i18n: "min" is a keyword
537 # i18n: "min" is a keyword
537 hint = _(b"min first argument should be an iterable")
538 hint = _(b"min first argument should be an iterable")
538 raise error.ParseError(bytes(err), hint=hint)
539 raise error.ParseError(bytes(err), hint=hint)
539
540
540
541
541 @templatefunc(b'mod(a, b)')
542 @templatefunc(b'mod(a, b)')
542 def mod(context, mapping, args):
543 def mod(context, mapping, args):
543 """Calculate a mod b such that a / b + a mod b == a"""
544 """Calculate a mod b such that a / b + a mod b == a"""
544 if not len(args) == 2:
545 if not len(args) == 2:
545 # i18n: "mod" is a keyword
546 # i18n: "mod" is a keyword
546 raise error.ParseError(_(b"mod expects two arguments"))
547 raise error.ParseError(_(b"mod expects two arguments"))
547
548
548 func = lambda a, b: a % b
549 func = lambda a, b: a % b
549 return templateutil.runarithmetic(
550 return templateutil.runarithmetic(
550 context, mapping, (func, args[0], args[1])
551 context, mapping, (func, args[0], args[1])
551 )
552 )
552
553
553
554
554 @templatefunc(b'obsfateoperations(markers)')
555 @templatefunc(b'obsfateoperations(markers)')
555 def obsfateoperations(context, mapping, args):
556 def obsfateoperations(context, mapping, args):
556 """Compute obsfate related information based on markers (EXPERIMENTAL)"""
557 """Compute obsfate related information based on markers (EXPERIMENTAL)"""
557 if len(args) != 1:
558 if len(args) != 1:
558 # i18n: "obsfateoperations" is a keyword
559 # i18n: "obsfateoperations" is a keyword
559 raise error.ParseError(_(b"obsfateoperations expects one argument"))
560 raise error.ParseError(_(b"obsfateoperations expects one argument"))
560
561
561 markers = evalfuncarg(context, mapping, args[0])
562 markers = evalfuncarg(context, mapping, args[0])
562
563
563 try:
564 try:
564 data = obsutil.markersoperations(markers)
565 data = obsutil.markersoperations(markers)
565 return templateutil.hybridlist(data, name=b'operation')
566 return templateutil.hybridlist(data, name=b'operation')
566 except (TypeError, KeyError):
567 except (TypeError, KeyError):
567 # i18n: "obsfateoperations" is a keyword
568 # i18n: "obsfateoperations" is a keyword
568 errmsg = _(b"obsfateoperations first argument should be an iterable")
569 errmsg = _(b"obsfateoperations first argument should be an iterable")
569 raise error.ParseError(errmsg)
570 raise error.ParseError(errmsg)
570
571
571
572
572 @templatefunc(b'obsfatedate(markers)')
573 @templatefunc(b'obsfatedate(markers)')
573 def obsfatedate(context, mapping, args):
574 def obsfatedate(context, mapping, args):
574 """Compute obsfate related information based on markers (EXPERIMENTAL)"""
575 """Compute obsfate related information based on markers (EXPERIMENTAL)"""
575 if len(args) != 1:
576 if len(args) != 1:
576 # i18n: "obsfatedate" is a keyword
577 # i18n: "obsfatedate" is a keyword
577 raise error.ParseError(_(b"obsfatedate expects one argument"))
578 raise error.ParseError(_(b"obsfatedate expects one argument"))
578
579
579 markers = evalfuncarg(context, mapping, args[0])
580 markers = evalfuncarg(context, mapping, args[0])
580
581
581 try:
582 try:
582 # TODO: maybe this has to be a wrapped list of date wrappers?
583 # TODO: maybe this has to be a wrapped list of date wrappers?
583 data = obsutil.markersdates(markers)
584 data = obsutil.markersdates(markers)
584 return templateutil.hybridlist(data, name=b'date', fmt=b'%d %d')
585 return templateutil.hybridlist(data, name=b'date', fmt=b'%d %d')
585 except (TypeError, KeyError):
586 except (TypeError, KeyError):
586 # i18n: "obsfatedate" is a keyword
587 # i18n: "obsfatedate" is a keyword
587 errmsg = _(b"obsfatedate first argument should be an iterable")
588 errmsg = _(b"obsfatedate first argument should be an iterable")
588 raise error.ParseError(errmsg)
589 raise error.ParseError(errmsg)
589
590
590
591
591 @templatefunc(b'obsfateusers(markers)')
592 @templatefunc(b'obsfateusers(markers)')
592 def obsfateusers(context, mapping, args):
593 def obsfateusers(context, mapping, args):
593 """Compute obsfate related information based on markers (EXPERIMENTAL)"""
594 """Compute obsfate related information based on markers (EXPERIMENTAL)"""
594 if len(args) != 1:
595 if len(args) != 1:
595 # i18n: "obsfateusers" is a keyword
596 # i18n: "obsfateusers" is a keyword
596 raise error.ParseError(_(b"obsfateusers expects one argument"))
597 raise error.ParseError(_(b"obsfateusers expects one argument"))
597
598
598 markers = evalfuncarg(context, mapping, args[0])
599 markers = evalfuncarg(context, mapping, args[0])
599
600
600 try:
601 try:
601 data = obsutil.markersusers(markers)
602 data = obsutil.markersusers(markers)
602 return templateutil.hybridlist(data, name=b'user')
603 return templateutil.hybridlist(data, name=b'user')
603 except (TypeError, KeyError, ValueError):
604 except (TypeError, KeyError, ValueError):
604 # i18n: "obsfateusers" is a keyword
605 # i18n: "obsfateusers" is a keyword
605 msg = _(
606 msg = _(
606 b"obsfateusers first argument should be an iterable of "
607 b"obsfateusers first argument should be an iterable of "
607 b"obsmakers"
608 b"obsmakers"
608 )
609 )
609 raise error.ParseError(msg)
610 raise error.ParseError(msg)
610
611
611
612
612 @templatefunc(b'obsfateverb(successors, markers)')
613 @templatefunc(b'obsfateverb(successors, markers)')
613 def obsfateverb(context, mapping, args):
614 def obsfateverb(context, mapping, args):
614 """Compute obsfate related information based on successors (EXPERIMENTAL)"""
615 """Compute obsfate related information based on successors (EXPERIMENTAL)"""
615 if len(args) != 2:
616 if len(args) != 2:
616 # i18n: "obsfateverb" is a keyword
617 # i18n: "obsfateverb" is a keyword
617 raise error.ParseError(_(b"obsfateverb expects two arguments"))
618 raise error.ParseError(_(b"obsfateverb expects two arguments"))
618
619
619 successors = evalfuncarg(context, mapping, args[0])
620 successors = evalfuncarg(context, mapping, args[0])
620 markers = evalfuncarg(context, mapping, args[1])
621 markers = evalfuncarg(context, mapping, args[1])
621
622
622 try:
623 try:
623 return obsutil.obsfateverb(successors, markers)
624 return obsutil.obsfateverb(successors, markers)
624 except TypeError:
625 except TypeError:
625 # i18n: "obsfateverb" is a keyword
626 # i18n: "obsfateverb" is a keyword
626 errmsg = _(b"obsfateverb first argument should be countable")
627 errmsg = _(b"obsfateverb first argument should be countable")
627 raise error.ParseError(errmsg)
628 raise error.ParseError(errmsg)
628
629
629
630
630 @templatefunc(b'relpath(path)', requires={b'repo'})
631 @templatefunc(b'relpath(path)', requires={b'repo'})
631 def relpath(context, mapping, args):
632 def relpath(context, mapping, args):
632 """Convert a repository-absolute path into a filesystem path relative to
633 """Convert a repository-absolute path into a filesystem path relative to
633 the current working directory."""
634 the current working directory."""
634 if len(args) != 1:
635 if len(args) != 1:
635 # i18n: "relpath" is a keyword
636 # i18n: "relpath" is a keyword
636 raise error.ParseError(_(b"relpath expects one argument"))
637 raise error.ParseError(_(b"relpath expects one argument"))
637
638
638 repo = context.resource(mapping, b'repo')
639 repo = context.resource(mapping, b'repo')
639 path = evalstring(context, mapping, args[0])
640 path = evalstring(context, mapping, args[0])
640 return repo.pathto(path)
641 return repo.pathto(path)
641
642
642
643
643 @templatefunc(b'revset(query[, formatargs...])', requires={b'repo', b'cache'})
644 @templatefunc(b'revset(query[, formatargs...])', requires={b'repo', b'cache'})
644 def revset(context, mapping, args):
645 def revset(context, mapping, args):
645 """Execute a revision set query. See
646 """Execute a revision set query. See
646 :hg:`help revset`."""
647 :hg:`help revset`."""
647 if not len(args) > 0:
648 if not len(args) > 0:
648 # i18n: "revset" is a keyword
649 # i18n: "revset" is a keyword
649 raise error.ParseError(_(b"revset expects one or more arguments"))
650 raise error.ParseError(_(b"revset expects one or more arguments"))
650
651
651 raw = evalstring(context, mapping, args[0])
652 raw = evalstring(context, mapping, args[0])
652 repo = context.resource(mapping, b'repo')
653 repo = context.resource(mapping, b'repo')
653
654
654 def query(expr):
655 def query(expr):
655 m = revsetmod.match(repo.ui, expr, lookup=revsetmod.lookupfn(repo))
656 m = revsetmod.match(repo.ui, expr, lookup=revsetmod.lookupfn(repo))
656 return m(repo)
657 return m(repo)
657
658
658 if len(args) > 1:
659 if len(args) > 1:
659 key = None # dynamically-created revs shouldn't be cached
660 key = None # dynamically-created revs shouldn't be cached
660 formatargs = [evalfuncarg(context, mapping, a) for a in args[1:]]
661 formatargs = [evalfuncarg(context, mapping, a) for a in args[1:]]
661 revs = query(revsetlang.formatspec(raw, *formatargs))
662 revs = query(revsetlang.formatspec(raw, *formatargs))
662 else:
663 else:
663 cache = context.resource(mapping, b'cache')
664 cache = context.resource(mapping, b'cache')
664 revsetcache = cache.setdefault(b"revsetcache", {})
665 revsetcache = cache.setdefault(b"revsetcache", {})
665 key = raw
666 key = raw
666 if key in revsetcache:
667 if key in revsetcache:
667 revs = revsetcache[key]
668 revs = revsetcache[key]
668 else:
669 else:
669 revs = query(raw)
670 revs = query(raw)
670 revsetcache[key] = revs
671 revsetcache[key] = revs
671 return templateutil.revslist(repo, revs, name=b'revision', cachekey=key)
672 return templateutil.revslist(repo, revs, name=b'revision', cachekey=key)
672
673
673
674
674 @templatefunc(b'rstdoc(text, style)')
675 @templatefunc(b'rstdoc(text, style)')
675 def rstdoc(context, mapping, args):
676 def rstdoc(context, mapping, args):
676 """Format reStructuredText."""
677 """Format reStructuredText."""
677 if len(args) != 2:
678 if len(args) != 2:
678 # i18n: "rstdoc" is a keyword
679 # i18n: "rstdoc" is a keyword
679 raise error.ParseError(_(b"rstdoc expects two arguments"))
680 raise error.ParseError(_(b"rstdoc expects two arguments"))
680
681
681 text = evalstring(context, mapping, args[0])
682 text = evalstring(context, mapping, args[0])
682 style = evalstring(context, mapping, args[1])
683 style = evalstring(context, mapping, args[1])
683
684
684 return minirst.format(text, style=style, keep=[b'verbose'])
685 return minirst.format(text, style=style, keep=[b'verbose'])
685
686
686
687
687 @templatefunc(b'search(pattern, text)')
688 @templatefunc(b'search(pattern, text)')
688 def search(context, mapping, args):
689 def search(context, mapping, args):
689 """Look for the first text matching the regular expression pattern.
690 """Look for the first text matching the regular expression pattern.
690 Groups are accessible as ``{1}``, ``{2}``, ... in %-mapped template."""
691 Groups are accessible as ``{1}``, ``{2}``, ... in %-mapped template."""
691 if len(args) != 2:
692 if len(args) != 2:
692 # i18n: "search" is a keyword
693 # i18n: "search" is a keyword
693 raise error.ParseError(_(b'search expects two arguments'))
694 raise error.ParseError(_(b'search expects two arguments'))
694
695
695 pat = evalstring(context, mapping, args[0])
696 pat = evalstring(context, mapping, args[0])
696 src = evalstring(context, mapping, args[1])
697 src = evalstring(context, mapping, args[1])
697 try:
698 try:
698 patre = re.compile(pat)
699 patre = re.compile(pat)
699 except re.error:
700 except re.error:
700 # i18n: "search" is a keyword
701 # i18n: "search" is a keyword
701 raise error.ParseError(_(b'search got an invalid pattern: %s') % pat)
702 raise error.ParseError(_(b'search got an invalid pattern: %s') % pat)
702 # named groups shouldn't shadow *reserved* resource keywords
703 # named groups shouldn't shadow *reserved* resource keywords
703 badgroups = context.knownresourcekeys() & set(
704 badgroups = context.knownresourcekeys() & set(
704 pycompat.byteskwargs(patre.groupindex)
705 pycompat.byteskwargs(patre.groupindex)
705 )
706 )
706 if badgroups:
707 if badgroups:
707 raise error.ParseError(
708 raise error.ParseError(
708 # i18n: "search" is a keyword
709 # i18n: "search" is a keyword
709 _(b'invalid group %(group)s in search pattern: %(pat)s')
710 _(b'invalid group %(group)s in search pattern: %(pat)s')
710 % {
711 % {
711 b'group': b', '.join(b"'%s'" % g for g in sorted(badgroups)),
712 b'group': b', '.join(b"'%s'" % g for g in sorted(badgroups)),
712 b'pat': pat,
713 b'pat': pat,
713 }
714 }
714 )
715 )
715
716
716 match = patre.search(src)
717 match = patre.search(src)
717 if not match:
718 if not match:
718 return templateutil.mappingnone()
719 return templateutil.mappingnone()
719
720
720 lm = {b'0': match.group(0)}
721 lm = {b'0': match.group(0)}
721 lm.update((b'%d' % i, v) for i, v in enumerate(match.groups(), 1))
722 lm.update((b'%d' % i, v) for i, v in enumerate(match.groups(), 1))
722 lm.update(pycompat.byteskwargs(match.groupdict()))
723 lm.update(pycompat.byteskwargs(match.groupdict()))
723 return templateutil.mappingdict(lm, tmpl=b'{0}')
724 return templateutil.mappingdict(lm, tmpl=b'{0}')
724
725
725
726
726 @templatefunc(b'separate(sep, args...)', argspec=b'sep *args')
727 @templatefunc(b'separate(sep, args...)', argspec=b'sep *args')
727 def separate(context, mapping, args):
728 def separate(context, mapping, args):
728 """Add a separator between non-empty arguments."""
729 """Add a separator between non-empty arguments."""
729 if b'sep' not in args:
730 if b'sep' not in args:
730 # i18n: "separate" is a keyword
731 # i18n: "separate" is a keyword
731 raise error.ParseError(_(b"separate expects at least one argument"))
732 raise error.ParseError(_(b"separate expects at least one argument"))
732
733
733 sep = evalstring(context, mapping, args[b'sep'])
734 sep = evalstring(context, mapping, args[b'sep'])
734 first = True
735 first = True
735 for arg in args[b'args']:
736 for arg in args[b'args']:
736 argstr = evalstring(context, mapping, arg)
737 argstr = evalstring(context, mapping, arg)
737 if not argstr:
738 if not argstr:
738 continue
739 continue
739 if first:
740 if first:
740 first = False
741 first = False
741 else:
742 else:
742 yield sep
743 yield sep
743 yield argstr
744 yield argstr
744
745
745
746
746 @templatefunc(b'shortest(node, minlength=4)', requires={b'repo', b'cache'})
747 @templatefunc(b'shortest(node, minlength=4)', requires={b'repo', b'cache'})
747 def shortest(context, mapping, args):
748 def shortest(context, mapping, args):
748 """Obtain the shortest representation of
749 """Obtain the shortest representation of
749 a node."""
750 a node."""
750 if not (1 <= len(args) <= 2):
751 if not (1 <= len(args) <= 2):
751 # i18n: "shortest" is a keyword
752 # i18n: "shortest" is a keyword
752 raise error.ParseError(_(b"shortest() expects one or two arguments"))
753 raise error.ParseError(_(b"shortest() expects one or two arguments"))
753
754
754 hexnode = evalstring(context, mapping, args[0])
755 hexnode = evalstring(context, mapping, args[0])
755
756
756 minlength = 4
757 minlength = 4
757 if len(args) > 1:
758 if len(args) > 1:
758 minlength = evalinteger(
759 minlength = evalinteger(
759 context,
760 context,
760 mapping,
761 mapping,
761 args[1],
762 args[1],
762 # i18n: "shortest" is a keyword
763 # i18n: "shortest" is a keyword
763 _(b"shortest() expects an integer minlength"),
764 _(b"shortest() expects an integer minlength"),
764 )
765 )
765
766
766 repo = context.resource(mapping, b'repo')
767 repo = context.resource(mapping, b'repo')
767 hexnodelen = 2 * repo.nodeconstants.nodelen
768 hexnodelen = 2 * repo.nodeconstants.nodelen
768 if len(hexnode) > hexnodelen:
769 if len(hexnode) > hexnodelen:
769 return hexnode
770 return hexnode
770 elif len(hexnode) == hexnodelen:
771 elif len(hexnode) == hexnodelen:
771 try:
772 try:
772 node = bin(hexnode)
773 node = bin(hexnode)
773 except binascii.Error:
774 except binascii.Error:
774 return hexnode
775 return hexnode
775 else:
776 else:
776 try:
777 try:
777 node = scmutil.resolvehexnodeidprefix(repo, hexnode)
778 node = scmutil.resolvehexnodeidprefix(repo, hexnode)
778 except error.WdirUnsupported:
779 except error.WdirUnsupported:
779 node = repo.nodeconstants.wdirid
780 node = repo.nodeconstants.wdirid
780 except error.LookupError:
781 except error.LookupError:
781 return hexnode
782 return hexnode
782 if not node:
783 if not node:
783 return hexnode
784 return hexnode
784 cache = context.resource(mapping, b'cache')
785 cache = context.resource(mapping, b'cache')
785 try:
786 try:
786 return scmutil.shortesthexnodeidprefix(repo, node, minlength, cache)
787 return scmutil.shortesthexnodeidprefix(repo, node, minlength, cache)
787 except error.RepoLookupError:
788 except error.RepoLookupError:
788 return hexnode
789 return hexnode
789
790
790
791
791 @templatefunc(b'strip(text[, chars])')
792 @templatefunc(b'strip(text[, chars])')
792 def strip(context, mapping, args):
793 def strip(context, mapping, args):
793 """Strip characters from a string. By default,
794 """Strip characters from a string. By default,
794 strips all leading and trailing whitespace."""
795 strips all leading and trailing whitespace."""
795 if not (1 <= len(args) <= 2):
796 if not (1 <= len(args) <= 2):
796 # i18n: "strip" is a keyword
797 # i18n: "strip" is a keyword
797 raise error.ParseError(_(b"strip expects one or two arguments"))
798 raise error.ParseError(_(b"strip expects one or two arguments"))
798
799
799 text = evalstring(context, mapping, args[0])
800 text = evalstring(context, mapping, args[0])
800 if len(args) == 2:
801 if len(args) == 2:
801 chars = evalstring(context, mapping, args[1])
802 chars = evalstring(context, mapping, args[1])
802 return text.strip(chars)
803 return text.strip(chars)
803 return text.strip()
804 return text.strip()
804
805
805
806
806 @templatefunc(b'sub(pattern, replacement, expression)')
807 @templatefunc(b'sub(pattern, replacement, expression)')
807 def sub(context, mapping, args):
808 def sub(context, mapping, args):
808 """Perform text substitution
809 """Perform text substitution
809 using regular expressions."""
810 using regular expressions."""
810 if len(args) != 3:
811 if len(args) != 3:
811 # i18n: "sub" is a keyword
812 # i18n: "sub" is a keyword
812 raise error.ParseError(_(b"sub expects three arguments"))
813 raise error.ParseError(_(b"sub expects three arguments"))
813
814
814 pat = evalstring(context, mapping, args[0])
815 pat = evalstring(context, mapping, args[0])
815 rpl = evalstring(context, mapping, args[1])
816 rpl = evalstring(context, mapping, args[1])
816 src = evalstring(context, mapping, args[2])
817 src = evalstring(context, mapping, args[2])
817 try:
818 try:
818 patre = re.compile(pat)
819 patre = re.compile(pat)
819 except re.error:
820 except re.error:
820 # i18n: "sub" is a keyword
821 # i18n: "sub" is a keyword
821 raise error.ParseError(_(b"sub got an invalid pattern: %s") % pat)
822 raise error.ParseError(_(b"sub got an invalid pattern: %s") % pat)
822 try:
823 try:
823 yield patre.sub(rpl, src)
824 yield patre.sub(rpl, src)
824 except re.error:
825 except re.error:
825 # i18n: "sub" is a keyword
826 # i18n: "sub" is a keyword
826 raise error.ParseError(_(b"sub got an invalid replacement: %s") % rpl)
827 raise error.ParseError(_(b"sub got an invalid replacement: %s") % rpl)
827
828
828
829
829 @templatefunc(b'startswith(pattern, text)')
830 @templatefunc(b'startswith(pattern, text)')
830 def startswith(context, mapping, args):
831 def startswith(context, mapping, args):
831 """Returns the value from the "text" argument
832 """Returns the value from the "text" argument
832 if it begins with the content from the "pattern" argument."""
833 if it begins with the content from the "pattern" argument."""
833 if len(args) != 2:
834 if len(args) != 2:
834 # i18n: "startswith" is a keyword
835 # i18n: "startswith" is a keyword
835 raise error.ParseError(_(b"startswith expects two arguments"))
836 raise error.ParseError(_(b"startswith expects two arguments"))
836
837
837 patn = evalstring(context, mapping, args[0])
838 patn = evalstring(context, mapping, args[0])
838 text = evalstring(context, mapping, args[1])
839 text = evalstring(context, mapping, args[1])
839 if text.startswith(patn):
840 if text.startswith(patn):
840 return text
841 return text
841 return b''
842 return b''
842
843
843
844
844 @templatefunc(
845 @templatefunc(
845 b'subsetparents(rev, revset)',
846 b'subsetparents(rev, revset)',
846 argspec=b'rev revset',
847 argspec=b'rev revset',
847 requires={b'repo', b'cache'},
848 requires={b'repo', b'cache'},
848 )
849 )
849 def subsetparents(context, mapping, args):
850 def subsetparents(context, mapping, args):
850 """Look up parents of the rev in the sub graph given by the revset."""
851 """Look up parents of the rev in the sub graph given by the revset."""
851 if b'rev' not in args or b'revset' not in args:
852 if b'rev' not in args or b'revset' not in args:
852 # i18n: "subsetparents" is a keyword
853 # i18n: "subsetparents" is a keyword
853 raise error.ParseError(_(b"subsetparents expects two arguments"))
854 raise error.ParseError(_(b"subsetparents expects two arguments"))
854
855
855 repo = context.resource(mapping, b'repo')
856 repo = context.resource(mapping, b'repo')
856
857
857 rev = templateutil.evalinteger(context, mapping, args[b'rev'])
858 rev = templateutil.evalinteger(context, mapping, args[b'rev'])
858
859
859 # TODO: maybe subsetparents(rev) should be allowed. the default revset
860 # TODO: maybe subsetparents(rev) should be allowed. the default revset
860 # will be the revisions specified by -rREV argument.
861 # will be the revisions specified by -rREV argument.
861 q = templateutil.evalwrapped(context, mapping, args[b'revset'])
862 q = templateutil.evalwrapped(context, mapping, args[b'revset'])
862 if not isinstance(q, templateutil.revslist):
863 if not isinstance(q, templateutil.revslist):
863 # i18n: "subsetparents" is a keyword
864 # i18n: "subsetparents" is a keyword
864 raise error.ParseError(_(b"subsetparents expects a queried revset"))
865 raise error.ParseError(_(b"subsetparents expects a queried revset"))
865 subset = q.tovalue(context, mapping)
866 subset = q.tovalue(context, mapping)
866 key = q.cachekey
867 key = q.cachekey
867
868
868 if key:
869 if key:
869 # cache only if revset query isn't dynamic
870 # cache only if revset query isn't dynamic
870 cache = context.resource(mapping, b'cache')
871 cache = context.resource(mapping, b'cache')
871 walkercache = cache.setdefault(b'subsetparentswalker', {})
872 walkercache = cache.setdefault(b'subsetparentswalker', {})
872 if key in walkercache:
873 if key in walkercache:
873 walker = walkercache[key]
874 walker = walkercache[key]
874 else:
875 else:
875 walker = dagop.subsetparentswalker(repo, subset)
876 walker = dagop.subsetparentswalker(repo, subset)
876 walkercache[key] = walker
877 walkercache[key] = walker
877 else:
878 else:
878 # for one-shot use, specify startrev to limit the search space
879 # for one-shot use, specify startrev to limit the search space
879 walker = dagop.subsetparentswalker(repo, subset, startrev=rev)
880 walker = dagop.subsetparentswalker(repo, subset, startrev=rev)
880 return templateutil.revslist(repo, walker.parentsset(rev))
881 return templateutil.revslist(repo, walker.parentsset(rev))
881
882
882
883
883 @templatefunc(b'word(number, text[, separator])')
884 @templatefunc(b'word(number, text[, separator])')
884 def word(context, mapping, args):
885 def word(context, mapping, args):
885 """Return the nth word from a string."""
886 """Return the nth word from a string."""
886 if not (2 <= len(args) <= 3):
887 if not (2 <= len(args) <= 3):
887 # i18n: "word" is a keyword
888 # i18n: "word" is a keyword
888 raise error.ParseError(
889 raise error.ParseError(
889 _(b"word expects two or three arguments, got %d") % len(args)
890 _(b"word expects two or three arguments, got %d") % len(args)
890 )
891 )
891
892
892 num = evalinteger(
893 num = evalinteger(
893 context,
894 context,
894 mapping,
895 mapping,
895 args[0],
896 args[0],
896 # i18n: "word" is a keyword
897 # i18n: "word" is a keyword
897 _(b"word expects an integer index"),
898 _(b"word expects an integer index"),
898 )
899 )
899 text = evalstring(context, mapping, args[1])
900 text = evalstring(context, mapping, args[1])
900 if len(args) == 3:
901 if len(args) == 3:
901 splitter = evalstring(context, mapping, args[2])
902 splitter = evalstring(context, mapping, args[2])
902 else:
903 else:
903 splitter = None
904 splitter = None
904
905
905 tokens = text.split(splitter)
906 tokens = text.split(splitter)
906 if num >= len(tokens) or num < -len(tokens):
907 if num >= len(tokens) or num < -len(tokens):
907 return b''
908 return b''
908 else:
909 else:
909 return tokens[num]
910 return tokens[num]
910
911
911
912
912 def loadfunction(ui, extname, registrarobj):
913 def loadfunction(ui, extname, registrarobj):
913 """Load template function from specified registrarobj"""
914 """Load template function from specified registrarobj"""
914 for name, func in registrarobj._table.items():
915 for name, func in registrarobj._table.items():
915 funcs[name] = func
916 funcs[name] = func
916
917
917
918
918 # tell hggettext to extract docstrings from these functions:
919 # tell hggettext to extract docstrings from these functions:
919 i18nfunctions = funcs.values()
920 i18nfunctions = funcs.values()
General Comments 0
You need to be logged in to leave comments. Login now