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