##// END OF EJS Templates
ui: handle leading newlines/spaces/commas in configlist...
Thomas Arendsen Hein -
r11309:ef7636ef default
parent child Browse files
Show More
@@ -1,554 +1,554 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):
124 def setconfig(self, section, name, value):
125 for cfg in (self._ocfg, self._tcfg, self._ucfg):
125 for cfg in (self._ocfg, self._tcfg, self._ucfg):
126 cfg.set(section, name, value)
126 cfg.set(section, name, value)
127 self.fixconfig()
127 self.fixconfig()
128
128
129 def _data(self, untrusted):
129 def _data(self, untrusted):
130 return untrusted and self._ucfg or self._tcfg
130 return untrusted and self._ucfg or self._tcfg
131
131
132 def configsource(self, section, name, untrusted=False):
132 def configsource(self, section, name, untrusted=False):
133 return self._data(untrusted).source(section, name) or 'none'
133 return self._data(untrusted).source(section, name) or 'none'
134
134
135 def config(self, section, name, default=None, untrusted=False):
135 def config(self, section, name, default=None, untrusted=False):
136 value = self._data(untrusted).get(section, name, default)
136 value = self._data(untrusted).get(section, name, default)
137 if self.debugflag and not untrusted and self._reportuntrusted:
137 if self.debugflag and not untrusted and self._reportuntrusted:
138 uvalue = self._ucfg.get(section, name)
138 uvalue = self._ucfg.get(section, name)
139 if uvalue is not None and uvalue != value:
139 if uvalue is not None and uvalue != value:
140 self.debug(_("ignoring untrusted configuration option "
140 self.debug(_("ignoring untrusted configuration option "
141 "%s.%s = %s\n") % (section, name, uvalue))
141 "%s.%s = %s\n") % (section, name, uvalue))
142 return value
142 return value
143
143
144 def configbool(self, section, name, default=False, untrusted=False):
144 def configbool(self, section, name, default=False, untrusted=False):
145 v = self.config(section, name, None, untrusted)
145 v = self.config(section, name, None, untrusted)
146 if v is None:
146 if v is None:
147 return default
147 return default
148 if isinstance(v, bool):
148 if isinstance(v, bool):
149 return v
149 return v
150 if v.lower() not in _booleans:
150 if v.lower() not in _booleans:
151 raise error.ConfigError(_("%s.%s not a boolean ('%s')")
151 raise error.ConfigError(_("%s.%s not a boolean ('%s')")
152 % (section, name, v))
152 % (section, name, v))
153 return _booleans[v.lower()]
153 return _booleans[v.lower()]
154
154
155 def configlist(self, section, name, default=None, untrusted=False):
155 def configlist(self, section, name, default=None, untrusted=False):
156 """Return a list of comma/space separated strings"""
156 """Return a list of comma/space separated strings"""
157
157
158 def _parse_plain(parts, s, offset):
158 def _parse_plain(parts, s, offset):
159 whitespace = False
159 whitespace = False
160 while offset < len(s) and (s[offset].isspace() or s[offset] == ','):
160 while offset < len(s) and (s[offset].isspace() or s[offset] == ','):
161 whitespace = True
161 whitespace = True
162 offset += 1
162 offset += 1
163 if offset >= len(s):
163 if offset >= len(s):
164 return None, parts, offset
164 return None, parts, offset
165 if whitespace:
165 if whitespace:
166 parts.append('')
166 parts.append('')
167 if s[offset] == '"' and not parts[-1]:
167 if s[offset] == '"' and not parts[-1]:
168 return _parse_quote, parts, offset + 1
168 return _parse_quote, parts, offset + 1
169 elif s[offset] == '"' and parts[-1][-1] == '\\':
169 elif s[offset] == '"' and parts[-1][-1] == '\\':
170 parts[-1] = parts[-1][:-1] + s[offset]
170 parts[-1] = parts[-1][:-1] + s[offset]
171 return _parse_plain, parts, offset + 1
171 return _parse_plain, parts, offset + 1
172 parts[-1] += s[offset]
172 parts[-1] += s[offset]
173 return _parse_plain, parts, offset + 1
173 return _parse_plain, parts, offset + 1
174
174
175 def _parse_quote(parts, s, offset):
175 def _parse_quote(parts, s, offset):
176 if offset < len(s) and s[offset] == '"': # ""
176 if offset < len(s) and s[offset] == '"': # ""
177 parts.append('')
177 parts.append('')
178 offset += 1
178 offset += 1
179 while offset < len(s) and (s[offset].isspace() or
179 while offset < len(s) and (s[offset].isspace() or
180 s[offset] == ','):
180 s[offset] == ','):
181 offset += 1
181 offset += 1
182 return _parse_plain, parts, offset
182 return _parse_plain, parts, offset
183
183
184 while offset < len(s) and s[offset] != '"':
184 while offset < len(s) and s[offset] != '"':
185 if (s[offset] == '\\' and offset + 1 < len(s)
185 if (s[offset] == '\\' and offset + 1 < len(s)
186 and s[offset + 1] == '"'):
186 and s[offset + 1] == '"'):
187 offset += 1
187 offset += 1
188 parts[-1] += '"'
188 parts[-1] += '"'
189 else:
189 else:
190 parts[-1] += s[offset]
190 parts[-1] += s[offset]
191 offset += 1
191 offset += 1
192
192
193 if offset >= len(s):
193 if offset >= len(s):
194 real_parts = _configlist(parts[-1])
194 real_parts = _configlist(parts[-1])
195 if not real_parts:
195 if not real_parts:
196 parts[-1] = '"'
196 parts[-1] = '"'
197 else:
197 else:
198 real_parts[0] = '"' + real_parts[0]
198 real_parts[0] = '"' + real_parts[0]
199 parts = parts[:-1]
199 parts = parts[:-1]
200 parts.extend(real_parts)
200 parts.extend(real_parts)
201 return None, parts, offset
201 return None, parts, offset
202
202
203 offset += 1
203 offset += 1
204 while offset < len(s) and s[offset] in [' ', ',']:
204 while offset < len(s) and s[offset] in [' ', ',']:
205 offset += 1
205 offset += 1
206
206
207 if offset < len(s):
207 if offset < len(s):
208 if offset + 1 == len(s) and s[offset] == '"':
208 if offset + 1 == len(s) and s[offset] == '"':
209 parts[-1] += '"'
209 parts[-1] += '"'
210 offset += 1
210 offset += 1
211 else:
211 else:
212 parts.append('')
212 parts.append('')
213 else:
213 else:
214 return None, parts, offset
214 return None, parts, offset
215
215
216 return _parse_plain, parts, offset
216 return _parse_plain, parts, offset
217
217
218 def _configlist(s):
218 def _configlist(s):
219 s = s.rstrip(' ,')
219 s = s.rstrip(' ,')
220 if not s:
220 if not s:
221 return None
221 return None
222 parser, parts, offset = _parse_plain, [''], 0
222 parser, parts, offset = _parse_plain, [''], 0
223 while parser:
223 while parser:
224 parser, parts, offset = parser(parts, s, offset)
224 parser, parts, offset = parser(parts, s, offset)
225 return parts
225 return parts
226
226
227 result = self.config(section, name, untrusted=untrusted)
227 result = self.config(section, name, untrusted=untrusted)
228 if result is None:
228 if result is None:
229 result = default or []
229 result = default or []
230 if isinstance(result, basestring):
230 if isinstance(result, basestring):
231 result = _configlist(result)
231 result = _configlist(result.lstrip(' ,\n'))
232 if result is None:
232 if result is None:
233 result = default or []
233 result = default or []
234 return result
234 return result
235
235
236 def has_section(self, section, untrusted=False):
236 def has_section(self, section, untrusted=False):
237 '''tell whether section exists in config.'''
237 '''tell whether section exists in config.'''
238 return section in self._data(untrusted)
238 return section in self._data(untrusted)
239
239
240 def configitems(self, section, untrusted=False):
240 def configitems(self, section, untrusted=False):
241 items = self._data(untrusted).items(section)
241 items = self._data(untrusted).items(section)
242 if self.debugflag and not untrusted and self._reportuntrusted:
242 if self.debugflag and not untrusted and self._reportuntrusted:
243 for k, v in self._ucfg.items(section):
243 for k, v in self._ucfg.items(section):
244 if self._tcfg.get(section, k) != v:
244 if self._tcfg.get(section, k) != v:
245 self.debug(_("ignoring untrusted configuration option "
245 self.debug(_("ignoring untrusted configuration option "
246 "%s.%s = %s\n") % (section, k, v))
246 "%s.%s = %s\n") % (section, k, v))
247 return items
247 return items
248
248
249 def walkconfig(self, untrusted=False):
249 def walkconfig(self, untrusted=False):
250 cfg = self._data(untrusted)
250 cfg = self._data(untrusted)
251 for section in cfg.sections():
251 for section in cfg.sections():
252 for name, value in self.configitems(section, untrusted):
252 for name, value in self.configitems(section, untrusted):
253 yield section, name, str(value).replace('\n', '\\n')
253 yield section, name, str(value).replace('\n', '\\n')
254
254
255 def plain(self):
255 def plain(self):
256 return 'HGPLAIN' in os.environ
256 return 'HGPLAIN' in os.environ
257
257
258 def username(self):
258 def username(self):
259 """Return default username to be used in commits.
259 """Return default username to be used in commits.
260
260
261 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
261 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
262 and stop searching if one of these is set.
262 and stop searching if one of these is set.
263 If not found and ui.askusername is True, ask the user, else use
263 If not found and ui.askusername is True, ask the user, else use
264 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
264 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
265 """
265 """
266 user = os.environ.get("HGUSER")
266 user = os.environ.get("HGUSER")
267 if user is None:
267 if user is None:
268 user = self.config("ui", "username")
268 user = self.config("ui", "username")
269 if user is not None:
269 if user is not None:
270 user = os.path.expandvars(user)
270 user = os.path.expandvars(user)
271 if user is None:
271 if user is None:
272 user = os.environ.get("EMAIL")
272 user = os.environ.get("EMAIL")
273 if user is None and self.configbool("ui", "askusername"):
273 if user is None and self.configbool("ui", "askusername"):
274 user = self.prompt(_("enter a commit username:"), default=None)
274 user = self.prompt(_("enter a commit username:"), default=None)
275 if user is None and not self.interactive():
275 if user is None and not self.interactive():
276 try:
276 try:
277 user = '%s@%s' % (util.getuser(), socket.getfqdn())
277 user = '%s@%s' % (util.getuser(), socket.getfqdn())
278 self.warn(_("No username found, using '%s' instead\n") % user)
278 self.warn(_("No username found, using '%s' instead\n") % user)
279 except KeyError:
279 except KeyError:
280 pass
280 pass
281 if not user:
281 if not user:
282 raise util.Abort(_('no username supplied (see "hg help config")'))
282 raise util.Abort(_('no username supplied (see "hg help config")'))
283 if "\n" in user:
283 if "\n" in user:
284 raise util.Abort(_("username %s contains a newline\n") % repr(user))
284 raise util.Abort(_("username %s contains a newline\n") % repr(user))
285 return user
285 return user
286
286
287 def shortuser(self, user):
287 def shortuser(self, user):
288 """Return a short representation of a user name or email address."""
288 """Return a short representation of a user name or email address."""
289 if not self.verbose:
289 if not self.verbose:
290 user = util.shortuser(user)
290 user = util.shortuser(user)
291 return user
291 return user
292
292
293 def _path(self, loc):
293 def _path(self, loc):
294 p = self.config('paths', loc)
294 p = self.config('paths', loc)
295 if p:
295 if p:
296 if '%%' in p:
296 if '%%' in p:
297 self.warn("(deprecated '%%' in path %s=%s from %s)\n" %
297 self.warn("(deprecated '%%' in path %s=%s from %s)\n" %
298 (loc, p, self.configsource('paths', loc)))
298 (loc, p, self.configsource('paths', loc)))
299 p = p.replace('%%', '%')
299 p = p.replace('%%', '%')
300 p = util.expandpath(p)
300 p = util.expandpath(p)
301 return p
301 return p
302
302
303 def expandpath(self, loc, default=None):
303 def expandpath(self, loc, default=None):
304 """Return repository location relative to cwd or from [paths]"""
304 """Return repository location relative to cwd or from [paths]"""
305 if "://" in loc or os.path.isdir(os.path.join(loc, '.hg')):
305 if "://" in loc or os.path.isdir(os.path.join(loc, '.hg')):
306 return loc
306 return loc
307
307
308 path = self._path(loc)
308 path = self._path(loc)
309 if not path and default is not None:
309 if not path and default is not None:
310 path = self._path(default)
310 path = self._path(default)
311 return path or loc
311 return path or loc
312
312
313 def pushbuffer(self):
313 def pushbuffer(self):
314 self._buffers.append([])
314 self._buffers.append([])
315
315
316 def popbuffer(self, labeled=False):
316 def popbuffer(self, labeled=False):
317 '''pop the last buffer and return the buffered output
317 '''pop the last buffer and return the buffered output
318
318
319 If labeled is True, any labels associated with buffered
319 If labeled is True, any labels associated with buffered
320 output will be handled. By default, this has no effect
320 output will be handled. By default, this has no effect
321 on the output returned, but extensions and GUI tools may
321 on the output returned, but extensions and GUI tools may
322 handle this argument and returned styled output. If output
322 handle this argument and returned styled output. If output
323 is being buffered so it can be captured and parsed or
323 is being buffered so it can be captured and parsed or
324 processed, labeled should not be set to True.
324 processed, labeled should not be set to True.
325 '''
325 '''
326 return "".join(self._buffers.pop())
326 return "".join(self._buffers.pop())
327
327
328 def write(self, *args, **opts):
328 def write(self, *args, **opts):
329 '''write args to output
329 '''write args to output
330
330
331 By default, this method simply writes to the buffer or stdout,
331 By default, this method simply writes to the buffer or stdout,
332 but extensions or GUI tools may override this method,
332 but extensions or GUI tools may override this method,
333 write_err(), popbuffer(), and label() to style output from
333 write_err(), popbuffer(), and label() to style output from
334 various parts of hg.
334 various parts of hg.
335
335
336 An optional keyword argument, "label", can be passed in.
336 An optional keyword argument, "label", can be passed in.
337 This should be a string containing label names separated by
337 This should be a string containing label names separated by
338 space. Label names take the form of "topic.type". For example,
338 space. Label names take the form of "topic.type". For example,
339 ui.debug() issues a label of "ui.debug".
339 ui.debug() issues a label of "ui.debug".
340
340
341 When labeling output for a specific command, a label of
341 When labeling output for a specific command, a label of
342 "cmdname.type" is recommended. For example, status issues
342 "cmdname.type" is recommended. For example, status issues
343 a label of "status.modified" for modified files.
343 a label of "status.modified" for modified files.
344 '''
344 '''
345 if self._buffers:
345 if self._buffers:
346 self._buffers[-1].extend([str(a) for a in args])
346 self._buffers[-1].extend([str(a) for a in args])
347 else:
347 else:
348 for a in args:
348 for a in args:
349 sys.stdout.write(str(a))
349 sys.stdout.write(str(a))
350
350
351 def write_err(self, *args, **opts):
351 def write_err(self, *args, **opts):
352 try:
352 try:
353 if not getattr(sys.stdout, 'closed', False):
353 if not getattr(sys.stdout, 'closed', False):
354 sys.stdout.flush()
354 sys.stdout.flush()
355 for a in args:
355 for a in args:
356 sys.stderr.write(str(a))
356 sys.stderr.write(str(a))
357 # stderr may be buffered under win32 when redirected to files,
357 # stderr may be buffered under win32 when redirected to files,
358 # including stdout.
358 # including stdout.
359 if not getattr(sys.stderr, 'closed', False):
359 if not getattr(sys.stderr, 'closed', False):
360 sys.stderr.flush()
360 sys.stderr.flush()
361 except IOError, inst:
361 except IOError, inst:
362 if inst.errno != errno.EPIPE:
362 if inst.errno != errno.EPIPE:
363 raise
363 raise
364
364
365 def flush(self):
365 def flush(self):
366 try: sys.stdout.flush()
366 try: sys.stdout.flush()
367 except: pass
367 except: pass
368 try: sys.stderr.flush()
368 try: sys.stderr.flush()
369 except: pass
369 except: pass
370
370
371 def interactive(self):
371 def interactive(self):
372 i = self.configbool("ui", "interactive", None)
372 i = self.configbool("ui", "interactive", None)
373 if i is None:
373 if i is None:
374 try:
374 try:
375 return sys.stdin.isatty()
375 return sys.stdin.isatty()
376 except AttributeError:
376 except AttributeError:
377 # some environments replace stdin without implementing isatty
377 # some environments replace stdin without implementing isatty
378 # usually those are non-interactive
378 # usually those are non-interactive
379 return False
379 return False
380
380
381 return i
381 return i
382
382
383 def _readline(self, prompt=''):
383 def _readline(self, prompt=''):
384 if sys.stdin.isatty():
384 if sys.stdin.isatty():
385 try:
385 try:
386 # magically add command line editing support, where
386 # magically add command line editing support, where
387 # available
387 # available
388 import readline
388 import readline
389 # force demandimport to really load the module
389 # force demandimport to really load the module
390 readline.read_history_file
390 readline.read_history_file
391 # windows sometimes raises something other than ImportError
391 # windows sometimes raises something other than ImportError
392 except Exception:
392 except Exception:
393 pass
393 pass
394 line = raw_input(prompt)
394 line = raw_input(prompt)
395 # When stdin is in binary mode on Windows, it can cause
395 # When stdin is in binary mode on Windows, it can cause
396 # raw_input() to emit an extra trailing carriage return
396 # raw_input() to emit an extra trailing carriage return
397 if os.linesep == '\r\n' and line and line[-1] == '\r':
397 if os.linesep == '\r\n' and line and line[-1] == '\r':
398 line = line[:-1]
398 line = line[:-1]
399 return line
399 return line
400
400
401 def prompt(self, msg, default="y"):
401 def prompt(self, msg, default="y"):
402 """Prompt user with msg, read response.
402 """Prompt user with msg, read response.
403 If ui is not interactive, the default is returned.
403 If ui is not interactive, the default is returned.
404 """
404 """
405 if not self.interactive():
405 if not self.interactive():
406 self.write(msg, ' ', default, "\n")
406 self.write(msg, ' ', default, "\n")
407 return default
407 return default
408 try:
408 try:
409 r = self._readline(msg + ' ')
409 r = self._readline(msg + ' ')
410 if not r:
410 if not r:
411 return default
411 return default
412 return r
412 return r
413 except EOFError:
413 except EOFError:
414 raise util.Abort(_('response expected'))
414 raise util.Abort(_('response expected'))
415
415
416 def promptchoice(self, msg, choices, default=0):
416 def promptchoice(self, msg, choices, default=0):
417 """Prompt user with msg, read response, and ensure it matches
417 """Prompt user with msg, read response, and ensure it matches
418 one of the provided choices. The index of the choice is returned.
418 one of the provided choices. The index of the choice is returned.
419 choices is a sequence of acceptable responses with the format:
419 choices is a sequence of acceptable responses with the format:
420 ('&None', 'E&xec', 'Sym&link') Responses are case insensitive.
420 ('&None', 'E&xec', 'Sym&link') Responses are case insensitive.
421 If ui is not interactive, the default is returned.
421 If ui is not interactive, the default is returned.
422 """
422 """
423 resps = [s[s.index('&')+1].lower() for s in choices]
423 resps = [s[s.index('&')+1].lower() for s in choices]
424 while True:
424 while True:
425 r = self.prompt(msg, resps[default])
425 r = self.prompt(msg, resps[default])
426 if r.lower() in resps:
426 if r.lower() in resps:
427 return resps.index(r.lower())
427 return resps.index(r.lower())
428 self.write(_("unrecognized response\n"))
428 self.write(_("unrecognized response\n"))
429
429
430 def getpass(self, prompt=None, default=None):
430 def getpass(self, prompt=None, default=None):
431 if not self.interactive():
431 if not self.interactive():
432 return default
432 return default
433 try:
433 try:
434 return getpass.getpass(prompt or _('password: '))
434 return getpass.getpass(prompt or _('password: '))
435 except EOFError:
435 except EOFError:
436 raise util.Abort(_('response expected'))
436 raise util.Abort(_('response expected'))
437 def status(self, *msg, **opts):
437 def status(self, *msg, **opts):
438 '''write status message to output (if ui.quiet is False)
438 '''write status message to output (if ui.quiet is False)
439
439
440 This adds an output label of "ui.status".
440 This adds an output label of "ui.status".
441 '''
441 '''
442 if not self.quiet:
442 if not self.quiet:
443 opts['label'] = opts.get('label', '') + ' ui.status'
443 opts['label'] = opts.get('label', '') + ' ui.status'
444 self.write(*msg, **opts)
444 self.write(*msg, **opts)
445 def warn(self, *msg, **opts):
445 def warn(self, *msg, **opts):
446 '''write warning message to output (stderr)
446 '''write warning message to output (stderr)
447
447
448 This adds an output label of "ui.warning".
448 This adds an output label of "ui.warning".
449 '''
449 '''
450 opts['label'] = opts.get('label', '') + ' ui.warning'
450 opts['label'] = opts.get('label', '') + ' ui.warning'
451 self.write_err(*msg, **opts)
451 self.write_err(*msg, **opts)
452 def note(self, *msg, **opts):
452 def note(self, *msg, **opts):
453 '''write note to output (if ui.verbose is True)
453 '''write note to output (if ui.verbose is True)
454
454
455 This adds an output label of "ui.note".
455 This adds an output label of "ui.note".
456 '''
456 '''
457 if self.verbose:
457 if self.verbose:
458 opts['label'] = opts.get('label', '') + ' ui.note'
458 opts['label'] = opts.get('label', '') + ' ui.note'
459 self.write(*msg, **opts)
459 self.write(*msg, **opts)
460 def debug(self, *msg, **opts):
460 def debug(self, *msg, **opts):
461 '''write debug message to output (if ui.debugflag is True)
461 '''write debug message to output (if ui.debugflag is True)
462
462
463 This adds an output label of "ui.debug".
463 This adds an output label of "ui.debug".
464 '''
464 '''
465 if self.debugflag:
465 if self.debugflag:
466 opts['label'] = opts.get('label', '') + ' ui.debug'
466 opts['label'] = opts.get('label', '') + ' ui.debug'
467 self.write(*msg, **opts)
467 self.write(*msg, **opts)
468 def edit(self, text, user):
468 def edit(self, text, user):
469 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
469 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
470 text=True)
470 text=True)
471 try:
471 try:
472 f = os.fdopen(fd, "w")
472 f = os.fdopen(fd, "w")
473 f.write(text)
473 f.write(text)
474 f.close()
474 f.close()
475
475
476 editor = self.geteditor()
476 editor = self.geteditor()
477
477
478 util.system("%s \"%s\"" % (editor, name),
478 util.system("%s \"%s\"" % (editor, name),
479 environ={'HGUSER': user},
479 environ={'HGUSER': user},
480 onerr=util.Abort, errprefix=_("edit failed"))
480 onerr=util.Abort, errprefix=_("edit failed"))
481
481
482 f = open(name)
482 f = open(name)
483 t = f.read()
483 t = f.read()
484 f.close()
484 f.close()
485 finally:
485 finally:
486 os.unlink(name)
486 os.unlink(name)
487
487
488 return t
488 return t
489
489
490 def traceback(self, exc=None):
490 def traceback(self, exc=None):
491 '''print exception traceback if traceback printing enabled.
491 '''print exception traceback if traceback printing enabled.
492 only to call in exception handler. returns true if traceback
492 only to call in exception handler. returns true if traceback
493 printed.'''
493 printed.'''
494 if self.tracebackflag:
494 if self.tracebackflag:
495 if exc:
495 if exc:
496 traceback.print_exception(exc[0], exc[1], exc[2])
496 traceback.print_exception(exc[0], exc[1], exc[2])
497 else:
497 else:
498 traceback.print_exc()
498 traceback.print_exc()
499 return self.tracebackflag
499 return self.tracebackflag
500
500
501 def geteditor(self):
501 def geteditor(self):
502 '''return editor to use'''
502 '''return editor to use'''
503 return (os.environ.get("HGEDITOR") or
503 return (os.environ.get("HGEDITOR") or
504 self.config("ui", "editor") or
504 self.config("ui", "editor") or
505 os.environ.get("VISUAL") or
505 os.environ.get("VISUAL") or
506 os.environ.get("EDITOR", "vi"))
506 os.environ.get("EDITOR", "vi"))
507
507
508 def progress(self, topic, pos, item="", unit="", total=None):
508 def progress(self, topic, pos, item="", unit="", total=None):
509 '''show a progress message
509 '''show a progress message
510
510
511 With stock hg, this is simply a debug message that is hidden
511 With stock hg, this is simply a debug message that is hidden
512 by default, but with extensions or GUI tools it may be
512 by default, but with extensions or GUI tools it may be
513 visible. 'topic' is the current operation, 'item' is a
513 visible. 'topic' is the current operation, 'item' is a
514 non-numeric marker of the current position (ie the currently
514 non-numeric marker of the current position (ie the currently
515 in-process file), 'pos' is the current numeric position (ie
515 in-process file), 'pos' is the current numeric position (ie
516 revision, bytes, etc.), unit is a corresponding unit label,
516 revision, bytes, etc.), unit is a corresponding unit label,
517 and total is the highest expected pos.
517 and total is the highest expected pos.
518
518
519 Multiple nested topics may be active at a time.
519 Multiple nested topics may be active at a time.
520
520
521 All topics should be marked closed by setting pos to None at
521 All topics should be marked closed by setting pos to None at
522 termination.
522 termination.
523 '''
523 '''
524
524
525 if pos == None or not self.debugflag:
525 if pos == None or not self.debugflag:
526 return
526 return
527
527
528 if unit:
528 if unit:
529 unit = ' ' + unit
529 unit = ' ' + unit
530 if item:
530 if item:
531 item = ' ' + item
531 item = ' ' + item
532
532
533 if total:
533 if total:
534 pct = 100.0 * pos / total
534 pct = 100.0 * pos / total
535 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
535 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
536 % (topic, item, pos, total, unit, pct))
536 % (topic, item, pos, total, unit, pct))
537 else:
537 else:
538 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
538 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
539
539
540 def label(self, msg, label):
540 def label(self, msg, label):
541 '''style msg based on supplied label
541 '''style msg based on supplied label
542
542
543 Like ui.write(), this just returns msg unchanged, but extensions
543 Like ui.write(), this just returns msg unchanged, but extensions
544 and GUI tools can override it to allow styling output without
544 and GUI tools can override it to allow styling output without
545 writing it.
545 writing it.
546
546
547 ui.write(s, 'label') is equivalent to
547 ui.write(s, 'label') is equivalent to
548 ui.write(ui.label(s, 'label')).
548 ui.write(ui.label(s, 'label')).
549
549
550 Callers of ui.label() should pass labeled text back to
550 Callers of ui.label() should pass labeled text back to
551 ui.write() with a label of 'ui.labeled' so implementations know
551 ui.write() with a label of 'ui.labeled' so implementations know
552 that the text has already been escaped and marked up.
552 that the text has already been escaped and marked up.
553 '''
553 '''
554 return msg
554 return msg
@@ -1,81 +1,83 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2
2
3 from mercurial import ui, dispatch, error
3 from mercurial import ui, dispatch, error
4
4
5 testui = ui.ui()
5 testui = ui.ui()
6 parsed = dispatch._parseconfig(testui, [
6 parsed = dispatch._parseconfig(testui, [
7 'values.string=string value',
7 'values.string=string value',
8 'values.bool1=true',
8 'values.bool1=true',
9 'values.bool2=false',
9 'values.bool2=false',
10 'lists.list1=foo',
10 'lists.list1=foo',
11 'lists.list2=foo bar baz',
11 'lists.list2=foo bar baz',
12 'lists.list3=alice, bob',
12 'lists.list3=alice, bob',
13 'lists.list4=foo bar baz alice, bob',
13 'lists.list4=foo bar baz alice, bob',
14 'lists.list5=abc d"ef"g "hij def"',
14 'lists.list5=abc d"ef"g "hij def"',
15 'lists.list6="hello world", "how are you?"',
15 'lists.list6="hello world", "how are you?"',
16 'lists.list7=Do"Not"Separate',
16 'lists.list7=Do"Not"Separate',
17 'lists.list8="Do"Separate',
17 'lists.list8="Do"Separate',
18 'lists.list9="Do\\"NotSeparate"',
18 'lists.list9="Do\\"NotSeparate"',
19 'lists.list10=string "with extraneous" quotation mark"',
19 'lists.list10=string "with extraneous" quotation mark"',
20 'lists.list11=x, y',
20 'lists.list11=x, y',
21 'lists.list12="x", "y"',
21 'lists.list12="x", "y"',
22 'lists.list13=""" key = "x", "y" """',
22 'lists.list13=""" key = "x", "y" """',
23 'lists.list14=,,,, ',
23 'lists.list14=,,,, ',
24 'lists.list15=" just with starting quotation',
24 'lists.list15=" just with starting quotation',
25 'lists.list16="longer quotation" with "no ending quotation',
25 'lists.list16="longer quotation" with "no ending quotation',
26 'lists.list17=this is \\" "not a quotation mark"',
26 'lists.list17=this is \\" "not a quotation mark"',
27 'lists.list18=\n \n\nding\ndong',
27 ])
28 ])
28
29
29 print repr(testui.configitems('values'))
30 print repr(testui.configitems('values'))
30 print repr(testui.configitems('lists'))
31 print repr(testui.configitems('lists'))
31 print "---"
32 print "---"
32 print repr(testui.config('values', 'string'))
33 print repr(testui.config('values', 'string'))
33 print repr(testui.config('values', 'bool1'))
34 print repr(testui.config('values', 'bool1'))
34 print repr(testui.config('values', 'bool2'))
35 print repr(testui.config('values', 'bool2'))
35 print repr(testui.config('values', 'unknown'))
36 print repr(testui.config('values', 'unknown'))
36 print "---"
37 print "---"
37 try:
38 try:
38 print repr(testui.configbool('values', 'string'))
39 print repr(testui.configbool('values', 'string'))
39 except error.ConfigError, inst:
40 except error.ConfigError, inst:
40 print inst
41 print inst
41 print repr(testui.configbool('values', 'bool1'))
42 print repr(testui.configbool('values', 'bool1'))
42 print repr(testui.configbool('values', 'bool2'))
43 print repr(testui.configbool('values', 'bool2'))
43 print repr(testui.configbool('values', 'bool2', True))
44 print repr(testui.configbool('values', 'bool2', True))
44 print repr(testui.configbool('values', 'unknown'))
45 print repr(testui.configbool('values', 'unknown'))
45 print repr(testui.configbool('values', 'unknown', True))
46 print repr(testui.configbool('values', 'unknown', True))
46 print "---"
47 print "---"
47 print repr(testui.configlist('lists', 'list1'))
48 print repr(testui.configlist('lists', 'list1'))
48 print repr(testui.configlist('lists', 'list2'))
49 print repr(testui.configlist('lists', 'list2'))
49 print repr(testui.configlist('lists', 'list3'))
50 print repr(testui.configlist('lists', 'list3'))
50 print repr(testui.configlist('lists', 'list4'))
51 print repr(testui.configlist('lists', 'list4'))
51 print repr(testui.configlist('lists', 'list4', ['foo']))
52 print repr(testui.configlist('lists', 'list4', ['foo']))
52 print repr(testui.configlist('lists', 'list5'))
53 print repr(testui.configlist('lists', 'list5'))
53 print repr(testui.configlist('lists', 'list6'))
54 print repr(testui.configlist('lists', 'list6'))
54 print repr(testui.configlist('lists', 'list7'))
55 print repr(testui.configlist('lists', 'list7'))
55 print repr(testui.configlist('lists', 'list8'))
56 print repr(testui.configlist('lists', 'list8'))
56 print repr(testui.configlist('lists', 'list9'))
57 print repr(testui.configlist('lists', 'list9'))
57 print repr(testui.configlist('lists', 'list10'))
58 print repr(testui.configlist('lists', 'list10'))
58 print repr(testui.configlist('lists', 'list11'))
59 print repr(testui.configlist('lists', 'list11'))
59 print repr(testui.configlist('lists', 'list12'))
60 print repr(testui.configlist('lists', 'list12'))
60 print repr(testui.configlist('lists', 'list13'))
61 print repr(testui.configlist('lists', 'list13'))
61 print repr(testui.configlist('lists', 'list14'))
62 print repr(testui.configlist('lists', 'list14'))
62 print repr(testui.configlist('lists', 'list15'))
63 print repr(testui.configlist('lists', 'list15'))
63 print repr(testui.configlist('lists', 'list16'))
64 print repr(testui.configlist('lists', 'list16'))
64 print repr(testui.configlist('lists', 'list17'))
65 print repr(testui.configlist('lists', 'list17'))
66 print repr(testui.configlist('lists', 'list18'))
65 print repr(testui.configlist('lists', 'unknown'))
67 print repr(testui.configlist('lists', 'unknown'))
66 print repr(testui.configlist('lists', 'unknown', ''))
68 print repr(testui.configlist('lists', 'unknown', ''))
67 print repr(testui.configlist('lists', 'unknown', 'foo'))
69 print repr(testui.configlist('lists', 'unknown', 'foo'))
68 print repr(testui.configlist('lists', 'unknown', ['foo']))
70 print repr(testui.configlist('lists', 'unknown', ['foo']))
69 print repr(testui.configlist('lists', 'unknown', 'foo bar'))
71 print repr(testui.configlist('lists', 'unknown', 'foo bar'))
70 print repr(testui.configlist('lists', 'unknown', 'foo, bar'))
72 print repr(testui.configlist('lists', 'unknown', 'foo, bar'))
71 print repr(testui.configlist('lists', 'unknown', ['foo bar']))
73 print repr(testui.configlist('lists', 'unknown', ['foo bar']))
72 print repr(testui.configlist('lists', 'unknown', ['foo', 'bar']))
74 print repr(testui.configlist('lists', 'unknown', ['foo', 'bar']))
73
75
74 print repr(testui.config('values', 'String'))
76 print repr(testui.config('values', 'String'))
75
77
76 def function():
78 def function():
77 pass
79 pass
78
80
79 # values that aren't strings should work
81 # values that aren't strings should work
80 testui.setconfig('hook', 'commit', function)
82 testui.setconfig('hook', 'commit', function)
81 print function == testui.config('hook', 'commit')
83 print function == testui.config('hook', 'commit')
@@ -1,43 +1,44 b''
1 [('string', 'string value'), ('bool1', 'true'), ('bool2', 'false')]
1 [('string', 'string value'), ('bool1', 'true'), ('bool2', 'false')]
2 [('list1', 'foo'), ('list2', 'foo bar baz'), ('list3', 'alice, bob'), ('list4', 'foo bar baz alice, bob'), ('list5', 'abc d"ef"g "hij def"'), ('list6', '"hello world", "how are you?"'), ('list7', 'Do"Not"Separate'), ('list8', '"Do"Separate'), ('list9', '"Do\\"NotSeparate"'), ('list10', 'string "with extraneous" quotation mark"'), ('list11', 'x, y'), ('list12', '"x", "y"'), ('list13', '""" key = "x", "y" """'), ('list14', ',,,, '), ('list15', '" just with starting quotation'), ('list16', '"longer quotation" with "no ending quotation'), ('list17', 'this is \\" "not a quotation mark"')]
2 [('list1', 'foo'), ('list2', 'foo bar baz'), ('list3', 'alice, bob'), ('list4', 'foo bar baz alice, bob'), ('list5', 'abc d"ef"g "hij def"'), ('list6', '"hello world", "how are you?"'), ('list7', 'Do"Not"Separate'), ('list8', '"Do"Separate'), ('list9', '"Do\\"NotSeparate"'), ('list10', 'string "with extraneous" quotation mark"'), ('list11', 'x, y'), ('list12', '"x", "y"'), ('list13', '""" key = "x", "y" """'), ('list14', ',,,, '), ('list15', '" just with starting quotation'), ('list16', '"longer quotation" with "no ending quotation'), ('list17', 'this is \\" "not a quotation mark"'), ('list18', '\n \n\nding\ndong')]
3 ---
3 ---
4 'string value'
4 'string value'
5 'true'
5 'true'
6 'false'
6 'false'
7 None
7 None
8 ---
8 ---
9 values.string not a boolean ('string value')
9 values.string not a boolean ('string value')
10 True
10 True
11 False
11 False
12 False
12 False
13 False
13 False
14 True
14 True
15 ---
15 ---
16 ['foo']
16 ['foo']
17 ['foo', 'bar', 'baz']
17 ['foo', 'bar', 'baz']
18 ['alice', 'bob']
18 ['alice', 'bob']
19 ['foo', 'bar', 'baz', 'alice', 'bob']
19 ['foo', 'bar', 'baz', 'alice', 'bob']
20 ['foo', 'bar', 'baz', 'alice', 'bob']
20 ['foo', 'bar', 'baz', 'alice', 'bob']
21 ['abc', 'd"ef"g', 'hij def']
21 ['abc', 'd"ef"g', 'hij def']
22 ['hello world', 'how are you?']
22 ['hello world', 'how are you?']
23 ['Do"Not"Separate']
23 ['Do"Not"Separate']
24 ['Do', 'Separate']
24 ['Do', 'Separate']
25 ['Do"NotSeparate']
25 ['Do"NotSeparate']
26 ['string', 'with extraneous', 'quotation', 'mark"']
26 ['string', 'with extraneous', 'quotation', 'mark"']
27 ['x', 'y']
27 ['x', 'y']
28 ['x', 'y']
28 ['x', 'y']
29 ['', ' key = ', 'x"', 'y', '', '"']
29 ['', ' key = ', 'x"', 'y', '', '"']
30 []
30 []
31 ['"', 'just', 'with', 'starting', 'quotation']
31 ['"', 'just', 'with', 'starting', 'quotation']
32 ['longer quotation', 'with', '"no', 'ending', 'quotation']
32 ['longer quotation', 'with', '"no', 'ending', 'quotation']
33 ['this', 'is', '"', 'not a quotation mark']
33 ['this', 'is', '"', 'not a quotation mark']
34 ['ding', 'dong']
34 []
35 []
35 []
36 []
36 ['foo']
37 ['foo']
37 ['foo']
38 ['foo']
38 ['foo', 'bar']
39 ['foo', 'bar']
39 ['foo', 'bar']
40 ['foo', 'bar']
40 ['foo bar']
41 ['foo bar']
41 ['foo', 'bar']
42 ['foo', 'bar']
42 None
43 None
43 True
44 True
General Comments 0
You need to be logged in to leave comments. Login now