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