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