##// END OF EJS Templates
ui: add ui.formatted configuration variable and accessor function....
Dan Villiom Podlaski Christiansen -
r11324:cdf6d861 default
parent child Browse files
Show More
@@ -1,550 +1,565
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 256 return 'HGPLAIN' in os.environ
257 257
258 258 def username(self):
259 259 """Return default username to be used in commits.
260 260
261 261 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
262 262 and stop searching if one of these is set.
263 263 If not found and ui.askusername is True, ask the user, else use
264 264 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
265 265 """
266 266 user = os.environ.get("HGUSER")
267 267 if user is None:
268 268 user = self.config("ui", "username")
269 269 if user is not None:
270 270 user = os.path.expandvars(user)
271 271 if user is None:
272 272 user = os.environ.get("EMAIL")
273 273 if user is None and self.configbool("ui", "askusername"):
274 274 user = self.prompt(_("enter a commit username:"), default=None)
275 275 if user is None and not self.interactive():
276 276 try:
277 277 user = '%s@%s' % (util.getuser(), socket.getfqdn())
278 278 self.warn(_("No username found, using '%s' instead\n") % user)
279 279 except KeyError:
280 280 pass
281 281 if not user:
282 282 raise util.Abort(_('no username supplied (see "hg help config")'))
283 283 if "\n" in user:
284 284 raise util.Abort(_("username %s contains a newline\n") % repr(user))
285 285 return user
286 286
287 287 def shortuser(self, user):
288 288 """Return a short representation of a user name or email address."""
289 289 if not self.verbose:
290 290 user = util.shortuser(user)
291 291 return user
292 292
293 293 def _path(self, loc):
294 294 p = self.config('paths', loc)
295 295 if p:
296 296 if '%%' in p:
297 297 self.warn("(deprecated '%%' in path %s=%s from %s)\n" %
298 298 (loc, p, self.configsource('paths', loc)))
299 299 p = p.replace('%%', '%')
300 300 p = util.expandpath(p)
301 301 return p
302 302
303 303 def expandpath(self, loc, default=None):
304 304 """Return repository location relative to cwd or from [paths]"""
305 305 if "://" in loc or os.path.isdir(os.path.join(loc, '.hg')):
306 306 return loc
307 307
308 308 path = self._path(loc)
309 309 if not path and default is not None:
310 310 path = self._path(default)
311 311 return path or loc
312 312
313 313 def pushbuffer(self):
314 314 self._buffers.append([])
315 315
316 316 def popbuffer(self, labeled=False):
317 317 '''pop the last buffer and return the buffered output
318 318
319 319 If labeled is True, any labels associated with buffered
320 320 output will be handled. By default, this has no effect
321 321 on the output returned, but extensions and GUI tools may
322 322 handle this argument and returned styled output. If output
323 323 is being buffered so it can be captured and parsed or
324 324 processed, labeled should not be set to True.
325 325 '''
326 326 return "".join(self._buffers.pop())
327 327
328 328 def write(self, *args, **opts):
329 329 '''write args to output
330 330
331 331 By default, this method simply writes to the buffer or stdout,
332 332 but extensions or GUI tools may override this method,
333 333 write_err(), popbuffer(), and label() to style output from
334 334 various parts of hg.
335 335
336 336 An optional keyword argument, "label", can be passed in.
337 337 This should be a string containing label names separated by
338 338 space. Label names take the form of "topic.type". For example,
339 339 ui.debug() issues a label of "ui.debug".
340 340
341 341 When labeling output for a specific command, a label of
342 342 "cmdname.type" is recommended. For example, status issues
343 343 a label of "status.modified" for modified files.
344 344 '''
345 345 if self._buffers:
346 346 self._buffers[-1].extend([str(a) for a in args])
347 347 else:
348 348 for a in args:
349 349 sys.stdout.write(str(a))
350 350
351 351 def write_err(self, *args, **opts):
352 352 try:
353 353 if not getattr(sys.stdout, 'closed', False):
354 354 sys.stdout.flush()
355 355 for a in args:
356 356 sys.stderr.write(str(a))
357 357 # stderr may be buffered under win32 when redirected to files,
358 358 # including stdout.
359 359 if not getattr(sys.stderr, 'closed', False):
360 360 sys.stderr.flush()
361 361 except IOError, inst:
362 362 if inst.errno != errno.EPIPE:
363 363 raise
364 364
365 365 def flush(self):
366 366 try: sys.stdout.flush()
367 367 except: pass
368 368 try: sys.stderr.flush()
369 369 except: pass
370 370
371 371 def interactive(self):
372 372 i = self.configbool("ui", "interactive", None)
373 373 if i is None:
374 374 try:
375 375 return sys.stdin.isatty()
376 376 except AttributeError:
377 377 # some environments replace stdin without implementing isatty
378 378 # usually those are non-interactive
379 379 return False
380 380
381 381 return i
382 382
383 def formatted(self):
384 if self.plain():
385 return False
386
387 i = self.configbool("ui", "formatted", None)
388 if i is None:
389 try:
390 return sys.stdout.isatty()
391 except AttributeError:
392 # some environments replace stdout without implementing isatty
393 # usually those are non-interactive
394 return False
395
396 return i
397
383 398 def _readline(self, prompt=''):
384 399 if sys.stdin.isatty():
385 400 try:
386 401 # magically add command line editing support, where
387 402 # available
388 403 import readline
389 404 # force demandimport to really load the module
390 405 readline.read_history_file
391 406 # windows sometimes raises something other than ImportError
392 407 except Exception:
393 408 pass
394 409 line = raw_input(prompt)
395 410 # When stdin is in binary mode on Windows, it can cause
396 411 # raw_input() to emit an extra trailing carriage return
397 412 if os.linesep == '\r\n' and line and line[-1] == '\r':
398 413 line = line[:-1]
399 414 return line
400 415
401 416 def prompt(self, msg, default="y"):
402 417 """Prompt user with msg, read response.
403 418 If ui is not interactive, the default is returned.
404 419 """
405 420 if not self.interactive():
406 421 self.write(msg, ' ', default, "\n")
407 422 return default
408 423 try:
409 424 r = self._readline(msg + ' ')
410 425 if not r:
411 426 return default
412 427 return r
413 428 except EOFError:
414 429 raise util.Abort(_('response expected'))
415 430
416 431 def promptchoice(self, msg, choices, default=0):
417 432 """Prompt user with msg, read response, and ensure it matches
418 433 one of the provided choices. The index of the choice is returned.
419 434 choices is a sequence of acceptable responses with the format:
420 435 ('&None', 'E&xec', 'Sym&link') Responses are case insensitive.
421 436 If ui is not interactive, the default is returned.
422 437 """
423 438 resps = [s[s.index('&')+1].lower() for s in choices]
424 439 while True:
425 440 r = self.prompt(msg, resps[default])
426 441 if r.lower() in resps:
427 442 return resps.index(r.lower())
428 443 self.write(_("unrecognized response\n"))
429 444
430 445 def getpass(self, prompt=None, default=None):
431 446 if not self.interactive():
432 447 return default
433 448 try:
434 449 return getpass.getpass(prompt or _('password: '))
435 450 except EOFError:
436 451 raise util.Abort(_('response expected'))
437 452 def status(self, *msg, **opts):
438 453 '''write status message to output (if ui.quiet is False)
439 454
440 455 This adds an output label of "ui.status".
441 456 '''
442 457 if not self.quiet:
443 458 opts['label'] = opts.get('label', '') + ' ui.status'
444 459 self.write(*msg, **opts)
445 460 def warn(self, *msg, **opts):
446 461 '''write warning message to output (stderr)
447 462
448 463 This adds an output label of "ui.warning".
449 464 '''
450 465 opts['label'] = opts.get('label', '') + ' ui.warning'
451 466 self.write_err(*msg, **opts)
452 467 def note(self, *msg, **opts):
453 468 '''write note to output (if ui.verbose is True)
454 469
455 470 This adds an output label of "ui.note".
456 471 '''
457 472 if self.verbose:
458 473 opts['label'] = opts.get('label', '') + ' ui.note'
459 474 self.write(*msg, **opts)
460 475 def debug(self, *msg, **opts):
461 476 '''write debug message to output (if ui.debugflag is True)
462 477
463 478 This adds an output label of "ui.debug".
464 479 '''
465 480 if self.debugflag:
466 481 opts['label'] = opts.get('label', '') + ' ui.debug'
467 482 self.write(*msg, **opts)
468 483 def edit(self, text, user):
469 484 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
470 485 text=True)
471 486 try:
472 487 f = os.fdopen(fd, "w")
473 488 f.write(text)
474 489 f.close()
475 490
476 491 editor = self.geteditor()
477 492
478 493 util.system("%s \"%s\"" % (editor, name),
479 494 environ={'HGUSER': user},
480 495 onerr=util.Abort, errprefix=_("edit failed"))
481 496
482 497 f = open(name)
483 498 t = f.read()
484 499 f.close()
485 500 finally:
486 501 os.unlink(name)
487 502
488 503 return t
489 504
490 505 def traceback(self, exc=None):
491 506 '''print exception traceback if traceback printing enabled.
492 507 only to call in exception handler. returns true if traceback
493 508 printed.'''
494 509 if self.tracebackflag:
495 510 if exc:
496 511 traceback.print_exception(exc[0], exc[1], exc[2])
497 512 else:
498 513 traceback.print_exc()
499 514 return self.tracebackflag
500 515
501 516 def geteditor(self):
502 517 '''return editor to use'''
503 518 return (os.environ.get("HGEDITOR") or
504 519 self.config("ui", "editor") or
505 520 os.environ.get("VISUAL") or
506 521 os.environ.get("EDITOR", "vi"))
507 522
508 523 def progress(self, topic, pos, item="", unit="", total=None):
509 524 '''show a progress message
510 525
511 526 With stock hg, this is simply a debug message that is hidden
512 527 by default, but with extensions or GUI tools it may be
513 528 visible. 'topic' is the current operation, 'item' is a
514 529 non-numeric marker of the current position (ie the currently
515 530 in-process file), 'pos' is the current numeric position (ie
516 531 revision, bytes, etc.), unit is a corresponding unit label,
517 532 and total is the highest expected pos.
518 533
519 534 Multiple nested topics may be active at a time.
520 535
521 536 All topics should be marked closed by setting pos to None at
522 537 termination.
523 538 '''
524 539
525 540 if pos == None or not self.debugflag:
526 541 return
527 542
528 543 if unit:
529 544 unit = ' ' + unit
530 545 if item:
531 546 item = ' ' + item
532 547
533 548 if total:
534 549 pct = 100.0 * pos / total
535 550 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
536 551 % (topic, item, pos, total, unit, pct))
537 552 else:
538 553 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
539 554
540 555 def label(self, msg, label):
541 556 '''style msg based on supplied label
542 557
543 558 Like ui.write(), this just returns msg unchanged, but extensions
544 559 and GUI tools can override it to allow styling output without
545 560 writing it.
546 561
547 562 ui.write(s, 'label') is equivalent to
548 563 ui.write(ui.label(s, 'label')).
549 564 '''
550 565 return msg
General Comments 0
You need to be logged in to leave comments. Login now