##// END OF EJS Templates
ui: add a variable to control whether hooks should be called...
Idan Kamara -
r17048:15d4d475 stable
parent child Browse files
Show More
@@ -1,181 +1,184 b''
1 1 # hook.py - hook support for mercurial
2 2 #
3 3 # Copyright 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 i18n import _
9 9 import os, sys
10 10 import extensions, util
11 11
12 12 def _pythonhook(ui, repo, name, hname, funcname, args, throw):
13 13 '''call python hook. hook is callable object, looked up as
14 14 name in python module. if callable returns "true", hook
15 15 fails, else passes. if hook raises exception, treated as
16 16 hook failure. exception propagates if throw is "true".
17 17
18 18 reason for "true" meaning "hook failed" is so that
19 19 unmodified commands (e.g. mercurial.commands.update) can
20 20 be run as hooks without wrappers to convert return values.'''
21 21
22 22 ui.note(_("calling hook %s: %s\n") % (hname, funcname))
23 23 obj = funcname
24 24 if not util.safehasattr(obj, '__call__'):
25 25 d = funcname.rfind('.')
26 26 if d == -1:
27 27 raise util.Abort(_('%s hook is invalid ("%s" not in '
28 28 'a module)') % (hname, funcname))
29 29 modname = funcname[:d]
30 30 oldpaths = sys.path
31 31 if util.mainfrozen():
32 32 # binary installs require sys.path manipulation
33 33 modpath, modfile = os.path.split(modname)
34 34 if modpath and modfile:
35 35 sys.path = sys.path[:] + [modpath]
36 36 modname = modfile
37 37 try:
38 38 obj = __import__(modname)
39 39 except ImportError:
40 40 e1 = sys.exc_type, sys.exc_value, sys.exc_traceback
41 41 try:
42 42 # extensions are loaded with hgext_ prefix
43 43 obj = __import__("hgext_%s" % modname)
44 44 except ImportError:
45 45 e2 = sys.exc_type, sys.exc_value, sys.exc_traceback
46 46 if ui.tracebackflag:
47 47 ui.warn(_('exception from first failed import attempt:\n'))
48 48 ui.traceback(e1)
49 49 if ui.tracebackflag:
50 50 ui.warn(_('exception from second failed import attempt:\n'))
51 51 ui.traceback(e2)
52 52 raise util.Abort(_('%s hook is invalid '
53 53 '(import of "%s" failed)') %
54 54 (hname, modname))
55 55 sys.path = oldpaths
56 56 try:
57 57 for p in funcname.split('.')[1:]:
58 58 obj = getattr(obj, p)
59 59 except AttributeError:
60 60 raise util.Abort(_('%s hook is invalid '
61 61 '("%s" is not defined)') %
62 62 (hname, funcname))
63 63 if not util.safehasattr(obj, '__call__'):
64 64 raise util.Abort(_('%s hook is invalid '
65 65 '("%s" is not callable)') %
66 66 (hname, funcname))
67 67 try:
68 68 try:
69 69 # redirect IO descriptors the the ui descriptors so hooks
70 70 # that write directly to these don't mess up the command
71 71 # protocol when running through the command server
72 72 old = sys.stdout, sys.stderr, sys.stdin
73 73 sys.stdout, sys.stderr, sys.stdin = ui.fout, ui.ferr, ui.fin
74 74
75 75 r = obj(ui=ui, repo=repo, hooktype=name, **args)
76 76 except KeyboardInterrupt:
77 77 raise
78 78 except Exception, exc:
79 79 if isinstance(exc, util.Abort):
80 80 ui.warn(_('error: %s hook failed: %s\n') %
81 81 (hname, exc.args[0]))
82 82 else:
83 83 ui.warn(_('error: %s hook raised an exception: '
84 84 '%s\n') % (hname, exc))
85 85 if throw:
86 86 raise
87 87 ui.traceback()
88 88 return True
89 89 finally:
90 90 sys.stdout, sys.stderr, sys.stdin = old
91 91 if r:
92 92 if throw:
93 93 raise util.Abort(_('%s hook failed') % hname)
94 94 ui.warn(_('warning: %s hook failed\n') % hname)
95 95 return r
96 96
97 97 def _exthook(ui, repo, name, cmd, args, throw):
98 98 ui.note(_("running hook %s: %s\n") % (name, cmd))
99 99
100 100 env = {}
101 101 for k, v in args.iteritems():
102 102 if util.safehasattr(v, '__call__'):
103 103 v = v()
104 104 if isinstance(v, dict):
105 105 # make the dictionary element order stable across Python
106 106 # implementations
107 107 v = ('{' +
108 108 ', '.join('%r: %r' % i for i in sorted(v.iteritems())) +
109 109 '}')
110 110 env['HG_' + k.upper()] = v
111 111
112 112 if repo:
113 113 cwd = repo.root
114 114 else:
115 115 cwd = os.getcwd()
116 116 if 'HG_URL' in env and env['HG_URL'].startswith('remote:http'):
117 117 r = util.system(cmd, environ=env, cwd=cwd, out=ui)
118 118 else:
119 119 r = util.system(cmd, environ=env, cwd=cwd, out=ui.fout)
120 120 if r:
121 121 desc, r = util.explainexit(r)
122 122 if throw:
123 123 raise util.Abort(_('%s hook %s') % (name, desc))
124 124 ui.warn(_('warning: %s hook %s\n') % (name, desc))
125 125 return r
126 126
127 127 def _allhooks(ui):
128 128 hooks = []
129 129 for name, cmd in ui.configitems('hooks'):
130 130 if not name.startswith('priority'):
131 131 priority = ui.configint('hooks', 'priority.%s' % name, 0)
132 132 hooks.append((-priority, len(hooks), name, cmd))
133 133 return [(k, v) for p, o, k, v in sorted(hooks)]
134 134
135 135 _redirect = False
136 136 def redirect(state):
137 137 global _redirect
138 138 _redirect = state
139 139
140 140 def hook(ui, repo, name, throw=False, **args):
141 if not ui.callhooks:
142 return False
143
141 144 r = False
142 145
143 146 oldstdout = -1
144 147 if _redirect:
145 148 try:
146 149 stdoutno = sys.__stdout__.fileno()
147 150 stderrno = sys.__stderr__.fileno()
148 151 # temporarily redirect stdout to stderr, if possible
149 152 if stdoutno >= 0 and stderrno >= 0:
150 153 sys.__stdout__.flush()
151 154 oldstdout = os.dup(stdoutno)
152 155 os.dup2(stderrno, stdoutno)
153 156 except AttributeError:
154 157 # __stdout/err__ doesn't have fileno(), it's not a real file
155 158 pass
156 159
157 160 try:
158 161 for hname, cmd in _allhooks(ui):
159 162 if hname.split('.')[0] != name or not cmd:
160 163 continue
161 164 if util.safehasattr(cmd, '__call__'):
162 165 r = _pythonhook(ui, repo, name, hname, cmd, args, throw) or r
163 166 elif cmd.startswith('python:'):
164 167 if cmd.count(':') >= 2:
165 168 path, cmd = cmd[7:].rsplit(':', 1)
166 169 path = util.expandpath(path)
167 170 if repo:
168 171 path = os.path.join(repo.root, path)
169 172 mod = extensions.loadpath(path, 'hghook.%s' % hname)
170 173 hookfn = getattr(mod, cmd)
171 174 else:
172 175 hookfn = cmd[7:].strip()
173 176 r = _pythonhook(ui, repo, name, hname, hookfn, args, throw) or r
174 177 else:
175 178 r = _exthook(ui, repo, hname, cmd, args, throw) or r
176 179 finally:
177 180 if _redirect and oldstdout >= 0:
178 181 os.dup2(oldstdout, stdoutno)
179 182 os.close(oldstdout)
180 183
181 184 return r
@@ -1,753 +1,755 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 i18n import _
9 9 import errno, getpass, os, socket, sys, tempfile, traceback
10 10 import config, scmutil, util, error, formatter
11 11
12 12 class ui(object):
13 13 def __init__(self, src=None):
14 14 self._buffers = []
15 15 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
16 16 self._reportuntrusted = True
17 17 self._ocfg = config.config() # overlay
18 18 self._tcfg = config.config() # trusted
19 19 self._ucfg = config.config() # untrusted
20 20 self._trustusers = set()
21 21 self._trustgroups = set()
22 self.callhooks = True
22 23
23 24 if src:
24 25 self.fout = src.fout
25 26 self.ferr = src.ferr
26 27 self.fin = src.fin
27 28
28 29 self._tcfg = src._tcfg.copy()
29 30 self._ucfg = src._ucfg.copy()
30 31 self._ocfg = src._ocfg.copy()
31 32 self._trustusers = src._trustusers.copy()
32 33 self._trustgroups = src._trustgroups.copy()
33 34 self.environ = src.environ
35 self.callhooks = src.callhooks
34 36 self.fixconfig()
35 37 else:
36 38 self.fout = sys.stdout
37 39 self.ferr = sys.stderr
38 40 self.fin = sys.stdin
39 41
40 42 # shared read-only environment
41 43 self.environ = os.environ
42 44 # we always trust global config files
43 45 for f in scmutil.rcpath():
44 46 self.readconfig(f, trust=True)
45 47
46 48 def copy(self):
47 49 return self.__class__(self)
48 50
49 51 def formatter(self, topic, opts):
50 52 return formatter.formatter(self, topic, opts)
51 53
52 54 def _trusted(self, fp, f):
53 55 st = util.fstat(fp)
54 56 if util.isowner(st):
55 57 return True
56 58
57 59 tusers, tgroups = self._trustusers, self._trustgroups
58 60 if '*' in tusers or '*' in tgroups:
59 61 return True
60 62
61 63 user = util.username(st.st_uid)
62 64 group = util.groupname(st.st_gid)
63 65 if user in tusers or group in tgroups or user == util.username():
64 66 return True
65 67
66 68 if self._reportuntrusted:
67 69 self.warn(_('Not trusting file %s from untrusted '
68 70 'user %s, group %s\n') % (f, user, group))
69 71 return False
70 72
71 73 def readconfig(self, filename, root=None, trust=False,
72 74 sections=None, remap=None):
73 75 try:
74 76 fp = open(filename)
75 77 except IOError:
76 78 if not sections: # ignore unless we were looking for something
77 79 return
78 80 raise
79 81
80 82 cfg = config.config()
81 83 trusted = sections or trust or self._trusted(fp, filename)
82 84
83 85 try:
84 86 cfg.read(filename, fp, sections=sections, remap=remap)
85 87 fp.close()
86 88 except error.ConfigError, inst:
87 89 if trusted:
88 90 raise
89 91 self.warn(_("Ignored: %s\n") % str(inst))
90 92
91 93 if self.plain():
92 94 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
93 95 'logtemplate', 'style',
94 96 'traceback', 'verbose'):
95 97 if k in cfg['ui']:
96 98 del cfg['ui'][k]
97 99 for k, v in cfg.items('defaults'):
98 100 del cfg['defaults'][k]
99 101 # Don't remove aliases from the configuration if in the exceptionlist
100 102 if self.plain('alias'):
101 103 for k, v in cfg.items('alias'):
102 104 del cfg['alias'][k]
103 105
104 106 if trusted:
105 107 self._tcfg.update(cfg)
106 108 self._tcfg.update(self._ocfg)
107 109 self._ucfg.update(cfg)
108 110 self._ucfg.update(self._ocfg)
109 111
110 112 if root is None:
111 113 root = os.path.expanduser('~')
112 114 self.fixconfig(root=root)
113 115
114 116 def fixconfig(self, root=None, section=None):
115 117 if section in (None, 'paths'):
116 118 # expand vars and ~
117 119 # translate paths relative to root (or home) into absolute paths
118 120 root = root or os.getcwd()
119 121 for c in self._tcfg, self._ucfg, self._ocfg:
120 122 for n, p in c.items('paths'):
121 123 if not p:
122 124 continue
123 125 if '%%' in p:
124 126 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
125 127 % (n, p, self.configsource('paths', n)))
126 128 p = p.replace('%%', '%')
127 129 p = util.expandpath(p)
128 130 if not util.hasscheme(p) and not os.path.isabs(p):
129 131 p = os.path.normpath(os.path.join(root, p))
130 132 c.set("paths", n, p)
131 133
132 134 if section in (None, 'ui'):
133 135 # update ui options
134 136 self.debugflag = self.configbool('ui', 'debug')
135 137 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
136 138 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
137 139 if self.verbose and self.quiet:
138 140 self.quiet = self.verbose = False
139 141 self._reportuntrusted = self.debugflag or self.configbool("ui",
140 142 "report_untrusted", True)
141 143 self.tracebackflag = self.configbool('ui', 'traceback', False)
142 144
143 145 if section in (None, 'trusted'):
144 146 # update trust information
145 147 self._trustusers.update(self.configlist('trusted', 'users'))
146 148 self._trustgroups.update(self.configlist('trusted', 'groups'))
147 149
148 150 def backupconfig(self, section, item):
149 151 return (self._ocfg.backup(section, item),
150 152 self._tcfg.backup(section, item),
151 153 self._ucfg.backup(section, item),)
152 154 def restoreconfig(self, data):
153 155 self._ocfg.restore(data[0])
154 156 self._tcfg.restore(data[1])
155 157 self._ucfg.restore(data[2])
156 158
157 159 def setconfig(self, section, name, value, overlay=True):
158 160 if overlay:
159 161 self._ocfg.set(section, name, value)
160 162 self._tcfg.set(section, name, value)
161 163 self._ucfg.set(section, name, value)
162 164 self.fixconfig(section=section)
163 165
164 166 def _data(self, untrusted):
165 167 return untrusted and self._ucfg or self._tcfg
166 168
167 169 def configsource(self, section, name, untrusted=False):
168 170 return self._data(untrusted).source(section, name) or 'none'
169 171
170 172 def config(self, section, name, default=None, untrusted=False):
171 173 if isinstance(name, list):
172 174 alternates = name
173 175 else:
174 176 alternates = [name]
175 177
176 178 for n in alternates:
177 179 value = self._data(untrusted).get(section, name, None)
178 180 if value is not None:
179 181 name = n
180 182 break
181 183 else:
182 184 value = default
183 185
184 186 if self.debugflag and not untrusted and self._reportuntrusted:
185 187 uvalue = self._ucfg.get(section, name)
186 188 if uvalue is not None and uvalue != value:
187 189 self.debug("ignoring untrusted configuration option "
188 190 "%s.%s = %s\n" % (section, name, uvalue))
189 191 return value
190 192
191 193 def configpath(self, section, name, default=None, untrusted=False):
192 194 'get a path config item, expanded relative to repo root or config file'
193 195 v = self.config(section, name, default, untrusted)
194 196 if v is None:
195 197 return None
196 198 if not os.path.isabs(v) or "://" not in v:
197 199 src = self.configsource(section, name, untrusted)
198 200 if ':' in src:
199 201 base = os.path.dirname(src.rsplit(':')[0])
200 202 v = os.path.join(base, os.path.expanduser(v))
201 203 return v
202 204
203 205 def configbool(self, section, name, default=False, untrusted=False):
204 206 """parse a configuration element as a boolean
205 207
206 208 >>> u = ui(); s = 'foo'
207 209 >>> u.setconfig(s, 'true', 'yes')
208 210 >>> u.configbool(s, 'true')
209 211 True
210 212 >>> u.setconfig(s, 'false', 'no')
211 213 >>> u.configbool(s, 'false')
212 214 False
213 215 >>> u.configbool(s, 'unknown')
214 216 False
215 217 >>> u.configbool(s, 'unknown', True)
216 218 True
217 219 >>> u.setconfig(s, 'invalid', 'somevalue')
218 220 >>> u.configbool(s, 'invalid')
219 221 Traceback (most recent call last):
220 222 ...
221 223 ConfigError: foo.invalid is not a boolean ('somevalue')
222 224 """
223 225
224 226 v = self.config(section, name, None, untrusted)
225 227 if v is None:
226 228 return default
227 229 if isinstance(v, bool):
228 230 return v
229 231 b = util.parsebool(v)
230 232 if b is None:
231 233 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
232 234 % (section, name, v))
233 235 return b
234 236
235 237 def configint(self, section, name, default=None, untrusted=False):
236 238 """parse a configuration element as an integer
237 239
238 240 >>> u = ui(); s = 'foo'
239 241 >>> u.setconfig(s, 'int1', '42')
240 242 >>> u.configint(s, 'int1')
241 243 42
242 244 >>> u.setconfig(s, 'int2', '-42')
243 245 >>> u.configint(s, 'int2')
244 246 -42
245 247 >>> u.configint(s, 'unknown', 7)
246 248 7
247 249 >>> u.setconfig(s, 'invalid', 'somevalue')
248 250 >>> u.configint(s, 'invalid')
249 251 Traceback (most recent call last):
250 252 ...
251 253 ConfigError: foo.invalid is not an integer ('somevalue')
252 254 """
253 255
254 256 v = self.config(section, name, None, untrusted)
255 257 if v is None:
256 258 return default
257 259 try:
258 260 return int(v)
259 261 except ValueError:
260 262 raise error.ConfigError(_("%s.%s is not an integer ('%s')")
261 263 % (section, name, v))
262 264
263 265 def configlist(self, section, name, default=None, untrusted=False):
264 266 """parse a configuration element as a list of comma/space separated
265 267 strings
266 268
267 269 >>> u = ui(); s = 'foo'
268 270 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
269 271 >>> u.configlist(s, 'list1')
270 272 ['this', 'is', 'a small', 'test']
271 273 """
272 274
273 275 def _parse_plain(parts, s, offset):
274 276 whitespace = False
275 277 while offset < len(s) and (s[offset].isspace() or s[offset] == ','):
276 278 whitespace = True
277 279 offset += 1
278 280 if offset >= len(s):
279 281 return None, parts, offset
280 282 if whitespace:
281 283 parts.append('')
282 284 if s[offset] == '"' and not parts[-1]:
283 285 return _parse_quote, parts, offset + 1
284 286 elif s[offset] == '"' and parts[-1][-1] == '\\':
285 287 parts[-1] = parts[-1][:-1] + s[offset]
286 288 return _parse_plain, parts, offset + 1
287 289 parts[-1] += s[offset]
288 290 return _parse_plain, parts, offset + 1
289 291
290 292 def _parse_quote(parts, s, offset):
291 293 if offset < len(s) and s[offset] == '"': # ""
292 294 parts.append('')
293 295 offset += 1
294 296 while offset < len(s) and (s[offset].isspace() or
295 297 s[offset] == ','):
296 298 offset += 1
297 299 return _parse_plain, parts, offset
298 300
299 301 while offset < len(s) and s[offset] != '"':
300 302 if (s[offset] == '\\' and offset + 1 < len(s)
301 303 and s[offset + 1] == '"'):
302 304 offset += 1
303 305 parts[-1] += '"'
304 306 else:
305 307 parts[-1] += s[offset]
306 308 offset += 1
307 309
308 310 if offset >= len(s):
309 311 real_parts = _configlist(parts[-1])
310 312 if not real_parts:
311 313 parts[-1] = '"'
312 314 else:
313 315 real_parts[0] = '"' + real_parts[0]
314 316 parts = parts[:-1]
315 317 parts.extend(real_parts)
316 318 return None, parts, offset
317 319
318 320 offset += 1
319 321 while offset < len(s) and s[offset] in [' ', ',']:
320 322 offset += 1
321 323
322 324 if offset < len(s):
323 325 if offset + 1 == len(s) and s[offset] == '"':
324 326 parts[-1] += '"'
325 327 offset += 1
326 328 else:
327 329 parts.append('')
328 330 else:
329 331 return None, parts, offset
330 332
331 333 return _parse_plain, parts, offset
332 334
333 335 def _configlist(s):
334 336 s = s.rstrip(' ,')
335 337 if not s:
336 338 return []
337 339 parser, parts, offset = _parse_plain, [''], 0
338 340 while parser:
339 341 parser, parts, offset = parser(parts, s, offset)
340 342 return parts
341 343
342 344 result = self.config(section, name, untrusted=untrusted)
343 345 if result is None:
344 346 result = default or []
345 347 if isinstance(result, basestring):
346 348 result = _configlist(result.lstrip(' ,\n'))
347 349 if result is None:
348 350 result = default or []
349 351 return result
350 352
351 353 def has_section(self, section, untrusted=False):
352 354 '''tell whether section exists in config.'''
353 355 return section in self._data(untrusted)
354 356
355 357 def configitems(self, section, untrusted=False):
356 358 items = self._data(untrusted).items(section)
357 359 if self.debugflag and not untrusted and self._reportuntrusted:
358 360 for k, v in self._ucfg.items(section):
359 361 if self._tcfg.get(section, k) != v:
360 362 self.debug("ignoring untrusted configuration option "
361 363 "%s.%s = %s\n" % (section, k, v))
362 364 return items
363 365
364 366 def walkconfig(self, untrusted=False):
365 367 cfg = self._data(untrusted)
366 368 for section in cfg.sections():
367 369 for name, value in self.configitems(section, untrusted):
368 370 yield section, name, value
369 371
370 372 def plain(self, feature=None):
371 373 '''is plain mode active?
372 374
373 375 Plain mode means that all configuration variables which affect
374 376 the behavior and output of Mercurial should be
375 377 ignored. Additionally, the output should be stable,
376 378 reproducible and suitable for use in scripts or applications.
377 379
378 380 The only way to trigger plain mode is by setting either the
379 381 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
380 382
381 383 The return value can either be
382 384 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
383 385 - True otherwise
384 386 '''
385 387 if 'HGPLAIN' not in os.environ and 'HGPLAINEXCEPT' not in os.environ:
386 388 return False
387 389 exceptions = os.environ.get('HGPLAINEXCEPT', '').strip().split(',')
388 390 if feature and exceptions:
389 391 return feature not in exceptions
390 392 return True
391 393
392 394 def username(self):
393 395 """Return default username to be used in commits.
394 396
395 397 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
396 398 and stop searching if one of these is set.
397 399 If not found and ui.askusername is True, ask the user, else use
398 400 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
399 401 """
400 402 user = os.environ.get("HGUSER")
401 403 if user is None:
402 404 user = self.config("ui", "username")
403 405 if user is not None:
404 406 user = os.path.expandvars(user)
405 407 if user is None:
406 408 user = os.environ.get("EMAIL")
407 409 if user is None and self.configbool("ui", "askusername"):
408 410 user = self.prompt(_("enter a commit username:"), default=None)
409 411 if user is None and not self.interactive():
410 412 try:
411 413 user = '%s@%s' % (util.getuser(), socket.getfqdn())
412 414 self.warn(_("No username found, using '%s' instead\n") % user)
413 415 except KeyError:
414 416 pass
415 417 if not user:
416 418 raise util.Abort(_('no username supplied (see "hg help config")'))
417 419 if "\n" in user:
418 420 raise util.Abort(_("username %s contains a newline\n") % repr(user))
419 421 return user
420 422
421 423 def shortuser(self, user):
422 424 """Return a short representation of a user name or email address."""
423 425 if not self.verbose:
424 426 user = util.shortuser(user)
425 427 return user
426 428
427 429 def expandpath(self, loc, default=None):
428 430 """Return repository location relative to cwd or from [paths]"""
429 431 if util.hasscheme(loc) or os.path.isdir(os.path.join(loc, '.hg')):
430 432 return loc
431 433
432 434 path = self.config('paths', loc)
433 435 if not path and default is not None:
434 436 path = self.config('paths', default)
435 437 return path or loc
436 438
437 439 def pushbuffer(self):
438 440 self._buffers.append([])
439 441
440 442 def popbuffer(self, labeled=False):
441 443 '''pop the last buffer and return the buffered output
442 444
443 445 If labeled is True, any labels associated with buffered
444 446 output will be handled. By default, this has no effect
445 447 on the output returned, but extensions and GUI tools may
446 448 handle this argument and returned styled output. If output
447 449 is being buffered so it can be captured and parsed or
448 450 processed, labeled should not be set to True.
449 451 '''
450 452 return "".join(self._buffers.pop())
451 453
452 454 def write(self, *args, **opts):
453 455 '''write args to output
454 456
455 457 By default, this method simply writes to the buffer or stdout,
456 458 but extensions or GUI tools may override this method,
457 459 write_err(), popbuffer(), and label() to style output from
458 460 various parts of hg.
459 461
460 462 An optional keyword argument, "label", can be passed in.
461 463 This should be a string containing label names separated by
462 464 space. Label names take the form of "topic.type". For example,
463 465 ui.debug() issues a label of "ui.debug".
464 466
465 467 When labeling output for a specific command, a label of
466 468 "cmdname.type" is recommended. For example, status issues
467 469 a label of "status.modified" for modified files.
468 470 '''
469 471 if self._buffers:
470 472 self._buffers[-1].extend([str(a) for a in args])
471 473 else:
472 474 for a in args:
473 475 self.fout.write(str(a))
474 476
475 477 def write_err(self, *args, **opts):
476 478 try:
477 479 if not getattr(self.fout, 'closed', False):
478 480 self.fout.flush()
479 481 for a in args:
480 482 self.ferr.write(str(a))
481 483 # stderr may be buffered under win32 when redirected to files,
482 484 # including stdout.
483 485 if not getattr(self.ferr, 'closed', False):
484 486 self.ferr.flush()
485 487 except IOError, inst:
486 488 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
487 489 raise
488 490
489 491 def flush(self):
490 492 try: self.fout.flush()
491 493 except: pass
492 494 try: self.ferr.flush()
493 495 except: pass
494 496
495 497 def interactive(self):
496 498 '''is interactive input allowed?
497 499
498 500 An interactive session is a session where input can be reasonably read
499 501 from `sys.stdin'. If this function returns false, any attempt to read
500 502 from stdin should fail with an error, unless a sensible default has been
501 503 specified.
502 504
503 505 Interactiveness is triggered by the value of the `ui.interactive'
504 506 configuration variable or - if it is unset - when `sys.stdin' points
505 507 to a terminal device.
506 508
507 509 This function refers to input only; for output, see `ui.formatted()'.
508 510 '''
509 511 i = self.configbool("ui", "interactive", None)
510 512 if i is None:
511 513 # some environments replace stdin without implementing isatty
512 514 # usually those are non-interactive
513 515 return util.isatty(self.fin)
514 516
515 517 return i
516 518
517 519 def termwidth(self):
518 520 '''how wide is the terminal in columns?
519 521 '''
520 522 if 'COLUMNS' in os.environ:
521 523 try:
522 524 return int(os.environ['COLUMNS'])
523 525 except ValueError:
524 526 pass
525 527 return util.termwidth()
526 528
527 529 def formatted(self):
528 530 '''should formatted output be used?
529 531
530 532 It is often desirable to format the output to suite the output medium.
531 533 Examples of this are truncating long lines or colorizing messages.
532 534 However, this is not often not desirable when piping output into other
533 535 utilities, e.g. `grep'.
534 536
535 537 Formatted output is triggered by the value of the `ui.formatted'
536 538 configuration variable or - if it is unset - when `sys.stdout' points
537 539 to a terminal device. Please note that `ui.formatted' should be
538 540 considered an implementation detail; it is not intended for use outside
539 541 Mercurial or its extensions.
540 542
541 543 This function refers to output only; for input, see `ui.interactive()'.
542 544 This function always returns false when in plain mode, see `ui.plain()'.
543 545 '''
544 546 if self.plain():
545 547 return False
546 548
547 549 i = self.configbool("ui", "formatted", None)
548 550 if i is None:
549 551 # some environments replace stdout without implementing isatty
550 552 # usually those are non-interactive
551 553 return util.isatty(self.fout)
552 554
553 555 return i
554 556
555 557 def _readline(self, prompt=''):
556 558 if util.isatty(self.fin):
557 559 try:
558 560 # magically add command line editing support, where
559 561 # available
560 562 import readline
561 563 # force demandimport to really load the module
562 564 readline.read_history_file
563 565 # windows sometimes raises something other than ImportError
564 566 except Exception:
565 567 pass
566 568
567 569 # call write() so output goes through subclassed implementation
568 570 # e.g. color extension on Windows
569 571 self.write(prompt)
570 572
571 573 # instead of trying to emulate raw_input, swap (self.fin,
572 574 # self.fout) with (sys.stdin, sys.stdout)
573 575 oldin = sys.stdin
574 576 oldout = sys.stdout
575 577 sys.stdin = self.fin
576 578 sys.stdout = self.fout
577 579 line = raw_input(' ')
578 580 sys.stdin = oldin
579 581 sys.stdout = oldout
580 582
581 583 # When stdin is in binary mode on Windows, it can cause
582 584 # raw_input() to emit an extra trailing carriage return
583 585 if os.linesep == '\r\n' and line and line[-1] == '\r':
584 586 line = line[:-1]
585 587 return line
586 588
587 589 def prompt(self, msg, default="y"):
588 590 """Prompt user with msg, read response.
589 591 If ui is not interactive, the default is returned.
590 592 """
591 593 if not self.interactive():
592 594 self.write(msg, ' ', default, "\n")
593 595 return default
594 596 try:
595 597 r = self._readline(self.label(msg, 'ui.prompt'))
596 598 if not r:
597 599 return default
598 600 return r
599 601 except EOFError:
600 602 raise util.Abort(_('response expected'))
601 603
602 604 def promptchoice(self, msg, choices, default=0):
603 605 """Prompt user with msg, read response, and ensure it matches
604 606 one of the provided choices. The index of the choice is returned.
605 607 choices is a sequence of acceptable responses with the format:
606 608 ('&None', 'E&xec', 'Sym&link') Responses are case insensitive.
607 609 If ui is not interactive, the default is returned.
608 610 """
609 611 resps = [s[s.index('&')+1].lower() for s in choices]
610 612 while True:
611 613 r = self.prompt(msg, resps[default])
612 614 if r.lower() in resps:
613 615 return resps.index(r.lower())
614 616 self.write(_("unrecognized response\n"))
615 617
616 618 def getpass(self, prompt=None, default=None):
617 619 if not self.interactive():
618 620 return default
619 621 try:
620 622 return getpass.getpass(prompt or _('password: '))
621 623 except EOFError:
622 624 raise util.Abort(_('response expected'))
623 625 def status(self, *msg, **opts):
624 626 '''write status message to output (if ui.quiet is False)
625 627
626 628 This adds an output label of "ui.status".
627 629 '''
628 630 if not self.quiet:
629 631 opts['label'] = opts.get('label', '') + ' ui.status'
630 632 self.write(*msg, **opts)
631 633 def warn(self, *msg, **opts):
632 634 '''write warning message to output (stderr)
633 635
634 636 This adds an output label of "ui.warning".
635 637 '''
636 638 opts['label'] = opts.get('label', '') + ' ui.warning'
637 639 self.write_err(*msg, **opts)
638 640 def note(self, *msg, **opts):
639 641 '''write note to output (if ui.verbose is True)
640 642
641 643 This adds an output label of "ui.note".
642 644 '''
643 645 if self.verbose:
644 646 opts['label'] = opts.get('label', '') + ' ui.note'
645 647 self.write(*msg, **opts)
646 648 def debug(self, *msg, **opts):
647 649 '''write debug message to output (if ui.debugflag is True)
648 650
649 651 This adds an output label of "ui.debug".
650 652 '''
651 653 if self.debugflag:
652 654 opts['label'] = opts.get('label', '') + ' ui.debug'
653 655 self.write(*msg, **opts)
654 656 def edit(self, text, user):
655 657 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
656 658 text=True)
657 659 try:
658 660 f = os.fdopen(fd, "w")
659 661 f.write(text)
660 662 f.close()
661 663
662 664 editor = self.geteditor()
663 665
664 666 util.system("%s \"%s\"" % (editor, name),
665 667 environ={'HGUSER': user},
666 668 onerr=util.Abort, errprefix=_("edit failed"),
667 669 out=self.fout)
668 670
669 671 f = open(name)
670 672 t = f.read()
671 673 f.close()
672 674 finally:
673 675 os.unlink(name)
674 676
675 677 return t
676 678
677 679 def traceback(self, exc=None):
678 680 '''print exception traceback if traceback printing enabled.
679 681 only to call in exception handler. returns true if traceback
680 682 printed.'''
681 683 if self.tracebackflag:
682 684 if exc:
683 685 traceback.print_exception(exc[0], exc[1], exc[2], file=self.ferr)
684 686 else:
685 687 traceback.print_exc(file=self.ferr)
686 688 return self.tracebackflag
687 689
688 690 def geteditor(self):
689 691 '''return editor to use'''
690 692 if sys.platform == 'plan9':
691 693 # vi is the MIPS instruction simulator on Plan 9. We
692 694 # instead default to E to plumb commit messages to
693 695 # avoid confusion.
694 696 editor = 'E'
695 697 else:
696 698 editor = 'vi'
697 699 return (os.environ.get("HGEDITOR") or
698 700 self.config("ui", "editor") or
699 701 os.environ.get("VISUAL") or
700 702 os.environ.get("EDITOR", editor))
701 703
702 704 def progress(self, topic, pos, item="", unit="", total=None):
703 705 '''show a progress message
704 706
705 707 With stock hg, this is simply a debug message that is hidden
706 708 by default, but with extensions or GUI tools it may be
707 709 visible. 'topic' is the current operation, 'item' is a
708 710 non-numeric marker of the current position (ie the currently
709 711 in-process file), 'pos' is the current numeric position (ie
710 712 revision, bytes, etc.), unit is a corresponding unit label,
711 713 and total is the highest expected pos.
712 714
713 715 Multiple nested topics may be active at a time.
714 716
715 717 All topics should be marked closed by setting pos to None at
716 718 termination.
717 719 '''
718 720
719 721 if pos is None or not self.debugflag:
720 722 return
721 723
722 724 if unit:
723 725 unit = ' ' + unit
724 726 if item:
725 727 item = ' ' + item
726 728
727 729 if total:
728 730 pct = 100.0 * pos / total
729 731 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
730 732 % (topic, item, pos, total, unit, pct))
731 733 else:
732 734 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
733 735
734 736 def log(self, service, message):
735 737 '''hook for logging facility extensions
736 738
737 739 service should be a readily-identifiable subsystem, which will
738 740 allow filtering.
739 741 message should be a newline-terminated string to log.
740 742 '''
741 743 pass
742 744
743 745 def label(self, msg, label):
744 746 '''style msg based on supplied label
745 747
746 748 Like ui.write(), this just returns msg unchanged, but extensions
747 749 and GUI tools can override it to allow styling output without
748 750 writing it.
749 751
750 752 ui.write(s, 'label') is equivalent to
751 753 ui.write(ui.label(s, 'label')).
752 754 '''
753 755 return msg
General Comments 0
You need to be logged in to leave comments. Login now