##// END OF EJS Templates
ui: represent paths as classes...
Gregory Szorc -
r24250:9c32eea2 default
parent child Browse files
Show More
@@ -1,925 +1,974 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, formatter
11 11 from node import hex
12 12
13 13 samplehgrcs = {
14 14 'user':
15 15 """# example user config (see "hg help config" for more info)
16 16 [ui]
17 17 # name and email, e.g.
18 18 # username = Jane Doe <jdoe@example.com>
19 19 username =
20 20
21 21 [extensions]
22 22 # uncomment these lines to enable some popular extensions
23 23 # (see "hg help extensions" for more info)
24 24 #
25 25 # pager =
26 26 # progress =
27 27 # color =""",
28 28
29 29 'cloned':
30 30 """# example repository config (see "hg help config" for more info)
31 31 [paths]
32 32 default = %s
33 33
34 34 # path aliases to other clones of this repo in URLs or filesystem paths
35 35 # (see "hg help config.paths" for more info)
36 36 #
37 37 # default-push = ssh://jdoe@example.net/hg/jdoes-fork
38 38 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
39 39 # my-clone = /home/jdoe/jdoes-clone
40 40
41 41 [ui]
42 42 # name and email (local to this repository, optional), e.g.
43 43 # username = Jane Doe <jdoe@example.com>
44 44 """,
45 45
46 46 'local':
47 47 """# example repository config (see "hg help config" for more info)
48 48 [paths]
49 49 # path aliases to other clones of this repo in URLs or filesystem paths
50 50 # (see "hg help config.paths" for more info)
51 51 #
52 52 # default = http://example.com/hg/example-repo
53 53 # default-push = ssh://jdoe@example.net/hg/jdoes-fork
54 54 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
55 55 # my-clone = /home/jdoe/jdoes-clone
56 56
57 57 [ui]
58 58 # name and email (local to this repository, optional), e.g.
59 59 # username = Jane Doe <jdoe@example.com>
60 60 """,
61 61
62 62 'global':
63 63 """# example system-wide hg config (see "hg help config" for more info)
64 64
65 65 [extensions]
66 66 # uncomment these lines to enable some popular extensions
67 67 # (see "hg help extensions" for more info)
68 68 #
69 69 # blackbox =
70 70 # progress =
71 71 # color =
72 72 # pager =""",
73 73 }
74 74
75 75 class ui(object):
76 76 def __init__(self, src=None):
77 77 # _buffers: used for temporary capture of output
78 78 self._buffers = []
79 79 # _bufferstates: Should the temporary capture includes stderr
80 80 self._bufferstates = []
81 81 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
82 82 self._reportuntrusted = True
83 83 self._ocfg = config.config() # overlay
84 84 self._tcfg = config.config() # trusted
85 85 self._ucfg = config.config() # untrusted
86 86 self._trustusers = set()
87 87 self._trustgroups = set()
88 88 self.callhooks = True
89 89
90 90 if src:
91 91 self.fout = src.fout
92 92 self.ferr = src.ferr
93 93 self.fin = src.fin
94 94
95 95 self._tcfg = src._tcfg.copy()
96 96 self._ucfg = src._ucfg.copy()
97 97 self._ocfg = src._ocfg.copy()
98 98 self._trustusers = src._trustusers.copy()
99 99 self._trustgroups = src._trustgroups.copy()
100 100 self.environ = src.environ
101 101 self.callhooks = src.callhooks
102 102 self.fixconfig()
103 103 else:
104 104 self.fout = sys.stdout
105 105 self.ferr = sys.stderr
106 106 self.fin = sys.stdin
107 107
108 108 # shared read-only environment
109 109 self.environ = os.environ
110 110 # we always trust global config files
111 111 for f in scmutil.rcpath():
112 112 self.readconfig(f, trust=True)
113 113
114 114 def copy(self):
115 115 return self.__class__(self)
116 116
117 117 def formatter(self, topic, opts):
118 118 return formatter.formatter(self, topic, opts)
119 119
120 120 def _trusted(self, fp, f):
121 121 st = util.fstat(fp)
122 122 if util.isowner(st):
123 123 return True
124 124
125 125 tusers, tgroups = self._trustusers, self._trustgroups
126 126 if '*' in tusers or '*' in tgroups:
127 127 return True
128 128
129 129 user = util.username(st.st_uid)
130 130 group = util.groupname(st.st_gid)
131 131 if user in tusers or group in tgroups or user == util.username():
132 132 return True
133 133
134 134 if self._reportuntrusted:
135 135 self.warn(_('not trusting file %s from untrusted '
136 136 'user %s, group %s\n') % (f, user, group))
137 137 return False
138 138
139 139 def readconfig(self, filename, root=None, trust=False,
140 140 sections=None, remap=None):
141 141 try:
142 142 fp = open(filename)
143 143 except IOError:
144 144 if not sections: # ignore unless we were looking for something
145 145 return
146 146 raise
147 147
148 148 cfg = config.config()
149 149 trusted = sections or trust or self._trusted(fp, filename)
150 150
151 151 try:
152 152 cfg.read(filename, fp, sections=sections, remap=remap)
153 153 fp.close()
154 154 except error.ConfigError, inst:
155 155 if trusted:
156 156 raise
157 157 self.warn(_("ignored: %s\n") % str(inst))
158 158
159 159 if self.plain():
160 160 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
161 161 'logtemplate', 'style',
162 162 'traceback', 'verbose'):
163 163 if k in cfg['ui']:
164 164 del cfg['ui'][k]
165 165 for k, v in cfg.items('defaults'):
166 166 del cfg['defaults'][k]
167 167 # Don't remove aliases from the configuration if in the exceptionlist
168 168 if self.plain('alias'):
169 169 for k, v in cfg.items('alias'):
170 170 del cfg['alias'][k]
171 171
172 172 if trusted:
173 173 self._tcfg.update(cfg)
174 174 self._tcfg.update(self._ocfg)
175 175 self._ucfg.update(cfg)
176 176 self._ucfg.update(self._ocfg)
177 177
178 178 if root is None:
179 179 root = os.path.expanduser('~')
180 180 self.fixconfig(root=root)
181 181
182 182 def fixconfig(self, root=None, section=None):
183 183 if section in (None, 'paths'):
184 184 # expand vars and ~
185 185 # translate paths relative to root (or home) into absolute paths
186 186 root = root or os.getcwd()
187 187 for c in self._tcfg, self._ucfg, self._ocfg:
188 188 for n, p in c.items('paths'):
189 189 if not p:
190 190 continue
191 191 if '%%' in p:
192 192 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
193 193 % (n, p, self.configsource('paths', n)))
194 194 p = p.replace('%%', '%')
195 195 p = util.expandpath(p)
196 196 if not util.hasscheme(p) and not os.path.isabs(p):
197 197 p = os.path.normpath(os.path.join(root, p))
198 198 c.set("paths", n, p)
199 199
200 200 if section in (None, 'ui'):
201 201 # update ui options
202 202 self.debugflag = self.configbool('ui', 'debug')
203 203 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
204 204 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
205 205 if self.verbose and self.quiet:
206 206 self.quiet = self.verbose = False
207 207 self._reportuntrusted = self.debugflag or self.configbool("ui",
208 208 "report_untrusted", True)
209 209 self.tracebackflag = self.configbool('ui', 'traceback', False)
210 210
211 211 if section in (None, 'trusted'):
212 212 # update trust information
213 213 self._trustusers.update(self.configlist('trusted', 'users'))
214 214 self._trustgroups.update(self.configlist('trusted', 'groups'))
215 215
216 216 def backupconfig(self, section, item):
217 217 return (self._ocfg.backup(section, item),
218 218 self._tcfg.backup(section, item),
219 219 self._ucfg.backup(section, item),)
220 220 def restoreconfig(self, data):
221 221 self._ocfg.restore(data[0])
222 222 self._tcfg.restore(data[1])
223 223 self._ucfg.restore(data[2])
224 224
225 225 def setconfig(self, section, name, value, source=''):
226 226 for cfg in (self._ocfg, self._tcfg, self._ucfg):
227 227 cfg.set(section, name, value, source)
228 228 self.fixconfig(section=section)
229 229
230 230 def _data(self, untrusted):
231 231 return untrusted and self._ucfg or self._tcfg
232 232
233 233 def configsource(self, section, name, untrusted=False):
234 234 return self._data(untrusted).source(section, name) or 'none'
235 235
236 236 def config(self, section, name, default=None, untrusted=False):
237 237 if isinstance(name, list):
238 238 alternates = name
239 239 else:
240 240 alternates = [name]
241 241
242 242 for n in alternates:
243 243 value = self._data(untrusted).get(section, n, None)
244 244 if value is not None:
245 245 name = n
246 246 break
247 247 else:
248 248 value = default
249 249
250 250 if self.debugflag and not untrusted and self._reportuntrusted:
251 251 for n in alternates:
252 252 uvalue = self._ucfg.get(section, n)
253 253 if uvalue is not None and uvalue != value:
254 254 self.debug("ignoring untrusted configuration option "
255 255 "%s.%s = %s\n" % (section, n, uvalue))
256 256 return value
257 257
258 258 def configpath(self, section, name, default=None, untrusted=False):
259 259 'get a path config item, expanded relative to repo root or config file'
260 260 v = self.config(section, name, default, untrusted)
261 261 if v is None:
262 262 return None
263 263 if not os.path.isabs(v) or "://" not in v:
264 264 src = self.configsource(section, name, untrusted)
265 265 if ':' in src:
266 266 base = os.path.dirname(src.rsplit(':')[0])
267 267 v = os.path.join(base, os.path.expanduser(v))
268 268 return v
269 269
270 270 def configbool(self, section, name, default=False, untrusted=False):
271 271 """parse a configuration element as a boolean
272 272
273 273 >>> u = ui(); s = 'foo'
274 274 >>> u.setconfig(s, 'true', 'yes')
275 275 >>> u.configbool(s, 'true')
276 276 True
277 277 >>> u.setconfig(s, 'false', 'no')
278 278 >>> u.configbool(s, 'false')
279 279 False
280 280 >>> u.configbool(s, 'unknown')
281 281 False
282 282 >>> u.configbool(s, 'unknown', True)
283 283 True
284 284 >>> u.setconfig(s, 'invalid', 'somevalue')
285 285 >>> u.configbool(s, 'invalid')
286 286 Traceback (most recent call last):
287 287 ...
288 288 ConfigError: foo.invalid is not a boolean ('somevalue')
289 289 """
290 290
291 291 v = self.config(section, name, None, untrusted)
292 292 if v is None:
293 293 return default
294 294 if isinstance(v, bool):
295 295 return v
296 296 b = util.parsebool(v)
297 297 if b is None:
298 298 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
299 299 % (section, name, v))
300 300 return b
301 301
302 302 def configint(self, section, name, default=None, untrusted=False):
303 303 """parse a configuration element as an integer
304 304
305 305 >>> u = ui(); s = 'foo'
306 306 >>> u.setconfig(s, 'int1', '42')
307 307 >>> u.configint(s, 'int1')
308 308 42
309 309 >>> u.setconfig(s, 'int2', '-42')
310 310 >>> u.configint(s, 'int2')
311 311 -42
312 312 >>> u.configint(s, 'unknown', 7)
313 313 7
314 314 >>> u.setconfig(s, 'invalid', 'somevalue')
315 315 >>> u.configint(s, 'invalid')
316 316 Traceback (most recent call last):
317 317 ...
318 318 ConfigError: foo.invalid is not an integer ('somevalue')
319 319 """
320 320
321 321 v = self.config(section, name, None, untrusted)
322 322 if v is None:
323 323 return default
324 324 try:
325 325 return int(v)
326 326 except ValueError:
327 327 raise error.ConfigError(_("%s.%s is not an integer ('%s')")
328 328 % (section, name, v))
329 329
330 330 def configbytes(self, section, name, default=0, untrusted=False):
331 331 """parse a configuration element as a quantity in bytes
332 332
333 333 Units can be specified as b (bytes), k or kb (kilobytes), m or
334 334 mb (megabytes), g or gb (gigabytes).
335 335
336 336 >>> u = ui(); s = 'foo'
337 337 >>> u.setconfig(s, 'val1', '42')
338 338 >>> u.configbytes(s, 'val1')
339 339 42
340 340 >>> u.setconfig(s, 'val2', '42.5 kb')
341 341 >>> u.configbytes(s, 'val2')
342 342 43520
343 343 >>> u.configbytes(s, 'unknown', '7 MB')
344 344 7340032
345 345 >>> u.setconfig(s, 'invalid', 'somevalue')
346 346 >>> u.configbytes(s, 'invalid')
347 347 Traceback (most recent call last):
348 348 ...
349 349 ConfigError: foo.invalid is not a byte quantity ('somevalue')
350 350 """
351 351
352 352 value = self.config(section, name)
353 353 if value is None:
354 354 if not isinstance(default, str):
355 355 return default
356 356 value = default
357 357 try:
358 358 return util.sizetoint(value)
359 359 except error.ParseError:
360 360 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
361 361 % (section, name, value))
362 362
363 363 def configlist(self, section, name, default=None, untrusted=False):
364 364 """parse a configuration element as a list of comma/space separated
365 365 strings
366 366
367 367 >>> u = ui(); s = 'foo'
368 368 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
369 369 >>> u.configlist(s, 'list1')
370 370 ['this', 'is', 'a small', 'test']
371 371 """
372 372
373 373 def _parse_plain(parts, s, offset):
374 374 whitespace = False
375 375 while offset < len(s) and (s[offset].isspace() or s[offset] == ','):
376 376 whitespace = True
377 377 offset += 1
378 378 if offset >= len(s):
379 379 return None, parts, offset
380 380 if whitespace:
381 381 parts.append('')
382 382 if s[offset] == '"' and not parts[-1]:
383 383 return _parse_quote, parts, offset + 1
384 384 elif s[offset] == '"' and parts[-1][-1] == '\\':
385 385 parts[-1] = parts[-1][:-1] + s[offset]
386 386 return _parse_plain, parts, offset + 1
387 387 parts[-1] += s[offset]
388 388 return _parse_plain, parts, offset + 1
389 389
390 390 def _parse_quote(parts, s, offset):
391 391 if offset < len(s) and s[offset] == '"': # ""
392 392 parts.append('')
393 393 offset += 1
394 394 while offset < len(s) and (s[offset].isspace() or
395 395 s[offset] == ','):
396 396 offset += 1
397 397 return _parse_plain, parts, offset
398 398
399 399 while offset < len(s) and s[offset] != '"':
400 400 if (s[offset] == '\\' and offset + 1 < len(s)
401 401 and s[offset + 1] == '"'):
402 402 offset += 1
403 403 parts[-1] += '"'
404 404 else:
405 405 parts[-1] += s[offset]
406 406 offset += 1
407 407
408 408 if offset >= len(s):
409 409 real_parts = _configlist(parts[-1])
410 410 if not real_parts:
411 411 parts[-1] = '"'
412 412 else:
413 413 real_parts[0] = '"' + real_parts[0]
414 414 parts = parts[:-1]
415 415 parts.extend(real_parts)
416 416 return None, parts, offset
417 417
418 418 offset += 1
419 419 while offset < len(s) and s[offset] in [' ', ',']:
420 420 offset += 1
421 421
422 422 if offset < len(s):
423 423 if offset + 1 == len(s) and s[offset] == '"':
424 424 parts[-1] += '"'
425 425 offset += 1
426 426 else:
427 427 parts.append('')
428 428 else:
429 429 return None, parts, offset
430 430
431 431 return _parse_plain, parts, offset
432 432
433 433 def _configlist(s):
434 434 s = s.rstrip(' ,')
435 435 if not s:
436 436 return []
437 437 parser, parts, offset = _parse_plain, [''], 0
438 438 while parser:
439 439 parser, parts, offset = parser(parts, s, offset)
440 440 return parts
441 441
442 442 result = self.config(section, name, untrusted=untrusted)
443 443 if result is None:
444 444 result = default or []
445 445 if isinstance(result, basestring):
446 446 result = _configlist(result.lstrip(' ,\n'))
447 447 if result is None:
448 448 result = default or []
449 449 return result
450 450
451 451 def has_section(self, section, untrusted=False):
452 452 '''tell whether section exists in config.'''
453 453 return section in self._data(untrusted)
454 454
455 455 def configitems(self, section, untrusted=False):
456 456 items = self._data(untrusted).items(section)
457 457 if self.debugflag and not untrusted and self._reportuntrusted:
458 458 for k, v in self._ucfg.items(section):
459 459 if self._tcfg.get(section, k) != v:
460 460 self.debug("ignoring untrusted configuration option "
461 461 "%s.%s = %s\n" % (section, k, v))
462 462 return items
463 463
464 464 def walkconfig(self, untrusted=False):
465 465 cfg = self._data(untrusted)
466 466 for section in cfg.sections():
467 467 for name, value in self.configitems(section, untrusted):
468 468 yield section, name, value
469 469
470 470 def plain(self, feature=None):
471 471 '''is plain mode active?
472 472
473 473 Plain mode means that all configuration variables which affect
474 474 the behavior and output of Mercurial should be
475 475 ignored. Additionally, the output should be stable,
476 476 reproducible and suitable for use in scripts or applications.
477 477
478 478 The only way to trigger plain mode is by setting either the
479 479 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
480 480
481 481 The return value can either be
482 482 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
483 483 - True otherwise
484 484 '''
485 485 if 'HGPLAIN' not in os.environ and 'HGPLAINEXCEPT' not in os.environ:
486 486 return False
487 487 exceptions = os.environ.get('HGPLAINEXCEPT', '').strip().split(',')
488 488 if feature and exceptions:
489 489 return feature not in exceptions
490 490 return True
491 491
492 492 def username(self):
493 493 """Return default username to be used in commits.
494 494
495 495 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
496 496 and stop searching if one of these is set.
497 497 If not found and ui.askusername is True, ask the user, else use
498 498 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
499 499 """
500 500 user = os.environ.get("HGUSER")
501 501 if user is None:
502 502 user = self.config("ui", ["username", "user"])
503 503 if user is not None:
504 504 user = os.path.expandvars(user)
505 505 if user is None:
506 506 user = os.environ.get("EMAIL")
507 507 if user is None and self.configbool("ui", "askusername"):
508 508 user = self.prompt(_("enter a commit username:"), default=None)
509 509 if user is None and not self.interactive():
510 510 try:
511 511 user = '%s@%s' % (util.getuser(), socket.getfqdn())
512 512 self.warn(_("no username found, using '%s' instead\n") % user)
513 513 except KeyError:
514 514 pass
515 515 if not user:
516 516 raise util.Abort(_('no username supplied'),
517 517 hint=_('use "hg config --edit" '
518 518 'to set your username'))
519 519 if "\n" in user:
520 520 raise util.Abort(_("username %s contains a newline\n") % repr(user))
521 521 return user
522 522
523 523 def shortuser(self, user):
524 524 """Return a short representation of a user name or email address."""
525 525 if not self.verbose:
526 526 user = util.shortuser(user)
527 527 return user
528 528
529 529 def expandpath(self, loc, default=None):
530 530 """Return repository location relative to cwd or from [paths]"""
531 531 if util.hasscheme(loc) or os.path.isdir(os.path.join(loc, '.hg')):
532 532 return loc
533 533
534 path = self.config('paths', loc)
535 if not path and default is not None:
536 path = self.config('paths', default)
537 return path or loc
534 p = self.paths.getpath(loc, default=default)
535 if p:
536 return p.loc
537 return loc
538
539 @util.propertycache
540 def paths(self):
541 return paths(self)
538 542
539 543 def pushbuffer(self, error=False):
540 544 """install a buffer to capture standard output of the ui object
541 545
542 546 If error is True, the error output will be captured too."""
543 547 self._buffers.append([])
544 548 self._bufferstates.append(error)
545 549
546 550 def popbuffer(self, labeled=False):
547 551 '''pop the last buffer and return the buffered output
548 552
549 553 If labeled is True, any labels associated with buffered
550 554 output will be handled. By default, this has no effect
551 555 on the output returned, but extensions and GUI tools may
552 556 handle this argument and returned styled output. If output
553 557 is being buffered so it can be captured and parsed or
554 558 processed, labeled should not be set to True.
555 559 '''
556 560 self._bufferstates.pop()
557 561 return "".join(self._buffers.pop())
558 562
559 563 def write(self, *args, **opts):
560 564 '''write args to output
561 565
562 566 By default, this method simply writes to the buffer or stdout,
563 567 but extensions or GUI tools may override this method,
564 568 write_err(), popbuffer(), and label() to style output from
565 569 various parts of hg.
566 570
567 571 An optional keyword argument, "label", can be passed in.
568 572 This should be a string containing label names separated by
569 573 space. Label names take the form of "topic.type". For example,
570 574 ui.debug() issues a label of "ui.debug".
571 575
572 576 When labeling output for a specific command, a label of
573 577 "cmdname.type" is recommended. For example, status issues
574 578 a label of "status.modified" for modified files.
575 579 '''
576 580 if self._buffers:
577 581 self._buffers[-1].extend([str(a) for a in args])
578 582 else:
579 583 for a in args:
580 584 self.fout.write(str(a))
581 585
582 586 def write_err(self, *args, **opts):
583 587 try:
584 588 if self._bufferstates and self._bufferstates[-1]:
585 589 return self.write(*args, **opts)
586 590 if not getattr(self.fout, 'closed', False):
587 591 self.fout.flush()
588 592 for a in args:
589 593 self.ferr.write(str(a))
590 594 # stderr may be buffered under win32 when redirected to files,
591 595 # including stdout.
592 596 if not getattr(self.ferr, 'closed', False):
593 597 self.ferr.flush()
594 598 except IOError, inst:
595 599 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
596 600 raise
597 601
598 602 def flush(self):
599 603 try: self.fout.flush()
600 604 except (IOError, ValueError): pass
601 605 try: self.ferr.flush()
602 606 except (IOError, ValueError): pass
603 607
604 608 def _isatty(self, fh):
605 609 if self.configbool('ui', 'nontty', False):
606 610 return False
607 611 return util.isatty(fh)
608 612
609 613 def interactive(self):
610 614 '''is interactive input allowed?
611 615
612 616 An interactive session is a session where input can be reasonably read
613 617 from `sys.stdin'. If this function returns false, any attempt to read
614 618 from stdin should fail with an error, unless a sensible default has been
615 619 specified.
616 620
617 621 Interactiveness is triggered by the value of the `ui.interactive'
618 622 configuration variable or - if it is unset - when `sys.stdin' points
619 623 to a terminal device.
620 624
621 625 This function refers to input only; for output, see `ui.formatted()'.
622 626 '''
623 627 i = self.configbool("ui", "interactive", None)
624 628 if i is None:
625 629 # some environments replace stdin without implementing isatty
626 630 # usually those are non-interactive
627 631 return self._isatty(self.fin)
628 632
629 633 return i
630 634
631 635 def termwidth(self):
632 636 '''how wide is the terminal in columns?
633 637 '''
634 638 if 'COLUMNS' in os.environ:
635 639 try:
636 640 return int(os.environ['COLUMNS'])
637 641 except ValueError:
638 642 pass
639 643 return util.termwidth()
640 644
641 645 def formatted(self):
642 646 '''should formatted output be used?
643 647
644 648 It is often desirable to format the output to suite the output medium.
645 649 Examples of this are truncating long lines or colorizing messages.
646 650 However, this is not often not desirable when piping output into other
647 651 utilities, e.g. `grep'.
648 652
649 653 Formatted output is triggered by the value of the `ui.formatted'
650 654 configuration variable or - if it is unset - when `sys.stdout' points
651 655 to a terminal device. Please note that `ui.formatted' should be
652 656 considered an implementation detail; it is not intended for use outside
653 657 Mercurial or its extensions.
654 658
655 659 This function refers to output only; for input, see `ui.interactive()'.
656 660 This function always returns false when in plain mode, see `ui.plain()'.
657 661 '''
658 662 if self.plain():
659 663 return False
660 664
661 665 i = self.configbool("ui", "formatted", None)
662 666 if i is None:
663 667 # some environments replace stdout without implementing isatty
664 668 # usually those are non-interactive
665 669 return self._isatty(self.fout)
666 670
667 671 return i
668 672
669 673 def _readline(self, prompt=''):
670 674 if self._isatty(self.fin):
671 675 try:
672 676 # magically add command line editing support, where
673 677 # available
674 678 import readline
675 679 # force demandimport to really load the module
676 680 readline.read_history_file
677 681 # windows sometimes raises something other than ImportError
678 682 except Exception:
679 683 pass
680 684
681 685 # call write() so output goes through subclassed implementation
682 686 # e.g. color extension on Windows
683 687 self.write(prompt)
684 688
685 689 # instead of trying to emulate raw_input, swap (self.fin,
686 690 # self.fout) with (sys.stdin, sys.stdout)
687 691 oldin = sys.stdin
688 692 oldout = sys.stdout
689 693 sys.stdin = self.fin
690 694 sys.stdout = self.fout
691 695 # prompt ' ' must exist; otherwise readline may delete entire line
692 696 # - http://bugs.python.org/issue12833
693 697 line = raw_input(' ')
694 698 sys.stdin = oldin
695 699 sys.stdout = oldout
696 700
697 701 # When stdin is in binary mode on Windows, it can cause
698 702 # raw_input() to emit an extra trailing carriage return
699 703 if os.linesep == '\r\n' and line and line[-1] == '\r':
700 704 line = line[:-1]
701 705 return line
702 706
703 707 def prompt(self, msg, default="y"):
704 708 """Prompt user with msg, read response.
705 709 If ui is not interactive, the default is returned.
706 710 """
707 711 if not self.interactive():
708 712 self.write(msg, ' ', default, "\n")
709 713 return default
710 714 try:
711 715 r = self._readline(self.label(msg, 'ui.prompt'))
712 716 if not r:
713 717 r = default
714 718 if self.configbool('ui', 'promptecho'):
715 719 self.write(r, "\n")
716 720 return r
717 721 except EOFError:
718 722 raise util.Abort(_('response expected'))
719 723
720 724 @staticmethod
721 725 def extractchoices(prompt):
722 726 """Extract prompt message and list of choices from specified prompt.
723 727
724 728 This returns tuple "(message, choices)", and "choices" is the
725 729 list of tuple "(response character, text without &)".
726 730 """
727 731 parts = prompt.split('$$')
728 732 msg = parts[0].rstrip(' ')
729 733 choices = [p.strip(' ') for p in parts[1:]]
730 734 return (msg,
731 735 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
732 736 for s in choices])
733 737
734 738 def promptchoice(self, prompt, default=0):
735 739 """Prompt user with a message, read response, and ensure it matches
736 740 one of the provided choices. The prompt is formatted as follows:
737 741
738 742 "would you like fries with that (Yn)? $$ &Yes $$ &No"
739 743
740 744 The index of the choice is returned. Responses are case
741 745 insensitive. If ui is not interactive, the default is
742 746 returned.
743 747 """
744 748
745 749 msg, choices = self.extractchoices(prompt)
746 750 resps = [r for r, t in choices]
747 751 while True:
748 752 r = self.prompt(msg, resps[default])
749 753 if r.lower() in resps:
750 754 return resps.index(r.lower())
751 755 self.write(_("unrecognized response\n"))
752 756
753 757 def getpass(self, prompt=None, default=None):
754 758 if not self.interactive():
755 759 return default
756 760 try:
757 761 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
758 762 # disable getpass() only if explicitly specified. it's still valid
759 763 # to interact with tty even if fin is not a tty.
760 764 if self.configbool('ui', 'nontty'):
761 765 return self.fin.readline().rstrip('\n')
762 766 else:
763 767 return getpass.getpass('')
764 768 except EOFError:
765 769 raise util.Abort(_('response expected'))
766 770 def status(self, *msg, **opts):
767 771 '''write status message to output (if ui.quiet is False)
768 772
769 773 This adds an output label of "ui.status".
770 774 '''
771 775 if not self.quiet:
772 776 opts['label'] = opts.get('label', '') + ' ui.status'
773 777 self.write(*msg, **opts)
774 778 def warn(self, *msg, **opts):
775 779 '''write warning message to output (stderr)
776 780
777 781 This adds an output label of "ui.warning".
778 782 '''
779 783 opts['label'] = opts.get('label', '') + ' ui.warning'
780 784 self.write_err(*msg, **opts)
781 785 def note(self, *msg, **opts):
782 786 '''write note to output (if ui.verbose is True)
783 787
784 788 This adds an output label of "ui.note".
785 789 '''
786 790 if self.verbose:
787 791 opts['label'] = opts.get('label', '') + ' ui.note'
788 792 self.write(*msg, **opts)
789 793 def debug(self, *msg, **opts):
790 794 '''write debug message to output (if ui.debugflag is True)
791 795
792 796 This adds an output label of "ui.debug".
793 797 '''
794 798 if self.debugflag:
795 799 opts['label'] = opts.get('label', '') + ' ui.debug'
796 800 self.write(*msg, **opts)
797 801 def edit(self, text, user, extra={}, editform=None):
798 802 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
799 803 text=True)
800 804 try:
801 805 f = os.fdopen(fd, "w")
802 806 f.write(text)
803 807 f.close()
804 808
805 809 environ = {'HGUSER': user}
806 810 if 'transplant_source' in extra:
807 811 environ.update({'HGREVISION': hex(extra['transplant_source'])})
808 812 for label in ('source', 'rebase_source'):
809 813 if label in extra:
810 814 environ.update({'HGREVISION': extra[label]})
811 815 break
812 816 if editform:
813 817 environ.update({'HGEDITFORM': editform})
814 818
815 819 editor = self.geteditor()
816 820
817 821 self.system("%s \"%s\"" % (editor, name),
818 822 environ=environ,
819 823 onerr=util.Abort, errprefix=_("edit failed"))
820 824
821 825 f = open(name)
822 826 t = f.read()
823 827 f.close()
824 828 finally:
825 829 os.unlink(name)
826 830
827 831 return t
828 832
829 833 def system(self, cmd, environ={}, cwd=None, onerr=None, errprefix=None):
830 834 '''execute shell command with appropriate output stream. command
831 835 output will be redirected if fout is not stdout.
832 836 '''
833 837 return util.system(cmd, environ=environ, cwd=cwd, onerr=onerr,
834 838 errprefix=errprefix, out=self.fout)
835 839
836 840 def traceback(self, exc=None, force=False):
837 841 '''print exception traceback if traceback printing enabled or forced.
838 842 only to call in exception handler. returns true if traceback
839 843 printed.'''
840 844 if self.tracebackflag or force:
841 845 if exc is None:
842 846 exc = sys.exc_info()
843 847 cause = getattr(exc[1], 'cause', None)
844 848
845 849 if cause is not None:
846 850 causetb = traceback.format_tb(cause[2])
847 851 exctb = traceback.format_tb(exc[2])
848 852 exconly = traceback.format_exception_only(cause[0], cause[1])
849 853
850 854 # exclude frame where 'exc' was chained and rethrown from exctb
851 855 self.write_err('Traceback (most recent call last):\n',
852 856 ''.join(exctb[:-1]),
853 857 ''.join(causetb),
854 858 ''.join(exconly))
855 859 else:
856 860 traceback.print_exception(exc[0], exc[1], exc[2],
857 861 file=self.ferr)
858 862 return self.tracebackflag or force
859 863
860 864 def geteditor(self):
861 865 '''return editor to use'''
862 866 if sys.platform == 'plan9':
863 867 # vi is the MIPS instruction simulator on Plan 9. We
864 868 # instead default to E to plumb commit messages to
865 869 # avoid confusion.
866 870 editor = 'E'
867 871 else:
868 872 editor = 'vi'
869 873 return (os.environ.get("HGEDITOR") or
870 874 self.config("ui", "editor") or
871 875 os.environ.get("VISUAL") or
872 876 os.environ.get("EDITOR", editor))
873 877
874 878 def progress(self, topic, pos, item="", unit="", total=None):
875 879 '''show a progress message
876 880
877 881 With stock hg, this is simply a debug message that is hidden
878 882 by default, but with extensions or GUI tools it may be
879 883 visible. 'topic' is the current operation, 'item' is a
880 884 non-numeric marker of the current position (i.e. the currently
881 885 in-process file), 'pos' is the current numeric position (i.e.
882 886 revision, bytes, etc.), unit is a corresponding unit label,
883 887 and total is the highest expected pos.
884 888
885 889 Multiple nested topics may be active at a time.
886 890
887 891 All topics should be marked closed by setting pos to None at
888 892 termination.
889 893 '''
890 894
891 895 if pos is None or not self.debugflag:
892 896 return
893 897
894 898 if unit:
895 899 unit = ' ' + unit
896 900 if item:
897 901 item = ' ' + item
898 902
899 903 if total:
900 904 pct = 100.0 * pos / total
901 905 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
902 906 % (topic, item, pos, total, unit, pct))
903 907 else:
904 908 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
905 909
906 910 def log(self, service, *msg, **opts):
907 911 '''hook for logging facility extensions
908 912
909 913 service should be a readily-identifiable subsystem, which will
910 914 allow filtering.
911 915 message should be a newline-terminated string to log.
912 916 '''
913 917 pass
914 918
915 919 def label(self, msg, label):
916 920 '''style msg based on supplied label
917 921
918 922 Like ui.write(), this just returns msg unchanged, but extensions
919 923 and GUI tools can override it to allow styling output without
920 924 writing it.
921 925
922 926 ui.write(s, 'label') is equivalent to
923 927 ui.write(ui.label(s, 'label')).
924 928 '''
925 929 return msg
930
931 class paths(dict):
932 """Represents a collection of paths and their configs.
933
934 Data is initially derived from ui instances and the config files they have
935 loaded.
936 """
937 def __init__(self, ui):
938 dict.__init__(self)
939
940 for name, loc in ui.configitems('paths'):
941 # No location is the same as not existing.
942 if not loc:
943 continue
944 self[name] = path(name, rawloc=loc)
945
946 def getpath(self, name, default=None):
947 """Return a ``path`` for the specified name, falling back to a default.
948
949 Returns the first of ``name`` or ``default`` that is present, or None
950 if neither is present.
951 """
952 try:
953 return self[name]
954 except KeyError:
955 if default is not None:
956 try:
957 return self[default]
958 except KeyError:
959 pass
960
961 return None
962
963 class path(object):
964 """Represents an individual path and its configuration."""
965
966 def __init__(self, name, rawloc=None):
967 """Construct a path from its config options.
968
969 ``name`` is the symbolic name of the path.
970 ``rawloc`` is the raw location, as defined in the config.
971 """
972 self.name = name
973 # We'll do more intelligent things with rawloc in the future.
974 self.loc = rawloc
General Comments 0
You need to be logged in to leave comments. Login now