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