eggs/mercurial-1.7.3-py2.6-linux-x86_64.egg/mercurial/ui.py
changeset 69 c6bca38c1cbf
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eggs/mercurial-1.7.3-py2.6-linux-x86_64.egg/mercurial/ui.py	Sat Jan 08 11:20:57 2011 +0530
@@ -0,0 +1,626 @@
+# ui.py - user interface bits for mercurial
+#
+# Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+from i18n import _
+import errno, getpass, os, socket, sys, tempfile, traceback
+import config, util, error
+
+class ui(object):
+    def __init__(self, src=None):
+        self._buffers = []
+        self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
+        self._reportuntrusted = True
+        self._ocfg = config.config() # overlay
+        self._tcfg = config.config() # trusted
+        self._ucfg = config.config() # untrusted
+        self._trustusers = set()
+        self._trustgroups = set()
+
+        if src:
+            self._tcfg = src._tcfg.copy()
+            self._ucfg = src._ucfg.copy()
+            self._ocfg = src._ocfg.copy()
+            self._trustusers = src._trustusers.copy()
+            self._trustgroups = src._trustgroups.copy()
+            self.environ = src.environ
+            self.fixconfig()
+        else:
+            # shared read-only environment
+            self.environ = os.environ
+            # we always trust global config files
+            for f in util.rcpath():
+                self.readconfig(f, trust=True)
+
+    def copy(self):
+        return self.__class__(self)
+
+    def _is_trusted(self, fp, f):
+        st = util.fstat(fp)
+        if util.isowner(st):
+            return True
+
+        tusers, tgroups = self._trustusers, self._trustgroups
+        if '*' in tusers or '*' in tgroups:
+            return True
+
+        user = util.username(st.st_uid)
+        group = util.groupname(st.st_gid)
+        if user in tusers or group in tgroups or user == util.username():
+            return True
+
+        if self._reportuntrusted:
+            self.warn(_('Not trusting file %s from untrusted '
+                        'user %s, group %s\n') % (f, user, group))
+        return False
+
+    def readconfig(self, filename, root=None, trust=False,
+                   sections=None, remap=None):
+        try:
+            fp = open(filename)
+        except IOError:
+            if not sections: # ignore unless we were looking for something
+                return
+            raise
+
+        cfg = config.config()
+        trusted = sections or trust or self._is_trusted(fp, filename)
+
+        try:
+            cfg.read(filename, fp, sections=sections, remap=remap)
+        except error.ConfigError, inst:
+            if trusted:
+                raise
+            self.warn(_("Ignored: %s\n") % str(inst))
+
+        if self.plain():
+            for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
+                      'logtemplate', 'style',
+                      'traceback', 'verbose'):
+                if k in cfg['ui']:
+                    del cfg['ui'][k]
+            for k, v in cfg.items('alias'):
+                del cfg['alias'][k]
+            for k, v in cfg.items('defaults'):
+                del cfg['defaults'][k]
+
+        if trusted:
+            self._tcfg.update(cfg)
+            self._tcfg.update(self._ocfg)
+        self._ucfg.update(cfg)
+        self._ucfg.update(self._ocfg)
+
+        if root is None:
+            root = os.path.expanduser('~')
+        self.fixconfig(root=root)
+
+    def fixconfig(self, root=None, section=None):
+        if section in (None, 'paths'):
+            # expand vars and ~
+            # translate paths relative to root (or home) into absolute paths
+            root = root or os.getcwd()
+            for c in self._tcfg, self._ucfg, self._ocfg:
+                for n, p in c.items('paths'):
+                    if not p:
+                        continue
+                    if '%%' in p:
+                        self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
+                                  % (n, p, self.configsource('paths', n)))
+                        p = p.replace('%%', '%')
+                    p = util.expandpath(p)
+                    if '://' not in p and not os.path.isabs(p):
+                        p = os.path.normpath(os.path.join(root, p))
+                    c.set("paths", n, p)
+
+        if section in (None, 'ui'):
+            # update ui options
+            self.debugflag = self.configbool('ui', 'debug')
+            self.verbose = self.debugflag or self.configbool('ui', 'verbose')
+            self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
+            if self.verbose and self.quiet:
+                self.quiet = self.verbose = False
+            self._reportuntrusted = self.configbool("ui", "report_untrusted",
+                                        True)
+            self.tracebackflag = self.configbool('ui', 'traceback', False)
+
+        if section in (None, 'trusted'):
+            # update trust information
+            self._trustusers.update(self.configlist('trusted', 'users'))
+            self._trustgroups.update(self.configlist('trusted', 'groups'))
+
+    def setconfig(self, section, name, value, overlay=True):
+        if overlay:
+            self._ocfg.set(section, name, value)
+        self._tcfg.set(section, name, value)
+        self._ucfg.set(section, name, value)
+        self.fixconfig(section=section)
+
+    def _data(self, untrusted):
+        return untrusted and self._ucfg or self._tcfg
+
+    def configsource(self, section, name, untrusted=False):
+        return self._data(untrusted).source(section, name) or 'none'
+
+    def config(self, section, name, default=None, untrusted=False):
+        value = self._data(untrusted).get(section, name, default)
+        if self.debugflag and not untrusted and self._reportuntrusted:
+            uvalue = self._ucfg.get(section, name)
+            if uvalue is not None and uvalue != value:
+                self.debug(_("ignoring untrusted configuration option "
+                             "%s.%s = %s\n") % (section, name, uvalue))
+        return value
+
+    def configbool(self, section, name, default=False, untrusted=False):
+        v = self.config(section, name, None, untrusted)
+        if v is None:
+            return default
+        if isinstance(v, bool):
+            return v
+        b = util.parsebool(v)
+        if b is None:
+            raise error.ConfigError(_("%s.%s not a boolean ('%s')")
+                                    % (section, name, v))
+        return b
+
+    def configlist(self, section, name, default=None, untrusted=False):
+        """Return a list of comma/space separated strings"""
+
+        def _parse_plain(parts, s, offset):
+            whitespace = False
+            while offset < len(s) and (s[offset].isspace() or s[offset] == ','):
+                whitespace = True
+                offset += 1
+            if offset >= len(s):
+                return None, parts, offset
+            if whitespace:
+                parts.append('')
+            if s[offset] == '"' and not parts[-1]:
+                return _parse_quote, parts, offset + 1
+            elif s[offset] == '"' and parts[-1][-1] == '\\':
+                parts[-1] = parts[-1][:-1] + s[offset]
+                return _parse_plain, parts, offset + 1
+            parts[-1] += s[offset]
+            return _parse_plain, parts, offset + 1
+
+        def _parse_quote(parts, s, offset):
+            if offset < len(s) and s[offset] == '"': # ""
+                parts.append('')
+                offset += 1
+                while offset < len(s) and (s[offset].isspace() or
+                        s[offset] == ','):
+                    offset += 1
+                return _parse_plain, parts, offset
+
+            while offset < len(s) and s[offset] != '"':
+                if (s[offset] == '\\' and offset + 1 < len(s)
+                        and s[offset + 1] == '"'):
+                    offset += 1
+                    parts[-1] += '"'
+                else:
+                    parts[-1] += s[offset]
+                offset += 1
+
+            if offset >= len(s):
+                real_parts = _configlist(parts[-1])
+                if not real_parts:
+                    parts[-1] = '"'
+                else:
+                    real_parts[0] = '"' + real_parts[0]
+                    parts = parts[:-1]
+                    parts.extend(real_parts)
+                return None, parts, offset
+
+            offset += 1
+            while offset < len(s) and s[offset] in [' ', ',']:
+                offset += 1
+
+            if offset < len(s):
+                if offset + 1 == len(s) and s[offset] == '"':
+                    parts[-1] += '"'
+                    offset += 1
+                else:
+                    parts.append('')
+            else:
+                return None, parts, offset
+
+            return _parse_plain, parts, offset
+
+        def _configlist(s):
+            s = s.rstrip(' ,')
+            if not s:
+                return []
+            parser, parts, offset = _parse_plain, [''], 0
+            while parser:
+                parser, parts, offset = parser(parts, s, offset)
+            return parts
+
+        result = self.config(section, name, untrusted=untrusted)
+        if result is None:
+            result = default or []
+        if isinstance(result, basestring):
+            result = _configlist(result.lstrip(' ,\n'))
+            if result is None:
+                result = default or []
+        return result
+
+    def has_section(self, section, untrusted=False):
+        '''tell whether section exists in config.'''
+        return section in self._data(untrusted)
+
+    def configitems(self, section, untrusted=False):
+        items = self._data(untrusted).items(section)
+        if self.debugflag and not untrusted and self._reportuntrusted:
+            for k, v in self._ucfg.items(section):
+                if self._tcfg.get(section, k) != v:
+                    self.debug(_("ignoring untrusted configuration option "
+                                "%s.%s = %s\n") % (section, k, v))
+        return items
+
+    def walkconfig(self, untrusted=False):
+        cfg = self._data(untrusted)
+        for section in cfg.sections():
+            for name, value in self.configitems(section, untrusted):
+                yield section, name, str(value).replace('\n', '\\n')
+
+    def plain(self):
+        '''is plain mode active?
+
+        Plain mode means that all configuration variables which affect the
+        behavior and output of Mercurial should be ignored. Additionally, the
+        output should be stable, reproducible and suitable for use in scripts or
+        applications.
+
+        The only way to trigger plain mode is by setting the `HGPLAIN'
+        environment variable.
+        '''
+        return 'HGPLAIN' in os.environ
+
+    def username(self):
+        """Return default username to be used in commits.
+
+        Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
+        and stop searching if one of these is set.
+        If not found and ui.askusername is True, ask the user, else use
+        ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
+        """
+        user = os.environ.get("HGUSER")
+        if user is None:
+            user = self.config("ui", "username")
+            if user is not None:
+                user = os.path.expandvars(user)
+        if user is None:
+            user = os.environ.get("EMAIL")
+        if user is None and self.configbool("ui", "askusername"):
+            user = self.prompt(_("enter a commit username:"), default=None)
+        if user is None and not self.interactive():
+            try:
+                user = '%s@%s' % (util.getuser(), socket.getfqdn())
+                self.warn(_("No username found, using '%s' instead\n") % user)
+            except KeyError:
+                pass
+        if not user:
+            raise util.Abort(_('no username supplied (see "hg help config")'))
+        if "\n" in user:
+            raise util.Abort(_("username %s contains a newline\n") % repr(user))
+        return user
+
+    def shortuser(self, user):
+        """Return a short representation of a user name or email address."""
+        if not self.verbose:
+            user = util.shortuser(user)
+        return user
+
+    def expandpath(self, loc, default=None):
+        """Return repository location relative to cwd or from [paths]"""
+        if "://" in loc or os.path.isdir(os.path.join(loc, '.hg')):
+            return loc
+
+        path = self.config('paths', loc)
+        if not path and default is not None:
+            path = self.config('paths', default)
+        return path or loc
+
+    def pushbuffer(self):
+        self._buffers.append([])
+
+    def popbuffer(self, labeled=False):
+        '''pop the last buffer and return the buffered output
+
+        If labeled is True, any labels associated with buffered
+        output will be handled. By default, this has no effect
+        on the output returned, but extensions and GUI tools may
+        handle this argument and returned styled output. If output
+        is being buffered so it can be captured and parsed or
+        processed, labeled should not be set to True.
+        '''
+        return "".join(self._buffers.pop())
+
+    def write(self, *args, **opts):
+        '''write args to output
+
+        By default, this method simply writes to the buffer or stdout,
+        but extensions or GUI tools may override this method,
+        write_err(), popbuffer(), and label() to style output from
+        various parts of hg.
+
+        An optional keyword argument, "label", can be passed in.
+        This should be a string containing label names separated by
+        space. Label names take the form of "topic.type". For example,
+        ui.debug() issues a label of "ui.debug".
+
+        When labeling output for a specific command, a label of
+        "cmdname.type" is recommended. For example, status issues
+        a label of "status.modified" for modified files.
+        '''
+        if self._buffers:
+            self._buffers[-1].extend([str(a) for a in args])
+        else:
+            for a in args:
+                sys.stdout.write(str(a))
+
+    def write_err(self, *args, **opts):
+        try:
+            if not getattr(sys.stdout, 'closed', False):
+                sys.stdout.flush()
+            for a in args:
+                sys.stderr.write(str(a))
+            # stderr may be buffered under win32 when redirected to files,
+            # including stdout.
+            if not getattr(sys.stderr, 'closed', False):
+                sys.stderr.flush()
+        except IOError, inst:
+            if inst.errno not in (errno.EPIPE, errno.EIO):
+                raise
+
+    def flush(self):
+        try: sys.stdout.flush()
+        except: pass
+        try: sys.stderr.flush()
+        except: pass
+
+    def interactive(self):
+        '''is interactive input allowed?
+
+        An interactive session is a session where input can be reasonably read
+        from `sys.stdin'. If this function returns false, any attempt to read
+        from stdin should fail with an error, unless a sensible default has been
+        specified.
+
+        Interactiveness is triggered by the value of the `ui.interactive'
+        configuration variable or - if it is unset - when `sys.stdin' points
+        to a terminal device.
+
+        This function refers to input only; for output, see `ui.formatted()'.
+        '''
+        i = self.configbool("ui", "interactive", None)
+        if i is None:
+            try:
+                return sys.stdin.isatty()
+            except AttributeError:
+                # some environments replace stdin without implementing isatty
+                # usually those are non-interactive
+                return False
+
+        return i
+
+    def termwidth(self):
+        '''how wide is the terminal in columns?
+        '''
+        if 'COLUMNS' in os.environ:
+            try:
+                return int(os.environ['COLUMNS'])
+            except ValueError:
+                pass
+        return util.termwidth()
+
+    def formatted(self):
+        '''should formatted output be used?
+
+        It is often desirable to format the output to suite the output medium.
+        Examples of this are truncating long lines or colorizing messages.
+        However, this is not often not desirable when piping output into other
+        utilities, e.g. `grep'.
+
+        Formatted output is triggered by the value of the `ui.formatted'
+        configuration variable or - if it is unset - when `sys.stdout' points
+        to a terminal device. Please note that `ui.formatted' should be
+        considered an implementation detail; it is not intended for use outside
+        Mercurial or its extensions.
+
+        This function refers to output only; for input, see `ui.interactive()'.
+        This function always returns false when in plain mode, see `ui.plain()'.
+        '''
+        if self.plain():
+            return False
+
+        i = self.configbool("ui", "formatted", None)
+        if i is None:
+            try:
+                return sys.stdout.isatty()
+            except AttributeError:
+                # some environments replace stdout without implementing isatty
+                # usually those are non-interactive
+                return False
+
+        return i
+
+    def _readline(self, prompt=''):
+        if sys.stdin.isatty():
+            try:
+                # magically add command line editing support, where
+                # available
+                import readline
+                # force demandimport to really load the module
+                readline.read_history_file
+                # windows sometimes raises something other than ImportError
+            except Exception:
+                pass
+        line = raw_input(prompt)
+        # When stdin is in binary mode on Windows, it can cause
+        # raw_input() to emit an extra trailing carriage return
+        if os.linesep == '\r\n' and line and line[-1] == '\r':
+            line = line[:-1]
+        return line
+
+    def prompt(self, msg, default="y"):
+        """Prompt user with msg, read response.
+        If ui is not interactive, the default is returned.
+        """
+        if not self.interactive():
+            self.write(msg, ' ', default, "\n")
+            return default
+        try:
+            r = self._readline(msg + ' ')
+            if not r:
+                return default
+            return r
+        except EOFError:
+            raise util.Abort(_('response expected'))
+
+    def promptchoice(self, msg, choices, default=0):
+        """Prompt user with msg, read response, and ensure it matches
+        one of the provided choices. The index of the choice is returned.
+        choices is a sequence of acceptable responses with the format:
+        ('&None', 'E&xec', 'Sym&link') Responses are case insensitive.
+        If ui is not interactive, the default is returned.
+        """
+        resps = [s[s.index('&')+1].lower() for s in choices]
+        while True:
+            r = self.prompt(msg, resps[default])
+            if r.lower() in resps:
+                return resps.index(r.lower())
+            self.write(_("unrecognized response\n"))
+
+    def getpass(self, prompt=None, default=None):
+        if not self.interactive():
+            return default
+        try:
+            return getpass.getpass(prompt or _('password: '))
+        except EOFError:
+            raise util.Abort(_('response expected'))
+    def status(self, *msg, **opts):
+        '''write status message to output (if ui.quiet is False)
+
+        This adds an output label of "ui.status".
+        '''
+        if not self.quiet:
+            opts['label'] = opts.get('label', '') + ' ui.status'
+            self.write(*msg, **opts)
+    def warn(self, *msg, **opts):
+        '''write warning message to output (stderr)
+
+        This adds an output label of "ui.warning".
+        '''
+        opts['label'] = opts.get('label', '') + ' ui.warning'
+        self.write_err(*msg, **opts)
+    def note(self, *msg, **opts):
+        '''write note to output (if ui.verbose is True)
+
+        This adds an output label of "ui.note".
+        '''
+        if self.verbose:
+            opts['label'] = opts.get('label', '') + ' ui.note'
+            self.write(*msg, **opts)
+    def debug(self, *msg, **opts):
+        '''write debug message to output (if ui.debugflag is True)
+
+        This adds an output label of "ui.debug".
+        '''
+        if self.debugflag:
+            opts['label'] = opts.get('label', '') + ' ui.debug'
+            self.write(*msg, **opts)
+    def edit(self, text, user):
+        (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
+                                      text=True)
+        try:
+            f = os.fdopen(fd, "w")
+            f.write(text)
+            f.close()
+
+            editor = self.geteditor()
+
+            util.system("%s \"%s\"" % (editor, name),
+                        environ={'HGUSER': user},
+                        onerr=util.Abort, errprefix=_("edit failed"))
+
+            f = open(name)
+            t = f.read()
+            f.close()
+        finally:
+            os.unlink(name)
+
+        return t
+
+    def traceback(self, exc=None):
+        '''print exception traceback if traceback printing enabled.
+        only to call in exception handler. returns true if traceback
+        printed.'''
+        if self.tracebackflag:
+            if exc:
+                traceback.print_exception(exc[0], exc[1], exc[2])
+            else:
+                traceback.print_exc()
+        return self.tracebackflag
+
+    def geteditor(self):
+        '''return editor to use'''
+        return (os.environ.get("HGEDITOR") or
+                self.config("ui", "editor") or
+                os.environ.get("VISUAL") or
+                os.environ.get("EDITOR", "vi"))
+
+    def progress(self, topic, pos, item="", unit="", total=None):
+        '''show a progress message
+
+        With stock hg, this is simply a debug message that is hidden
+        by default, but with extensions or GUI tools it may be
+        visible. 'topic' is the current operation, 'item' is a
+        non-numeric marker of the current position (ie the currently
+        in-process file), 'pos' is the current numeric position (ie
+        revision, bytes, etc.), unit is a corresponding unit label,
+        and total is the highest expected pos.
+
+        Multiple nested topics may be active at a time.
+
+        All topics should be marked closed by setting pos to None at
+        termination.
+        '''
+
+        if pos == None or not self.debugflag:
+            return
+
+        if unit:
+            unit = ' ' + unit
+        if item:
+            item = ' ' + item
+
+        if total:
+            pct = 100.0 * pos / total
+            self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
+                     % (topic, item, pos, total, unit, pct))
+        else:
+            self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
+
+    def log(self, service, message):
+        '''hook for logging facility extensions
+
+        service should be a readily-identifiable subsystem, which will
+        allow filtering.
+        message should be a newline-terminated string to log.
+        '''
+        pass
+
+    def label(self, msg, label):
+        '''style msg based on supplied label
+
+        Like ui.write(), this just returns msg unchanged, but extensions
+        and GUI tools can override it to allow styling output without
+        writing it.
+
+        ui.write(s, 'label') is equivalent to
+        ui.write(ui.label(s, 'label')).
+        '''
+        return msg