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