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