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