##// END OF EJS Templates
formatter: fix handling of None value in templater mapping...
Yuya Nishihara -
r43645:7e20b705 stable
parent child Browse files
Show More
@@ -1,839 +1,843 b''
1 # formatter.py - generic output formatting for mercurial
1 # formatter.py - generic output formatting for mercurial
2 #
2 #
3 # Copyright 2012 Matt Mackall <mpm@selenic.com>
3 # Copyright 2012 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 """Generic output formatting for Mercurial
8 """Generic output formatting for Mercurial
9
9
10 The formatter provides API to show data in various ways. The following
10 The formatter provides API to show data in various ways. The following
11 functions should be used in place of ui.write():
11 functions should be used in place of ui.write():
12
12
13 - fm.write() for unconditional output
13 - fm.write() for unconditional output
14 - fm.condwrite() to show some extra data conditionally in plain output
14 - fm.condwrite() to show some extra data conditionally in plain output
15 - fm.context() to provide changectx to template output
15 - fm.context() to provide changectx to template output
16 - fm.data() to provide extra data to JSON or template output
16 - fm.data() to provide extra data to JSON or template output
17 - fm.plain() to show raw text that isn't provided to JSON or template output
17 - fm.plain() to show raw text that isn't provided to JSON or template output
18
18
19 To show structured data (e.g. date tuples, dicts, lists), apply fm.format*()
19 To show structured data (e.g. date tuples, dicts, lists), apply fm.format*()
20 beforehand so the data is converted to the appropriate data type. Use
20 beforehand so the data is converted to the appropriate data type. Use
21 fm.isplain() if you need to convert or format data conditionally which isn't
21 fm.isplain() if you need to convert or format data conditionally which isn't
22 supported by the formatter API.
22 supported by the formatter API.
23
23
24 To build nested structure (i.e. a list of dicts), use fm.nested().
24 To build nested structure (i.e. a list of dicts), use fm.nested().
25
25
26 See also https://www.mercurial-scm.org/wiki/GenericTemplatingPlan
26 See also https://www.mercurial-scm.org/wiki/GenericTemplatingPlan
27
27
28 fm.condwrite() vs 'if cond:':
28 fm.condwrite() vs 'if cond:':
29
29
30 In most cases, use fm.condwrite() so users can selectively show the data
30 In most cases, use fm.condwrite() so users can selectively show the data
31 in template output. If it's costly to build data, use plain 'if cond:' with
31 in template output. If it's costly to build data, use plain 'if cond:' with
32 fm.write().
32 fm.write().
33
33
34 fm.nested() vs fm.formatdict() (or fm.formatlist()):
34 fm.nested() vs fm.formatdict() (or fm.formatlist()):
35
35
36 fm.nested() should be used to form a tree structure (a list of dicts of
36 fm.nested() should be used to form a tree structure (a list of dicts of
37 lists of dicts...) which can be accessed through template keywords, e.g.
37 lists of dicts...) which can be accessed through template keywords, e.g.
38 "{foo % "{bar % {...}} {baz % {...}}"}". On the other hand, fm.formatdict()
38 "{foo % "{bar % {...}} {baz % {...}}"}". On the other hand, fm.formatdict()
39 exports a dict-type object to template, which can be accessed by e.g.
39 exports a dict-type object to template, which can be accessed by e.g.
40 "{get(foo, key)}" function.
40 "{get(foo, key)}" function.
41
41
42 Doctest helper:
42 Doctest helper:
43
43
44 >>> def show(fn, verbose=False, **opts):
44 >>> def show(fn, verbose=False, **opts):
45 ... import sys
45 ... import sys
46 ... from . import ui as uimod
46 ... from . import ui as uimod
47 ... ui = uimod.ui()
47 ... ui = uimod.ui()
48 ... ui.verbose = verbose
48 ... ui.verbose = verbose
49 ... ui.pushbuffer()
49 ... ui.pushbuffer()
50 ... try:
50 ... try:
51 ... return fn(ui, ui.formatter(pycompat.sysbytes(fn.__name__),
51 ... return fn(ui, ui.formatter(pycompat.sysbytes(fn.__name__),
52 ... pycompat.byteskwargs(opts)))
52 ... pycompat.byteskwargs(opts)))
53 ... finally:
53 ... finally:
54 ... print(pycompat.sysstr(ui.popbuffer()), end='')
54 ... print(pycompat.sysstr(ui.popbuffer()), end='')
55
55
56 Basic example:
56 Basic example:
57
57
58 >>> def files(ui, fm):
58 >>> def files(ui, fm):
59 ... files = [(b'foo', 123, (0, 0)), (b'bar', 456, (1, 0))]
59 ... files = [(b'foo', 123, (0, 0)), (b'bar', 456, (1, 0))]
60 ... for f in files:
60 ... for f in files:
61 ... fm.startitem()
61 ... fm.startitem()
62 ... fm.write(b'path', b'%s', f[0])
62 ... fm.write(b'path', b'%s', f[0])
63 ... fm.condwrite(ui.verbose, b'date', b' %s',
63 ... fm.condwrite(ui.verbose, b'date', b' %s',
64 ... fm.formatdate(f[2], b'%Y-%m-%d %H:%M:%S'))
64 ... fm.formatdate(f[2], b'%Y-%m-%d %H:%M:%S'))
65 ... fm.data(size=f[1])
65 ... fm.data(size=f[1])
66 ... fm.plain(b'\\n')
66 ... fm.plain(b'\\n')
67 ... fm.end()
67 ... fm.end()
68 >>> show(files)
68 >>> show(files)
69 foo
69 foo
70 bar
70 bar
71 >>> show(files, verbose=True)
71 >>> show(files, verbose=True)
72 foo 1970-01-01 00:00:00
72 foo 1970-01-01 00:00:00
73 bar 1970-01-01 00:00:01
73 bar 1970-01-01 00:00:01
74 >>> show(files, template=b'json')
74 >>> show(files, template=b'json')
75 [
75 [
76 {
76 {
77 "date": [0, 0],
77 "date": [0, 0],
78 "path": "foo",
78 "path": "foo",
79 "size": 123
79 "size": 123
80 },
80 },
81 {
81 {
82 "date": [1, 0],
82 "date": [1, 0],
83 "path": "bar",
83 "path": "bar",
84 "size": 456
84 "size": 456
85 }
85 }
86 ]
86 ]
87 >>> show(files, template=b'path: {path}\\ndate: {date|rfc3339date}\\n')
87 >>> show(files, template=b'path: {path}\\ndate: {date|rfc3339date}\\n')
88 path: foo
88 path: foo
89 date: 1970-01-01T00:00:00+00:00
89 date: 1970-01-01T00:00:00+00:00
90 path: bar
90 path: bar
91 date: 1970-01-01T00:00:01+00:00
91 date: 1970-01-01T00:00:01+00:00
92
92
93 Nested example:
93 Nested example:
94
94
95 >>> def subrepos(ui, fm):
95 >>> def subrepos(ui, fm):
96 ... fm.startitem()
96 ... fm.startitem()
97 ... fm.write(b'reponame', b'[%s]\\n', b'baz')
97 ... fm.write(b'reponame', b'[%s]\\n', b'baz')
98 ... files(ui, fm.nested(b'files', tmpl=b'{reponame}'))
98 ... files(ui, fm.nested(b'files', tmpl=b'{reponame}'))
99 ... fm.end()
99 ... fm.end()
100 >>> show(subrepos)
100 >>> show(subrepos)
101 [baz]
101 [baz]
102 foo
102 foo
103 bar
103 bar
104 >>> show(subrepos, template=b'{reponame}: {join(files % "{path}", ", ")}\\n')
104 >>> show(subrepos, template=b'{reponame}: {join(files % "{path}", ", ")}\\n')
105 baz: foo, bar
105 baz: foo, bar
106 """
106 """
107
107
108 from __future__ import absolute_import, print_function
108 from __future__ import absolute_import, print_function
109
109
110 import contextlib
110 import contextlib
111 import itertools
111 import itertools
112 import os
112 import os
113
113
114 from .i18n import _
114 from .i18n import _
115 from .node import (
115 from .node import (
116 hex,
116 hex,
117 short,
117 short,
118 )
118 )
119 from .thirdparty import attr
119 from .thirdparty import attr
120
120
121 from . import (
121 from . import (
122 error,
122 error,
123 pycompat,
123 pycompat,
124 templatefilters,
124 templatefilters,
125 templatekw,
125 templatekw,
126 templater,
126 templater,
127 templateutil,
127 templateutil,
128 util,
128 util,
129 )
129 )
130 from .utils import (
130 from .utils import (
131 cborutil,
131 cborutil,
132 dateutil,
132 dateutil,
133 stringutil,
133 stringutil,
134 )
134 )
135
135
136 pickle = util.pickle
136 pickle = util.pickle
137
137
138
138
139 def isprintable(obj):
139 def isprintable(obj):
140 """Check if the given object can be directly passed in to formatter's
140 """Check if the given object can be directly passed in to formatter's
141 write() and data() functions
141 write() and data() functions
142
142
143 Returns False if the object is unsupported or must be pre-processed by
143 Returns False if the object is unsupported or must be pre-processed by
144 formatdate(), formatdict(), or formatlist().
144 formatdate(), formatdict(), or formatlist().
145 """
145 """
146 return isinstance(obj, (type(None), bool, int, pycompat.long, float, bytes))
146 return isinstance(obj, (type(None), bool, int, pycompat.long, float, bytes))
147
147
148
148
149 class _nullconverter(object):
149 class _nullconverter(object):
150 '''convert non-primitive data types to be processed by formatter'''
150 '''convert non-primitive data types to be processed by formatter'''
151
151
152 # set to True if context object should be stored as item
152 # set to True if context object should be stored as item
153 storecontext = False
153 storecontext = False
154
154
155 @staticmethod
155 @staticmethod
156 def wrapnested(data, tmpl, sep):
156 def wrapnested(data, tmpl, sep):
157 '''wrap nested data by appropriate type'''
157 '''wrap nested data by appropriate type'''
158 return data
158 return data
159
159
160 @staticmethod
160 @staticmethod
161 def formatdate(date, fmt):
161 def formatdate(date, fmt):
162 '''convert date tuple to appropriate format'''
162 '''convert date tuple to appropriate format'''
163 # timestamp can be float, but the canonical form should be int
163 # timestamp can be float, but the canonical form should be int
164 ts, tz = date
164 ts, tz = date
165 return (int(ts), tz)
165 return (int(ts), tz)
166
166
167 @staticmethod
167 @staticmethod
168 def formatdict(data, key, value, fmt, sep):
168 def formatdict(data, key, value, fmt, sep):
169 '''convert dict or key-value pairs to appropriate dict format'''
169 '''convert dict or key-value pairs to appropriate dict format'''
170 # use plain dict instead of util.sortdict so that data can be
170 # use plain dict instead of util.sortdict so that data can be
171 # serialized as a builtin dict in pickle output
171 # serialized as a builtin dict in pickle output
172 return dict(data)
172 return dict(data)
173
173
174 @staticmethod
174 @staticmethod
175 def formatlist(data, name, fmt, sep):
175 def formatlist(data, name, fmt, sep):
176 '''convert iterable to appropriate list format'''
176 '''convert iterable to appropriate list format'''
177 return list(data)
177 return list(data)
178
178
179
179
180 class baseformatter(object):
180 class baseformatter(object):
181 def __init__(self, ui, topic, opts, converter):
181 def __init__(self, ui, topic, opts, converter):
182 self._ui = ui
182 self._ui = ui
183 self._topic = topic
183 self._topic = topic
184 self._opts = opts
184 self._opts = opts
185 self._converter = converter
185 self._converter = converter
186 self._item = None
186 self._item = None
187 # function to convert node to string suitable for this output
187 # function to convert node to string suitable for this output
188 self.hexfunc = hex
188 self.hexfunc = hex
189
189
190 def __enter__(self):
190 def __enter__(self):
191 return self
191 return self
192
192
193 def __exit__(self, exctype, excvalue, traceback):
193 def __exit__(self, exctype, excvalue, traceback):
194 if exctype is None:
194 if exctype is None:
195 self.end()
195 self.end()
196
196
197 def _showitem(self):
197 def _showitem(self):
198 '''show a formatted item once all data is collected'''
198 '''show a formatted item once all data is collected'''
199
199
200 def startitem(self):
200 def startitem(self):
201 '''begin an item in the format list'''
201 '''begin an item in the format list'''
202 if self._item is not None:
202 if self._item is not None:
203 self._showitem()
203 self._showitem()
204 self._item = {}
204 self._item = {}
205
205
206 def formatdate(self, date, fmt=b'%a %b %d %H:%M:%S %Y %1%2'):
206 def formatdate(self, date, fmt=b'%a %b %d %H:%M:%S %Y %1%2'):
207 '''convert date tuple to appropriate format'''
207 '''convert date tuple to appropriate format'''
208 return self._converter.formatdate(date, fmt)
208 return self._converter.formatdate(date, fmt)
209
209
210 def formatdict(self, data, key=b'key', value=b'value', fmt=None, sep=b' '):
210 def formatdict(self, data, key=b'key', value=b'value', fmt=None, sep=b' '):
211 '''convert dict or key-value pairs to appropriate dict format'''
211 '''convert dict or key-value pairs to appropriate dict format'''
212 return self._converter.formatdict(data, key, value, fmt, sep)
212 return self._converter.formatdict(data, key, value, fmt, sep)
213
213
214 def formatlist(self, data, name, fmt=None, sep=b' '):
214 def formatlist(self, data, name, fmt=None, sep=b' '):
215 '''convert iterable to appropriate list format'''
215 '''convert iterable to appropriate list format'''
216 # name is mandatory argument for now, but it could be optional if
216 # name is mandatory argument for now, but it could be optional if
217 # we have default template keyword, e.g. {item}
217 # we have default template keyword, e.g. {item}
218 return self._converter.formatlist(data, name, fmt, sep)
218 return self._converter.formatlist(data, name, fmt, sep)
219
219
220 def context(self, **ctxs):
220 def context(self, **ctxs):
221 '''insert context objects to be used to render template keywords'''
221 '''insert context objects to be used to render template keywords'''
222 ctxs = pycompat.byteskwargs(ctxs)
222 ctxs = pycompat.byteskwargs(ctxs)
223 assert all(k in {b'repo', b'ctx', b'fctx'} for k in ctxs)
223 assert all(k in {b'repo', b'ctx', b'fctx'} for k in ctxs)
224 if self._converter.storecontext:
224 if self._converter.storecontext:
225 # populate missing resources in fctx -> ctx -> repo order
225 # populate missing resources in fctx -> ctx -> repo order
226 if b'fctx' in ctxs and b'ctx' not in ctxs:
226 if b'fctx' in ctxs and b'ctx' not in ctxs:
227 ctxs[b'ctx'] = ctxs[b'fctx'].changectx()
227 ctxs[b'ctx'] = ctxs[b'fctx'].changectx()
228 if b'ctx' in ctxs and b'repo' not in ctxs:
228 if b'ctx' in ctxs and b'repo' not in ctxs:
229 ctxs[b'repo'] = ctxs[b'ctx'].repo()
229 ctxs[b'repo'] = ctxs[b'ctx'].repo()
230 self._item.update(ctxs)
230 self._item.update(ctxs)
231
231
232 def datahint(self):
232 def datahint(self):
233 '''set of field names to be referenced'''
233 '''set of field names to be referenced'''
234 return set()
234 return set()
235
235
236 def data(self, **data):
236 def data(self, **data):
237 '''insert data into item that's not shown in default output'''
237 '''insert data into item that's not shown in default output'''
238 data = pycompat.byteskwargs(data)
238 data = pycompat.byteskwargs(data)
239 self._item.update(data)
239 self._item.update(data)
240
240
241 def write(self, fields, deftext, *fielddata, **opts):
241 def write(self, fields, deftext, *fielddata, **opts):
242 '''do default text output while assigning data to item'''
242 '''do default text output while assigning data to item'''
243 fieldkeys = fields.split()
243 fieldkeys = fields.split()
244 assert len(fieldkeys) == len(fielddata), (fieldkeys, fielddata)
244 assert len(fieldkeys) == len(fielddata), (fieldkeys, fielddata)
245 self._item.update(zip(fieldkeys, fielddata))
245 self._item.update(zip(fieldkeys, fielddata))
246
246
247 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
247 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
248 '''do conditional write (primarily for plain formatter)'''
248 '''do conditional write (primarily for plain formatter)'''
249 fieldkeys = fields.split()
249 fieldkeys = fields.split()
250 assert len(fieldkeys) == len(fielddata)
250 assert len(fieldkeys) == len(fielddata)
251 self._item.update(zip(fieldkeys, fielddata))
251 self._item.update(zip(fieldkeys, fielddata))
252
252
253 def plain(self, text, **opts):
253 def plain(self, text, **opts):
254 '''show raw text for non-templated mode'''
254 '''show raw text for non-templated mode'''
255
255
256 def isplain(self):
256 def isplain(self):
257 '''check for plain formatter usage'''
257 '''check for plain formatter usage'''
258 return False
258 return False
259
259
260 def nested(self, field, tmpl=None, sep=b''):
260 def nested(self, field, tmpl=None, sep=b''):
261 '''sub formatter to store nested data in the specified field'''
261 '''sub formatter to store nested data in the specified field'''
262 data = []
262 data = []
263 self._item[field] = self._converter.wrapnested(data, tmpl, sep)
263 self._item[field] = self._converter.wrapnested(data, tmpl, sep)
264 return _nestedformatter(self._ui, self._converter, data)
264 return _nestedformatter(self._ui, self._converter, data)
265
265
266 def end(self):
266 def end(self):
267 '''end output for the formatter'''
267 '''end output for the formatter'''
268 if self._item is not None:
268 if self._item is not None:
269 self._showitem()
269 self._showitem()
270
270
271
271
272 def nullformatter(ui, topic, opts):
272 def nullformatter(ui, topic, opts):
273 '''formatter that prints nothing'''
273 '''formatter that prints nothing'''
274 return baseformatter(ui, topic, opts, converter=_nullconverter)
274 return baseformatter(ui, topic, opts, converter=_nullconverter)
275
275
276
276
277 class _nestedformatter(baseformatter):
277 class _nestedformatter(baseformatter):
278 '''build sub items and store them in the parent formatter'''
278 '''build sub items and store them in the parent formatter'''
279
279
280 def __init__(self, ui, converter, data):
280 def __init__(self, ui, converter, data):
281 baseformatter.__init__(
281 baseformatter.__init__(
282 self, ui, topic=b'', opts={}, converter=converter
282 self, ui, topic=b'', opts={}, converter=converter
283 )
283 )
284 self._data = data
284 self._data = data
285
285
286 def _showitem(self):
286 def _showitem(self):
287 self._data.append(self._item)
287 self._data.append(self._item)
288
288
289
289
290 def _iteritems(data):
290 def _iteritems(data):
291 '''iterate key-value pairs in stable order'''
291 '''iterate key-value pairs in stable order'''
292 if isinstance(data, dict):
292 if isinstance(data, dict):
293 return sorted(pycompat.iteritems(data))
293 return sorted(pycompat.iteritems(data))
294 return data
294 return data
295
295
296
296
297 class _plainconverter(object):
297 class _plainconverter(object):
298 '''convert non-primitive data types to text'''
298 '''convert non-primitive data types to text'''
299
299
300 storecontext = False
300 storecontext = False
301
301
302 @staticmethod
302 @staticmethod
303 def wrapnested(data, tmpl, sep):
303 def wrapnested(data, tmpl, sep):
304 raise error.ProgrammingError(b'plainformatter should never be nested')
304 raise error.ProgrammingError(b'plainformatter should never be nested')
305
305
306 @staticmethod
306 @staticmethod
307 def formatdate(date, fmt):
307 def formatdate(date, fmt):
308 '''stringify date tuple in the given format'''
308 '''stringify date tuple in the given format'''
309 return dateutil.datestr(date, fmt)
309 return dateutil.datestr(date, fmt)
310
310
311 @staticmethod
311 @staticmethod
312 def formatdict(data, key, value, fmt, sep):
312 def formatdict(data, key, value, fmt, sep):
313 '''stringify key-value pairs separated by sep'''
313 '''stringify key-value pairs separated by sep'''
314 prefmt = pycompat.identity
314 prefmt = pycompat.identity
315 if fmt is None:
315 if fmt is None:
316 fmt = b'%s=%s'
316 fmt = b'%s=%s'
317 prefmt = pycompat.bytestr
317 prefmt = pycompat.bytestr
318 return sep.join(
318 return sep.join(
319 fmt % (prefmt(k), prefmt(v)) for k, v in _iteritems(data)
319 fmt % (prefmt(k), prefmt(v)) for k, v in _iteritems(data)
320 )
320 )
321
321
322 @staticmethod
322 @staticmethod
323 def formatlist(data, name, fmt, sep):
323 def formatlist(data, name, fmt, sep):
324 '''stringify iterable separated by sep'''
324 '''stringify iterable separated by sep'''
325 prefmt = pycompat.identity
325 prefmt = pycompat.identity
326 if fmt is None:
326 if fmt is None:
327 fmt = b'%s'
327 fmt = b'%s'
328 prefmt = pycompat.bytestr
328 prefmt = pycompat.bytestr
329 return sep.join(fmt % prefmt(e) for e in data)
329 return sep.join(fmt % prefmt(e) for e in data)
330
330
331
331
332 class plainformatter(baseformatter):
332 class plainformatter(baseformatter):
333 '''the default text output scheme'''
333 '''the default text output scheme'''
334
334
335 def __init__(self, ui, out, topic, opts):
335 def __init__(self, ui, out, topic, opts):
336 baseformatter.__init__(self, ui, topic, opts, _plainconverter)
336 baseformatter.__init__(self, ui, topic, opts, _plainconverter)
337 if ui.debugflag:
337 if ui.debugflag:
338 self.hexfunc = hex
338 self.hexfunc = hex
339 else:
339 else:
340 self.hexfunc = short
340 self.hexfunc = short
341 if ui is out:
341 if ui is out:
342 self._write = ui.write
342 self._write = ui.write
343 else:
343 else:
344 self._write = lambda s, **opts: out.write(s)
344 self._write = lambda s, **opts: out.write(s)
345
345
346 def startitem(self):
346 def startitem(self):
347 pass
347 pass
348
348
349 def data(self, **data):
349 def data(self, **data):
350 pass
350 pass
351
351
352 def write(self, fields, deftext, *fielddata, **opts):
352 def write(self, fields, deftext, *fielddata, **opts):
353 self._write(deftext % fielddata, **opts)
353 self._write(deftext % fielddata, **opts)
354
354
355 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
355 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
356 '''do conditional write'''
356 '''do conditional write'''
357 if cond:
357 if cond:
358 self._write(deftext % fielddata, **opts)
358 self._write(deftext % fielddata, **opts)
359
359
360 def plain(self, text, **opts):
360 def plain(self, text, **opts):
361 self._write(text, **opts)
361 self._write(text, **opts)
362
362
363 def isplain(self):
363 def isplain(self):
364 return True
364 return True
365
365
366 def nested(self, field, tmpl=None, sep=b''):
366 def nested(self, field, tmpl=None, sep=b''):
367 # nested data will be directly written to ui
367 # nested data will be directly written to ui
368 return self
368 return self
369
369
370 def end(self):
370 def end(self):
371 pass
371 pass
372
372
373
373
374 class debugformatter(baseformatter):
374 class debugformatter(baseformatter):
375 def __init__(self, ui, out, topic, opts):
375 def __init__(self, ui, out, topic, opts):
376 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
376 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
377 self._out = out
377 self._out = out
378 self._out.write(b"%s = [\n" % self._topic)
378 self._out.write(b"%s = [\n" % self._topic)
379
379
380 def _showitem(self):
380 def _showitem(self):
381 self._out.write(
381 self._out.write(
382 b' %s,\n' % stringutil.pprint(self._item, indent=4, level=1)
382 b' %s,\n' % stringutil.pprint(self._item, indent=4, level=1)
383 )
383 )
384
384
385 def end(self):
385 def end(self):
386 baseformatter.end(self)
386 baseformatter.end(self)
387 self._out.write(b"]\n")
387 self._out.write(b"]\n")
388
388
389
389
390 class pickleformatter(baseformatter):
390 class pickleformatter(baseformatter):
391 def __init__(self, ui, out, topic, opts):
391 def __init__(self, ui, out, topic, opts):
392 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
392 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
393 self._out = out
393 self._out = out
394 self._data = []
394 self._data = []
395
395
396 def _showitem(self):
396 def _showitem(self):
397 self._data.append(self._item)
397 self._data.append(self._item)
398
398
399 def end(self):
399 def end(self):
400 baseformatter.end(self)
400 baseformatter.end(self)
401 self._out.write(pickle.dumps(self._data))
401 self._out.write(pickle.dumps(self._data))
402
402
403
403
404 class cborformatter(baseformatter):
404 class cborformatter(baseformatter):
405 '''serialize items as an indefinite-length CBOR array'''
405 '''serialize items as an indefinite-length CBOR array'''
406
406
407 def __init__(self, ui, out, topic, opts):
407 def __init__(self, ui, out, topic, opts):
408 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
408 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
409 self._out = out
409 self._out = out
410 self._out.write(cborutil.BEGIN_INDEFINITE_ARRAY)
410 self._out.write(cborutil.BEGIN_INDEFINITE_ARRAY)
411
411
412 def _showitem(self):
412 def _showitem(self):
413 self._out.write(b''.join(cborutil.streamencode(self._item)))
413 self._out.write(b''.join(cborutil.streamencode(self._item)))
414
414
415 def end(self):
415 def end(self):
416 baseformatter.end(self)
416 baseformatter.end(self)
417 self._out.write(cborutil.BREAK)
417 self._out.write(cborutil.BREAK)
418
418
419
419
420 class jsonformatter(baseformatter):
420 class jsonformatter(baseformatter):
421 def __init__(self, ui, out, topic, opts):
421 def __init__(self, ui, out, topic, opts):
422 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
422 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
423 self._out = out
423 self._out = out
424 self._out.write(b"[")
424 self._out.write(b"[")
425 self._first = True
425 self._first = True
426
426
427 def _showitem(self):
427 def _showitem(self):
428 if self._first:
428 if self._first:
429 self._first = False
429 self._first = False
430 else:
430 else:
431 self._out.write(b",")
431 self._out.write(b",")
432
432
433 self._out.write(b"\n {\n")
433 self._out.write(b"\n {\n")
434 first = True
434 first = True
435 for k, v in sorted(self._item.items()):
435 for k, v in sorted(self._item.items()):
436 if first:
436 if first:
437 first = False
437 first = False
438 else:
438 else:
439 self._out.write(b",\n")
439 self._out.write(b",\n")
440 u = templatefilters.json(v, paranoid=False)
440 u = templatefilters.json(v, paranoid=False)
441 self._out.write(b' "%s": %s' % (k, u))
441 self._out.write(b' "%s": %s' % (k, u))
442 self._out.write(b"\n }")
442 self._out.write(b"\n }")
443
443
444 def end(self):
444 def end(self):
445 baseformatter.end(self)
445 baseformatter.end(self)
446 self._out.write(b"\n]\n")
446 self._out.write(b"\n]\n")
447
447
448
448
449 class _templateconverter(object):
449 class _templateconverter(object):
450 '''convert non-primitive data types to be processed by templater'''
450 '''convert non-primitive data types to be processed by templater'''
451
451
452 storecontext = True
452 storecontext = True
453
453
454 @staticmethod
454 @staticmethod
455 def wrapnested(data, tmpl, sep):
455 def wrapnested(data, tmpl, sep):
456 '''wrap nested data by templatable type'''
456 '''wrap nested data by templatable type'''
457 return templateutil.mappinglist(data, tmpl=tmpl, sep=sep)
457 return templateutil.mappinglist(data, tmpl=tmpl, sep=sep)
458
458
459 @staticmethod
459 @staticmethod
460 def formatdate(date, fmt):
460 def formatdate(date, fmt):
461 '''return date tuple'''
461 '''return date tuple'''
462 return templateutil.date(date)
462 return templateutil.date(date)
463
463
464 @staticmethod
464 @staticmethod
465 def formatdict(data, key, value, fmt, sep):
465 def formatdict(data, key, value, fmt, sep):
466 '''build object that can be evaluated as either plain string or dict'''
466 '''build object that can be evaluated as either plain string or dict'''
467 data = util.sortdict(_iteritems(data))
467 data = util.sortdict(_iteritems(data))
468
468
469 def f():
469 def f():
470 yield _plainconverter.formatdict(data, key, value, fmt, sep)
470 yield _plainconverter.formatdict(data, key, value, fmt, sep)
471
471
472 return templateutil.hybriddict(
472 return templateutil.hybriddict(
473 data, key=key, value=value, fmt=fmt, gen=f
473 data, key=key, value=value, fmt=fmt, gen=f
474 )
474 )
475
475
476 @staticmethod
476 @staticmethod
477 def formatlist(data, name, fmt, sep):
477 def formatlist(data, name, fmt, sep):
478 '''build object that can be evaluated as either plain string or list'''
478 '''build object that can be evaluated as either plain string or list'''
479 data = list(data)
479 data = list(data)
480
480
481 def f():
481 def f():
482 yield _plainconverter.formatlist(data, name, fmt, sep)
482 yield _plainconverter.formatlist(data, name, fmt, sep)
483
483
484 return templateutil.hybridlist(data, name=name, fmt=fmt, gen=f)
484 return templateutil.hybridlist(data, name=name, fmt=fmt, gen=f)
485
485
486
486
487 class templateformatter(baseformatter):
487 class templateformatter(baseformatter):
488 def __init__(self, ui, out, topic, opts, spec, overridetemplates=None):
488 def __init__(self, ui, out, topic, opts, spec, overridetemplates=None):
489 baseformatter.__init__(self, ui, topic, opts, _templateconverter)
489 baseformatter.__init__(self, ui, topic, opts, _templateconverter)
490 self._out = out
490 self._out = out
491 self._tref = spec.ref
491 self._tref = spec.ref
492 self._t = loadtemplater(
492 self._t = loadtemplater(
493 ui,
493 ui,
494 spec,
494 spec,
495 defaults=templatekw.keywords,
495 defaults=templatekw.keywords,
496 resources=templateresources(ui),
496 resources=templateresources(ui),
497 cache=templatekw.defaulttempl,
497 cache=templatekw.defaulttempl,
498 )
498 )
499 if overridetemplates:
499 if overridetemplates:
500 self._t.cache.update(overridetemplates)
500 self._t.cache.update(overridetemplates)
501 self._parts = templatepartsmap(
501 self._parts = templatepartsmap(
502 spec, self._t, [b'docheader', b'docfooter', b'separator']
502 spec, self._t, [b'docheader', b'docfooter', b'separator']
503 )
503 )
504 self._counter = itertools.count()
504 self._counter = itertools.count()
505 self._renderitem(b'docheader', {})
505 self._renderitem(b'docheader', {})
506
506
507 def _showitem(self):
507 def _showitem(self):
508 item = self._item.copy()
508 item = self._item.copy()
509 item[b'index'] = index = next(self._counter)
509 item[b'index'] = index = next(self._counter)
510 if index > 0:
510 if index > 0:
511 self._renderitem(b'separator', {})
511 self._renderitem(b'separator', {})
512 self._renderitem(self._tref, item)
512 self._renderitem(self._tref, item)
513
513
514 def _renderitem(self, part, item):
514 def _renderitem(self, part, item):
515 if part not in self._parts:
515 if part not in self._parts:
516 return
516 return
517 ref = self._parts[part]
517 ref = self._parts[part]
518 # None can't be put in the mapping dict since it means <unset>
519 for k, v in item.items():
520 if v is None:
521 item[k] = templateutil.wrappedvalue(v)
518 self._out.write(self._t.render(ref, item))
522 self._out.write(self._t.render(ref, item))
519
523
520 @util.propertycache
524 @util.propertycache
521 def _symbolsused(self):
525 def _symbolsused(self):
522 return self._t.symbolsused(self._tref)
526 return self._t.symbolsused(self._tref)
523
527
524 def datahint(self):
528 def datahint(self):
525 '''set of field names to be referenced from the template'''
529 '''set of field names to be referenced from the template'''
526 return self._symbolsused[0]
530 return self._symbolsused[0]
527
531
528 def end(self):
532 def end(self):
529 baseformatter.end(self)
533 baseformatter.end(self)
530 self._renderitem(b'docfooter', {})
534 self._renderitem(b'docfooter', {})
531
535
532
536
533 @attr.s(frozen=True)
537 @attr.s(frozen=True)
534 class templatespec(object):
538 class templatespec(object):
535 ref = attr.ib()
539 ref = attr.ib()
536 tmpl = attr.ib()
540 tmpl = attr.ib()
537 mapfile = attr.ib()
541 mapfile = attr.ib()
538 refargs = attr.ib(default=None)
542 refargs = attr.ib(default=None)
539
543
540
544
541 def lookuptemplate(ui, topic, tmpl):
545 def lookuptemplate(ui, topic, tmpl):
542 """Find the template matching the given -T/--template spec 'tmpl'
546 """Find the template matching the given -T/--template spec 'tmpl'
543
547
544 'tmpl' can be any of the following:
548 'tmpl' can be any of the following:
545
549
546 - a literal template (e.g. '{rev}')
550 - a literal template (e.g. '{rev}')
547 - a reference to built-in template (i.e. formatter)
551 - a reference to built-in template (i.e. formatter)
548 - a map-file name or path (e.g. 'changelog')
552 - a map-file name or path (e.g. 'changelog')
549 - a reference to [templates] in config file
553 - a reference to [templates] in config file
550 - a path to raw template file
554 - a path to raw template file
551
555
552 A map file defines a stand-alone template environment. If a map file
556 A map file defines a stand-alone template environment. If a map file
553 selected, all templates defined in the file will be loaded, and the
557 selected, all templates defined in the file will be loaded, and the
554 template matching the given topic will be rendered. Aliases won't be
558 template matching the given topic will be rendered. Aliases won't be
555 loaded from user config, but from the map file.
559 loaded from user config, but from the map file.
556
560
557 If no map file selected, all templates in [templates] section will be
561 If no map file selected, all templates in [templates] section will be
558 available as well as aliases in [templatealias].
562 available as well as aliases in [templatealias].
559 """
563 """
560
564
561 if not tmpl:
565 if not tmpl:
562 return templatespec(None, None, None)
566 return templatespec(None, None, None)
563
567
564 # looks like a literal template?
568 # looks like a literal template?
565 if b'{' in tmpl:
569 if b'{' in tmpl:
566 return templatespec(b'', tmpl, None)
570 return templatespec(b'', tmpl, None)
567
571
568 # a reference to built-in (formatter) template
572 # a reference to built-in (formatter) template
569 if tmpl in {b'cbor', b'json', b'pickle', b'debug'}:
573 if tmpl in {b'cbor', b'json', b'pickle', b'debug'}:
570 return templatespec(tmpl, None, None)
574 return templatespec(tmpl, None, None)
571
575
572 # a function-style reference to built-in template
576 # a function-style reference to built-in template
573 func, fsep, ftail = tmpl.partition(b'(')
577 func, fsep, ftail = tmpl.partition(b'(')
574 if func in {b'cbor', b'json'} and fsep and ftail.endswith(b')'):
578 if func in {b'cbor', b'json'} and fsep and ftail.endswith(b')'):
575 templater.parseexpr(tmpl) # make sure syntax errors are confined
579 templater.parseexpr(tmpl) # make sure syntax errors are confined
576 return templatespec(func, None, None, refargs=ftail[:-1])
580 return templatespec(func, None, None, refargs=ftail[:-1])
577
581
578 # perhaps a stock style?
582 # perhaps a stock style?
579 if not os.path.split(tmpl)[0]:
583 if not os.path.split(tmpl)[0]:
580 mapname = templater.templatepath(
584 mapname = templater.templatepath(
581 b'map-cmdline.' + tmpl
585 b'map-cmdline.' + tmpl
582 ) or templater.templatepath(tmpl)
586 ) or templater.templatepath(tmpl)
583 if mapname and os.path.isfile(mapname):
587 if mapname and os.path.isfile(mapname):
584 return templatespec(topic, None, mapname)
588 return templatespec(topic, None, mapname)
585
589
586 # perhaps it's a reference to [templates]
590 # perhaps it's a reference to [templates]
587 if ui.config(b'templates', tmpl):
591 if ui.config(b'templates', tmpl):
588 return templatespec(tmpl, None, None)
592 return templatespec(tmpl, None, None)
589
593
590 if tmpl == b'list':
594 if tmpl == b'list':
591 ui.write(_(b"available styles: %s\n") % templater.stylelist())
595 ui.write(_(b"available styles: %s\n") % templater.stylelist())
592 raise error.Abort(_(b"specify a template"))
596 raise error.Abort(_(b"specify a template"))
593
597
594 # perhaps it's a path to a map or a template
598 # perhaps it's a path to a map or a template
595 if (b'/' in tmpl or b'\\' in tmpl) and os.path.isfile(tmpl):
599 if (b'/' in tmpl or b'\\' in tmpl) and os.path.isfile(tmpl):
596 # is it a mapfile for a style?
600 # is it a mapfile for a style?
597 if os.path.basename(tmpl).startswith(b"map-"):
601 if os.path.basename(tmpl).startswith(b"map-"):
598 return templatespec(topic, None, os.path.realpath(tmpl))
602 return templatespec(topic, None, os.path.realpath(tmpl))
599 with util.posixfile(tmpl, b'rb') as f:
603 with util.posixfile(tmpl, b'rb') as f:
600 tmpl = f.read()
604 tmpl = f.read()
601 return templatespec(b'', tmpl, None)
605 return templatespec(b'', tmpl, None)
602
606
603 # constant string?
607 # constant string?
604 return templatespec(b'', tmpl, None)
608 return templatespec(b'', tmpl, None)
605
609
606
610
607 def templatepartsmap(spec, t, partnames):
611 def templatepartsmap(spec, t, partnames):
608 """Create a mapping of {part: ref}"""
612 """Create a mapping of {part: ref}"""
609 partsmap = {spec.ref: spec.ref} # initial ref must exist in t
613 partsmap = {spec.ref: spec.ref} # initial ref must exist in t
610 if spec.mapfile:
614 if spec.mapfile:
611 partsmap.update((p, p) for p in partnames if p in t)
615 partsmap.update((p, p) for p in partnames if p in t)
612 elif spec.ref:
616 elif spec.ref:
613 for part in partnames:
617 for part in partnames:
614 ref = b'%s:%s' % (spec.ref, part) # select config sub-section
618 ref = b'%s:%s' % (spec.ref, part) # select config sub-section
615 if ref in t:
619 if ref in t:
616 partsmap[part] = ref
620 partsmap[part] = ref
617 return partsmap
621 return partsmap
618
622
619
623
620 def loadtemplater(ui, spec, defaults=None, resources=None, cache=None):
624 def loadtemplater(ui, spec, defaults=None, resources=None, cache=None):
621 """Create a templater from either a literal template or loading from
625 """Create a templater from either a literal template or loading from
622 a map file"""
626 a map file"""
623 assert not (spec.tmpl and spec.mapfile)
627 assert not (spec.tmpl and spec.mapfile)
624 if spec.mapfile:
628 if spec.mapfile:
625 frommapfile = templater.templater.frommapfile
629 frommapfile = templater.templater.frommapfile
626 return frommapfile(
630 return frommapfile(
627 spec.mapfile, defaults=defaults, resources=resources, cache=cache
631 spec.mapfile, defaults=defaults, resources=resources, cache=cache
628 )
632 )
629 return maketemplater(
633 return maketemplater(
630 ui, spec.tmpl, defaults=defaults, resources=resources, cache=cache
634 ui, spec.tmpl, defaults=defaults, resources=resources, cache=cache
631 )
635 )
632
636
633
637
634 def maketemplater(ui, tmpl, defaults=None, resources=None, cache=None):
638 def maketemplater(ui, tmpl, defaults=None, resources=None, cache=None):
635 """Create a templater from a string template 'tmpl'"""
639 """Create a templater from a string template 'tmpl'"""
636 aliases = ui.configitems(b'templatealias')
640 aliases = ui.configitems(b'templatealias')
637 t = templater.templater(
641 t = templater.templater(
638 defaults=defaults, resources=resources, cache=cache, aliases=aliases
642 defaults=defaults, resources=resources, cache=cache, aliases=aliases
639 )
643 )
640 t.cache.update(
644 t.cache.update(
641 (k, templater.unquotestring(v)) for k, v in ui.configitems(b'templates')
645 (k, templater.unquotestring(v)) for k, v in ui.configitems(b'templates')
642 )
646 )
643 if tmpl:
647 if tmpl:
644 t.cache[b''] = tmpl
648 t.cache[b''] = tmpl
645 return t
649 return t
646
650
647
651
648 # marker to denote a resource to be loaded on demand based on mapping values
652 # marker to denote a resource to be loaded on demand based on mapping values
649 # (e.g. (ctx, path) -> fctx)
653 # (e.g. (ctx, path) -> fctx)
650 _placeholder = object()
654 _placeholder = object()
651
655
652
656
653 class templateresources(templater.resourcemapper):
657 class templateresources(templater.resourcemapper):
654 """Resource mapper designed for the default templatekw and function"""
658 """Resource mapper designed for the default templatekw and function"""
655
659
656 def __init__(self, ui, repo=None):
660 def __init__(self, ui, repo=None):
657 self._resmap = {
661 self._resmap = {
658 b'cache': {}, # for templatekw/funcs to store reusable data
662 b'cache': {}, # for templatekw/funcs to store reusable data
659 b'repo': repo,
663 b'repo': repo,
660 b'ui': ui,
664 b'ui': ui,
661 }
665 }
662
666
663 def availablekeys(self, mapping):
667 def availablekeys(self, mapping):
664 return {
668 return {
665 k for k in self.knownkeys() if self._getsome(mapping, k) is not None
669 k for k in self.knownkeys() if self._getsome(mapping, k) is not None
666 }
670 }
667
671
668 def knownkeys(self):
672 def knownkeys(self):
669 return {b'cache', b'ctx', b'fctx', b'repo', b'revcache', b'ui'}
673 return {b'cache', b'ctx', b'fctx', b'repo', b'revcache', b'ui'}
670
674
671 def lookup(self, mapping, key):
675 def lookup(self, mapping, key):
672 if key not in self.knownkeys():
676 if key not in self.knownkeys():
673 return None
677 return None
674 v = self._getsome(mapping, key)
678 v = self._getsome(mapping, key)
675 if v is _placeholder:
679 if v is _placeholder:
676 v = mapping[key] = self._loadermap[key](self, mapping)
680 v = mapping[key] = self._loadermap[key](self, mapping)
677 return v
681 return v
678
682
679 def populatemap(self, context, origmapping, newmapping):
683 def populatemap(self, context, origmapping, newmapping):
680 mapping = {}
684 mapping = {}
681 if self._hasnodespec(newmapping):
685 if self._hasnodespec(newmapping):
682 mapping[b'revcache'] = {} # per-ctx cache
686 mapping[b'revcache'] = {} # per-ctx cache
683 if self._hasnodespec(origmapping) and self._hasnodespec(newmapping):
687 if self._hasnodespec(origmapping) and self._hasnodespec(newmapping):
684 orignode = templateutil.runsymbol(context, origmapping, b'node')
688 orignode = templateutil.runsymbol(context, origmapping, b'node')
685 mapping[b'originalnode'] = orignode
689 mapping[b'originalnode'] = orignode
686 # put marker to override 'ctx'/'fctx' in mapping if any, and flag
690 # put marker to override 'ctx'/'fctx' in mapping if any, and flag
687 # its existence to be reported by availablekeys()
691 # its existence to be reported by availablekeys()
688 if b'ctx' not in newmapping and self._hasliteral(newmapping, b'node'):
692 if b'ctx' not in newmapping and self._hasliteral(newmapping, b'node'):
689 mapping[b'ctx'] = _placeholder
693 mapping[b'ctx'] = _placeholder
690 if b'fctx' not in newmapping and self._hasliteral(newmapping, b'path'):
694 if b'fctx' not in newmapping and self._hasliteral(newmapping, b'path'):
691 mapping[b'fctx'] = _placeholder
695 mapping[b'fctx'] = _placeholder
692 return mapping
696 return mapping
693
697
694 def _getsome(self, mapping, key):
698 def _getsome(self, mapping, key):
695 v = mapping.get(key)
699 v = mapping.get(key)
696 if v is not None:
700 if v is not None:
697 return v
701 return v
698 return self._resmap.get(key)
702 return self._resmap.get(key)
699
703
700 def _hasliteral(self, mapping, key):
704 def _hasliteral(self, mapping, key):
701 """Test if a literal value is set or unset in the given mapping"""
705 """Test if a literal value is set or unset in the given mapping"""
702 return key in mapping and not callable(mapping[key])
706 return key in mapping and not callable(mapping[key])
703
707
704 def _getliteral(self, mapping, key):
708 def _getliteral(self, mapping, key):
705 """Return value of the given name if it is a literal"""
709 """Return value of the given name if it is a literal"""
706 v = mapping.get(key)
710 v = mapping.get(key)
707 if callable(v):
711 if callable(v):
708 return None
712 return None
709 return v
713 return v
710
714
711 def _hasnodespec(self, mapping):
715 def _hasnodespec(self, mapping):
712 """Test if context revision is set or unset in the given mapping"""
716 """Test if context revision is set or unset in the given mapping"""
713 return b'node' in mapping or b'ctx' in mapping
717 return b'node' in mapping or b'ctx' in mapping
714
718
715 def _loadctx(self, mapping):
719 def _loadctx(self, mapping):
716 repo = self._getsome(mapping, b'repo')
720 repo = self._getsome(mapping, b'repo')
717 node = self._getliteral(mapping, b'node')
721 node = self._getliteral(mapping, b'node')
718 if repo is None or node is None:
722 if repo is None or node is None:
719 return
723 return
720 try:
724 try:
721 return repo[node]
725 return repo[node]
722 except error.RepoLookupError:
726 except error.RepoLookupError:
723 return None # maybe hidden/non-existent node
727 return None # maybe hidden/non-existent node
724
728
725 def _loadfctx(self, mapping):
729 def _loadfctx(self, mapping):
726 ctx = self._getsome(mapping, b'ctx')
730 ctx = self._getsome(mapping, b'ctx')
727 path = self._getliteral(mapping, b'path')
731 path = self._getliteral(mapping, b'path')
728 if ctx is None or path is None:
732 if ctx is None or path is None:
729 return None
733 return None
730 try:
734 try:
731 return ctx[path]
735 return ctx[path]
732 except error.LookupError:
736 except error.LookupError:
733 return None # maybe removed file?
737 return None # maybe removed file?
734
738
735 _loadermap = {
739 _loadermap = {
736 b'ctx': _loadctx,
740 b'ctx': _loadctx,
737 b'fctx': _loadfctx,
741 b'fctx': _loadfctx,
738 }
742 }
739
743
740
744
741 def _internaltemplateformatter(
745 def _internaltemplateformatter(
742 ui,
746 ui,
743 out,
747 out,
744 topic,
748 topic,
745 opts,
749 opts,
746 spec,
750 spec,
747 tmpl,
751 tmpl,
748 docheader=b'',
752 docheader=b'',
749 docfooter=b'',
753 docfooter=b'',
750 separator=b'',
754 separator=b'',
751 ):
755 ):
752 """Build template formatter that handles customizable built-in templates
756 """Build template formatter that handles customizable built-in templates
753 such as -Tjson(...)"""
757 such as -Tjson(...)"""
754 templates = {spec.ref: tmpl}
758 templates = {spec.ref: tmpl}
755 if docheader:
759 if docheader:
756 templates[b'%s:docheader' % spec.ref] = docheader
760 templates[b'%s:docheader' % spec.ref] = docheader
757 if docfooter:
761 if docfooter:
758 templates[b'%s:docfooter' % spec.ref] = docfooter
762 templates[b'%s:docfooter' % spec.ref] = docfooter
759 if separator:
763 if separator:
760 templates[b'%s:separator' % spec.ref] = separator
764 templates[b'%s:separator' % spec.ref] = separator
761 return templateformatter(
765 return templateformatter(
762 ui, out, topic, opts, spec, overridetemplates=templates
766 ui, out, topic, opts, spec, overridetemplates=templates
763 )
767 )
764
768
765
769
766 def formatter(ui, out, topic, opts):
770 def formatter(ui, out, topic, opts):
767 spec = lookuptemplate(ui, topic, opts.get(b'template', b''))
771 spec = lookuptemplate(ui, topic, opts.get(b'template', b''))
768 if spec.ref == b"cbor" and spec.refargs is not None:
772 if spec.ref == b"cbor" and spec.refargs is not None:
769 return _internaltemplateformatter(
773 return _internaltemplateformatter(
770 ui,
774 ui,
771 out,
775 out,
772 topic,
776 topic,
773 opts,
777 opts,
774 spec,
778 spec,
775 tmpl=b'{dict(%s)|cbor}' % spec.refargs,
779 tmpl=b'{dict(%s)|cbor}' % spec.refargs,
776 docheader=cborutil.BEGIN_INDEFINITE_ARRAY,
780 docheader=cborutil.BEGIN_INDEFINITE_ARRAY,
777 docfooter=cborutil.BREAK,
781 docfooter=cborutil.BREAK,
778 )
782 )
779 elif spec.ref == b"cbor":
783 elif spec.ref == b"cbor":
780 return cborformatter(ui, out, topic, opts)
784 return cborformatter(ui, out, topic, opts)
781 elif spec.ref == b"json" and spec.refargs is not None:
785 elif spec.ref == b"json" and spec.refargs is not None:
782 return _internaltemplateformatter(
786 return _internaltemplateformatter(
783 ui,
787 ui,
784 out,
788 out,
785 topic,
789 topic,
786 opts,
790 opts,
787 spec,
791 spec,
788 tmpl=b'{dict(%s)|json}' % spec.refargs,
792 tmpl=b'{dict(%s)|json}' % spec.refargs,
789 docheader=b'[\n ',
793 docheader=b'[\n ',
790 docfooter=b'\n]\n',
794 docfooter=b'\n]\n',
791 separator=b',\n ',
795 separator=b',\n ',
792 )
796 )
793 elif spec.ref == b"json":
797 elif spec.ref == b"json":
794 return jsonformatter(ui, out, topic, opts)
798 return jsonformatter(ui, out, topic, opts)
795 elif spec.ref == b"pickle":
799 elif spec.ref == b"pickle":
796 assert spec.refargs is None, r'function-style not supported'
800 assert spec.refargs is None, r'function-style not supported'
797 return pickleformatter(ui, out, topic, opts)
801 return pickleformatter(ui, out, topic, opts)
798 elif spec.ref == b"debug":
802 elif spec.ref == b"debug":
799 assert spec.refargs is None, r'function-style not supported'
803 assert spec.refargs is None, r'function-style not supported'
800 return debugformatter(ui, out, topic, opts)
804 return debugformatter(ui, out, topic, opts)
801 elif spec.ref or spec.tmpl or spec.mapfile:
805 elif spec.ref or spec.tmpl or spec.mapfile:
802 assert spec.refargs is None, r'function-style not supported'
806 assert spec.refargs is None, r'function-style not supported'
803 return templateformatter(ui, out, topic, opts, spec)
807 return templateformatter(ui, out, topic, opts, spec)
804 # developer config: ui.formatdebug
808 # developer config: ui.formatdebug
805 elif ui.configbool(b'ui', b'formatdebug'):
809 elif ui.configbool(b'ui', b'formatdebug'):
806 return debugformatter(ui, out, topic, opts)
810 return debugformatter(ui, out, topic, opts)
807 # deprecated config: ui.formatjson
811 # deprecated config: ui.formatjson
808 elif ui.configbool(b'ui', b'formatjson'):
812 elif ui.configbool(b'ui', b'formatjson'):
809 return jsonformatter(ui, out, topic, opts)
813 return jsonformatter(ui, out, topic, opts)
810 return plainformatter(ui, out, topic, opts)
814 return plainformatter(ui, out, topic, opts)
811
815
812
816
813 @contextlib.contextmanager
817 @contextlib.contextmanager
814 def openformatter(ui, filename, topic, opts):
818 def openformatter(ui, filename, topic, opts):
815 """Create a formatter that writes outputs to the specified file
819 """Create a formatter that writes outputs to the specified file
816
820
817 Must be invoked using the 'with' statement.
821 Must be invoked using the 'with' statement.
818 """
822 """
819 with util.posixfile(filename, b'wb') as out:
823 with util.posixfile(filename, b'wb') as out:
820 with formatter(ui, out, topic, opts) as fm:
824 with formatter(ui, out, topic, opts) as fm:
821 yield fm
825 yield fm
822
826
823
827
824 @contextlib.contextmanager
828 @contextlib.contextmanager
825 def _neverending(fm):
829 def _neverending(fm):
826 yield fm
830 yield fm
827
831
828
832
829 def maybereopen(fm, filename):
833 def maybereopen(fm, filename):
830 """Create a formatter backed by file if filename specified, else return
834 """Create a formatter backed by file if filename specified, else return
831 the given formatter
835 the given formatter
832
836
833 Must be invoked using the 'with' statement. This will never call fm.end()
837 Must be invoked using the 'with' statement. This will never call fm.end()
834 of the given formatter.
838 of the given formatter.
835 """
839 """
836 if filename:
840 if filename:
837 return openformatter(fm._ui, filename, fm._topic, fm._opts)
841 return openformatter(fm._ui, filename, fm._topic, fm._opts)
838 else:
842 else:
839 return _neverending(fm)
843 return _neverending(fm)
@@ -1,392 +1,392 b''
1 hide outer repo
1 hide outer repo
2 $ hg init
2 $ hg init
3
3
4 Invalid syntax: no value
4 Invalid syntax: no value
5
5
6 $ cat > .hg/hgrc << EOF
6 $ cat > .hg/hgrc << EOF
7 > novaluekey
7 > novaluekey
8 > EOF
8 > EOF
9 $ hg showconfig
9 $ hg showconfig
10 hg: parse error at $TESTTMP/.hg/hgrc:1: novaluekey
10 hg: parse error at $TESTTMP/.hg/hgrc:1: novaluekey
11 [255]
11 [255]
12
12
13 Invalid syntax: no key
13 Invalid syntax: no key
14
14
15 $ cat > .hg/hgrc << EOF
15 $ cat > .hg/hgrc << EOF
16 > =nokeyvalue
16 > =nokeyvalue
17 > EOF
17 > EOF
18 $ hg showconfig
18 $ hg showconfig
19 hg: parse error at $TESTTMP/.hg/hgrc:1: =nokeyvalue
19 hg: parse error at $TESTTMP/.hg/hgrc:1: =nokeyvalue
20 [255]
20 [255]
21
21
22 Test hint about invalid syntax from leading white space
22 Test hint about invalid syntax from leading white space
23
23
24 $ cat > .hg/hgrc << EOF
24 $ cat > .hg/hgrc << EOF
25 > key=value
25 > key=value
26 > EOF
26 > EOF
27 $ hg showconfig
27 $ hg showconfig
28 hg: parse error at $TESTTMP/.hg/hgrc:1: key=value
28 hg: parse error at $TESTTMP/.hg/hgrc:1: key=value
29 unexpected leading whitespace
29 unexpected leading whitespace
30 [255]
30 [255]
31
31
32 $ cat > .hg/hgrc << EOF
32 $ cat > .hg/hgrc << EOF
33 > [section]
33 > [section]
34 > key=value
34 > key=value
35 > EOF
35 > EOF
36 $ hg showconfig
36 $ hg showconfig
37 hg: parse error at $TESTTMP/.hg/hgrc:1: [section]
37 hg: parse error at $TESTTMP/.hg/hgrc:1: [section]
38 unexpected leading whitespace
38 unexpected leading whitespace
39 [255]
39 [255]
40
40
41 Reset hgrc
41 Reset hgrc
42
42
43 $ echo > .hg/hgrc
43 $ echo > .hg/hgrc
44
44
45 Test case sensitive configuration
45 Test case sensitive configuration
46
46
47 $ cat <<EOF >> $HGRCPATH
47 $ cat <<EOF >> $HGRCPATH
48 > [Section]
48 > [Section]
49 > KeY = Case Sensitive
49 > KeY = Case Sensitive
50 > key = lower case
50 > key = lower case
51 > EOF
51 > EOF
52
52
53 $ hg showconfig Section
53 $ hg showconfig Section
54 Section.KeY=Case Sensitive
54 Section.KeY=Case Sensitive
55 Section.key=lower case
55 Section.key=lower case
56
56
57 $ hg showconfig Section -Tjson
57 $ hg showconfig Section -Tjson
58 [
58 [
59 {
59 {
60 "defaultvalue": null,
60 "defaultvalue": null,
61 "name": "Section.KeY",
61 "name": "Section.KeY",
62 "source": "*.hgrc:*", (glob)
62 "source": "*.hgrc:*", (glob)
63 "value": "Case Sensitive"
63 "value": "Case Sensitive"
64 },
64 },
65 {
65 {
66 "defaultvalue": null,
66 "defaultvalue": null,
67 "name": "Section.key",
67 "name": "Section.key",
68 "source": "*.hgrc:*", (glob)
68 "source": "*.hgrc:*", (glob)
69 "value": "lower case"
69 "value": "lower case"
70 }
70 }
71 ]
71 ]
72 $ hg showconfig Section.KeY -Tjson
72 $ hg showconfig Section.KeY -Tjson
73 [
73 [
74 {
74 {
75 "defaultvalue": null,
75 "defaultvalue": null,
76 "name": "Section.KeY",
76 "name": "Section.KeY",
77 "source": "*.hgrc:*", (glob)
77 "source": "*.hgrc:*", (glob)
78 "value": "Case Sensitive"
78 "value": "Case Sensitive"
79 }
79 }
80 ]
80 ]
81 $ hg showconfig -Tjson | tail -7
81 $ hg showconfig -Tjson | tail -7
82 {
82 {
83 "defaultvalue": null,
83 "defaultvalue": null,
84 "name": "*", (glob)
84 "name": "*", (glob)
85 "source": "*", (glob)
85 "source": "*", (glob)
86 "value": "*" (glob)
86 "value": "*" (glob)
87 }
87 }
88 ]
88 ]
89
89
90 Test config default of various types:
90 Test config default of various types:
91
91
92 {"defaultvalue": ""} for -T'json(defaultvalue)' looks weird, but that's
92 {"defaultvalue": ""} for -T'json(defaultvalue)' looks weird, but that's
93 how the templater works. Unknown keywords are evaluated to "".
93 how the templater works. Unknown keywords are evaluated to "".
94
94
95 dynamicdefault
95 dynamicdefault
96
96
97 $ hg config --config alias.foo= alias -Tjson
97 $ hg config --config alias.foo= alias -Tjson
98 [
98 [
99 {
99 {
100 "name": "alias.foo",
100 "name": "alias.foo",
101 "source": "--config",
101 "source": "--config",
102 "value": ""
102 "value": ""
103 }
103 }
104 ]
104 ]
105 $ hg config --config alias.foo= alias -T'json(defaultvalue)'
105 $ hg config --config alias.foo= alias -T'json(defaultvalue)'
106 [
106 [
107 {"defaultvalue": ""}
107 {"defaultvalue": ""}
108 ]
108 ]
109 $ hg config --config alias.foo= alias -T'{defaultvalue}\n'
109 $ hg config --config alias.foo= alias -T'{defaultvalue}\n'
110
110
111
111
112 null
112 null
113
113
114 $ hg config --config auth.cookiefile= auth -Tjson
114 $ hg config --config auth.cookiefile= auth -Tjson
115 [
115 [
116 {
116 {
117 "defaultvalue": null,
117 "defaultvalue": null,
118 "name": "auth.cookiefile",
118 "name": "auth.cookiefile",
119 "source": "--config",
119 "source": "--config",
120 "value": ""
120 "value": ""
121 }
121 }
122 ]
122 ]
123 $ hg config --config auth.cookiefile= auth -T'json(defaultvalue)'
123 $ hg config --config auth.cookiefile= auth -T'json(defaultvalue)'
124 [
124 [
125 {"defaultvalue": ""}
125 {"defaultvalue": null}
126 ]
126 ]
127 $ hg config --config auth.cookiefile= auth -T'{defaultvalue}\n'
127 $ hg config --config auth.cookiefile= auth -T'{defaultvalue}\n'
128
128
129
129
130 false
130 false
131
131
132 $ hg config --config commands.commit.post-status= commands -Tjson
132 $ hg config --config commands.commit.post-status= commands -Tjson
133 [
133 [
134 {
134 {
135 "defaultvalue": false,
135 "defaultvalue": false,
136 "name": "commands.commit.post-status",
136 "name": "commands.commit.post-status",
137 "source": "--config",
137 "source": "--config",
138 "value": ""
138 "value": ""
139 }
139 }
140 ]
140 ]
141 $ hg config --config commands.commit.post-status= commands -T'json(defaultvalue)'
141 $ hg config --config commands.commit.post-status= commands -T'json(defaultvalue)'
142 [
142 [
143 {"defaultvalue": false}
143 {"defaultvalue": false}
144 ]
144 ]
145 $ hg config --config commands.commit.post-status= commands -T'{defaultvalue}\n'
145 $ hg config --config commands.commit.post-status= commands -T'{defaultvalue}\n'
146 False
146 False
147
147
148 true
148 true
149
149
150 $ hg config --config format.dotencode= format -Tjson
150 $ hg config --config format.dotencode= format -Tjson
151 [
151 [
152 {
152 {
153 "defaultvalue": true,
153 "defaultvalue": true,
154 "name": "format.dotencode",
154 "name": "format.dotencode",
155 "source": "--config",
155 "source": "--config",
156 "value": ""
156 "value": ""
157 }
157 }
158 ]
158 ]
159 $ hg config --config format.dotencode= format -T'json(defaultvalue)'
159 $ hg config --config format.dotencode= format -T'json(defaultvalue)'
160 [
160 [
161 {"defaultvalue": true}
161 {"defaultvalue": true}
162 ]
162 ]
163 $ hg config --config format.dotencode= format -T'{defaultvalue}\n'
163 $ hg config --config format.dotencode= format -T'{defaultvalue}\n'
164 True
164 True
165
165
166 bytes
166 bytes
167
167
168 $ hg config --config commands.resolve.mark-check= commands -Tjson
168 $ hg config --config commands.resolve.mark-check= commands -Tjson
169 [
169 [
170 {
170 {
171 "defaultvalue": "none",
171 "defaultvalue": "none",
172 "name": "commands.resolve.mark-check",
172 "name": "commands.resolve.mark-check",
173 "source": "--config",
173 "source": "--config",
174 "value": ""
174 "value": ""
175 }
175 }
176 ]
176 ]
177 $ hg config --config commands.resolve.mark-check= commands -T'json(defaultvalue)'
177 $ hg config --config commands.resolve.mark-check= commands -T'json(defaultvalue)'
178 [
178 [
179 {"defaultvalue": "none"}
179 {"defaultvalue": "none"}
180 ]
180 ]
181 $ hg config --config commands.resolve.mark-check= commands -T'{defaultvalue}\n'
181 $ hg config --config commands.resolve.mark-check= commands -T'{defaultvalue}\n'
182 none
182 none
183
183
184 empty list
184 empty list
185
185
186 $ hg config --config commands.show.aliasprefix= commands -Tjson
186 $ hg config --config commands.show.aliasprefix= commands -Tjson
187 [
187 [
188 {
188 {
189 "defaultvalue": [],
189 "defaultvalue": [],
190 "name": "commands.show.aliasprefix",
190 "name": "commands.show.aliasprefix",
191 "source": "--config",
191 "source": "--config",
192 "value": ""
192 "value": ""
193 }
193 }
194 ]
194 ]
195 $ hg config --config commands.show.aliasprefix= commands -T'json(defaultvalue)'
195 $ hg config --config commands.show.aliasprefix= commands -T'json(defaultvalue)'
196 [
196 [
197 {"defaultvalue": []}
197 {"defaultvalue": []}
198 ]
198 ]
199 $ hg config --config commands.show.aliasprefix= commands -T'{defaultvalue}\n'
199 $ hg config --config commands.show.aliasprefix= commands -T'{defaultvalue}\n'
200
200
201
201
202 nonempty list
202 nonempty list
203
203
204 $ hg config --config progress.format= progress -Tjson
204 $ hg config --config progress.format= progress -Tjson
205 [
205 [
206 {
206 {
207 "defaultvalue": ["topic", "bar", "number", "estimate"],
207 "defaultvalue": ["topic", "bar", "number", "estimate"],
208 "name": "progress.format",
208 "name": "progress.format",
209 "source": "--config",
209 "source": "--config",
210 "value": ""
210 "value": ""
211 }
211 }
212 ]
212 ]
213 $ hg config --config progress.format= progress -T'json(defaultvalue)'
213 $ hg config --config progress.format= progress -T'json(defaultvalue)'
214 [
214 [
215 {"defaultvalue": ["topic", "bar", "number", "estimate"]}
215 {"defaultvalue": ["topic", "bar", "number", "estimate"]}
216 ]
216 ]
217 $ hg config --config progress.format= progress -T'{defaultvalue}\n'
217 $ hg config --config progress.format= progress -T'{defaultvalue}\n'
218 topic bar number estimate
218 topic bar number estimate
219
219
220 int
220 int
221
221
222 $ hg config --config profiling.freq= profiling -Tjson
222 $ hg config --config profiling.freq= profiling -Tjson
223 [
223 [
224 {
224 {
225 "defaultvalue": 1000,
225 "defaultvalue": 1000,
226 "name": "profiling.freq",
226 "name": "profiling.freq",
227 "source": "--config",
227 "source": "--config",
228 "value": ""
228 "value": ""
229 }
229 }
230 ]
230 ]
231 $ hg config --config profiling.freq= profiling -T'json(defaultvalue)'
231 $ hg config --config profiling.freq= profiling -T'json(defaultvalue)'
232 [
232 [
233 {"defaultvalue": 1000}
233 {"defaultvalue": 1000}
234 ]
234 ]
235 $ hg config --config profiling.freq= profiling -T'{defaultvalue}\n'
235 $ hg config --config profiling.freq= profiling -T'{defaultvalue}\n'
236 1000
236 1000
237
237
238 float
238 float
239
239
240 $ hg config --config profiling.showmax= profiling -Tjson
240 $ hg config --config profiling.showmax= profiling -Tjson
241 [
241 [
242 {
242 {
243 "defaultvalue": 0.999,
243 "defaultvalue": 0.999,
244 "name": "profiling.showmax",
244 "name": "profiling.showmax",
245 "source": "--config",
245 "source": "--config",
246 "value": ""
246 "value": ""
247 }
247 }
248 ]
248 ]
249 $ hg config --config profiling.showmax= profiling -T'json(defaultvalue)'
249 $ hg config --config profiling.showmax= profiling -T'json(defaultvalue)'
250 [
250 [
251 {"defaultvalue": 0.999}
251 {"defaultvalue": 0.999}
252 ]
252 ]
253 $ hg config --config profiling.showmax= profiling -T'{defaultvalue}\n'
253 $ hg config --config profiling.showmax= profiling -T'{defaultvalue}\n'
254 0.999
254 0.999
255
255
256 Test empty config source:
256 Test empty config source:
257
257
258 $ cat <<EOF > emptysource.py
258 $ cat <<EOF > emptysource.py
259 > def reposetup(ui, repo):
259 > def reposetup(ui, repo):
260 > ui.setconfig(b'empty', b'source', b'value')
260 > ui.setconfig(b'empty', b'source', b'value')
261 > EOF
261 > EOF
262 $ cp .hg/hgrc .hg/hgrc.orig
262 $ cp .hg/hgrc .hg/hgrc.orig
263 $ cat <<EOF >> .hg/hgrc
263 $ cat <<EOF >> .hg/hgrc
264 > [extensions]
264 > [extensions]
265 > emptysource = `pwd`/emptysource.py
265 > emptysource = `pwd`/emptysource.py
266 > EOF
266 > EOF
267
267
268 $ hg config --debug empty.source
268 $ hg config --debug empty.source
269 read config from: * (glob)
269 read config from: * (glob)
270 none: value
270 none: value
271 $ hg config empty.source -Tjson
271 $ hg config empty.source -Tjson
272 [
272 [
273 {
273 {
274 "defaultvalue": null,
274 "defaultvalue": null,
275 "name": "empty.source",
275 "name": "empty.source",
276 "source": "",
276 "source": "",
277 "value": "value"
277 "value": "value"
278 }
278 }
279 ]
279 ]
280
280
281 $ cp .hg/hgrc.orig .hg/hgrc
281 $ cp .hg/hgrc.orig .hg/hgrc
282
282
283 Test "%unset"
283 Test "%unset"
284
284
285 $ cat >> $HGRCPATH <<EOF
285 $ cat >> $HGRCPATH <<EOF
286 > [unsettest]
286 > [unsettest]
287 > local-hgrcpath = should be unset (HGRCPATH)
287 > local-hgrcpath = should be unset (HGRCPATH)
288 > %unset local-hgrcpath
288 > %unset local-hgrcpath
289 >
289 >
290 > global = should be unset (HGRCPATH)
290 > global = should be unset (HGRCPATH)
291 >
291 >
292 > both = should be unset (HGRCPATH)
292 > both = should be unset (HGRCPATH)
293 >
293 >
294 > set-after-unset = should be unset (HGRCPATH)
294 > set-after-unset = should be unset (HGRCPATH)
295 > EOF
295 > EOF
296
296
297 $ cat >> .hg/hgrc <<EOF
297 $ cat >> .hg/hgrc <<EOF
298 > [unsettest]
298 > [unsettest]
299 > local-hgrc = should be unset (.hg/hgrc)
299 > local-hgrc = should be unset (.hg/hgrc)
300 > %unset local-hgrc
300 > %unset local-hgrc
301 >
301 >
302 > %unset global
302 > %unset global
303 >
303 >
304 > both = should be unset (.hg/hgrc)
304 > both = should be unset (.hg/hgrc)
305 > %unset both
305 > %unset both
306 >
306 >
307 > set-after-unset = should be unset (.hg/hgrc)
307 > set-after-unset = should be unset (.hg/hgrc)
308 > %unset set-after-unset
308 > %unset set-after-unset
309 > set-after-unset = should be set (.hg/hgrc)
309 > set-after-unset = should be set (.hg/hgrc)
310 > EOF
310 > EOF
311
311
312 $ hg showconfig unsettest
312 $ hg showconfig unsettest
313 unsettest.set-after-unset=should be set (.hg/hgrc)
313 unsettest.set-after-unset=should be set (.hg/hgrc)
314
314
315 Test exit code when no config matches
315 Test exit code when no config matches
316
316
317 $ hg config Section.idontexist
317 $ hg config Section.idontexist
318 [1]
318 [1]
319
319
320 sub-options in [paths] aren't expanded
320 sub-options in [paths] aren't expanded
321
321
322 $ cat > .hg/hgrc << EOF
322 $ cat > .hg/hgrc << EOF
323 > [paths]
323 > [paths]
324 > foo = ~/foo
324 > foo = ~/foo
325 > foo:suboption = ~/foo
325 > foo:suboption = ~/foo
326 > EOF
326 > EOF
327
327
328 $ hg showconfig paths
328 $ hg showconfig paths
329 paths.foo:suboption=~/foo
329 paths.foo:suboption=~/foo
330 paths.foo=$TESTTMP/foo
330 paths.foo=$TESTTMP/foo
331
331
332 edit failure
332 edit failure
333
333
334 $ HGEDITOR=false hg config --edit
334 $ HGEDITOR=false hg config --edit
335 abort: edit failed: false exited with status 1
335 abort: edit failed: false exited with status 1
336 [255]
336 [255]
337
337
338 config affected by environment variables
338 config affected by environment variables
339
339
340 $ EDITOR=e1 VISUAL=e2 hg config --debug | grep 'ui\.editor'
340 $ EDITOR=e1 VISUAL=e2 hg config --debug | grep 'ui\.editor'
341 $VISUAL: ui.editor=e2
341 $VISUAL: ui.editor=e2
342
342
343 $ VISUAL=e2 hg config --debug --config ui.editor=e3 | grep 'ui\.editor'
343 $ VISUAL=e2 hg config --debug --config ui.editor=e3 | grep 'ui\.editor'
344 --config: ui.editor=e3
344 --config: ui.editor=e3
345
345
346 $ PAGER=p1 hg config --debug | grep 'pager\.pager'
346 $ PAGER=p1 hg config --debug | grep 'pager\.pager'
347 $PAGER: pager.pager=p1
347 $PAGER: pager.pager=p1
348
348
349 $ PAGER=p1 hg config --debug --config pager.pager=p2 | grep 'pager\.pager'
349 $ PAGER=p1 hg config --debug --config pager.pager=p2 | grep 'pager\.pager'
350 --config: pager.pager=p2
350 --config: pager.pager=p2
351
351
352 verify that aliases are evaluated as well
352 verify that aliases are evaluated as well
353
353
354 $ hg init aliastest
354 $ hg init aliastest
355 $ cd aliastest
355 $ cd aliastest
356 $ cat > .hg/hgrc << EOF
356 $ cat > .hg/hgrc << EOF
357 > [ui]
357 > [ui]
358 > user = repo user
358 > user = repo user
359 > EOF
359 > EOF
360 $ touch index
360 $ touch index
361 $ unset HGUSER
361 $ unset HGUSER
362 $ hg ci -Am test
362 $ hg ci -Am test
363 adding index
363 adding index
364 $ hg log --template '{author}\n'
364 $ hg log --template '{author}\n'
365 repo user
365 repo user
366 $ cd ..
366 $ cd ..
367
367
368 alias has lower priority
368 alias has lower priority
369
369
370 $ hg init aliaspriority
370 $ hg init aliaspriority
371 $ cd aliaspriority
371 $ cd aliaspriority
372 $ cat > .hg/hgrc << EOF
372 $ cat > .hg/hgrc << EOF
373 > [ui]
373 > [ui]
374 > user = alias user
374 > user = alias user
375 > username = repo user
375 > username = repo user
376 > EOF
376 > EOF
377 $ touch index
377 $ touch index
378 $ unset HGUSER
378 $ unset HGUSER
379 $ hg ci -Am test
379 $ hg ci -Am test
380 adding index
380 adding index
381 $ hg log --template '{author}\n'
381 $ hg log --template '{author}\n'
382 repo user
382 repo user
383 $ cd ..
383 $ cd ..
384
384
385 configs should be read in lexicographical order
385 configs should be read in lexicographical order
386
386
387 $ mkdir configs
387 $ mkdir configs
388 $ for i in `$TESTDIR/seq.py 10 99`; do
388 $ for i in `$TESTDIR/seq.py 10 99`; do
389 > printf "[section]\nkey=$i" > configs/$i.rc
389 > printf "[section]\nkey=$i" > configs/$i.rc
390 > done
390 > done
391 $ HGRCPATH=configs hg config section.key
391 $ HGRCPATH=configs hg config section.key
392 99
392 99
General Comments 0
You need to be logged in to leave comments. Login now