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