ui.py
363 lines
| 13.4 KiB
| text/x-python
|
PythonLexer
/ mercurial / ui.py
|
r207 | # ui.py - user interface bits for mercurial | ||
# | ||||
# Copyright 2005 Matt Mackall <mpm@selenic.com> | ||||
# | ||||
# This software may be used and distributed according to the terms | ||||
# of the GNU General Public License, incorporated herein by reference. | ||||
|
r1400 | from i18n import gettext as _ | ||
|
r613 | from demandload import * | ||
|
r2292 | demandload(globals(), "errno getpass os re smtplib socket sys tempfile") | ||
|
r2470 | demandload(globals(), "ConfigParser templater traceback util") | ||
|
r207 | |||
|
r1559 | class ui(object): | ||
|
r207 | def __init__(self, verbose=False, debug=False, quiet=False, | ||
|
r2166 | interactive=True, traceback=False, parentui=None): | ||
|
r960 | self.overlay = {} | ||
|
r1839 | if parentui is None: | ||
|
r1874 | # this is the parent of all ui children | ||
self.parentui = None | ||||
self.cdata = ConfigParser.SafeConfigParser() | ||||
|
r1951 | self.readconfig(util.rcpath()) | ||
|
r285 | |||
|
r1839 | self.quiet = self.configbool("ui", "quiet") | ||
self.verbose = self.configbool("ui", "verbose") | ||||
self.debugflag = self.configbool("ui", "debug") | ||||
self.interactive = self.configbool("ui", "interactive", True) | ||||
|
r2166 | self.traceback = traceback | ||
|
r285 | |||
|
r1839 | self.updateopts(verbose, debug, quiet, interactive) | ||
self.diffcache = None | ||||
|
r2033 | self.header = [] | ||
self.prev_header = [] | ||||
|
r2072 | self.revlogopts = self.configrevlog() | ||
|
r1866 | else: | ||
|
r1874 | # parentui may point to an ui object which is already a child | ||
self.parentui = parentui.parentui or parentui | ||||
parent_cdata = self.parentui.cdata | ||||
self.cdata = ConfigParser.SafeConfigParser(parent_cdata.defaults()) | ||||
# make interpolation work | ||||
for section in parent_cdata.sections(): | ||||
self.cdata.add_section(section) | ||||
for name, value in parent_cdata.items(section, raw=True): | ||||
self.cdata.set(section, name, value) | ||||
|
r1839 | |||
def __getattr__(self, key): | ||||
return getattr(self.parentui, key) | ||||
|
r1071 | |||
def updateopts(self, verbose=False, debug=False, quiet=False, | ||||
|
r2293 | interactive=True, traceback=False, config=[]): | ||
|
r285 | self.quiet = (self.quiet or quiet) and not verbose and not debug | ||
self.verbose = (self.verbose or verbose) or debug | ||||
self.debugflag = (self.debugflag or debug) | ||||
self.interactive = (self.interactive and interactive) | ||||
|
r2166 | self.traceback = self.traceback or traceback | ||
|
r2293 | for cfg in config: | ||
try: | ||||
name, value = cfg.split('=', 1) | ||||
section, name = name.split('.', 1) | ||||
if not self.cdata.has_section(section): | ||||
self.cdata.add_section(section) | ||||
if not section or not name: | ||||
raise IndexError | ||||
self.cdata.set(section, name, value) | ||||
except (IndexError, ValueError): | ||||
raise util.Abort(_('malformed --config option: %s') % cfg) | ||||
|
r285 | |||
|
r1893 | def readconfig(self, fn, root=None): | ||
|
r1483 | if isinstance(fn, basestring): | ||
fn = [fn] | ||||
for f in fn: | ||||
try: | ||||
self.cdata.read(f) | ||||
except ConfigParser.ParsingError, inst: | ||||
raise util.Abort(_("Failed to parse %s\n%s") % (f, inst)) | ||||
|
r1893 | # translate paths relative to root (or home) into absolute paths | ||
if root is None: | ||||
root = os.path.expanduser('~') | ||||
for name, path in self.configitems("paths"): | ||||
|
r2579 | if path and "://" not in path and not os.path.isabs(path): | ||
|
r1893 | self.cdata.set("paths", name, os.path.join(root, path)) | ||
|
r337 | |||
|
r960 | def setconfig(self, section, name, val): | ||
self.overlay[(section, name)] = val | ||||
def config(self, section, name, default=None): | ||||
if self.overlay.has_key((section, name)): | ||||
return self.overlay[(section, name)] | ||||
if self.cdata.has_option(section, name): | ||||
|
r1876 | try: | ||
return self.cdata.get(section, name) | ||||
except ConfigParser.InterpolationError, inst: | ||||
raise util.Abort(_("Error in configuration:\n%s") % inst) | ||||
|
r1839 | if self.parentui is None: | ||
return default | ||||
else: | ||||
return self.parentui.config(section, name, default) | ||||
|
r285 | |||
|
r2499 | def configlist(self, section, name, default=None): | ||
"""Return a list of comma/space separated strings""" | ||||
result = self.config(section, name) | ||||
if result is None: | ||||
|
r2502 | result = default or [] | ||
if isinstance(result, basestring): | ||||
result = result.replace(",", " ").split() | ||||
return result | ||||
|
r2499 | |||
|
r960 | def configbool(self, section, name, default=False): | ||
if self.overlay.has_key((section, name)): | ||||
return self.overlay[(section, name)] | ||||
if self.cdata.has_option(section, name): | ||||
|
r1876 | try: | ||
return self.cdata.getboolean(section, name) | ||||
except ConfigParser.InterpolationError, inst: | ||||
raise util.Abort(_("Error in configuration:\n%s") % inst) | ||||
|
r1839 | if self.parentui is None: | ||
return default | ||||
else: | ||||
return self.parentui.configbool(section, name, default) | ||||
|
r285 | |||
|
r2343 | def has_config(self, section): | ||
'''tell whether section exists in config.''' | ||||
return self.cdata.has_section(section) | ||||
|
r285 | def configitems(self, section): | ||
|
r1839 | items = {} | ||
if self.parentui is not None: | ||||
items = dict(self.parentui.configitems(section)) | ||||
|
r285 | if self.cdata.has_section(section): | ||
|
r1876 | try: | ||
items.update(dict(self.cdata.items(section))) | ||||
except ConfigParser.InterpolationError, inst: | ||||
raise util.Abort(_("Error in configuration:\n%s") % inst) | ||||
|
r1839 | x = items.items() | ||
x.sort() | ||||
return x | ||||
|
r285 | |||
|
r1839 | def walkconfig(self, seen=None): | ||
if seen is None: | ||||
seen = {} | ||||
|
r1028 | for (section, name), value in self.overlay.iteritems(): | ||
yield section, name, value | ||||
seen[section, name] = 1 | ||||
for section in self.cdata.sections(): | ||||
for name, value in self.cdata.items(section): | ||||
if (section, name) in seen: continue | ||||
yield section, name, value.replace('\n', '\\n') | ||||
seen[section, name] = 1 | ||||
|
r1839 | if self.parentui is not None: | ||
for parent in self.parentui.walkconfig(seen): | ||||
yield parent | ||||
|
r1028 | |||
|
r1071 | def extensions(self): | ||
|
r2403 | result = self.configitems("extensions") | ||
for i, (key, value) in enumerate(result): | ||||
if value: | ||||
result[i] = (key, os.path.expanduser(value)) | ||||
return result | ||||
|
r1071 | |||
|
r2003 | def hgignorefiles(self): | ||
|
r2403 | result = [] | ||
for key, value in self.configitems("ui"): | ||||
if key == 'ignore' or key.startswith('ignore.'): | ||||
result.append(os.path.expanduser(value)) | ||||
return result | ||||
|
r2003 | |||
|
r2072 | def configrevlog(self): | ||
|
r2403 | result = {} | ||
for key, value in self.configitems("revlog"): | ||||
result[key.lower()] = value | ||||
return result | ||||
|
r2388 | |||
|
r1637 | def diffopts(self): | ||
if self.diffcache: | ||||
return self.diffcache | ||||
|
r2580 | result = {'showfunc': True, 'ignorews': False, | ||
'ignorewsamount': False, 'ignoreblanklines': False} | ||||
|
r2403 | for key, value in self.configitems("diff"): | ||
if value: | ||||
result[key.lower()] = (value.lower() == 'true') | ||||
self.diffcache = result | ||||
return result | ||||
|
r1637 | |||
|
r608 | def username(self): | ||
|
r1985 | """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. | ||||
Abort if found username is an empty string to force specifying | ||||
the commit user elsewhere, e.g. with line option or repo hgrc. | ||||
|
r2343 | If not found, use ($LOGNAME or $USER or $LNAME or | ||
$USERNAME) +"@full.hostname". | ||||
|
r1985 | """ | ||
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: | ||||
|
r2343 | try: | ||
user = '%s@%s' % (getpass.getuser(), socket.getfqdn()) | ||||
except KeyError: | ||||
raise util.Abort(_("Please specify a username.")) | ||||
|
r1985 | return user | ||
|
r608 | |||
|
r1129 | def shortuser(self, user): | ||
"""Return a short representation of a user name or email address.""" | ||||
|
r1903 | if not self.verbose: user = util.shortuser(user) | ||
|
r1129 | return user | ||
|
r2494 | def expandpath(self, loc, default=None): | ||
|
r1892 | """Return repository location relative to cwd or from [paths]""" | ||
|
r2579 | if "://" in loc or os.path.exists(loc): | ||
|
r1892 | return loc | ||
|
r2495 | path = self.config("paths", loc) | ||
if not path and default is not None: | ||||
path = self.config("paths", default) | ||||
|
r2498 | return path or loc | ||
|
r506 | |||
|
r2598 | def setconfig_remoteopts(self, **opts): | ||
if opts.get('ssh'): | ||||
self.setconfig("ui", "ssh", opts['ssh']) | ||||
if opts.get('remotecmd'): | ||||
self.setconfig("ui", "remotecmd", opts['remotecmd']) | ||||
|
r207 | def write(self, *args): | ||
|
r2033 | if self.header: | ||
if self.header != self.prev_header: | ||||
self.prev_header = self.header | ||||
self.write(*self.header) | ||||
self.header = [] | ||||
|
r207 | for a in args: | ||
sys.stdout.write(str(a)) | ||||
|
r565 | |||
|
r2033 | def write_header(self, *args): | ||
for a in args: | ||||
self.header.append(str(a)) | ||||
|
r565 | def write_err(self, *args): | ||
|
r1989 | try: | ||
if not sys.stdout.closed: sys.stdout.flush() | ||||
for a in args: | ||||
sys.stderr.write(str(a)) | ||||
except IOError, inst: | ||||
if inst.errno != errno.EPIPE: | ||||
raise | ||||
|
r565 | |||
|
r1837 | def flush(self): | ||
|
r2013 | try: sys.stdout.flush() | ||
except: pass | ||||
try: sys.stderr.flush() | ||||
except: pass | ||||
|
r1837 | |||
|
r207 | def readline(self): | ||
return sys.stdin.readline()[:-1] | ||||
|
r2281 | def prompt(self, msg, pat=None, default="y"): | ||
|
r207 | if not self.interactive: return default | ||
while 1: | ||||
self.write(msg, " ") | ||||
r = self.readline() | ||||
|
r2281 | if not pat or re.match(pat, r): | ||
|
r207 | return r | ||
else: | ||||
|
r1402 | self.write(_("unrecognized response\n")) | ||
|
r2281 | def getpass(self, prompt=None, default=None): | ||
if not self.interactive: return default | ||||
return getpass.getpass(prompt or _('password: ')) | ||||
|
r207 | def status(self, *msg): | ||
if not self.quiet: self.write(*msg) | ||||
|
r234 | def warn(self, *msg): | ||
|
r565 | self.write_err(*msg) | ||
|
r207 | def note(self, *msg): | ||
if self.verbose: self.write(*msg) | ||||
def debug(self, *msg): | ||||
if self.debugflag: self.write(*msg) | ||||
|
r1983 | def edit(self, text, user): | ||
|
r2206 | (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt", | ||
text=True) | ||||
|
r1984 | try: | ||
f = os.fdopen(fd, "w") | ||||
f.write(text) | ||||
f.close() | ||||
editor = (os.environ.get("HGEDITOR") or | ||||
self.config("ui", "editor") or | ||||
os.environ.get("EDITOR", "vi")) | ||||
|
r207 | |||
|
r1984 | util.system("%s \"%s\"" % (editor, name), | ||
environ={'HGUSER': user}, | ||||
onerr=util.Abort, errprefix=_("edit failed")) | ||||
|
r608 | |||
|
r1984 | f = open(name) | ||
t = f.read() | ||||
f.close() | ||||
t = re.sub("(?m)^HG:.*\n", "", t) | ||||
finally: | ||||
os.unlink(name) | ||||
|
r662 | |||
|
r207 | return t | ||
|
r2200 | |||
def sendmail(self): | ||||
|
r2292 | '''send mail message. object returned has one method, sendmail. | ||
call as sendmail(sender, list-of-recipients, msg).''' | ||||
def smtp(): | ||||
'''send mail using smtp.''' | ||||
|
r2583 | local_hostname = self.config('smtp', 'local_hostname') | ||
s = smtplib.SMTP(local_hostname=local_hostname) | ||||
|
r2292 | mailhost = self.config('smtp', 'host') | ||
if not mailhost: | ||||
raise util.Abort(_('no [smtp]host in hgrc - cannot send mail')) | ||||
mailport = int(self.config('smtp', 'port', 25)) | ||||
self.note(_('sending mail: smtp host %s, port %s\n') % | ||||
(mailhost, mailport)) | ||||
s.connect(host=mailhost, port=mailport) | ||||
if self.configbool('smtp', 'tls'): | ||||
self.note(_('(using tls)\n')) | ||||
s.ehlo() | ||||
s.starttls() | ||||
s.ehlo() | ||||
username = self.config('smtp', 'username') | ||||
password = self.config('smtp', 'password') | ||||
if username and password: | ||||
self.note(_('(authenticating to mail server as %s)\n') % | ||||
(username)) | ||||
s.login(username, password) | ||||
return s | ||||
class sendmail(object): | ||||
'''send mail using sendmail.''' | ||||
def __init__(self, ui, program): | ||||
self.ui = ui | ||||
self.program = program | ||||
def sendmail(self, sender, recipients, msg): | ||||
cmdline = '%s -f %s %s' % ( | ||||
self.program, templater.email(sender), | ||||
' '.join(map(templater.email, recipients))) | ||||
self.ui.note(_('sending mail: %s\n') % cmdline) | ||||
fp = os.popen(cmdline, 'w') | ||||
fp.write(msg) | ||||
ret = fp.close() | ||||
if ret: | ||||
raise util.Abort('%s %s' % ( | ||||
os.path.basename(self.program.split(None, 1)[0]), | ||||
util.explain_exit(ret)[0])) | ||||
method = self.config('email', 'method', 'smtp') | ||||
if method == 'smtp': | ||||
mail = smtp() | ||||
else: | ||||
mail = sendmail(self, method) | ||||
return mail | ||||
|
r2335 | |||
def print_exc(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 | ||||