##// END OF EJS Templates
formatter: add option to redirect output to file object...
Yuya Nishihara -
r32573:012e0da5 default
parent child Browse files
Show More
@@ -1,430 +1,434 b''
1 1 # formatter.py - generic output formatting for mercurial
2 2 #
3 3 # Copyright 2012 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 """Generic output formatting for Mercurial
9 9
10 10 The formatter provides API to show data in various ways. The following
11 11 functions should be used in place of ui.write():
12 12
13 13 - fm.write() for unconditional output
14 14 - fm.condwrite() to show some extra data conditionally in plain output
15 15 - fm.context() to provide changectx to template output
16 16 - fm.data() to provide extra data to JSON or template output
17 17 - fm.plain() to show raw text that isn't provided to JSON or template output
18 18
19 19 To show structured data (e.g. date tuples, dicts, lists), apply fm.format*()
20 20 beforehand so the data is converted to the appropriate data type. Use
21 21 fm.isplain() if you need to convert or format data conditionally which isn't
22 22 supported by the formatter API.
23 23
24 24 To build nested structure (i.e. a list of dicts), use fm.nested().
25 25
26 26 See also https://www.mercurial-scm.org/wiki/GenericTemplatingPlan
27 27
28 28 fm.condwrite() vs 'if cond:':
29 29
30 30 In most cases, use fm.condwrite() so users can selectively show the data
31 31 in template output. If it's costly to build data, use plain 'if cond:' with
32 32 fm.write().
33 33
34 34 fm.nested() vs fm.formatdict() (or fm.formatlist()):
35 35
36 36 fm.nested() should be used to form a tree structure (a list of dicts of
37 37 lists of dicts...) which can be accessed through template keywords, e.g.
38 38 "{foo % "{bar % {...}} {baz % {...}}"}". On the other hand, fm.formatdict()
39 39 exports a dict-type object to template, which can be accessed by e.g.
40 40 "{get(foo, key)}" function.
41 41
42 42 Doctest helper:
43 43
44 44 >>> def show(fn, verbose=False, **opts):
45 45 ... import sys
46 46 ... from . import ui as uimod
47 47 ... ui = uimod.ui()
48 48 ... ui.fout = sys.stdout # redirect to doctest
49 49 ... ui.verbose = verbose
50 50 ... return fn(ui, ui.formatter(fn.__name__, opts))
51 51
52 52 Basic example:
53 53
54 54 >>> def files(ui, fm):
55 55 ... files = [('foo', 123, (0, 0)), ('bar', 456, (1, 0))]
56 56 ... for f in files:
57 57 ... fm.startitem()
58 58 ... fm.write('path', '%s', f[0])
59 59 ... fm.condwrite(ui.verbose, 'date', ' %s',
60 60 ... fm.formatdate(f[2], '%Y-%m-%d %H:%M:%S'))
61 61 ... fm.data(size=f[1])
62 62 ... fm.plain('\\n')
63 63 ... fm.end()
64 64 >>> show(files)
65 65 foo
66 66 bar
67 67 >>> show(files, verbose=True)
68 68 foo 1970-01-01 00:00:00
69 69 bar 1970-01-01 00:00:01
70 70 >>> show(files, template='json')
71 71 [
72 72 {
73 73 "date": [0, 0],
74 74 "path": "foo",
75 75 "size": 123
76 76 },
77 77 {
78 78 "date": [1, 0],
79 79 "path": "bar",
80 80 "size": 456
81 81 }
82 82 ]
83 83 >>> show(files, template='path: {path}\\ndate: {date|rfc3339date}\\n')
84 84 path: foo
85 85 date: 1970-01-01T00:00:00+00:00
86 86 path: bar
87 87 date: 1970-01-01T00:00:01+00:00
88 88
89 89 Nested example:
90 90
91 91 >>> def subrepos(ui, fm):
92 92 ... fm.startitem()
93 93 ... fm.write('repo', '[%s]\\n', 'baz')
94 94 ... files(ui, fm.nested('files'))
95 95 ... fm.end()
96 96 >>> show(subrepos)
97 97 [baz]
98 98 foo
99 99 bar
100 100 >>> show(subrepos, template='{repo}: {join(files % "{path}", ", ")}\\n')
101 101 baz: foo, bar
102 102 """
103 103
104 104 from __future__ import absolute_import
105 105
106 106 import itertools
107 107 import os
108 108
109 109 from .i18n import _
110 110 from .node import (
111 111 hex,
112 112 short,
113 113 )
114 114
115 115 from . import (
116 116 error,
117 117 pycompat,
118 118 templatefilters,
119 119 templatekw,
120 120 templater,
121 121 util,
122 122 )
123 123
124 124 pickle = util.pickle
125 125
126 126 class _nullconverter(object):
127 127 '''convert non-primitive data types to be processed by formatter'''
128 128 @staticmethod
129 129 def formatdate(date, fmt):
130 130 '''convert date tuple to appropriate format'''
131 131 return date
132 132 @staticmethod
133 133 def formatdict(data, key, value, fmt, sep):
134 134 '''convert dict or key-value pairs to appropriate dict format'''
135 135 # use plain dict instead of util.sortdict so that data can be
136 136 # serialized as a builtin dict in pickle output
137 137 return dict(data)
138 138 @staticmethod
139 139 def formatlist(data, name, fmt, sep):
140 140 '''convert iterable to appropriate list format'''
141 141 return list(data)
142 142
143 143 class baseformatter(object):
144 144 def __init__(self, ui, topic, opts, converter):
145 145 self._ui = ui
146 146 self._topic = topic
147 147 self._style = opts.get("style")
148 148 self._template = opts.get("template")
149 149 self._converter = converter
150 150 self._item = None
151 151 # function to convert node to string suitable for this output
152 152 self.hexfunc = hex
153 153 def __enter__(self):
154 154 return self
155 155 def __exit__(self, exctype, excvalue, traceback):
156 156 if exctype is None:
157 157 self.end()
158 158 def _showitem(self):
159 159 '''show a formatted item once all data is collected'''
160 160 pass
161 161 def startitem(self):
162 162 '''begin an item in the format list'''
163 163 if self._item is not None:
164 164 self._showitem()
165 165 self._item = {}
166 166 def formatdate(self, date, fmt='%a %b %d %H:%M:%S %Y %1%2'):
167 167 '''convert date tuple to appropriate format'''
168 168 return self._converter.formatdate(date, fmt)
169 169 def formatdict(self, data, key='key', value='value', fmt='%s=%s', sep=' '):
170 170 '''convert dict or key-value pairs to appropriate dict format'''
171 171 return self._converter.formatdict(data, key, value, fmt, sep)
172 172 def formatlist(self, data, name, fmt='%s', sep=' '):
173 173 '''convert iterable to appropriate list format'''
174 174 # name is mandatory argument for now, but it could be optional if
175 175 # we have default template keyword, e.g. {item}
176 176 return self._converter.formatlist(data, name, fmt, sep)
177 177 def context(self, **ctxs):
178 178 '''insert context objects to be used to render template keywords'''
179 179 pass
180 180 def data(self, **data):
181 181 '''insert data into item that's not shown in default output'''
182 182 data = pycompat.byteskwargs(data)
183 183 self._item.update(data)
184 184 def write(self, fields, deftext, *fielddata, **opts):
185 185 '''do default text output while assigning data to item'''
186 186 fieldkeys = fields.split()
187 187 assert len(fieldkeys) == len(fielddata)
188 188 self._item.update(zip(fieldkeys, fielddata))
189 189 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
190 190 '''do conditional write (primarily for plain formatter)'''
191 191 fieldkeys = fields.split()
192 192 assert len(fieldkeys) == len(fielddata)
193 193 self._item.update(zip(fieldkeys, fielddata))
194 194 def plain(self, text, **opts):
195 195 '''show raw text for non-templated mode'''
196 196 pass
197 197 def isplain(self):
198 198 '''check for plain formatter usage'''
199 199 return False
200 200 def nested(self, field):
201 201 '''sub formatter to store nested data in the specified field'''
202 202 self._item[field] = data = []
203 203 return _nestedformatter(self._ui, self._converter, data)
204 204 def end(self):
205 205 '''end output for the formatter'''
206 206 if self._item is not None:
207 207 self._showitem()
208 208
209 209 class _nestedformatter(baseformatter):
210 210 '''build sub items and store them in the parent formatter'''
211 211 def __init__(self, ui, converter, data):
212 212 baseformatter.__init__(self, ui, topic='', opts={}, converter=converter)
213 213 self._data = data
214 214 def _showitem(self):
215 215 self._data.append(self._item)
216 216
217 217 def _iteritems(data):
218 218 '''iterate key-value pairs in stable order'''
219 219 if isinstance(data, dict):
220 220 return sorted(data.iteritems())
221 221 return data
222 222
223 223 class _plainconverter(object):
224 224 '''convert non-primitive data types to text'''
225 225 @staticmethod
226 226 def formatdate(date, fmt):
227 227 '''stringify date tuple in the given format'''
228 228 return util.datestr(date, fmt)
229 229 @staticmethod
230 230 def formatdict(data, key, value, fmt, sep):
231 231 '''stringify key-value pairs separated by sep'''
232 232 return sep.join(fmt % (k, v) for k, v in _iteritems(data))
233 233 @staticmethod
234 234 def formatlist(data, name, fmt, sep):
235 235 '''stringify iterable separated by sep'''
236 236 return sep.join(fmt % e for e in data)
237 237
238 238 class plainformatter(baseformatter):
239 239 '''the default text output scheme'''
240 def __init__(self, ui, topic, opts):
240 def __init__(self, ui, out, topic, opts):
241 241 baseformatter.__init__(self, ui, topic, opts, _plainconverter)
242 242 if ui.debugflag:
243 243 self.hexfunc = hex
244 244 else:
245 245 self.hexfunc = short
246 if ui is out:
247 self._write = ui.write
248 else:
249 self._write = lambda s, **opts: out.write(s)
246 250 def startitem(self):
247 251 pass
248 252 def data(self, **data):
249 253 pass
250 254 def write(self, fields, deftext, *fielddata, **opts):
251 self._ui.write(deftext % fielddata, **opts)
255 self._write(deftext % fielddata, **opts)
252 256 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
253 257 '''do conditional write'''
254 258 if cond:
255 self._ui.write(deftext % fielddata, **opts)
259 self._write(deftext % fielddata, **opts)
256 260 def plain(self, text, **opts):
257 self._ui.write(text, **opts)
261 self._write(text, **opts)
258 262 def isplain(self):
259 263 return True
260 264 def nested(self, field):
261 265 # nested data will be directly written to ui
262 266 return self
263 267 def end(self):
264 268 pass
265 269
266 270 class debugformatter(baseformatter):
267 271 def __init__(self, ui, out, topic, opts):
268 272 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
269 273 self._out = out
270 274 self._out.write("%s = [\n" % self._topic)
271 275 def _showitem(self):
272 276 self._out.write(" " + repr(self._item) + ",\n")
273 277 def end(self):
274 278 baseformatter.end(self)
275 279 self._out.write("]\n")
276 280
277 281 class pickleformatter(baseformatter):
278 282 def __init__(self, ui, out, topic, opts):
279 283 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
280 284 self._out = out
281 285 self._data = []
282 286 def _showitem(self):
283 287 self._data.append(self._item)
284 288 def end(self):
285 289 baseformatter.end(self)
286 290 self._out.write(pickle.dumps(self._data))
287 291
288 292 class jsonformatter(baseformatter):
289 293 def __init__(self, ui, out, topic, opts):
290 294 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
291 295 self._out = out
292 296 self._out.write("[")
293 297 self._first = True
294 298 def _showitem(self):
295 299 if self._first:
296 300 self._first = False
297 301 else:
298 302 self._out.write(",")
299 303
300 304 self._out.write("\n {\n")
301 305 first = True
302 306 for k, v in sorted(self._item.items()):
303 307 if first:
304 308 first = False
305 309 else:
306 310 self._out.write(",\n")
307 311 u = templatefilters.json(v, paranoid=False)
308 312 self._out.write(' "%s": %s' % (k, u))
309 313 self._out.write("\n }")
310 314 def end(self):
311 315 baseformatter.end(self)
312 316 self._out.write("\n]\n")
313 317
314 318 class _templateconverter(object):
315 319 '''convert non-primitive data types to be processed by templater'''
316 320 @staticmethod
317 321 def formatdate(date, fmt):
318 322 '''return date tuple'''
319 323 return date
320 324 @staticmethod
321 325 def formatdict(data, key, value, fmt, sep):
322 326 '''build object that can be evaluated as either plain string or dict'''
323 327 data = util.sortdict(_iteritems(data))
324 328 def f():
325 329 yield _plainconverter.formatdict(data, key, value, fmt, sep)
326 330 return templatekw.hybriddict(data, key=key, value=value, fmt=fmt,
327 331 gen=f())
328 332 @staticmethod
329 333 def formatlist(data, name, fmt, sep):
330 334 '''build object that can be evaluated as either plain string or list'''
331 335 data = list(data)
332 336 def f():
333 337 yield _plainconverter.formatlist(data, name, fmt, sep)
334 338 return templatekw.hybridlist(data, name=name, fmt=fmt, gen=f())
335 339
336 340 class templateformatter(baseformatter):
337 341 def __init__(self, ui, out, topic, opts):
338 342 baseformatter.__init__(self, ui, topic, opts, _templateconverter)
339 343 self._out = out
340 344 self._topic = topic
341 345 self._t = gettemplater(ui, topic, opts.get('template', ''),
342 346 cache=templatekw.defaulttempl)
343 347 self._counter = itertools.count()
344 348 self._cache = {} # for templatekw/funcs to store reusable data
345 349 def context(self, **ctxs):
346 350 '''insert context objects to be used to render template keywords'''
347 351 assert all(k == 'ctx' for k in ctxs)
348 352 self._item.update(ctxs)
349 353 def _showitem(self):
350 354 # TODO: add support for filectx. probably each template keyword or
351 355 # function will have to declare dependent resources. e.g.
352 356 # @templatekeyword(..., requires=('ctx',))
353 357 props = {}
354 358 if 'ctx' in self._item:
355 359 props.update(templatekw.keywords)
356 360 props['index'] = next(self._counter)
357 361 # explicitly-defined fields precede templatekw
358 362 props.update(self._item)
359 363 if 'ctx' in self._item:
360 364 # but template resources must be always available
361 365 props['templ'] = self._t
362 366 props['repo'] = props['ctx'].repo()
363 367 props['revcache'] = {}
364 368 g = self._t(self._topic, ui=self._ui, cache=self._cache, **props)
365 369 self._out.write(templater.stringify(g))
366 370
367 371 def lookuptemplate(ui, topic, tmpl):
368 372 # looks like a literal template?
369 373 if '{' in tmpl:
370 374 return tmpl, None
371 375
372 376 # perhaps a stock style?
373 377 if not os.path.split(tmpl)[0]:
374 378 mapname = (templater.templatepath('map-cmdline.' + tmpl)
375 379 or templater.templatepath(tmpl))
376 380 if mapname and os.path.isfile(mapname):
377 381 return None, mapname
378 382
379 383 # perhaps it's a reference to [templates]
380 384 t = ui.config('templates', tmpl)
381 385 if t:
382 386 return templater.unquotestring(t), None
383 387
384 388 if tmpl == 'list':
385 389 ui.write(_("available styles: %s\n") % templater.stylelist())
386 390 raise error.Abort(_("specify a template"))
387 391
388 392 # perhaps it's a path to a map or a template
389 393 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
390 394 # is it a mapfile for a style?
391 395 if os.path.basename(tmpl).startswith("map-"):
392 396 return None, os.path.realpath(tmpl)
393 397 tmpl = open(tmpl).read()
394 398 return tmpl, None
395 399
396 400 # constant string?
397 401 return tmpl, None
398 402
399 403 def gettemplater(ui, topic, spec, cache=None):
400 404 tmpl, mapfile = lookuptemplate(ui, topic, spec)
401 405 assert not (tmpl and mapfile)
402 406 if mapfile:
403 407 return templater.templater.frommapfile(mapfile, cache=cache)
404 408 return maketemplater(ui, topic, tmpl, cache=cache)
405 409
406 410 def maketemplater(ui, topic, tmpl, cache=None):
407 411 """Create a templater from a string template 'tmpl'"""
408 412 aliases = ui.configitems('templatealias')
409 413 t = templater.templater(cache=cache, aliases=aliases)
410 414 if tmpl:
411 415 t.cache[topic] = tmpl
412 416 return t
413 417
414 def formatter(ui, topic, opts):
418 def formatter(ui, out, topic, opts):
415 419 template = opts.get("template", "")
416 420 if template == "json":
417 return jsonformatter(ui, ui, topic, opts)
421 return jsonformatter(ui, out, topic, opts)
418 422 elif template == "pickle":
419 return pickleformatter(ui, ui, topic, opts)
423 return pickleformatter(ui, out, topic, opts)
420 424 elif template == "debug":
421 return debugformatter(ui, ui, topic, opts)
425 return debugformatter(ui, out, topic, opts)
422 426 elif template != "":
423 return templateformatter(ui, ui, topic, opts)
427 return templateformatter(ui, out, topic, opts)
424 428 # developer config: ui.formatdebug
425 429 elif ui.configbool('ui', 'formatdebug'):
426 return debugformatter(ui, ui, topic, opts)
430 return debugformatter(ui, out, topic, opts)
427 431 # deprecated config: ui.formatjson
428 432 elif ui.configbool('ui', 'formatjson'):
429 return jsonformatter(ui, ui, topic, opts)
430 return plainformatter(ui, topic, opts)
433 return jsonformatter(ui, out, topic, opts)
434 return plainformatter(ui, out, topic, opts)
@@ -1,1699 +1,1699 b''
1 1 # ui.py - user interface bits for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import collections
11 11 import contextlib
12 12 import errno
13 13 import getpass
14 14 import inspect
15 15 import os
16 16 import re
17 17 import signal
18 18 import socket
19 19 import subprocess
20 20 import sys
21 21 import tempfile
22 22 import traceback
23 23
24 24 from .i18n import _
25 25 from .node import hex
26 26
27 27 from . import (
28 28 color,
29 29 config,
30 30 encoding,
31 31 error,
32 32 formatter,
33 33 progress,
34 34 pycompat,
35 35 rcutil,
36 36 scmutil,
37 37 util,
38 38 )
39 39
40 40 urlreq = util.urlreq
41 41
42 42 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
43 43 _keepalnum = ''.join(c for c in map(pycompat.bytechr, range(256))
44 44 if not c.isalnum())
45 45
46 46 samplehgrcs = {
47 47 'user':
48 48 """# example user config (see 'hg help config' for more info)
49 49 [ui]
50 50 # name and email, e.g.
51 51 # username = Jane Doe <jdoe@example.com>
52 52 username =
53 53
54 54 # uncomment to disable color in command output
55 55 # (see 'hg help color' for details)
56 56 # color = never
57 57
58 58 # uncomment to disable command output pagination
59 59 # (see 'hg help pager' for details)
60 60 # paginate = never
61 61
62 62 [extensions]
63 63 # uncomment these lines to enable some popular extensions
64 64 # (see 'hg help extensions' for more info)
65 65 #
66 66 # churn =
67 67 """,
68 68
69 69 'cloned':
70 70 """# example repository config (see 'hg help config' for more info)
71 71 [paths]
72 72 default = %s
73 73
74 74 # path aliases to other clones of this repo in URLs or filesystem paths
75 75 # (see 'hg help config.paths' for more info)
76 76 #
77 77 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
78 78 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
79 79 # my-clone = /home/jdoe/jdoes-clone
80 80
81 81 [ui]
82 82 # name and email (local to this repository, optional), e.g.
83 83 # username = Jane Doe <jdoe@example.com>
84 84 """,
85 85
86 86 'local':
87 87 """# example repository config (see 'hg help config' for more info)
88 88 [paths]
89 89 # path aliases to other clones of this repo in URLs or filesystem paths
90 90 # (see 'hg help config.paths' for more info)
91 91 #
92 92 # default = http://example.com/hg/example-repo
93 93 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
94 94 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
95 95 # my-clone = /home/jdoe/jdoes-clone
96 96
97 97 [ui]
98 98 # name and email (local to this repository, optional), e.g.
99 99 # username = Jane Doe <jdoe@example.com>
100 100 """,
101 101
102 102 'global':
103 103 """# example system-wide hg config (see 'hg help config' for more info)
104 104
105 105 [ui]
106 106 # uncomment to disable color in command output
107 107 # (see 'hg help color' for details)
108 108 # color = never
109 109
110 110 # uncomment to disable command output pagination
111 111 # (see 'hg help pager' for details)
112 112 # paginate = never
113 113
114 114 [extensions]
115 115 # uncomment these lines to enable some popular extensions
116 116 # (see 'hg help extensions' for more info)
117 117 #
118 118 # blackbox =
119 119 # churn =
120 120 """,
121 121 }
122 122
123 123
124 124 class httppasswordmgrdbproxy(object):
125 125 """Delays loading urllib2 until it's needed."""
126 126 def __init__(self):
127 127 self._mgr = None
128 128
129 129 def _get_mgr(self):
130 130 if self._mgr is None:
131 131 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
132 132 return self._mgr
133 133
134 134 def add_password(self, *args, **kwargs):
135 135 return self._get_mgr().add_password(*args, **kwargs)
136 136
137 137 def find_user_password(self, *args, **kwargs):
138 138 return self._get_mgr().find_user_password(*args, **kwargs)
139 139
140 140 def _catchterm(*args):
141 141 raise error.SignalInterrupt
142 142
143 143 class ui(object):
144 144 def __init__(self, src=None):
145 145 """Create a fresh new ui object if no src given
146 146
147 147 Use uimod.ui.load() to create a ui which knows global and user configs.
148 148 In most cases, you should use ui.copy() to create a copy of an existing
149 149 ui object.
150 150 """
151 151 # _buffers: used for temporary capture of output
152 152 self._buffers = []
153 153 # _exithandlers: callbacks run at the end of a request
154 154 self._exithandlers = []
155 155 # 3-tuple describing how each buffer in the stack behaves.
156 156 # Values are (capture stderr, capture subprocesses, apply labels).
157 157 self._bufferstates = []
158 158 # When a buffer is active, defines whether we are expanding labels.
159 159 # This exists to prevent an extra list lookup.
160 160 self._bufferapplylabels = None
161 161 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
162 162 self._reportuntrusted = True
163 163 self._ocfg = config.config() # overlay
164 164 self._tcfg = config.config() # trusted
165 165 self._ucfg = config.config() # untrusted
166 166 self._trustusers = set()
167 167 self._trustgroups = set()
168 168 self.callhooks = True
169 169 # Insecure server connections requested.
170 170 self.insecureconnections = False
171 171 # Blocked time
172 172 self.logblockedtimes = False
173 173 # color mode: see mercurial/color.py for possible value
174 174 self._colormode = None
175 175 self._terminfoparams = {}
176 176 self._styles = {}
177 177
178 178 if src:
179 179 self._exithandlers = src._exithandlers
180 180 self.fout = src.fout
181 181 self.ferr = src.ferr
182 182 self.fin = src.fin
183 183 self.pageractive = src.pageractive
184 184 self._disablepager = src._disablepager
185 185
186 186 self._tcfg = src._tcfg.copy()
187 187 self._ucfg = src._ucfg.copy()
188 188 self._ocfg = src._ocfg.copy()
189 189 self._trustusers = src._trustusers.copy()
190 190 self._trustgroups = src._trustgroups.copy()
191 191 self.environ = src.environ
192 192 self.callhooks = src.callhooks
193 193 self.insecureconnections = src.insecureconnections
194 194 self._colormode = src._colormode
195 195 self._terminfoparams = src._terminfoparams.copy()
196 196 self._styles = src._styles.copy()
197 197
198 198 self.fixconfig()
199 199
200 200 self.httppasswordmgrdb = src.httppasswordmgrdb
201 201 self._blockedtimes = src._blockedtimes
202 202 else:
203 203 self.fout = util.stdout
204 204 self.ferr = util.stderr
205 205 self.fin = util.stdin
206 206 self.pageractive = False
207 207 self._disablepager = False
208 208
209 209 # shared read-only environment
210 210 self.environ = encoding.environ
211 211
212 212 self.httppasswordmgrdb = httppasswordmgrdbproxy()
213 213 self._blockedtimes = collections.defaultdict(int)
214 214
215 215 allowed = self.configlist('experimental', 'exportableenviron')
216 216 if '*' in allowed:
217 217 self._exportableenviron = self.environ
218 218 else:
219 219 self._exportableenviron = {}
220 220 for k in allowed:
221 221 if k in self.environ:
222 222 self._exportableenviron[k] = self.environ[k]
223 223
224 224 @classmethod
225 225 def load(cls):
226 226 """Create a ui and load global and user configs"""
227 227 u = cls()
228 228 # we always trust global config files and environment variables
229 229 for t, f in rcutil.rccomponents():
230 230 if t == 'path':
231 231 u.readconfig(f, trust=True)
232 232 elif t == 'items':
233 233 sections = set()
234 234 for section, name, value, source in f:
235 235 # do not set u._ocfg
236 236 # XXX clean this up once immutable config object is a thing
237 237 u._tcfg.set(section, name, value, source)
238 238 u._ucfg.set(section, name, value, source)
239 239 sections.add(section)
240 240 for section in sections:
241 241 u.fixconfig(section=section)
242 242 else:
243 243 raise error.ProgrammingError('unknown rctype: %s' % t)
244 244 return u
245 245
246 246 def copy(self):
247 247 return self.__class__(self)
248 248
249 249 def resetstate(self):
250 250 """Clear internal state that shouldn't persist across commands"""
251 251 if self._progbar:
252 252 self._progbar.resetstate() # reset last-print time of progress bar
253 253 self.httppasswordmgrdb = httppasswordmgrdbproxy()
254 254
255 255 @contextlib.contextmanager
256 256 def timeblockedsection(self, key):
257 257 # this is open-coded below - search for timeblockedsection to find them
258 258 starttime = util.timer()
259 259 try:
260 260 yield
261 261 finally:
262 262 self._blockedtimes[key + '_blocked'] += \
263 263 (util.timer() - starttime) * 1000
264 264
265 265 def formatter(self, topic, opts):
266 return formatter.formatter(self, topic, opts)
266 return formatter.formatter(self, self, topic, opts)
267 267
268 268 def _trusted(self, fp, f):
269 269 st = util.fstat(fp)
270 270 if util.isowner(st):
271 271 return True
272 272
273 273 tusers, tgroups = self._trustusers, self._trustgroups
274 274 if '*' in tusers or '*' in tgroups:
275 275 return True
276 276
277 277 user = util.username(st.st_uid)
278 278 group = util.groupname(st.st_gid)
279 279 if user in tusers or group in tgroups or user == util.username():
280 280 return True
281 281
282 282 if self._reportuntrusted:
283 283 self.warn(_('not trusting file %s from untrusted '
284 284 'user %s, group %s\n') % (f, user, group))
285 285 return False
286 286
287 287 def readconfig(self, filename, root=None, trust=False,
288 288 sections=None, remap=None):
289 289 try:
290 290 fp = open(filename, u'rb')
291 291 except IOError:
292 292 if not sections: # ignore unless we were looking for something
293 293 return
294 294 raise
295 295
296 296 cfg = config.config()
297 297 trusted = sections or trust or self._trusted(fp, filename)
298 298
299 299 try:
300 300 cfg.read(filename, fp, sections=sections, remap=remap)
301 301 fp.close()
302 302 except error.ConfigError as inst:
303 303 if trusted:
304 304 raise
305 305 self.warn(_("ignored: %s\n") % str(inst))
306 306
307 307 if self.plain():
308 308 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
309 309 'logtemplate', 'statuscopies', 'style',
310 310 'traceback', 'verbose'):
311 311 if k in cfg['ui']:
312 312 del cfg['ui'][k]
313 313 for k, v in cfg.items('defaults'):
314 314 del cfg['defaults'][k]
315 315 for k, v in cfg.items('commands'):
316 316 del cfg['commands'][k]
317 317 # Don't remove aliases from the configuration if in the exceptionlist
318 318 if self.plain('alias'):
319 319 for k, v in cfg.items('alias'):
320 320 del cfg['alias'][k]
321 321 if self.plain('revsetalias'):
322 322 for k, v in cfg.items('revsetalias'):
323 323 del cfg['revsetalias'][k]
324 324 if self.plain('templatealias'):
325 325 for k, v in cfg.items('templatealias'):
326 326 del cfg['templatealias'][k]
327 327
328 328 if trusted:
329 329 self._tcfg.update(cfg)
330 330 self._tcfg.update(self._ocfg)
331 331 self._ucfg.update(cfg)
332 332 self._ucfg.update(self._ocfg)
333 333
334 334 if root is None:
335 335 root = os.path.expanduser('~')
336 336 self.fixconfig(root=root)
337 337
338 338 def fixconfig(self, root=None, section=None):
339 339 if section in (None, 'paths'):
340 340 # expand vars and ~
341 341 # translate paths relative to root (or home) into absolute paths
342 342 root = root or pycompat.getcwd()
343 343 for c in self._tcfg, self._ucfg, self._ocfg:
344 344 for n, p in c.items('paths'):
345 345 # Ignore sub-options.
346 346 if ':' in n:
347 347 continue
348 348 if not p:
349 349 continue
350 350 if '%%' in p:
351 351 s = self.configsource('paths', n) or 'none'
352 352 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
353 353 % (n, p, s))
354 354 p = p.replace('%%', '%')
355 355 p = util.expandpath(p)
356 356 if not util.hasscheme(p) and not os.path.isabs(p):
357 357 p = os.path.normpath(os.path.join(root, p))
358 358 c.set("paths", n, p)
359 359
360 360 if section in (None, 'ui'):
361 361 # update ui options
362 362 self.debugflag = self.configbool('ui', 'debug')
363 363 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
364 364 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
365 365 if self.verbose and self.quiet:
366 366 self.quiet = self.verbose = False
367 367 self._reportuntrusted = self.debugflag or self.configbool("ui",
368 368 "report_untrusted", True)
369 369 self.tracebackflag = self.configbool('ui', 'traceback', False)
370 370 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
371 371
372 372 if section in (None, 'trusted'):
373 373 # update trust information
374 374 self._trustusers.update(self.configlist('trusted', 'users'))
375 375 self._trustgroups.update(self.configlist('trusted', 'groups'))
376 376
377 377 def backupconfig(self, section, item):
378 378 return (self._ocfg.backup(section, item),
379 379 self._tcfg.backup(section, item),
380 380 self._ucfg.backup(section, item),)
381 381 def restoreconfig(self, data):
382 382 self._ocfg.restore(data[0])
383 383 self._tcfg.restore(data[1])
384 384 self._ucfg.restore(data[2])
385 385
386 386 def setconfig(self, section, name, value, source=''):
387 387 for cfg in (self._ocfg, self._tcfg, self._ucfg):
388 388 cfg.set(section, name, value, source)
389 389 self.fixconfig(section=section)
390 390
391 391 def _data(self, untrusted):
392 392 return untrusted and self._ucfg or self._tcfg
393 393
394 394 def configsource(self, section, name, untrusted=False):
395 395 return self._data(untrusted).source(section, name)
396 396
397 397 def config(self, section, name, default=None, untrusted=False):
398 398 if isinstance(name, list):
399 399 alternates = name
400 400 else:
401 401 alternates = [name]
402 402
403 403 for n in alternates:
404 404 value = self._data(untrusted).get(section, n, None)
405 405 if value is not None:
406 406 name = n
407 407 break
408 408 else:
409 409 value = default
410 410
411 411 if self.debugflag and not untrusted and self._reportuntrusted:
412 412 for n in alternates:
413 413 uvalue = self._ucfg.get(section, n)
414 414 if uvalue is not None and uvalue != value:
415 415 self.debug("ignoring untrusted configuration option "
416 416 "%s.%s = %s\n" % (section, n, uvalue))
417 417 return value
418 418
419 419 def configsuboptions(self, section, name, default=None, untrusted=False):
420 420 """Get a config option and all sub-options.
421 421
422 422 Some config options have sub-options that are declared with the
423 423 format "key:opt = value". This method is used to return the main
424 424 option and all its declared sub-options.
425 425
426 426 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
427 427 is a dict of defined sub-options where keys and values are strings.
428 428 """
429 429 data = self._data(untrusted)
430 430 main = data.get(section, name, default)
431 431 if self.debugflag and not untrusted and self._reportuntrusted:
432 432 uvalue = self._ucfg.get(section, name)
433 433 if uvalue is not None and uvalue != main:
434 434 self.debug('ignoring untrusted configuration option '
435 435 '%s.%s = %s\n' % (section, name, uvalue))
436 436
437 437 sub = {}
438 438 prefix = '%s:' % name
439 439 for k, v in data.items(section):
440 440 if k.startswith(prefix):
441 441 sub[k[len(prefix):]] = v
442 442
443 443 if self.debugflag and not untrusted and self._reportuntrusted:
444 444 for k, v in sub.items():
445 445 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
446 446 if uvalue is not None and uvalue != v:
447 447 self.debug('ignoring untrusted configuration option '
448 448 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
449 449
450 450 return main, sub
451 451
452 452 def configpath(self, section, name, default=None, untrusted=False):
453 453 'get a path config item, expanded relative to repo root or config file'
454 454 v = self.config(section, name, default, untrusted)
455 455 if v is None:
456 456 return None
457 457 if not os.path.isabs(v) or "://" not in v:
458 458 src = self.configsource(section, name, untrusted)
459 459 if ':' in src:
460 460 base = os.path.dirname(src.rsplit(':')[0])
461 461 v = os.path.join(base, os.path.expanduser(v))
462 462 return v
463 463
464 464 def configbool(self, section, name, default=False, untrusted=False):
465 465 """parse a configuration element as a boolean
466 466
467 467 >>> u = ui(); s = 'foo'
468 468 >>> u.setconfig(s, 'true', 'yes')
469 469 >>> u.configbool(s, 'true')
470 470 True
471 471 >>> u.setconfig(s, 'false', 'no')
472 472 >>> u.configbool(s, 'false')
473 473 False
474 474 >>> u.configbool(s, 'unknown')
475 475 False
476 476 >>> u.configbool(s, 'unknown', True)
477 477 True
478 478 >>> u.setconfig(s, 'invalid', 'somevalue')
479 479 >>> u.configbool(s, 'invalid')
480 480 Traceback (most recent call last):
481 481 ...
482 482 ConfigError: foo.invalid is not a boolean ('somevalue')
483 483 """
484 484
485 485 v = self.config(section, name, None, untrusted)
486 486 if v is None:
487 487 return default
488 488 if isinstance(v, bool):
489 489 return v
490 490 b = util.parsebool(v)
491 491 if b is None:
492 492 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
493 493 % (section, name, v))
494 494 return b
495 495
496 496 def configwith(self, convert, section, name, default=None,
497 497 desc=None, untrusted=False):
498 498 """parse a configuration element with a conversion function
499 499
500 500 >>> u = ui(); s = 'foo'
501 501 >>> u.setconfig(s, 'float1', '42')
502 502 >>> u.configwith(float, s, 'float1')
503 503 42.0
504 504 >>> u.setconfig(s, 'float2', '-4.25')
505 505 >>> u.configwith(float, s, 'float2')
506 506 -4.25
507 507 >>> u.configwith(float, s, 'unknown', 7)
508 508 7
509 509 >>> u.setconfig(s, 'invalid', 'somevalue')
510 510 >>> u.configwith(float, s, 'invalid')
511 511 Traceback (most recent call last):
512 512 ...
513 513 ConfigError: foo.invalid is not a valid float ('somevalue')
514 514 >>> u.configwith(float, s, 'invalid', desc='womble')
515 515 Traceback (most recent call last):
516 516 ...
517 517 ConfigError: foo.invalid is not a valid womble ('somevalue')
518 518 """
519 519
520 520 v = self.config(section, name, None, untrusted)
521 521 if v is None:
522 522 return default
523 523 try:
524 524 return convert(v)
525 525 except (ValueError, error.ParseError):
526 526 if desc is None:
527 527 desc = convert.__name__
528 528 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
529 529 % (section, name, desc, v))
530 530
531 531 def configint(self, section, name, default=None, untrusted=False):
532 532 """parse a configuration element as an integer
533 533
534 534 >>> u = ui(); s = 'foo'
535 535 >>> u.setconfig(s, 'int1', '42')
536 536 >>> u.configint(s, 'int1')
537 537 42
538 538 >>> u.setconfig(s, 'int2', '-42')
539 539 >>> u.configint(s, 'int2')
540 540 -42
541 541 >>> u.configint(s, 'unknown', 7)
542 542 7
543 543 >>> u.setconfig(s, 'invalid', 'somevalue')
544 544 >>> u.configint(s, 'invalid')
545 545 Traceback (most recent call last):
546 546 ...
547 547 ConfigError: foo.invalid is not a valid integer ('somevalue')
548 548 """
549 549
550 550 return self.configwith(int, section, name, default, 'integer',
551 551 untrusted)
552 552
553 553 def configbytes(self, section, name, default=0, untrusted=False):
554 554 """parse a configuration element as a quantity in bytes
555 555
556 556 Units can be specified as b (bytes), k or kb (kilobytes), m or
557 557 mb (megabytes), g or gb (gigabytes).
558 558
559 559 >>> u = ui(); s = 'foo'
560 560 >>> u.setconfig(s, 'val1', '42')
561 561 >>> u.configbytes(s, 'val1')
562 562 42
563 563 >>> u.setconfig(s, 'val2', '42.5 kb')
564 564 >>> u.configbytes(s, 'val2')
565 565 43520
566 566 >>> u.configbytes(s, 'unknown', '7 MB')
567 567 7340032
568 568 >>> u.setconfig(s, 'invalid', 'somevalue')
569 569 >>> u.configbytes(s, 'invalid')
570 570 Traceback (most recent call last):
571 571 ...
572 572 ConfigError: foo.invalid is not a byte quantity ('somevalue')
573 573 """
574 574
575 575 value = self.config(section, name, None, untrusted)
576 576 if value is None:
577 577 if not isinstance(default, str):
578 578 return default
579 579 value = default
580 580 try:
581 581 return util.sizetoint(value)
582 582 except error.ParseError:
583 583 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
584 584 % (section, name, value))
585 585
586 586 def configlist(self, section, name, default=None, untrusted=False):
587 587 """parse a configuration element as a list of comma/space separated
588 588 strings
589 589
590 590 >>> u = ui(); s = 'foo'
591 591 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
592 592 >>> u.configlist(s, 'list1')
593 593 ['this', 'is', 'a small', 'test']
594 594 """
595 595 # default is not always a list
596 596 if isinstance(default, bytes):
597 597 default = config.parselist(default)
598 598 return self.configwith(config.parselist, section, name, default or [],
599 599 'list', untrusted)
600 600
601 601 def configdate(self, section, name, default=None, untrusted=False):
602 602 """parse a configuration element as a tuple of ints
603 603
604 604 >>> u = ui(); s = 'foo'
605 605 >>> u.setconfig(s, 'date', '0 0')
606 606 >>> u.configdate(s, 'date')
607 607 (0, 0)
608 608 """
609 609 if self.config(section, name, default, untrusted):
610 610 return self.configwith(util.parsedate, section, name, default,
611 611 'date', untrusted)
612 612 return default
613 613
614 614 def hasconfig(self, section, name, untrusted=False):
615 615 return self._data(untrusted).hasitem(section, name)
616 616
617 617 def has_section(self, section, untrusted=False):
618 618 '''tell whether section exists in config.'''
619 619 return section in self._data(untrusted)
620 620
621 621 def configitems(self, section, untrusted=False, ignoresub=False):
622 622 items = self._data(untrusted).items(section)
623 623 if ignoresub:
624 624 newitems = {}
625 625 for k, v in items:
626 626 if ':' not in k:
627 627 newitems[k] = v
628 628 items = newitems.items()
629 629 if self.debugflag and not untrusted and self._reportuntrusted:
630 630 for k, v in self._ucfg.items(section):
631 631 if self._tcfg.get(section, k) != v:
632 632 self.debug("ignoring untrusted configuration option "
633 633 "%s.%s = %s\n" % (section, k, v))
634 634 return items
635 635
636 636 def walkconfig(self, untrusted=False):
637 637 cfg = self._data(untrusted)
638 638 for section in cfg.sections():
639 639 for name, value in self.configitems(section, untrusted):
640 640 yield section, name, value
641 641
642 642 def plain(self, feature=None):
643 643 '''is plain mode active?
644 644
645 645 Plain mode means that all configuration variables which affect
646 646 the behavior and output of Mercurial should be
647 647 ignored. Additionally, the output should be stable,
648 648 reproducible and suitable for use in scripts or applications.
649 649
650 650 The only way to trigger plain mode is by setting either the
651 651 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
652 652
653 653 The return value can either be
654 654 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
655 655 - True otherwise
656 656 '''
657 657 if ('HGPLAIN' not in encoding.environ and
658 658 'HGPLAINEXCEPT' not in encoding.environ):
659 659 return False
660 660 exceptions = encoding.environ.get('HGPLAINEXCEPT',
661 661 '').strip().split(',')
662 662 if feature and exceptions:
663 663 return feature not in exceptions
664 664 return True
665 665
666 666 def username(self):
667 667 """Return default username to be used in commits.
668 668
669 669 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
670 670 and stop searching if one of these is set.
671 671 If not found and ui.askusername is True, ask the user, else use
672 672 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
673 673 """
674 674 user = encoding.environ.get("HGUSER")
675 675 if user is None:
676 676 user = self.config("ui", ["username", "user"])
677 677 if user is not None:
678 678 user = os.path.expandvars(user)
679 679 if user is None:
680 680 user = encoding.environ.get("EMAIL")
681 681 if user is None and self.configbool("ui", "askusername"):
682 682 user = self.prompt(_("enter a commit username:"), default=None)
683 683 if user is None and not self.interactive():
684 684 try:
685 685 user = '%s@%s' % (util.getuser(), socket.getfqdn())
686 686 self.warn(_("no username found, using '%s' instead\n") % user)
687 687 except KeyError:
688 688 pass
689 689 if not user:
690 690 raise error.Abort(_('no username supplied'),
691 691 hint=_("use 'hg config --edit' "
692 692 'to set your username'))
693 693 if "\n" in user:
694 694 raise error.Abort(_("username %s contains a newline\n")
695 695 % repr(user))
696 696 return user
697 697
698 698 def shortuser(self, user):
699 699 """Return a short representation of a user name or email address."""
700 700 if not self.verbose:
701 701 user = util.shortuser(user)
702 702 return user
703 703
704 704 def expandpath(self, loc, default=None):
705 705 """Return repository location relative to cwd or from [paths]"""
706 706 try:
707 707 p = self.paths.getpath(loc)
708 708 if p:
709 709 return p.rawloc
710 710 except error.RepoError:
711 711 pass
712 712
713 713 if default:
714 714 try:
715 715 p = self.paths.getpath(default)
716 716 if p:
717 717 return p.rawloc
718 718 except error.RepoError:
719 719 pass
720 720
721 721 return loc
722 722
723 723 @util.propertycache
724 724 def paths(self):
725 725 return paths(self)
726 726
727 727 def pushbuffer(self, error=False, subproc=False, labeled=False):
728 728 """install a buffer to capture standard output of the ui object
729 729
730 730 If error is True, the error output will be captured too.
731 731
732 732 If subproc is True, output from subprocesses (typically hooks) will be
733 733 captured too.
734 734
735 735 If labeled is True, any labels associated with buffered
736 736 output will be handled. By default, this has no effect
737 737 on the output returned, but extensions and GUI tools may
738 738 handle this argument and returned styled output. If output
739 739 is being buffered so it can be captured and parsed or
740 740 processed, labeled should not be set to True.
741 741 """
742 742 self._buffers.append([])
743 743 self._bufferstates.append((error, subproc, labeled))
744 744 self._bufferapplylabels = labeled
745 745
746 746 def popbuffer(self):
747 747 '''pop the last buffer and return the buffered output'''
748 748 self._bufferstates.pop()
749 749 if self._bufferstates:
750 750 self._bufferapplylabels = self._bufferstates[-1][2]
751 751 else:
752 752 self._bufferapplylabels = None
753 753
754 754 return "".join(self._buffers.pop())
755 755
756 756 def write(self, *args, **opts):
757 757 '''write args to output
758 758
759 759 By default, this method simply writes to the buffer or stdout.
760 760 Color mode can be set on the UI class to have the output decorated
761 761 with color modifier before being written to stdout.
762 762
763 763 The color used is controlled by an optional keyword argument, "label".
764 764 This should be a string containing label names separated by space.
765 765 Label names take the form of "topic.type". For example, ui.debug()
766 766 issues a label of "ui.debug".
767 767
768 768 When labeling output for a specific command, a label of
769 769 "cmdname.type" is recommended. For example, status issues
770 770 a label of "status.modified" for modified files.
771 771 '''
772 772 if self._buffers and not opts.get('prompt', False):
773 773 if self._bufferapplylabels:
774 774 label = opts.get('label', '')
775 775 self._buffers[-1].extend(self.label(a, label) for a in args)
776 776 else:
777 777 self._buffers[-1].extend(args)
778 778 elif self._colormode == 'win32':
779 779 # windows color printing is its own can of crab, defer to
780 780 # the color module and that is it.
781 781 color.win32print(self, self._write, *args, **opts)
782 782 else:
783 783 msgs = args
784 784 if self._colormode is not None:
785 785 label = opts.get('label', '')
786 786 msgs = [self.label(a, label) for a in args]
787 787 self._write(*msgs, **opts)
788 788
789 789 def _write(self, *msgs, **opts):
790 790 self._progclear()
791 791 # opencode timeblockedsection because this is a critical path
792 792 starttime = util.timer()
793 793 try:
794 794 for a in msgs:
795 795 self.fout.write(a)
796 796 except IOError as err:
797 797 raise error.StdioError(err)
798 798 finally:
799 799 self._blockedtimes['stdio_blocked'] += \
800 800 (util.timer() - starttime) * 1000
801 801
802 802 def write_err(self, *args, **opts):
803 803 self._progclear()
804 804 if self._bufferstates and self._bufferstates[-1][0]:
805 805 self.write(*args, **opts)
806 806 elif self._colormode == 'win32':
807 807 # windows color printing is its own can of crab, defer to
808 808 # the color module and that is it.
809 809 color.win32print(self, self._write_err, *args, **opts)
810 810 else:
811 811 msgs = args
812 812 if self._colormode is not None:
813 813 label = opts.get('label', '')
814 814 msgs = [self.label(a, label) for a in args]
815 815 self._write_err(*msgs, **opts)
816 816
817 817 def _write_err(self, *msgs, **opts):
818 818 try:
819 819 with self.timeblockedsection('stdio'):
820 820 if not getattr(self.fout, 'closed', False):
821 821 self.fout.flush()
822 822 for a in msgs:
823 823 self.ferr.write(a)
824 824 # stderr may be buffered under win32 when redirected to files,
825 825 # including stdout.
826 826 if not getattr(self.ferr, 'closed', False):
827 827 self.ferr.flush()
828 828 except IOError as inst:
829 829 raise error.StdioError(inst)
830 830
831 831 def flush(self):
832 832 # opencode timeblockedsection because this is a critical path
833 833 starttime = util.timer()
834 834 try:
835 835 try:
836 836 self.fout.flush()
837 837 except IOError as err:
838 838 raise error.StdioError(err)
839 839 finally:
840 840 try:
841 841 self.ferr.flush()
842 842 except IOError as err:
843 843 raise error.StdioError(err)
844 844 finally:
845 845 self._blockedtimes['stdio_blocked'] += \
846 846 (util.timer() - starttime) * 1000
847 847
848 848 def _isatty(self, fh):
849 849 if self.configbool('ui', 'nontty', False):
850 850 return False
851 851 return util.isatty(fh)
852 852
853 853 def disablepager(self):
854 854 self._disablepager = True
855 855
856 856 def pager(self, command):
857 857 """Start a pager for subsequent command output.
858 858
859 859 Commands which produce a long stream of output should call
860 860 this function to activate the user's preferred pagination
861 861 mechanism (which may be no pager). Calling this function
862 862 precludes any future use of interactive functionality, such as
863 863 prompting the user or activating curses.
864 864
865 865 Args:
866 866 command: The full, non-aliased name of the command. That is, "log"
867 867 not "history, "summary" not "summ", etc.
868 868 """
869 869 if (self._disablepager
870 870 or self.pageractive
871 871 or command in self.configlist('pager', 'ignore')
872 872 or not self.configbool('ui', 'paginate', True)
873 873 or not self.configbool('pager', 'attend-' + command, True)
874 874 # TODO: if we want to allow HGPLAINEXCEPT=pager,
875 875 # formatted() will need some adjustment.
876 876 or not self.formatted()
877 877 or self.plain()
878 878 # TODO: expose debugger-enabled on the UI object
879 879 or '--debugger' in pycompat.sysargv):
880 880 # We only want to paginate if the ui appears to be
881 881 # interactive, the user didn't say HGPLAIN or
882 882 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
883 883 return
884 884
885 885 pagercmd = self.config('pager', 'pager', rcutil.fallbackpager)
886 886 if not pagercmd:
887 887 return
888 888
889 889 pagerenv = {}
890 890 for name, value in rcutil.defaultpagerenv().items():
891 891 if name not in encoding.environ:
892 892 pagerenv[name] = value
893 893
894 894 self.debug('starting pager for command %r\n' % command)
895 895 self.flush()
896 896
897 897 wasformatted = self.formatted()
898 898 if util.safehasattr(signal, "SIGPIPE"):
899 899 signal.signal(signal.SIGPIPE, _catchterm)
900 900 if self._runpager(pagercmd, pagerenv):
901 901 self.pageractive = True
902 902 # Preserve the formatted-ness of the UI. This is important
903 903 # because we mess with stdout, which might confuse
904 904 # auto-detection of things being formatted.
905 905 self.setconfig('ui', 'formatted', wasformatted, 'pager')
906 906 self.setconfig('ui', 'interactive', False, 'pager')
907 907
908 908 # If pagermode differs from color.mode, reconfigure color now that
909 909 # pageractive is set.
910 910 cm = self._colormode
911 911 if cm != self.config('color', 'pagermode', cm):
912 912 color.setup(self)
913 913 else:
914 914 # If the pager can't be spawned in dispatch when --pager=on is
915 915 # given, don't try again when the command runs, to avoid a duplicate
916 916 # warning about a missing pager command.
917 917 self.disablepager()
918 918
919 919 def _runpager(self, command, env=None):
920 920 """Actually start the pager and set up file descriptors.
921 921
922 922 This is separate in part so that extensions (like chg) can
923 923 override how a pager is invoked.
924 924 """
925 925 if command == 'cat':
926 926 # Save ourselves some work.
927 927 return False
928 928 # If the command doesn't contain any of these characters, we
929 929 # assume it's a binary and exec it directly. This means for
930 930 # simple pager command configurations, we can degrade
931 931 # gracefully and tell the user about their broken pager.
932 932 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
933 933
934 934 if pycompat.osname == 'nt' and not shell:
935 935 # Window's built-in `more` cannot be invoked with shell=False, but
936 936 # its `more.com` can. Hide this implementation detail from the
937 937 # user so we can also get sane bad PAGER behavior. MSYS has
938 938 # `more.exe`, so do a cmd.exe style resolution of the executable to
939 939 # determine which one to use.
940 940 fullcmd = util.findexe(command)
941 941 if not fullcmd:
942 942 self.warn(_("missing pager command '%s', skipping pager\n")
943 943 % command)
944 944 return False
945 945
946 946 command = fullcmd
947 947
948 948 try:
949 949 pager = subprocess.Popen(
950 950 command, shell=shell, bufsize=-1,
951 951 close_fds=util.closefds, stdin=subprocess.PIPE,
952 952 stdout=util.stdout, stderr=util.stderr,
953 953 env=util.shellenviron(env))
954 954 except OSError as e:
955 955 if e.errno == errno.ENOENT and not shell:
956 956 self.warn(_("missing pager command '%s', skipping pager\n")
957 957 % command)
958 958 return False
959 959 raise
960 960
961 961 # back up original file descriptors
962 962 stdoutfd = os.dup(util.stdout.fileno())
963 963 stderrfd = os.dup(util.stderr.fileno())
964 964
965 965 os.dup2(pager.stdin.fileno(), util.stdout.fileno())
966 966 if self._isatty(util.stderr):
967 967 os.dup2(pager.stdin.fileno(), util.stderr.fileno())
968 968
969 969 @self.atexit
970 970 def killpager():
971 971 if util.safehasattr(signal, "SIGINT"):
972 972 signal.signal(signal.SIGINT, signal.SIG_IGN)
973 973 # restore original fds, closing pager.stdin copies in the process
974 974 os.dup2(stdoutfd, util.stdout.fileno())
975 975 os.dup2(stderrfd, util.stderr.fileno())
976 976 pager.stdin.close()
977 977 pager.wait()
978 978
979 979 return True
980 980
981 981 def atexit(self, func, *args, **kwargs):
982 982 '''register a function to run after dispatching a request
983 983
984 984 Handlers do not stay registered across request boundaries.'''
985 985 self._exithandlers.append((func, args, kwargs))
986 986 return func
987 987
988 988 def interface(self, feature):
989 989 """what interface to use for interactive console features?
990 990
991 991 The interface is controlled by the value of `ui.interface` but also by
992 992 the value of feature-specific configuration. For example:
993 993
994 994 ui.interface.histedit = text
995 995 ui.interface.chunkselector = curses
996 996
997 997 Here the features are "histedit" and "chunkselector".
998 998
999 999 The configuration above means that the default interfaces for commands
1000 1000 is curses, the interface for histedit is text and the interface for
1001 1001 selecting chunk is crecord (the best curses interface available).
1002 1002
1003 1003 Consider the following example:
1004 1004 ui.interface = curses
1005 1005 ui.interface.histedit = text
1006 1006
1007 1007 Then histedit will use the text interface and chunkselector will use
1008 1008 the default curses interface (crecord at the moment).
1009 1009 """
1010 1010 alldefaults = frozenset(["text", "curses"])
1011 1011
1012 1012 featureinterfaces = {
1013 1013 "chunkselector": [
1014 1014 "text",
1015 1015 "curses",
1016 1016 ]
1017 1017 }
1018 1018
1019 1019 # Feature-specific interface
1020 1020 if feature not in featureinterfaces.keys():
1021 1021 # Programming error, not user error
1022 1022 raise ValueError("Unknown feature requested %s" % feature)
1023 1023
1024 1024 availableinterfaces = frozenset(featureinterfaces[feature])
1025 1025 if alldefaults > availableinterfaces:
1026 1026 # Programming error, not user error. We need a use case to
1027 1027 # define the right thing to do here.
1028 1028 raise ValueError(
1029 1029 "Feature %s does not handle all default interfaces" %
1030 1030 feature)
1031 1031
1032 1032 if self.plain():
1033 1033 return "text"
1034 1034
1035 1035 # Default interface for all the features
1036 1036 defaultinterface = "text"
1037 1037 i = self.config("ui", "interface", None)
1038 1038 if i in alldefaults:
1039 1039 defaultinterface = i
1040 1040
1041 1041 choseninterface = defaultinterface
1042 1042 f = self.config("ui", "interface.%s" % feature, None)
1043 1043 if f in availableinterfaces:
1044 1044 choseninterface = f
1045 1045
1046 1046 if i is not None and defaultinterface != i:
1047 1047 if f is not None:
1048 1048 self.warn(_("invalid value for ui.interface: %s\n") %
1049 1049 (i,))
1050 1050 else:
1051 1051 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1052 1052 (i, choseninterface))
1053 1053 if f is not None and choseninterface != f:
1054 1054 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1055 1055 (feature, f, choseninterface))
1056 1056
1057 1057 return choseninterface
1058 1058
1059 1059 def interactive(self):
1060 1060 '''is interactive input allowed?
1061 1061
1062 1062 An interactive session is a session where input can be reasonably read
1063 1063 from `sys.stdin'. If this function returns false, any attempt to read
1064 1064 from stdin should fail with an error, unless a sensible default has been
1065 1065 specified.
1066 1066
1067 1067 Interactiveness is triggered by the value of the `ui.interactive'
1068 1068 configuration variable or - if it is unset - when `sys.stdin' points
1069 1069 to a terminal device.
1070 1070
1071 1071 This function refers to input only; for output, see `ui.formatted()'.
1072 1072 '''
1073 1073 i = self.configbool("ui", "interactive", None)
1074 1074 if i is None:
1075 1075 # some environments replace stdin without implementing isatty
1076 1076 # usually those are non-interactive
1077 1077 return self._isatty(self.fin)
1078 1078
1079 1079 return i
1080 1080
1081 1081 def termwidth(self):
1082 1082 '''how wide is the terminal in columns?
1083 1083 '''
1084 1084 if 'COLUMNS' in encoding.environ:
1085 1085 try:
1086 1086 return int(encoding.environ['COLUMNS'])
1087 1087 except ValueError:
1088 1088 pass
1089 1089 return scmutil.termsize(self)[0]
1090 1090
1091 1091 def formatted(self):
1092 1092 '''should formatted output be used?
1093 1093
1094 1094 It is often desirable to format the output to suite the output medium.
1095 1095 Examples of this are truncating long lines or colorizing messages.
1096 1096 However, this is not often not desirable when piping output into other
1097 1097 utilities, e.g. `grep'.
1098 1098
1099 1099 Formatted output is triggered by the value of the `ui.formatted'
1100 1100 configuration variable or - if it is unset - when `sys.stdout' points
1101 1101 to a terminal device. Please note that `ui.formatted' should be
1102 1102 considered an implementation detail; it is not intended for use outside
1103 1103 Mercurial or its extensions.
1104 1104
1105 1105 This function refers to output only; for input, see `ui.interactive()'.
1106 1106 This function always returns false when in plain mode, see `ui.plain()'.
1107 1107 '''
1108 1108 if self.plain():
1109 1109 return False
1110 1110
1111 1111 i = self.configbool("ui", "formatted", None)
1112 1112 if i is None:
1113 1113 # some environments replace stdout without implementing isatty
1114 1114 # usually those are non-interactive
1115 1115 return self._isatty(self.fout)
1116 1116
1117 1117 return i
1118 1118
1119 1119 def _readline(self, prompt=''):
1120 1120 if self._isatty(self.fin):
1121 1121 try:
1122 1122 # magically add command line editing support, where
1123 1123 # available
1124 1124 import readline
1125 1125 # force demandimport to really load the module
1126 1126 readline.read_history_file
1127 1127 # windows sometimes raises something other than ImportError
1128 1128 except Exception:
1129 1129 pass
1130 1130
1131 1131 # call write() so output goes through subclassed implementation
1132 1132 # e.g. color extension on Windows
1133 1133 self.write(prompt, prompt=True)
1134 1134
1135 1135 # instead of trying to emulate raw_input, swap (self.fin,
1136 1136 # self.fout) with (sys.stdin, sys.stdout)
1137 1137 oldin = sys.stdin
1138 1138 oldout = sys.stdout
1139 1139 sys.stdin = self.fin
1140 1140 sys.stdout = self.fout
1141 1141 # prompt ' ' must exist; otherwise readline may delete entire line
1142 1142 # - http://bugs.python.org/issue12833
1143 1143 with self.timeblockedsection('stdio'):
1144 1144 line = raw_input(' ')
1145 1145 sys.stdin = oldin
1146 1146 sys.stdout = oldout
1147 1147
1148 1148 # When stdin is in binary mode on Windows, it can cause
1149 1149 # raw_input() to emit an extra trailing carriage return
1150 1150 if pycompat.oslinesep == '\r\n' and line and line[-1] == '\r':
1151 1151 line = line[:-1]
1152 1152 return line
1153 1153
1154 1154 def prompt(self, msg, default="y"):
1155 1155 """Prompt user with msg, read response.
1156 1156 If ui is not interactive, the default is returned.
1157 1157 """
1158 1158 if not self.interactive():
1159 1159 self.write(msg, ' ', default or '', "\n")
1160 1160 return default
1161 1161 try:
1162 1162 r = self._readline(self.label(msg, 'ui.prompt'))
1163 1163 if not r:
1164 1164 r = default
1165 1165 if self.configbool('ui', 'promptecho'):
1166 1166 self.write(r, "\n")
1167 1167 return r
1168 1168 except EOFError:
1169 1169 raise error.ResponseExpected()
1170 1170
1171 1171 @staticmethod
1172 1172 def extractchoices(prompt):
1173 1173 """Extract prompt message and list of choices from specified prompt.
1174 1174
1175 1175 This returns tuple "(message, choices)", and "choices" is the
1176 1176 list of tuple "(response character, text without &)".
1177 1177
1178 1178 >>> ui.extractchoices("awake? $$ &Yes $$ &No")
1179 1179 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1180 1180 >>> ui.extractchoices("line\\nbreak? $$ &Yes $$ &No")
1181 1181 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1182 1182 >>> ui.extractchoices("want lots of $$money$$?$$Ye&s$$N&o")
1183 1183 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1184 1184 """
1185 1185
1186 1186 # Sadly, the prompt string may have been built with a filename
1187 1187 # containing "$$" so let's try to find the first valid-looking
1188 1188 # prompt to start parsing. Sadly, we also can't rely on
1189 1189 # choices containing spaces, ASCII, or basically anything
1190 1190 # except an ampersand followed by a character.
1191 1191 m = re.match(r'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1192 1192 msg = m.group(1)
1193 1193 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1194 1194 return (msg,
1195 1195 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
1196 1196 for s in choices])
1197 1197
1198 1198 def promptchoice(self, prompt, default=0):
1199 1199 """Prompt user with a message, read response, and ensure it matches
1200 1200 one of the provided choices. The prompt is formatted as follows:
1201 1201
1202 1202 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1203 1203
1204 1204 The index of the choice is returned. Responses are case
1205 1205 insensitive. If ui is not interactive, the default is
1206 1206 returned.
1207 1207 """
1208 1208
1209 1209 msg, choices = self.extractchoices(prompt)
1210 1210 resps = [r for r, t in choices]
1211 1211 while True:
1212 1212 r = self.prompt(msg, resps[default])
1213 1213 if r.lower() in resps:
1214 1214 return resps.index(r.lower())
1215 1215 self.write(_("unrecognized response\n"))
1216 1216
1217 1217 def getpass(self, prompt=None, default=None):
1218 1218 if not self.interactive():
1219 1219 return default
1220 1220 try:
1221 1221 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1222 1222 # disable getpass() only if explicitly specified. it's still valid
1223 1223 # to interact with tty even if fin is not a tty.
1224 1224 with self.timeblockedsection('stdio'):
1225 1225 if self.configbool('ui', 'nontty'):
1226 1226 l = self.fin.readline()
1227 1227 if not l:
1228 1228 raise EOFError
1229 1229 return l.rstrip('\n')
1230 1230 else:
1231 1231 return getpass.getpass('')
1232 1232 except EOFError:
1233 1233 raise error.ResponseExpected()
1234 1234 def status(self, *msg, **opts):
1235 1235 '''write status message to output (if ui.quiet is False)
1236 1236
1237 1237 This adds an output label of "ui.status".
1238 1238 '''
1239 1239 if not self.quiet:
1240 1240 opts[r'label'] = opts.get(r'label', '') + ' ui.status'
1241 1241 self.write(*msg, **opts)
1242 1242 def warn(self, *msg, **opts):
1243 1243 '''write warning message to output (stderr)
1244 1244
1245 1245 This adds an output label of "ui.warning".
1246 1246 '''
1247 1247 opts[r'label'] = opts.get(r'label', '') + ' ui.warning'
1248 1248 self.write_err(*msg, **opts)
1249 1249 def note(self, *msg, **opts):
1250 1250 '''write note to output (if ui.verbose is True)
1251 1251
1252 1252 This adds an output label of "ui.note".
1253 1253 '''
1254 1254 if self.verbose:
1255 1255 opts[r'label'] = opts.get(r'label', '') + ' ui.note'
1256 1256 self.write(*msg, **opts)
1257 1257 def debug(self, *msg, **opts):
1258 1258 '''write debug message to output (if ui.debugflag is True)
1259 1259
1260 1260 This adds an output label of "ui.debug".
1261 1261 '''
1262 1262 if self.debugflag:
1263 1263 opts[r'label'] = opts.get(r'label', '') + ' ui.debug'
1264 1264 self.write(*msg, **opts)
1265 1265
1266 1266 def edit(self, text, user, extra=None, editform=None, pending=None,
1267 1267 repopath=None):
1268 1268 extra_defaults = {
1269 1269 'prefix': 'editor',
1270 1270 'suffix': '.txt',
1271 1271 }
1272 1272 if extra is not None:
1273 1273 extra_defaults.update(extra)
1274 1274 extra = extra_defaults
1275 1275
1276 1276 rdir = None
1277 1277 if self.configbool('experimental', 'editortmpinhg'):
1278 1278 rdir = repopath
1279 1279 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1280 1280 suffix=extra['suffix'],
1281 1281 dir=rdir)
1282 1282 try:
1283 1283 f = os.fdopen(fd, r'wb')
1284 1284 f.write(util.tonativeeol(text))
1285 1285 f.close()
1286 1286
1287 1287 environ = {'HGUSER': user}
1288 1288 if 'transplant_source' in extra:
1289 1289 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1290 1290 for label in ('intermediate-source', 'source', 'rebase_source'):
1291 1291 if label in extra:
1292 1292 environ.update({'HGREVISION': extra[label]})
1293 1293 break
1294 1294 if editform:
1295 1295 environ.update({'HGEDITFORM': editform})
1296 1296 if pending:
1297 1297 environ.update({'HG_PENDING': pending})
1298 1298
1299 1299 editor = self.geteditor()
1300 1300
1301 1301 self.system("%s \"%s\"" % (editor, name),
1302 1302 environ=environ,
1303 1303 onerr=error.Abort, errprefix=_("edit failed"),
1304 1304 blockedtag='editor')
1305 1305
1306 1306 f = open(name, r'rb')
1307 1307 t = util.fromnativeeol(f.read())
1308 1308 f.close()
1309 1309 finally:
1310 1310 os.unlink(name)
1311 1311
1312 1312 return t
1313 1313
1314 1314 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1315 1315 blockedtag=None):
1316 1316 '''execute shell command with appropriate output stream. command
1317 1317 output will be redirected if fout is not stdout.
1318 1318
1319 1319 if command fails and onerr is None, return status, else raise onerr
1320 1320 object as exception.
1321 1321 '''
1322 1322 if blockedtag is None:
1323 1323 # Long cmds tend to be because of an absolute path on cmd. Keep
1324 1324 # the tail end instead
1325 1325 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1326 1326 blockedtag = 'unknown_system_' + cmdsuffix
1327 1327 out = self.fout
1328 1328 if any(s[1] for s in self._bufferstates):
1329 1329 out = self
1330 1330 with self.timeblockedsection(blockedtag):
1331 1331 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1332 1332 if rc and onerr:
1333 1333 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1334 1334 util.explainexit(rc)[0])
1335 1335 if errprefix:
1336 1336 errmsg = '%s: %s' % (errprefix, errmsg)
1337 1337 raise onerr(errmsg)
1338 1338 return rc
1339 1339
1340 1340 def _runsystem(self, cmd, environ, cwd, out):
1341 1341 """actually execute the given shell command (can be overridden by
1342 1342 extensions like chg)"""
1343 1343 return util.system(cmd, environ=environ, cwd=cwd, out=out)
1344 1344
1345 1345 def traceback(self, exc=None, force=False):
1346 1346 '''print exception traceback if traceback printing enabled or forced.
1347 1347 only to call in exception handler. returns true if traceback
1348 1348 printed.'''
1349 1349 if self.tracebackflag or force:
1350 1350 if exc is None:
1351 1351 exc = sys.exc_info()
1352 1352 cause = getattr(exc[1], 'cause', None)
1353 1353
1354 1354 if cause is not None:
1355 1355 causetb = traceback.format_tb(cause[2])
1356 1356 exctb = traceback.format_tb(exc[2])
1357 1357 exconly = traceback.format_exception_only(cause[0], cause[1])
1358 1358
1359 1359 # exclude frame where 'exc' was chained and rethrown from exctb
1360 1360 self.write_err('Traceback (most recent call last):\n',
1361 1361 ''.join(exctb[:-1]),
1362 1362 ''.join(causetb),
1363 1363 ''.join(exconly))
1364 1364 else:
1365 1365 output = traceback.format_exception(exc[0], exc[1], exc[2])
1366 1366 data = r''.join(output)
1367 1367 if pycompat.ispy3:
1368 1368 enc = pycompat.sysstr(encoding.encoding)
1369 1369 data = data.encode(enc, errors=r'replace')
1370 1370 self.write_err(data)
1371 1371 return self.tracebackflag or force
1372 1372
1373 1373 def geteditor(self):
1374 1374 '''return editor to use'''
1375 1375 if pycompat.sysplatform == 'plan9':
1376 1376 # vi is the MIPS instruction simulator on Plan 9. We
1377 1377 # instead default to E to plumb commit messages to
1378 1378 # avoid confusion.
1379 1379 editor = 'E'
1380 1380 else:
1381 1381 editor = 'vi'
1382 1382 return (encoding.environ.get("HGEDITOR") or
1383 1383 self.config("ui", "editor", editor))
1384 1384
1385 1385 @util.propertycache
1386 1386 def _progbar(self):
1387 1387 """setup the progbar singleton to the ui object"""
1388 1388 if (self.quiet or self.debugflag
1389 1389 or self.configbool('progress', 'disable', False)
1390 1390 or not progress.shouldprint(self)):
1391 1391 return None
1392 1392 return getprogbar(self)
1393 1393
1394 1394 def _progclear(self):
1395 1395 """clear progress bar output if any. use it before any output"""
1396 1396 if '_progbar' not in vars(self): # nothing loaded yet
1397 1397 return
1398 1398 if self._progbar is not None and self._progbar.printed:
1399 1399 self._progbar.clear()
1400 1400
1401 1401 def progress(self, topic, pos, item="", unit="", total=None):
1402 1402 '''show a progress message
1403 1403
1404 1404 By default a textual progress bar will be displayed if an operation
1405 1405 takes too long. 'topic' is the current operation, 'item' is a
1406 1406 non-numeric marker of the current position (i.e. the currently
1407 1407 in-process file), 'pos' is the current numeric position (i.e.
1408 1408 revision, bytes, etc.), unit is a corresponding unit label,
1409 1409 and total is the highest expected pos.
1410 1410
1411 1411 Multiple nested topics may be active at a time.
1412 1412
1413 1413 All topics should be marked closed by setting pos to None at
1414 1414 termination.
1415 1415 '''
1416 1416 if self._progbar is not None:
1417 1417 self._progbar.progress(topic, pos, item=item, unit=unit,
1418 1418 total=total)
1419 1419 if pos is None or not self.configbool('progress', 'debug'):
1420 1420 return
1421 1421
1422 1422 if unit:
1423 1423 unit = ' ' + unit
1424 1424 if item:
1425 1425 item = ' ' + item
1426 1426
1427 1427 if total:
1428 1428 pct = 100.0 * pos / total
1429 1429 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
1430 1430 % (topic, item, pos, total, unit, pct))
1431 1431 else:
1432 1432 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
1433 1433
1434 1434 def log(self, service, *msg, **opts):
1435 1435 '''hook for logging facility extensions
1436 1436
1437 1437 service should be a readily-identifiable subsystem, which will
1438 1438 allow filtering.
1439 1439
1440 1440 *msg should be a newline-terminated format string to log, and
1441 1441 then any values to %-format into that format string.
1442 1442
1443 1443 **opts currently has no defined meanings.
1444 1444 '''
1445 1445
1446 1446 def label(self, msg, label):
1447 1447 '''style msg based on supplied label
1448 1448
1449 1449 If some color mode is enabled, this will add the necessary control
1450 1450 characters to apply such color. In addition, 'debug' color mode adds
1451 1451 markup showing which label affects a piece of text.
1452 1452
1453 1453 ui.write(s, 'label') is equivalent to
1454 1454 ui.write(ui.label(s, 'label')).
1455 1455 '''
1456 1456 if self._colormode is not None:
1457 1457 return color.colorlabel(self, msg, label)
1458 1458 return msg
1459 1459
1460 1460 def develwarn(self, msg, stacklevel=1, config=None):
1461 1461 """issue a developer warning message
1462 1462
1463 1463 Use 'stacklevel' to report the offender some layers further up in the
1464 1464 stack.
1465 1465 """
1466 1466 if not self.configbool('devel', 'all-warnings'):
1467 1467 if config is not None and not self.configbool('devel', config):
1468 1468 return
1469 1469 msg = 'devel-warn: ' + msg
1470 1470 stacklevel += 1 # get in develwarn
1471 1471 if self.tracebackflag:
1472 1472 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1473 1473 self.log('develwarn', '%s at:\n%s' %
1474 1474 (msg, ''.join(util.getstackframes(stacklevel))))
1475 1475 else:
1476 1476 curframe = inspect.currentframe()
1477 1477 calframe = inspect.getouterframes(curframe, 2)
1478 1478 self.write_err('%s at: %s:%s (%s)\n'
1479 1479 % ((msg,) + calframe[stacklevel][1:4]))
1480 1480 self.log('develwarn', '%s at: %s:%s (%s)\n',
1481 1481 msg, *calframe[stacklevel][1:4])
1482 1482 curframe = calframe = None # avoid cycles
1483 1483
1484 1484 def deprecwarn(self, msg, version):
1485 1485 """issue a deprecation warning
1486 1486
1487 1487 - msg: message explaining what is deprecated and how to upgrade,
1488 1488 - version: last version where the API will be supported,
1489 1489 """
1490 1490 if not (self.configbool('devel', 'all-warnings')
1491 1491 or self.configbool('devel', 'deprec-warn')):
1492 1492 return
1493 1493 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1494 1494 " update your code.)") % version
1495 1495 self.develwarn(msg, stacklevel=2, config='deprec-warn')
1496 1496
1497 1497 def exportableenviron(self):
1498 1498 """The environment variables that are safe to export, e.g. through
1499 1499 hgweb.
1500 1500 """
1501 1501 return self._exportableenviron
1502 1502
1503 1503 @contextlib.contextmanager
1504 1504 def configoverride(self, overrides, source=""):
1505 1505 """Context manager for temporary config overrides
1506 1506 `overrides` must be a dict of the following structure:
1507 1507 {(section, name) : value}"""
1508 1508 backups = {}
1509 1509 try:
1510 1510 for (section, name), value in overrides.items():
1511 1511 backups[(section, name)] = self.backupconfig(section, name)
1512 1512 self.setconfig(section, name, value, source)
1513 1513 yield
1514 1514 finally:
1515 1515 for __, backup in backups.items():
1516 1516 self.restoreconfig(backup)
1517 1517 # just restoring ui.quiet config to the previous value is not enough
1518 1518 # as it does not update ui.quiet class member
1519 1519 if ('ui', 'quiet') in overrides:
1520 1520 self.fixconfig(section='ui')
1521 1521
1522 1522 class paths(dict):
1523 1523 """Represents a collection of paths and their configs.
1524 1524
1525 1525 Data is initially derived from ui instances and the config files they have
1526 1526 loaded.
1527 1527 """
1528 1528 def __init__(self, ui):
1529 1529 dict.__init__(self)
1530 1530
1531 1531 for name, loc in ui.configitems('paths', ignoresub=True):
1532 1532 # No location is the same as not existing.
1533 1533 if not loc:
1534 1534 continue
1535 1535 loc, sub = ui.configsuboptions('paths', name)
1536 1536 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1537 1537
1538 1538 def getpath(self, name, default=None):
1539 1539 """Return a ``path`` from a string, falling back to default.
1540 1540
1541 1541 ``name`` can be a named path or locations. Locations are filesystem
1542 1542 paths or URIs.
1543 1543
1544 1544 Returns None if ``name`` is not a registered path, a URI, or a local
1545 1545 path to a repo.
1546 1546 """
1547 1547 # Only fall back to default if no path was requested.
1548 1548 if name is None:
1549 1549 if not default:
1550 1550 default = ()
1551 1551 elif not isinstance(default, (tuple, list)):
1552 1552 default = (default,)
1553 1553 for k in default:
1554 1554 try:
1555 1555 return self[k]
1556 1556 except KeyError:
1557 1557 continue
1558 1558 return None
1559 1559
1560 1560 # Most likely empty string.
1561 1561 # This may need to raise in the future.
1562 1562 if not name:
1563 1563 return None
1564 1564
1565 1565 try:
1566 1566 return self[name]
1567 1567 except KeyError:
1568 1568 # Try to resolve as a local path or URI.
1569 1569 try:
1570 1570 # We don't pass sub-options in, so no need to pass ui instance.
1571 1571 return path(None, None, rawloc=name)
1572 1572 except ValueError:
1573 1573 raise error.RepoError(_('repository %s does not exist') %
1574 1574 name)
1575 1575
1576 1576 _pathsuboptions = {}
1577 1577
1578 1578 def pathsuboption(option, attr):
1579 1579 """Decorator used to declare a path sub-option.
1580 1580
1581 1581 Arguments are the sub-option name and the attribute it should set on
1582 1582 ``path`` instances.
1583 1583
1584 1584 The decorated function will receive as arguments a ``ui`` instance,
1585 1585 ``path`` instance, and the string value of this option from the config.
1586 1586 The function should return the value that will be set on the ``path``
1587 1587 instance.
1588 1588
1589 1589 This decorator can be used to perform additional verification of
1590 1590 sub-options and to change the type of sub-options.
1591 1591 """
1592 1592 def register(func):
1593 1593 _pathsuboptions[option] = (attr, func)
1594 1594 return func
1595 1595 return register
1596 1596
1597 1597 @pathsuboption('pushurl', 'pushloc')
1598 1598 def pushurlpathoption(ui, path, value):
1599 1599 u = util.url(value)
1600 1600 # Actually require a URL.
1601 1601 if not u.scheme:
1602 1602 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1603 1603 return None
1604 1604
1605 1605 # Don't support the #foo syntax in the push URL to declare branch to
1606 1606 # push.
1607 1607 if u.fragment:
1608 1608 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1609 1609 'ignoring)\n') % path.name)
1610 1610 u.fragment = None
1611 1611
1612 1612 return str(u)
1613 1613
1614 1614 @pathsuboption('pushrev', 'pushrev')
1615 1615 def pushrevpathoption(ui, path, value):
1616 1616 return value
1617 1617
1618 1618 class path(object):
1619 1619 """Represents an individual path and its configuration."""
1620 1620
1621 1621 def __init__(self, ui, name, rawloc=None, suboptions=None):
1622 1622 """Construct a path from its config options.
1623 1623
1624 1624 ``ui`` is the ``ui`` instance the path is coming from.
1625 1625 ``name`` is the symbolic name of the path.
1626 1626 ``rawloc`` is the raw location, as defined in the config.
1627 1627 ``pushloc`` is the raw locations pushes should be made to.
1628 1628
1629 1629 If ``name`` is not defined, we require that the location be a) a local
1630 1630 filesystem path with a .hg directory or b) a URL. If not,
1631 1631 ``ValueError`` is raised.
1632 1632 """
1633 1633 if not rawloc:
1634 1634 raise ValueError('rawloc must be defined')
1635 1635
1636 1636 # Locations may define branches via syntax <base>#<branch>.
1637 1637 u = util.url(rawloc)
1638 1638 branch = None
1639 1639 if u.fragment:
1640 1640 branch = u.fragment
1641 1641 u.fragment = None
1642 1642
1643 1643 self.url = u
1644 1644 self.branch = branch
1645 1645
1646 1646 self.name = name
1647 1647 self.rawloc = rawloc
1648 1648 self.loc = '%s' % u
1649 1649
1650 1650 # When given a raw location but not a symbolic name, validate the
1651 1651 # location is valid.
1652 1652 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1653 1653 raise ValueError('location is not a URL or path to a local '
1654 1654 'repo: %s' % rawloc)
1655 1655
1656 1656 suboptions = suboptions or {}
1657 1657
1658 1658 # Now process the sub-options. If a sub-option is registered, its
1659 1659 # attribute will always be present. The value will be None if there
1660 1660 # was no valid sub-option.
1661 1661 for suboption, (attr, func) in _pathsuboptions.iteritems():
1662 1662 if suboption not in suboptions:
1663 1663 setattr(self, attr, None)
1664 1664 continue
1665 1665
1666 1666 value = func(ui, self, suboptions[suboption])
1667 1667 setattr(self, attr, value)
1668 1668
1669 1669 def _isvalidlocalpath(self, path):
1670 1670 """Returns True if the given path is a potentially valid repository.
1671 1671 This is its own function so that extensions can change the definition of
1672 1672 'valid' in this case (like when pulling from a git repo into a hg
1673 1673 one)."""
1674 1674 return os.path.isdir(os.path.join(path, '.hg'))
1675 1675
1676 1676 @property
1677 1677 def suboptions(self):
1678 1678 """Return sub-options and their values for this path.
1679 1679
1680 1680 This is intended to be used for presentation purposes.
1681 1681 """
1682 1682 d = {}
1683 1683 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1684 1684 value = getattr(self, attr)
1685 1685 if value is not None:
1686 1686 d[subopt] = value
1687 1687 return d
1688 1688
1689 1689 # we instantiate one globally shared progress bar to avoid
1690 1690 # competing progress bars when multiple UI objects get created
1691 1691 _progresssingleton = None
1692 1692
1693 1693 def getprogbar(ui):
1694 1694 global _progresssingleton
1695 1695 if _progresssingleton is None:
1696 1696 # passing 'ui' object to the singleton is fishy,
1697 1697 # this is how the extension used to work but feel free to rework it.
1698 1698 _progresssingleton = progress.progbar(ui)
1699 1699 return _progresssingleton
General Comments 0
You need to be logged in to leave comments. Login now