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