summaryrefslogtreecommitdiff
path: root/sys/src/cmd/hg/mercurial/ui.py
diff options
context:
space:
mode:
authorcinap_lenrek <cinap_lenrek@localhost>2011-05-03 11:25:13 +0000
committercinap_lenrek <cinap_lenrek@localhost>2011-05-03 11:25:13 +0000
commit458120dd40db6b4df55a4e96b650e16798ef06a0 (patch)
tree8f82685be24fef97e715c6f5ca4c68d34d5074ee /sys/src/cmd/hg/mercurial/ui.py
parent3a742c699f6806c1145aea5149bf15de15a0afd7 (diff)
add hg and python
Diffstat (limited to 'sys/src/cmd/hg/mercurial/ui.py')
-rw-r--r--sys/src/cmd/hg/mercurial/ui.py381
1 files changed, 381 insertions, 0 deletions
diff --git a/sys/src/cmd/hg/mercurial/ui.py b/sys/src/cmd/hg/mercurial/ui.py
new file mode 100644
index 000000000..bd122f74a
--- /dev/null
+++ b/sys/src/cmd/hg/mercurial/ui.py
@@ -0,0 +1,381 @@
+# 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, incorporated herein by reference.
+
+from i18n import _
+import errno, getpass, os, socket, sys, tempfile, traceback
+import config, util, error
+
+_booleans = {'1': True, 'yes': True, 'true': True, 'on': True,
+ '0': False, 'no': False, 'false': False, 'off': False}
+
+class ui(object):
+ def __init__(self, src=None):
+ self._buffers = []
+ self.quiet = self.verbose = self.debugflag = self._traceback = 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.fixconfig()
+ else:
+ # 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 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):
+ # 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 p and "://" not in p and not os.path.isabs(p):
+ c.set("paths", n, os.path.normpath(os.path.join(root, p)))
+
+ # 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._traceback = self.configbool('ui', 'traceback', False)
+
+ # update trust information
+ self._trustusers.update(self.configlist('trusted', 'users'))
+ self._trustgroups.update(self.configlist('trusted', 'groups'))
+
+ def setconfig(self, section, name, value):
+ for cfg in (self._ocfg, self._tcfg, self._ucfg):
+ cfg.set(section, name, value)
+ self.fixconfig()
+
+ 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 v.lower() not in _booleans:
+ raise error.ConfigError(_("%s.%s not a boolean ('%s')")
+ % (section, name, v))
+ return _booleans[v.lower()]
+
+ def configlist(self, section, name, default=None, untrusted=False):
+ """Return a list of comma/space separated strings"""
+ result = self.config(section, name, untrusted=untrusted)
+ if result is None:
+ result = default or []
+ if isinstance(result, basestring):
+ result = result.replace(",", " ").split()
+ 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 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 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:
+ 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(_("Please specify a username."))
+ 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 _path(self, loc):
+ p = self.config('paths', loc)
+ if p and '%%' in p:
+ self.warn('(deprecated \'%%\' in path %s=%s from %s)\n' %
+ (loc, p, self.configsource('paths', loc)))
+ p = p.replace('%%', '%')
+ return p
+
+ 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._path(loc)
+ if not path and default is not None:
+ path = self._path(default)
+ return path or loc
+
+ def pushbuffer(self):
+ self._buffers.append([])
+
+ def popbuffer(self):
+ return "".join(self._buffers.pop())
+
+ def write(self, *args):
+ 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):
+ try:
+ if not sys.stdout.closed: 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 sys.stderr.closed: sys.stderr.flush()
+ except IOError, inst:
+ if inst.errno != errno.EPIPE:
+ raise
+
+ def flush(self):
+ try: sys.stdout.flush()
+ except: pass
+ try: sys.stderr.flush()
+ except: pass
+
+ def interactive(self):
+ i = self.configbool("ui", "interactive", None)
+ if i is None:
+ return sys.stdin.isatty()
+ 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):
+ if not self.quiet: self.write(*msg)
+ def warn(self, *msg):
+ self.write_err(*msg)
+ def note(self, *msg):
+ if self.verbose: self.write(*msg)
+ def debug(self, *msg):
+ if self.debugflag: self.write(*msg)
+ 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):
+ '''print exception traceback if traceback printing enabled.
+ only to call in exception handler. returns true if traceback
+ printed.'''
+ if self._traceback:
+ traceback.print_exc()
+ return self._traceback
+
+ 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.), units 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 units:
+ units = ' ' + units
+ if item:
+ item = ' ' + item
+
+ if total:
+ pct = 100.0 * pos / total
+ ui.debug('%s:%s %s/%s%s (%4.2g%%)\n'
+ % (topic, item, pos, total, units, pct))
+ else:
+ ui.debug('%s:%s %s%s\n' % (topic, item, pos, units))