##// END OF EJS Templates
ui: add logging hook
Matt Mackall -
r11984:2db0fccc default
parent child Browse files
Show More
@@ -1,606 +1,615 b''
1 # ui.py - user interface bits for mercurial
1 # ui.py - user interface bits for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from i18n import _
8 from i18n import _
9 import errno, getpass, os, socket, sys, tempfile, traceback
9 import errno, getpass, os, socket, sys, tempfile, traceback
10 import config, util, error
10 import config, util, error
11
11
12 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True,
12 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True,
13 '0': False, 'no': False, 'false': False, 'off': False}
13 '0': False, 'no': False, 'false': False, 'off': False}
14
14
15 class ui(object):
15 class ui(object):
16 def __init__(self, src=None):
16 def __init__(self, src=None):
17 self._buffers = []
17 self._buffers = []
18 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
18 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
19 self._reportuntrusted = True
19 self._reportuntrusted = True
20 self._ocfg = config.config() # overlay
20 self._ocfg = config.config() # overlay
21 self._tcfg = config.config() # trusted
21 self._tcfg = config.config() # trusted
22 self._ucfg = config.config() # untrusted
22 self._ucfg = config.config() # untrusted
23 self._trustusers = set()
23 self._trustusers = set()
24 self._trustgroups = set()
24 self._trustgroups = set()
25
25
26 if src:
26 if src:
27 self._tcfg = src._tcfg.copy()
27 self._tcfg = src._tcfg.copy()
28 self._ucfg = src._ucfg.copy()
28 self._ucfg = src._ucfg.copy()
29 self._ocfg = src._ocfg.copy()
29 self._ocfg = src._ocfg.copy()
30 self._trustusers = src._trustusers.copy()
30 self._trustusers = src._trustusers.copy()
31 self._trustgroups = src._trustgroups.copy()
31 self._trustgroups = src._trustgroups.copy()
32 self.environ = src.environ
32 self.environ = src.environ
33 self.fixconfig()
33 self.fixconfig()
34 else:
34 else:
35 # shared read-only environment
35 # shared read-only environment
36 self.environ = os.environ
36 self.environ = os.environ
37 # we always trust global config files
37 # we always trust global config files
38 for f in util.rcpath():
38 for f in util.rcpath():
39 self.readconfig(f, trust=True)
39 self.readconfig(f, trust=True)
40
40
41 def copy(self):
41 def copy(self):
42 return self.__class__(self)
42 return self.__class__(self)
43
43
44 def _is_trusted(self, fp, f):
44 def _is_trusted(self, fp, f):
45 st = util.fstat(fp)
45 st = util.fstat(fp)
46 if util.isowner(st):
46 if util.isowner(st):
47 return True
47 return True
48
48
49 tusers, tgroups = self._trustusers, self._trustgroups
49 tusers, tgroups = self._trustusers, self._trustgroups
50 if '*' in tusers or '*' in tgroups:
50 if '*' in tusers or '*' in tgroups:
51 return True
51 return True
52
52
53 user = util.username(st.st_uid)
53 user = util.username(st.st_uid)
54 group = util.groupname(st.st_gid)
54 group = util.groupname(st.st_gid)
55 if user in tusers or group in tgroups or user == util.username():
55 if user in tusers or group in tgroups or user == util.username():
56 return True
56 return True
57
57
58 if self._reportuntrusted:
58 if self._reportuntrusted:
59 self.warn(_('Not trusting file %s from untrusted '
59 self.warn(_('Not trusting file %s from untrusted '
60 'user %s, group %s\n') % (f, user, group))
60 'user %s, group %s\n') % (f, user, group))
61 return False
61 return False
62
62
63 def readconfig(self, filename, root=None, trust=False,
63 def readconfig(self, filename, root=None, trust=False,
64 sections=None, remap=None):
64 sections=None, remap=None):
65 try:
65 try:
66 fp = open(filename)
66 fp = open(filename)
67 except IOError:
67 except IOError:
68 if not sections: # ignore unless we were looking for something
68 if not sections: # ignore unless we were looking for something
69 return
69 return
70 raise
70 raise
71
71
72 cfg = config.config()
72 cfg = config.config()
73 trusted = sections or trust or self._is_trusted(fp, filename)
73 trusted = sections or trust or self._is_trusted(fp, filename)
74
74
75 try:
75 try:
76 cfg.read(filename, fp, sections=sections, remap=remap)
76 cfg.read(filename, fp, sections=sections, remap=remap)
77 except error.ConfigError, inst:
77 except error.ConfigError, inst:
78 if trusted:
78 if trusted:
79 raise
79 raise
80 self.warn(_("Ignored: %s\n") % str(inst))
80 self.warn(_("Ignored: %s\n") % str(inst))
81
81
82 if self.plain():
82 if self.plain():
83 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
83 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
84 'logtemplate', 'style',
84 'logtemplate', 'style',
85 'traceback', 'verbose'):
85 'traceback', 'verbose'):
86 if k in cfg['ui']:
86 if k in cfg['ui']:
87 del cfg['ui'][k]
87 del cfg['ui'][k]
88 for k, v in cfg.items('alias'):
88 for k, v in cfg.items('alias'):
89 del cfg['alias'][k]
89 del cfg['alias'][k]
90 for k, v in cfg.items('defaults'):
90 for k, v in cfg.items('defaults'):
91 del cfg['defaults'][k]
91 del cfg['defaults'][k]
92
92
93 if trusted:
93 if trusted:
94 self._tcfg.update(cfg)
94 self._tcfg.update(cfg)
95 self._tcfg.update(self._ocfg)
95 self._tcfg.update(self._ocfg)
96 self._ucfg.update(cfg)
96 self._ucfg.update(cfg)
97 self._ucfg.update(self._ocfg)
97 self._ucfg.update(self._ocfg)
98
98
99 if root is None:
99 if root is None:
100 root = os.path.expanduser('~')
100 root = os.path.expanduser('~')
101 self.fixconfig(root=root)
101 self.fixconfig(root=root)
102
102
103 def fixconfig(self, root=None):
103 def fixconfig(self, root=None):
104 # translate paths relative to root (or home) into absolute paths
104 # translate paths relative to root (or home) into absolute paths
105 root = root or os.getcwd()
105 root = root or os.getcwd()
106 for c in self._tcfg, self._ucfg, self._ocfg:
106 for c in self._tcfg, self._ucfg, self._ocfg:
107 for n, p in c.items('paths'):
107 for n, p in c.items('paths'):
108 if p and "://" not in p and not os.path.isabs(p):
108 if p and "://" not in p and not os.path.isabs(p):
109 c.set("paths", n, os.path.normpath(os.path.join(root, p)))
109 c.set("paths", n, os.path.normpath(os.path.join(root, p)))
110
110
111 # update ui options
111 # update ui options
112 self.debugflag = self.configbool('ui', 'debug')
112 self.debugflag = self.configbool('ui', 'debug')
113 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
113 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
114 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
114 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
115 if self.verbose and self.quiet:
115 if self.verbose and self.quiet:
116 self.quiet = self.verbose = False
116 self.quiet = self.verbose = False
117 self._reportuntrusted = self.configbool("ui", "report_untrusted", True)
117 self._reportuntrusted = self.configbool("ui", "report_untrusted", True)
118 self.tracebackflag = self.configbool('ui', 'traceback', False)
118 self.tracebackflag = self.configbool('ui', 'traceback', False)
119
119
120 # update trust information
120 # update trust information
121 self._trustusers.update(self.configlist('trusted', 'users'))
121 self._trustusers.update(self.configlist('trusted', 'users'))
122 self._trustgroups.update(self.configlist('trusted', 'groups'))
122 self._trustgroups.update(self.configlist('trusted', 'groups'))
123
123
124 def setconfig(self, section, name, value, overlay=True):
124 def setconfig(self, section, name, value, overlay=True):
125 if overlay:
125 if overlay:
126 self._ocfg.set(section, name, value)
126 self._ocfg.set(section, name, value)
127 self._tcfg.set(section, name, value)
127 self._tcfg.set(section, name, value)
128 self._ucfg.set(section, name, value)
128 self._ucfg.set(section, name, value)
129 self.fixconfig()
129 self.fixconfig()
130
130
131 def _data(self, untrusted):
131 def _data(self, untrusted):
132 return untrusted and self._ucfg or self._tcfg
132 return untrusted and self._ucfg or self._tcfg
133
133
134 def configsource(self, section, name, untrusted=False):
134 def configsource(self, section, name, untrusted=False):
135 return self._data(untrusted).source(section, name) or 'none'
135 return self._data(untrusted).source(section, name) or 'none'
136
136
137 def config(self, section, name, default=None, untrusted=False):
137 def config(self, section, name, default=None, untrusted=False):
138 value = self._data(untrusted).get(section, name, default)
138 value = self._data(untrusted).get(section, name, default)
139 if self.debugflag and not untrusted and self._reportuntrusted:
139 if self.debugflag and not untrusted and self._reportuntrusted:
140 uvalue = self._ucfg.get(section, name)
140 uvalue = self._ucfg.get(section, name)
141 if uvalue is not None and uvalue != value:
141 if uvalue is not None and uvalue != value:
142 self.debug(_("ignoring untrusted configuration option "
142 self.debug(_("ignoring untrusted configuration option "
143 "%s.%s = %s\n") % (section, name, uvalue))
143 "%s.%s = %s\n") % (section, name, uvalue))
144 return value
144 return value
145
145
146 def configbool(self, section, name, default=False, untrusted=False):
146 def configbool(self, section, name, default=False, untrusted=False):
147 v = self.config(section, name, None, untrusted)
147 v = self.config(section, name, None, untrusted)
148 if v is None:
148 if v is None:
149 return default
149 return default
150 if isinstance(v, bool):
150 if isinstance(v, bool):
151 return v
151 return v
152 if v.lower() not in _booleans:
152 if v.lower() not in _booleans:
153 raise error.ConfigError(_("%s.%s not a boolean ('%s')")
153 raise error.ConfigError(_("%s.%s not a boolean ('%s')")
154 % (section, name, v))
154 % (section, name, v))
155 return _booleans[v.lower()]
155 return _booleans[v.lower()]
156
156
157 def configlist(self, section, name, default=None, untrusted=False):
157 def configlist(self, section, name, default=None, untrusted=False):
158 """Return a list of comma/space separated strings"""
158 """Return a list of comma/space separated strings"""
159
159
160 def _parse_plain(parts, s, offset):
160 def _parse_plain(parts, s, offset):
161 whitespace = False
161 whitespace = False
162 while offset < len(s) and (s[offset].isspace() or s[offset] == ','):
162 while offset < len(s) and (s[offset].isspace() or s[offset] == ','):
163 whitespace = True
163 whitespace = True
164 offset += 1
164 offset += 1
165 if offset >= len(s):
165 if offset >= len(s):
166 return None, parts, offset
166 return None, parts, offset
167 if whitespace:
167 if whitespace:
168 parts.append('')
168 parts.append('')
169 if s[offset] == '"' and not parts[-1]:
169 if s[offset] == '"' and not parts[-1]:
170 return _parse_quote, parts, offset + 1
170 return _parse_quote, parts, offset + 1
171 elif s[offset] == '"' and parts[-1][-1] == '\\':
171 elif s[offset] == '"' and parts[-1][-1] == '\\':
172 parts[-1] = parts[-1][:-1] + s[offset]
172 parts[-1] = parts[-1][:-1] + s[offset]
173 return _parse_plain, parts, offset + 1
173 return _parse_plain, parts, offset + 1
174 parts[-1] += s[offset]
174 parts[-1] += s[offset]
175 return _parse_plain, parts, offset + 1
175 return _parse_plain, parts, offset + 1
176
176
177 def _parse_quote(parts, s, offset):
177 def _parse_quote(parts, s, offset):
178 if offset < len(s) and s[offset] == '"': # ""
178 if offset < len(s) and s[offset] == '"': # ""
179 parts.append('')
179 parts.append('')
180 offset += 1
180 offset += 1
181 while offset < len(s) and (s[offset].isspace() or
181 while offset < len(s) and (s[offset].isspace() or
182 s[offset] == ','):
182 s[offset] == ','):
183 offset += 1
183 offset += 1
184 return _parse_plain, parts, offset
184 return _parse_plain, parts, offset
185
185
186 while offset < len(s) and s[offset] != '"':
186 while offset < len(s) and s[offset] != '"':
187 if (s[offset] == '\\' and offset + 1 < len(s)
187 if (s[offset] == '\\' and offset + 1 < len(s)
188 and s[offset + 1] == '"'):
188 and s[offset + 1] == '"'):
189 offset += 1
189 offset += 1
190 parts[-1] += '"'
190 parts[-1] += '"'
191 else:
191 else:
192 parts[-1] += s[offset]
192 parts[-1] += s[offset]
193 offset += 1
193 offset += 1
194
194
195 if offset >= len(s):
195 if offset >= len(s):
196 real_parts = _configlist(parts[-1])
196 real_parts = _configlist(parts[-1])
197 if not real_parts:
197 if not real_parts:
198 parts[-1] = '"'
198 parts[-1] = '"'
199 else:
199 else:
200 real_parts[0] = '"' + real_parts[0]
200 real_parts[0] = '"' + real_parts[0]
201 parts = parts[:-1]
201 parts = parts[:-1]
202 parts.extend(real_parts)
202 parts.extend(real_parts)
203 return None, parts, offset
203 return None, parts, offset
204
204
205 offset += 1
205 offset += 1
206 while offset < len(s) and s[offset] in [' ', ',']:
206 while offset < len(s) and s[offset] in [' ', ',']:
207 offset += 1
207 offset += 1
208
208
209 if offset < len(s):
209 if offset < len(s):
210 if offset + 1 == len(s) and s[offset] == '"':
210 if offset + 1 == len(s) and s[offset] == '"':
211 parts[-1] += '"'
211 parts[-1] += '"'
212 offset += 1
212 offset += 1
213 else:
213 else:
214 parts.append('')
214 parts.append('')
215 else:
215 else:
216 return None, parts, offset
216 return None, parts, offset
217
217
218 return _parse_plain, parts, offset
218 return _parse_plain, parts, offset
219
219
220 def _configlist(s):
220 def _configlist(s):
221 s = s.rstrip(' ,')
221 s = s.rstrip(' ,')
222 if not s:
222 if not s:
223 return []
223 return []
224 parser, parts, offset = _parse_plain, [''], 0
224 parser, parts, offset = _parse_plain, [''], 0
225 while parser:
225 while parser:
226 parser, parts, offset = parser(parts, s, offset)
226 parser, parts, offset = parser(parts, s, offset)
227 return parts
227 return parts
228
228
229 result = self.config(section, name, untrusted=untrusted)
229 result = self.config(section, name, untrusted=untrusted)
230 if result is None:
230 if result is None:
231 result = default or []
231 result = default or []
232 if isinstance(result, basestring):
232 if isinstance(result, basestring):
233 result = _configlist(result.lstrip(' ,\n'))
233 result = _configlist(result.lstrip(' ,\n'))
234 if result is None:
234 if result is None:
235 result = default or []
235 result = default or []
236 return result
236 return result
237
237
238 def has_section(self, section, untrusted=False):
238 def has_section(self, section, untrusted=False):
239 '''tell whether section exists in config.'''
239 '''tell whether section exists in config.'''
240 return section in self._data(untrusted)
240 return section in self._data(untrusted)
241
241
242 def configitems(self, section, untrusted=False):
242 def configitems(self, section, untrusted=False):
243 items = self._data(untrusted).items(section)
243 items = self._data(untrusted).items(section)
244 if self.debugflag and not untrusted and self._reportuntrusted:
244 if self.debugflag and not untrusted and self._reportuntrusted:
245 for k, v in self._ucfg.items(section):
245 for k, v in self._ucfg.items(section):
246 if self._tcfg.get(section, k) != v:
246 if self._tcfg.get(section, k) != v:
247 self.debug(_("ignoring untrusted configuration option "
247 self.debug(_("ignoring untrusted configuration option "
248 "%s.%s = %s\n") % (section, k, v))
248 "%s.%s = %s\n") % (section, k, v))
249 return items
249 return items
250
250
251 def walkconfig(self, untrusted=False):
251 def walkconfig(self, untrusted=False):
252 cfg = self._data(untrusted)
252 cfg = self._data(untrusted)
253 for section in cfg.sections():
253 for section in cfg.sections():
254 for name, value in self.configitems(section, untrusted):
254 for name, value in self.configitems(section, untrusted):
255 yield section, name, str(value).replace('\n', '\\n')
255 yield section, name, str(value).replace('\n', '\\n')
256
256
257 def plain(self):
257 def plain(self):
258 '''is plain mode active?
258 '''is plain mode active?
259
259
260 Plain mode means that all configuration variables which affect the
260 Plain mode means that all configuration variables which affect the
261 behavior and output of Mercurial should be ignored. Additionally, the
261 behavior and output of Mercurial should be ignored. Additionally, the
262 output should be stable, reproducible and suitable for use in scripts or
262 output should be stable, reproducible and suitable for use in scripts or
263 applications.
263 applications.
264
264
265 The only way to trigger plain mode is by setting the `HGPLAIN'
265 The only way to trigger plain mode is by setting the `HGPLAIN'
266 environment variable.
266 environment variable.
267 '''
267 '''
268 return 'HGPLAIN' in os.environ
268 return 'HGPLAIN' in os.environ
269
269
270 def username(self):
270 def username(self):
271 """Return default username to be used in commits.
271 """Return default username to be used in commits.
272
272
273 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
273 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
274 and stop searching if one of these is set.
274 and stop searching if one of these is set.
275 If not found and ui.askusername is True, ask the user, else use
275 If not found and ui.askusername is True, ask the user, else use
276 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
276 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
277 """
277 """
278 user = os.environ.get("HGUSER")
278 user = os.environ.get("HGUSER")
279 if user is None:
279 if user is None:
280 user = self.config("ui", "username")
280 user = self.config("ui", "username")
281 if user is not None:
281 if user is not None:
282 user = os.path.expandvars(user)
282 user = os.path.expandvars(user)
283 if user is None:
283 if user is None:
284 user = os.environ.get("EMAIL")
284 user = os.environ.get("EMAIL")
285 if user is None and self.configbool("ui", "askusername"):
285 if user is None and self.configbool("ui", "askusername"):
286 user = self.prompt(_("enter a commit username:"), default=None)
286 user = self.prompt(_("enter a commit username:"), default=None)
287 if user is None and not self.interactive():
287 if user is None and not self.interactive():
288 try:
288 try:
289 user = '%s@%s' % (util.getuser(), socket.getfqdn())
289 user = '%s@%s' % (util.getuser(), socket.getfqdn())
290 self.warn(_("No username found, using '%s' instead\n") % user)
290 self.warn(_("No username found, using '%s' instead\n") % user)
291 except KeyError:
291 except KeyError:
292 pass
292 pass
293 if not user:
293 if not user:
294 raise util.Abort(_('no username supplied (see "hg help config")'))
294 raise util.Abort(_('no username supplied (see "hg help config")'))
295 if "\n" in user:
295 if "\n" in user:
296 raise util.Abort(_("username %s contains a newline\n") % repr(user))
296 raise util.Abort(_("username %s contains a newline\n") % repr(user))
297 return user
297 return user
298
298
299 def shortuser(self, user):
299 def shortuser(self, user):
300 """Return a short representation of a user name or email address."""
300 """Return a short representation of a user name or email address."""
301 if not self.verbose:
301 if not self.verbose:
302 user = util.shortuser(user)
302 user = util.shortuser(user)
303 return user
303 return user
304
304
305 def _path(self, loc):
305 def _path(self, loc):
306 p = self.config('paths', loc)
306 p = self.config('paths', loc)
307 if p:
307 if p:
308 if '%%' in p:
308 if '%%' in p:
309 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n") %
309 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n") %
310 (loc, p, self.configsource('paths', loc)))
310 (loc, p, self.configsource('paths', loc)))
311 p = p.replace('%%', '%')
311 p = p.replace('%%', '%')
312 p = util.expandpath(p)
312 p = util.expandpath(p)
313 return p
313 return p
314
314
315 def expandpath(self, loc, default=None):
315 def expandpath(self, loc, default=None):
316 """Return repository location relative to cwd or from [paths]"""
316 """Return repository location relative to cwd or from [paths]"""
317 if "://" in loc or os.path.isdir(os.path.join(loc, '.hg')):
317 if "://" in loc or os.path.isdir(os.path.join(loc, '.hg')):
318 return loc
318 return loc
319
319
320 path = self._path(loc)
320 path = self._path(loc)
321 if not path and default is not None:
321 if not path and default is not None:
322 path = self._path(default)
322 path = self._path(default)
323 return path or loc
323 return path or loc
324
324
325 def pushbuffer(self):
325 def pushbuffer(self):
326 self._buffers.append([])
326 self._buffers.append([])
327
327
328 def popbuffer(self, labeled=False):
328 def popbuffer(self, labeled=False):
329 '''pop the last buffer and return the buffered output
329 '''pop the last buffer and return the buffered output
330
330
331 If labeled is True, any labels associated with buffered
331 If labeled is True, any labels associated with buffered
332 output will be handled. By default, this has no effect
332 output will be handled. By default, this has no effect
333 on the output returned, but extensions and GUI tools may
333 on the output returned, but extensions and GUI tools may
334 handle this argument and returned styled output. If output
334 handle this argument and returned styled output. If output
335 is being buffered so it can be captured and parsed or
335 is being buffered so it can be captured and parsed or
336 processed, labeled should not be set to True.
336 processed, labeled should not be set to True.
337 '''
337 '''
338 return "".join(self._buffers.pop())
338 return "".join(self._buffers.pop())
339
339
340 def write(self, *args, **opts):
340 def write(self, *args, **opts):
341 '''write args to output
341 '''write args to output
342
342
343 By default, this method simply writes to the buffer or stdout,
343 By default, this method simply writes to the buffer or stdout,
344 but extensions or GUI tools may override this method,
344 but extensions or GUI tools may override this method,
345 write_err(), popbuffer(), and label() to style output from
345 write_err(), popbuffer(), and label() to style output from
346 various parts of hg.
346 various parts of hg.
347
347
348 An optional keyword argument, "label", can be passed in.
348 An optional keyword argument, "label", can be passed in.
349 This should be a string containing label names separated by
349 This should be a string containing label names separated by
350 space. Label names take the form of "topic.type". For example,
350 space. Label names take the form of "topic.type". For example,
351 ui.debug() issues a label of "ui.debug".
351 ui.debug() issues a label of "ui.debug".
352
352
353 When labeling output for a specific command, a label of
353 When labeling output for a specific command, a label of
354 "cmdname.type" is recommended. For example, status issues
354 "cmdname.type" is recommended. For example, status issues
355 a label of "status.modified" for modified files.
355 a label of "status.modified" for modified files.
356 '''
356 '''
357 if self._buffers:
357 if self._buffers:
358 self._buffers[-1].extend([str(a) for a in args])
358 self._buffers[-1].extend([str(a) for a in args])
359 else:
359 else:
360 for a in args:
360 for a in args:
361 sys.stdout.write(str(a))
361 sys.stdout.write(str(a))
362
362
363 def write_err(self, *args, **opts):
363 def write_err(self, *args, **opts):
364 try:
364 try:
365 if not getattr(sys.stdout, 'closed', False):
365 if not getattr(sys.stdout, 'closed', False):
366 sys.stdout.flush()
366 sys.stdout.flush()
367 for a in args:
367 for a in args:
368 sys.stderr.write(str(a))
368 sys.stderr.write(str(a))
369 # stderr may be buffered under win32 when redirected to files,
369 # stderr may be buffered under win32 when redirected to files,
370 # including stdout.
370 # including stdout.
371 if not getattr(sys.stderr, 'closed', False):
371 if not getattr(sys.stderr, 'closed', False):
372 sys.stderr.flush()
372 sys.stderr.flush()
373 except IOError, inst:
373 except IOError, inst:
374 if inst.errno not in (errno.EPIPE, errno.EIO):
374 if inst.errno not in (errno.EPIPE, errno.EIO):
375 raise
375 raise
376
376
377 def flush(self):
377 def flush(self):
378 try: sys.stdout.flush()
378 try: sys.stdout.flush()
379 except: pass
379 except: pass
380 try: sys.stderr.flush()
380 try: sys.stderr.flush()
381 except: pass
381 except: pass
382
382
383 def interactive(self):
383 def interactive(self):
384 '''is interactive input allowed?
384 '''is interactive input allowed?
385
385
386 An interactive session is a session where input can be reasonably read
386 An interactive session is a session where input can be reasonably read
387 from `sys.stdin'. If this function returns false, any attempt to read
387 from `sys.stdin'. If this function returns false, any attempt to read
388 from stdin should fail with an error, unless a sensible default has been
388 from stdin should fail with an error, unless a sensible default has been
389 specified.
389 specified.
390
390
391 Interactiveness is triggered by the value of the `ui.interactive'
391 Interactiveness is triggered by the value of the `ui.interactive'
392 configuration variable or - if it is unset - when `sys.stdin' points
392 configuration variable or - if it is unset - when `sys.stdin' points
393 to a terminal device.
393 to a terminal device.
394
394
395 This function refers to input only; for output, see `ui.formatted()'.
395 This function refers to input only; for output, see `ui.formatted()'.
396 '''
396 '''
397 i = self.configbool("ui", "interactive", None)
397 i = self.configbool("ui", "interactive", None)
398 if i is None:
398 if i is None:
399 try:
399 try:
400 return sys.stdin.isatty()
400 return sys.stdin.isatty()
401 except AttributeError:
401 except AttributeError:
402 # some environments replace stdin without implementing isatty
402 # some environments replace stdin without implementing isatty
403 # usually those are non-interactive
403 # usually those are non-interactive
404 return False
404 return False
405
405
406 return i
406 return i
407
407
408 def formatted(self):
408 def formatted(self):
409 '''should formatted output be used?
409 '''should formatted output be used?
410
410
411 It is often desirable to format the output to suite the output medium.
411 It is often desirable to format the output to suite the output medium.
412 Examples of this are truncating long lines or colorizing messages.
412 Examples of this are truncating long lines or colorizing messages.
413 However, this is not often not desirable when piping output into other
413 However, this is not often not desirable when piping output into other
414 utilities, e.g. `grep'.
414 utilities, e.g. `grep'.
415
415
416 Formatted output is triggered by the value of the `ui.formatted'
416 Formatted output is triggered by the value of the `ui.formatted'
417 configuration variable or - if it is unset - when `sys.stdout' points
417 configuration variable or - if it is unset - when `sys.stdout' points
418 to a terminal device. Please note that `ui.formatted' should be
418 to a terminal device. Please note that `ui.formatted' should be
419 considered an implementation detail; it is not intended for use outside
419 considered an implementation detail; it is not intended for use outside
420 Mercurial or its extensions.
420 Mercurial or its extensions.
421
421
422 This function refers to output only; for input, see `ui.interactive()'.
422 This function refers to output only; for input, see `ui.interactive()'.
423 This function always returns false when in plain mode, see `ui.plain()'.
423 This function always returns false when in plain mode, see `ui.plain()'.
424 '''
424 '''
425 if self.plain():
425 if self.plain():
426 return False
426 return False
427
427
428 i = self.configbool("ui", "formatted", None)
428 i = self.configbool("ui", "formatted", None)
429 if i is None:
429 if i is None:
430 try:
430 try:
431 return sys.stdout.isatty()
431 return sys.stdout.isatty()
432 except AttributeError:
432 except AttributeError:
433 # some environments replace stdout without implementing isatty
433 # some environments replace stdout without implementing isatty
434 # usually those are non-interactive
434 # usually those are non-interactive
435 return False
435 return False
436
436
437 return i
437 return i
438
438
439 def _readline(self, prompt=''):
439 def _readline(self, prompt=''):
440 if sys.stdin.isatty():
440 if sys.stdin.isatty():
441 try:
441 try:
442 # magically add command line editing support, where
442 # magically add command line editing support, where
443 # available
443 # available
444 import readline
444 import readline
445 # force demandimport to really load the module
445 # force demandimport to really load the module
446 readline.read_history_file
446 readline.read_history_file
447 # windows sometimes raises something other than ImportError
447 # windows sometimes raises something other than ImportError
448 except Exception:
448 except Exception:
449 pass
449 pass
450 line = raw_input(prompt)
450 line = raw_input(prompt)
451 # When stdin is in binary mode on Windows, it can cause
451 # When stdin is in binary mode on Windows, it can cause
452 # raw_input() to emit an extra trailing carriage return
452 # raw_input() to emit an extra trailing carriage return
453 if os.linesep == '\r\n' and line and line[-1] == '\r':
453 if os.linesep == '\r\n' and line and line[-1] == '\r':
454 line = line[:-1]
454 line = line[:-1]
455 return line
455 return line
456
456
457 def prompt(self, msg, default="y"):
457 def prompt(self, msg, default="y"):
458 """Prompt user with msg, read response.
458 """Prompt user with msg, read response.
459 If ui is not interactive, the default is returned.
459 If ui is not interactive, the default is returned.
460 """
460 """
461 if not self.interactive():
461 if not self.interactive():
462 self.write(msg, ' ', default, "\n")
462 self.write(msg, ' ', default, "\n")
463 return default
463 return default
464 try:
464 try:
465 r = self._readline(msg + ' ')
465 r = self._readline(msg + ' ')
466 if not r:
466 if not r:
467 return default
467 return default
468 return r
468 return r
469 except EOFError:
469 except EOFError:
470 raise util.Abort(_('response expected'))
470 raise util.Abort(_('response expected'))
471
471
472 def promptchoice(self, msg, choices, default=0):
472 def promptchoice(self, msg, choices, default=0):
473 """Prompt user with msg, read response, and ensure it matches
473 """Prompt user with msg, read response, and ensure it matches
474 one of the provided choices. The index of the choice is returned.
474 one of the provided choices. The index of the choice is returned.
475 choices is a sequence of acceptable responses with the format:
475 choices is a sequence of acceptable responses with the format:
476 ('&None', 'E&xec', 'Sym&link') Responses are case insensitive.
476 ('&None', 'E&xec', 'Sym&link') Responses are case insensitive.
477 If ui is not interactive, the default is returned.
477 If ui is not interactive, the default is returned.
478 """
478 """
479 resps = [s[s.index('&')+1].lower() for s in choices]
479 resps = [s[s.index('&')+1].lower() for s in choices]
480 while True:
480 while True:
481 r = self.prompt(msg, resps[default])
481 r = self.prompt(msg, resps[default])
482 if r.lower() in resps:
482 if r.lower() in resps:
483 return resps.index(r.lower())
483 return resps.index(r.lower())
484 self.write(_("unrecognized response\n"))
484 self.write(_("unrecognized response\n"))
485
485
486 def getpass(self, prompt=None, default=None):
486 def getpass(self, prompt=None, default=None):
487 if not self.interactive():
487 if not self.interactive():
488 return default
488 return default
489 try:
489 try:
490 return getpass.getpass(prompt or _('password: '))
490 return getpass.getpass(prompt or _('password: '))
491 except EOFError:
491 except EOFError:
492 raise util.Abort(_('response expected'))
492 raise util.Abort(_('response expected'))
493 def status(self, *msg, **opts):
493 def status(self, *msg, **opts):
494 '''write status message to output (if ui.quiet is False)
494 '''write status message to output (if ui.quiet is False)
495
495
496 This adds an output label of "ui.status".
496 This adds an output label of "ui.status".
497 '''
497 '''
498 if not self.quiet:
498 if not self.quiet:
499 opts['label'] = opts.get('label', '') + ' ui.status'
499 opts['label'] = opts.get('label', '') + ' ui.status'
500 self.write(*msg, **opts)
500 self.write(*msg, **opts)
501 def warn(self, *msg, **opts):
501 def warn(self, *msg, **opts):
502 '''write warning message to output (stderr)
502 '''write warning message to output (stderr)
503
503
504 This adds an output label of "ui.warning".
504 This adds an output label of "ui.warning".
505 '''
505 '''
506 opts['label'] = opts.get('label', '') + ' ui.warning'
506 opts['label'] = opts.get('label', '') + ' ui.warning'
507 self.write_err(*msg, **opts)
507 self.write_err(*msg, **opts)
508 def note(self, *msg, **opts):
508 def note(self, *msg, **opts):
509 '''write note to output (if ui.verbose is True)
509 '''write note to output (if ui.verbose is True)
510
510
511 This adds an output label of "ui.note".
511 This adds an output label of "ui.note".
512 '''
512 '''
513 if self.verbose:
513 if self.verbose:
514 opts['label'] = opts.get('label', '') + ' ui.note'
514 opts['label'] = opts.get('label', '') + ' ui.note'
515 self.write(*msg, **opts)
515 self.write(*msg, **opts)
516 def debug(self, *msg, **opts):
516 def debug(self, *msg, **opts):
517 '''write debug message to output (if ui.debugflag is True)
517 '''write debug message to output (if ui.debugflag is True)
518
518
519 This adds an output label of "ui.debug".
519 This adds an output label of "ui.debug".
520 '''
520 '''
521 if self.debugflag:
521 if self.debugflag:
522 opts['label'] = opts.get('label', '') + ' ui.debug'
522 opts['label'] = opts.get('label', '') + ' ui.debug'
523 self.write(*msg, **opts)
523 self.write(*msg, **opts)
524 def edit(self, text, user):
524 def edit(self, text, user):
525 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
525 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
526 text=True)
526 text=True)
527 try:
527 try:
528 f = os.fdopen(fd, "w")
528 f = os.fdopen(fd, "w")
529 f.write(text)
529 f.write(text)
530 f.close()
530 f.close()
531
531
532 editor = self.geteditor()
532 editor = self.geteditor()
533
533
534 util.system("%s \"%s\"" % (editor, name),
534 util.system("%s \"%s\"" % (editor, name),
535 environ={'HGUSER': user},
535 environ={'HGUSER': user},
536 onerr=util.Abort, errprefix=_("edit failed"))
536 onerr=util.Abort, errprefix=_("edit failed"))
537
537
538 f = open(name)
538 f = open(name)
539 t = f.read()
539 t = f.read()
540 f.close()
540 f.close()
541 finally:
541 finally:
542 os.unlink(name)
542 os.unlink(name)
543
543
544 return t
544 return t
545
545
546 def traceback(self, exc=None):
546 def traceback(self, exc=None):
547 '''print exception traceback if traceback printing enabled.
547 '''print exception traceback if traceback printing enabled.
548 only to call in exception handler. returns true if traceback
548 only to call in exception handler. returns true if traceback
549 printed.'''
549 printed.'''
550 if self.tracebackflag:
550 if self.tracebackflag:
551 if exc:
551 if exc:
552 traceback.print_exception(exc[0], exc[1], exc[2])
552 traceback.print_exception(exc[0], exc[1], exc[2])
553 else:
553 else:
554 traceback.print_exc()
554 traceback.print_exc()
555 return self.tracebackflag
555 return self.tracebackflag
556
556
557 def geteditor(self):
557 def geteditor(self):
558 '''return editor to use'''
558 '''return editor to use'''
559 return (os.environ.get("HGEDITOR") or
559 return (os.environ.get("HGEDITOR") or
560 self.config("ui", "editor") or
560 self.config("ui", "editor") or
561 os.environ.get("VISUAL") or
561 os.environ.get("VISUAL") or
562 os.environ.get("EDITOR", "vi"))
562 os.environ.get("EDITOR", "vi"))
563
563
564 def progress(self, topic, pos, item="", unit="", total=None):
564 def progress(self, topic, pos, item="", unit="", total=None):
565 '''show a progress message
565 '''show a progress message
566
566
567 With stock hg, this is simply a debug message that is hidden
567 With stock hg, this is simply a debug message that is hidden
568 by default, but with extensions or GUI tools it may be
568 by default, but with extensions or GUI tools it may be
569 visible. 'topic' is the current operation, 'item' is a
569 visible. 'topic' is the current operation, 'item' is a
570 non-numeric marker of the current position (ie the currently
570 non-numeric marker of the current position (ie the currently
571 in-process file), 'pos' is the current numeric position (ie
571 in-process file), 'pos' is the current numeric position (ie
572 revision, bytes, etc.), unit is a corresponding unit label,
572 revision, bytes, etc.), unit is a corresponding unit label,
573 and total is the highest expected pos.
573 and total is the highest expected pos.
574
574
575 Multiple nested topics may be active at a time.
575 Multiple nested topics may be active at a time.
576
576
577 All topics should be marked closed by setting pos to None at
577 All topics should be marked closed by setting pos to None at
578 termination.
578 termination.
579 '''
579 '''
580
580
581 if pos == None or not self.debugflag:
581 if pos == None or not self.debugflag:
582 return
582 return
583
583
584 if unit:
584 if unit:
585 unit = ' ' + unit
585 unit = ' ' + unit
586 if item:
586 if item:
587 item = ' ' + item
587 item = ' ' + item
588
588
589 if total:
589 if total:
590 pct = 100.0 * pos / total
590 pct = 100.0 * pos / total
591 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
591 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
592 % (topic, item, pos, total, unit, pct))
592 % (topic, item, pos, total, unit, pct))
593 else:
593 else:
594 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
594 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
595
595
596 def log(self, service, message):
597 '''hook for logging facility extensions
598
599 service should be a readily-identifiable subsystem, which will
600 allow filtering.
601 message should be a newline-terminated string to log.
602 '''
603 pass
604
596 def label(self, msg, label):
605 def label(self, msg, label):
597 '''style msg based on supplied label
606 '''style msg based on supplied label
598
607
599 Like ui.write(), this just returns msg unchanged, but extensions
608 Like ui.write(), this just returns msg unchanged, but extensions
600 and GUI tools can override it to allow styling output without
609 and GUI tools can override it to allow styling output without
601 writing it.
610 writing it.
602
611
603 ui.write(s, 'label') is equivalent to
612 ui.write(s, 'label') is equivalent to
604 ui.write(ui.label(s, 'label')).
613 ui.write(ui.label(s, 'label')).
605 '''
614 '''
606 return msg
615 return msg
General Comments 0
You need to be logged in to leave comments. Login now