##// END OF EJS Templates
hgwebdir: read --webdir-conf as actual configuration to ui (issue1586)...
Alexander Solovyov -
r8345:dcebff8a default
parent child Browse files
Show More
@@ -1,318 +1,317
1 1 # hgweb/hgwebdir_mod.py - Web interface for a directory of repositories.
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2, incorporated herein by reference.
8 8
9 9 import os
10 10 from mercurial.i18n import _
11 11 from mercurial import ui, hg, util, templater, templatefilters
12 from mercurial import config, error, encoding
12 from mercurial import error, encoding
13 13 from common import ErrorResponse, get_mtime, staticfile, paritygen,\
14 14 get_contact, HTTP_OK, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
15 15 from hgweb_mod import hgweb
16 16 from request import wsgirequest
17 17 import webutil
18 18
19 19 def cleannames(items):
20 20 return [(util.pconvert(name).strip('/'), path) for name, path in items]
21 21
22 22 class hgwebdir(object):
23 23
24 24 def __init__(self, conf, baseui=None):
25 25
26 26 if baseui:
27 27 self.ui = baseui.copy()
28 28 else:
29 29 self.ui = ui.ui()
30 30 self.ui.setconfig('ui', 'report_untrusted', 'off')
31 31 self.ui.setconfig('ui', 'interactive', 'off')
32 32
33 self.motd = None
34 self.style = 'paper'
35 self.stripecount = 1
36 33 self.repos_sorted = ('name', False)
37 self._baseurl = None
38 34
39 35 if isinstance(conf, (list, tuple)):
40 36 self.repos = cleannames(conf)
41 37 self.repos_sorted = ('', False)
42 38 elif isinstance(conf, dict):
43 39 self.repos = sorted(cleannames(conf.items()))
44 40 else:
45 if isinstance(conf, config.config):
46 cp = conf
47 else:
48 cp = config.config()
49 cp.read(conf)
41 self.ui.readconfig(conf, remap={'paths': 'hgweb-paths'})
50 42 self.repos = []
51 self.motd = cp.get('web', 'motd')
52 self.style = cp.get('web', 'style', 'paper')
53 self.stripecount = cp.get('web', 'stripes', 1)
54 self._baseurl = cp.get('web', 'baseurl')
55 if 'paths' in cp:
56 paths = cleannames(cp.items('paths'))
57 for prefix, root in paths:
58 roothead, roottail = os.path.split(root)
59 # "foo = /bar/*" makes every subrepo of /bar/ to be
60 # mounted as foo/subrepo
61 # and "foo = /bar/**" does even recurse inside the
62 # subdirectories, remember to use it without working dir.
63 try:
64 recurse = {'*': False, '**': True}[roottail]
65 except KeyError:
66 self.repos.append((prefix, root))
67 continue
68 roothead = os.path.normpath(roothead)
69 for path in util.walkrepos(roothead, followsym=True,
70 recurse=recurse):
71 path = os.path.normpath(path)
72 name = util.pconvert(path[len(roothead):]).strip('/')
73 if prefix:
74 name = prefix + '/' + name
75 self.repos.append((name, path))
76 for prefix, root in cp.items('collections'):
77 for path in util.walkrepos(root, followsym=True):
78 repo = os.path.normpath(path)
79 name = repo
80 if name.startswith(prefix):
81 name = name[len(prefix):]
82 self.repos.append((name.lstrip(os.sep), repo))
83 self.repos.sort()
43
44 self.motd = self.ui.config('web', 'motd')
45 self.style = self.ui.config('web', 'style', 'paper')
46 self.stripecount = self.ui.config('web', 'stripes', 1)
47 if self.stripecount:
48 self.stripecount = int(self.stripecount)
49 self._baseurl = self.ui.config('web', 'baseurl')
50
51 if self.repos:
52 return
53
54 for prefix, root in cleannames(self.ui.configitems('hgweb-paths')):
55 roothead, roottail = os.path.split(root)
56 # "foo = /bar/*" makes every subrepo of /bar/ to be
57 # mounted as foo/subrepo
58 # and "foo = /bar/**" also recurses into the subdirectories,
59 # remember to use it without working dir.
60 try:
61 recurse = {'*': False, '**': True}[roottail]
62 except KeyError:
63 self.repos.append((prefix, root))
64 continue
65 roothead = os.path.normpath(roothead)
66 for path in util.walkrepos(roothead, followsym=True,
67 recurse=recurse):
68 path = os.path.normpath(path)
69 name = util.pconvert(path[len(roothead):]).strip('/')
70 if prefix:
71 name = prefix + '/' + name
72 self.repos.append((name, path))
73
74 for prefix, root in self.ui.configitems('collections'):
75 for path in util.walkrepos(root, followsym=True):
76 repo = os.path.normpath(path)
77 name = repo
78 if name.startswith(prefix):
79 name = name[len(prefix):]
80 self.repos.append((name.lstrip(os.sep), repo))
81
82 self.repos.sort()
84 83
85 84 def run(self):
86 85 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
87 86 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
88 87 import mercurial.hgweb.wsgicgi as wsgicgi
89 88 wsgicgi.launch(self)
90 89
91 90 def __call__(self, env, respond):
92 91 req = wsgirequest(env, respond)
93 92 return self.run_wsgi(req)
94 93
95 94 def read_allowed(self, ui, req):
96 95 """Check allow_read and deny_read config options of a repo's ui object
97 96 to determine user permissions. By default, with neither option set (or
98 97 both empty), allow all users to read the repo. There are two ways a
99 98 user can be denied read access: (1) deny_read is not empty, and the
100 99 user is unauthenticated or deny_read contains user (or *), and (2)
101 100 allow_read is not empty and the user is not in allow_read. Return True
102 101 if user is allowed to read the repo, else return False."""
103 102
104 103 user = req.env.get('REMOTE_USER')
105 104
106 105 deny_read = ui.configlist('web', 'deny_read', untrusted=True)
107 106 if deny_read and (not user or deny_read == ['*'] or user in deny_read):
108 107 return False
109 108
110 109 allow_read = ui.configlist('web', 'allow_read', untrusted=True)
111 110 # by default, allow reading if no allow_read option has been set
112 111 if (not allow_read) or (allow_read == ['*']) or (user in allow_read):
113 112 return True
114 113
115 114 return False
116 115
117 116 def run_wsgi(self, req):
118 117
119 118 try:
120 119 try:
121 120
122 121 virtual = req.env.get("PATH_INFO", "").strip('/')
123 122 tmpl = self.templater(req)
124 123 ctype = tmpl('mimetype', encoding=encoding.encoding)
125 124 ctype = templater.stringify(ctype)
126 125
127 126 # a static file
128 127 if virtual.startswith('static/') or 'static' in req.form:
129 128 if virtual.startswith('static/'):
130 129 fname = virtual[7:]
131 130 else:
132 131 fname = req.form['static'][0]
133 132 static = templater.templatepath('static')
134 133 return (staticfile(static, fname, req),)
135 134
136 135 # top-level index
137 136 elif not virtual:
138 137 req.respond(HTTP_OK, ctype)
139 138 return self.makeindex(req, tmpl)
140 139
141 140 # nested indexes and hgwebs
142 141
143 142 repos = dict(self.repos)
144 143 while virtual:
145 144 real = repos.get(virtual)
146 145 if real:
147 146 req.env['REPO_NAME'] = virtual
148 147 try:
149 148 repo = hg.repository(self.ui, real)
150 149 return hgweb(repo).run_wsgi(req)
151 150 except IOError, inst:
152 151 msg = inst.strerror
153 152 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
154 153 except error.RepoError, inst:
155 154 raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
156 155
157 156 # browse subdirectories
158 157 subdir = virtual + '/'
159 158 if [r for r in repos if r.startswith(subdir)]:
160 159 req.respond(HTTP_OK, ctype)
161 160 return self.makeindex(req, tmpl, subdir)
162 161
163 162 up = virtual.rfind('/')
164 163 if up < 0:
165 164 break
166 165 virtual = virtual[:up]
167 166
168 167 # prefixes not found
169 168 req.respond(HTTP_NOT_FOUND, ctype)
170 169 return tmpl("notfound", repo=virtual)
171 170
172 171 except ErrorResponse, err:
173 172 req.respond(err, ctype)
174 173 return tmpl('error', error=err.message or '')
175 174 finally:
176 175 tmpl = None
177 176
178 177 def makeindex(self, req, tmpl, subdir=""):
179 178
180 179 def archivelist(ui, nodeid, url):
181 180 allowed = ui.configlist("web", "allow_archive", untrusted=True)
182 181 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
183 182 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
184 183 untrusted=True):
185 184 yield {"type" : i[0], "extension": i[1],
186 185 "node": nodeid, "url": url}
187 186
188 187 def entries(sortcolumn="", descending=False, subdir="", **map):
189 188 rows = []
190 189 parity = paritygen(self.stripecount)
191 190 for name, path in self.repos:
192 191 if not name.startswith(subdir):
193 192 continue
194 193 name = name[len(subdir):]
195 194
196 195 u = self.ui.copy()
197 196 try:
198 197 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
199 198 except Exception, e:
200 199 u.warn(_('error reading %s/.hg/hgrc: %s\n') % (path, e))
201 200 continue
202 201 def get(section, name, default=None):
203 202 return u.config(section, name, default, untrusted=True)
204 203
205 204 if u.configbool("web", "hidden", untrusted=True):
206 205 continue
207 206
208 207 if not self.read_allowed(u, req):
209 208 continue
210 209
211 210 parts = [name]
212 211 if 'PATH_INFO' in req.env:
213 212 parts.insert(0, req.env['PATH_INFO'].rstrip('/'))
214 213 if req.env['SCRIPT_NAME']:
215 214 parts.insert(0, req.env['SCRIPT_NAME'])
216 215 url = ('/'.join(parts).replace("//", "/")) + '/'
217 216
218 217 # update time with local timezone
219 218 try:
220 219 d = (get_mtime(path), util.makedate()[1])
221 220 except OSError:
222 221 continue
223 222
224 223 contact = get_contact(get)
225 224 description = get("web", "description", "")
226 225 name = get("web", "name", name)
227 226 row = dict(contact=contact or "unknown",
228 227 contact_sort=contact.upper() or "unknown",
229 228 name=name,
230 229 name_sort=name,
231 230 url=url,
232 231 description=description or "unknown",
233 232 description_sort=description.upper() or "unknown",
234 233 lastchange=d,
235 234 lastchange_sort=d[1]-d[0],
236 235 archives=archivelist(u, "tip", url))
237 236 if (not sortcolumn
238 237 or (sortcolumn, descending) == self.repos_sorted):
239 238 # fast path for unsorted output
240 239 row['parity'] = parity.next()
241 240 yield row
242 241 else:
243 242 rows.append((row["%s_sort" % sortcolumn], row))
244 243 if rows:
245 244 rows.sort()
246 245 if descending:
247 246 rows.reverse()
248 247 for key, row in rows:
249 248 row['parity'] = parity.next()
250 249 yield row
251 250
252 251 sortable = ["name", "description", "contact", "lastchange"]
253 252 sortcolumn, descending = self.repos_sorted
254 253 if 'sort' in req.form:
255 254 sortcolumn = req.form['sort'][0]
256 255 descending = sortcolumn.startswith('-')
257 256 if descending:
258 257 sortcolumn = sortcolumn[1:]
259 258 if sortcolumn not in sortable:
260 259 sortcolumn = ""
261 260
262 261 sort = [("sort_%s" % column,
263 262 "%s%s" % ((not descending and column == sortcolumn)
264 263 and "-" or "", column))
265 264 for column in sortable]
266 265
267 266 if self._baseurl is not None:
268 267 req.env['SCRIPT_NAME'] = self._baseurl
269 268
270 269 return tmpl("index", entries=entries, subdir=subdir,
271 270 sortcolumn=sortcolumn, descending=descending,
272 271 **dict(sort))
273 272
274 273 def templater(self, req):
275 274
276 275 def header(**map):
277 276 yield tmpl('header', encoding=encoding.encoding, **map)
278 277
279 278 def footer(**map):
280 279 yield tmpl("footer", **map)
281 280
282 281 def motd(**map):
283 282 if self.motd is not None:
284 283 yield self.motd
285 284 else:
286 285 yield config('web', 'motd', '')
287 286
288 287 def config(section, name, default=None, untrusted=True):
289 288 return self.ui.config(section, name, default, untrusted)
290 289
291 290 if self._baseurl is not None:
292 291 req.env['SCRIPT_NAME'] = self._baseurl
293 292
294 293 url = req.env.get('SCRIPT_NAME', '')
295 294 if not url.endswith('/'):
296 295 url += '/'
297 296
298 297 vars = {}
299 298 style = self.style
300 299 if 'style' in req.form:
301 300 vars['style'] = style = req.form['style'][0]
302 301 start = url[-1] == '?' and '&' or '?'
303 302 sessionvars = webutil.sessionvars(vars, start)
304 303
305 304 staticurl = config('web', 'staticurl') or url + 'static/'
306 305 if not staticurl.endswith('/'):
307 306 staticurl += '/'
308 307
309 308 style = 'style' in req.form and req.form['style'][0] or self.style
310 309 mapfile = templater.stylemap(style)
311 310 tmpl = templater.templater(mapfile, templatefilters.filters,
312 311 defaults={"header": header,
313 312 "footer": footer,
314 313 "motd": motd,
315 314 "url": url,
316 315 "staticurl": staticurl,
317 316 "sessionvars": sessionvars})
318 317 return tmpl
@@ -1,346 +1,346
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, incorporated herein by reference.
7 7
8 8 from i18n import _
9 9 import errno, getpass, os, re, socket, sys, tempfile, traceback
10 10 import config, util, error
11 11
12 12 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True,
13 13 '0': False, 'no': False, 'false': False, 'off': False}
14 14
15 15 class ui(object):
16 16 def __init__(self, src=None):
17 17 self._buffers = []
18 18 self.quiet = self.verbose = self.debugflag = self._traceback = False
19 19 self._reportuntrusted = True
20 20 self._ocfg = config.config() # overlay
21 21 self._tcfg = config.config() # trusted
22 22 self._ucfg = config.config() # untrusted
23 23 self._trustusers = {}
24 24 self._trustgroups = {}
25 25
26 26 if src:
27 27 self._tcfg = src._tcfg.copy()
28 28 self._ucfg = src._ucfg.copy()
29 29 self._ocfg = src._ocfg.copy()
30 30 self._trustusers = src._trustusers.copy()
31 31 self._trustgroups = src._trustgroups.copy()
32 32 self.fixconfig()
33 33 else:
34 34 # we always trust global config files
35 35 for f in util.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(fp, 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 sections=None):
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 cfg.read(filename, fp, sections=sections)
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 trusted:
80 80 self._tcfg.update(cfg)
81 81 self._tcfg.update(self._ocfg)
82 82 self._ucfg.update(cfg)
83 83 self._ucfg.update(self._ocfg)
84 84
85 85 if root is None:
86 86 root = os.path.expanduser('~')
87 87 self.fixconfig(root=root)
88 88
89 89 def fixconfig(self, root=None):
90 90 # translate paths relative to root (or home) into absolute paths
91 91 root = root or os.getcwd()
92 92 for c in self._tcfg, self._ucfg, self._ocfg:
93 93 for n, p in c.items('paths'):
94 94 if p and "://" not in p and not os.path.isabs(p):
95 95 c.set("paths", n, os.path.normpath(os.path.join(root, p)))
96 96
97 97 # update ui options
98 98 self.debugflag = self.configbool('ui', 'debug')
99 99 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
100 100 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
101 101 if self.verbose and self.quiet:
102 102 self.quiet = self.verbose = False
103 103 self._reportuntrusted = self.configbool("ui", "report_untrusted", True)
104 104 self._traceback = self.configbool('ui', 'traceback', False)
105 105
106 106 # update trust information
107 107 for user in self.configlist('trusted', 'users'):
108 108 self._trustusers[user] = 1
109 109 for group in self.configlist('trusted', 'groups'):
110 110 self._trustgroups[group] = 1
111 111
112 112 def setconfig(self, section, name, value):
113 113 for cfg in (self._ocfg, self._tcfg, self._ucfg):
114 114 cfg.set(section, name, value)
115 115 self.fixconfig()
116 116
117 117 def _data(self, untrusted):
118 118 return untrusted and self._ucfg or self._tcfg
119 119
120 120 def configsource(self, section, name, untrusted=False):
121 121 return self._data(untrusted).source(section, name) or 'none'
122 122
123 123 def config(self, section, name, default=None, untrusted=False):
124 124 value = self._data(untrusted).get(section, name, default)
125 125 if self.debugflag and not untrusted and self._reportuntrusted:
126 126 uvalue = self._ucfg.get(section, name)
127 127 if uvalue is not None and uvalue != value:
128 128 self.debug(_("ignoring untrusted configuration option "
129 129 "%s.%s = %s\n") % (section, name, uvalue))
130 130 return value
131 131
132 132 def configbool(self, section, name, default=False, untrusted=False):
133 133 v = self.config(section, name, None, untrusted)
134 134 if v == None:
135 135 return default
136 136 if v.lower() not in _booleans:
137 137 raise error.ConfigError(_("%s.%s not a boolean ('%s')")
138 138 % (section, name, v))
139 139 return _booleans[v.lower()]
140 140
141 141 def configlist(self, section, name, default=None, untrusted=False):
142 142 """Return a list of comma/space separated strings"""
143 143 result = self.config(section, name, untrusted=untrusted)
144 144 if result is None:
145 145 result = default or []
146 146 if isinstance(result, basestring):
147 147 result = result.replace(",", " ").split()
148 148 return result
149 149
150 150 def has_section(self, section, untrusted=False):
151 151 '''tell whether section exists in config.'''
152 152 return section in self._data(untrusted)
153 153
154 154 def configitems(self, section, untrusted=False):
155 155 items = self._data(untrusted).items(section)
156 156 if self.debugflag and not untrusted and self._reportuntrusted:
157 157 for k, v in self._ucfg.items(section):
158 158 if self._tcfg.get(section, k) != v:
159 159 self.debug(_("ignoring untrusted configuration option "
160 160 "%s.%s = %s\n") % (section, k, v))
161 161 return items
162 162
163 163 def walkconfig(self, untrusted=False):
164 164 cfg = self._data(untrusted)
165 165 for section in cfg.sections():
166 166 for name, value in self.configitems(section, untrusted):
167 167 yield section, name, str(value).replace('\n', '\\n')
168 168
169 169 def username(self):
170 170 """Return default username to be used in commits.
171 171
172 172 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
173 173 and stop searching if one of these is set.
174 174 If not found and ui.askusername is True, ask the user, else use
175 175 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
176 176 """
177 177 user = os.environ.get("HGUSER")
178 178 if user is None:
179 179 user = self.config("ui", "username")
180 180 if user is None:
181 181 user = os.environ.get("EMAIL")
182 182 if user is None and self.configbool("ui", "askusername"):
183 183 user = self.prompt(_("enter a commit username:"), default=None)
184 184 if user is None:
185 185 try:
186 186 user = '%s@%s' % (util.getuser(), socket.getfqdn())
187 187 self.warn(_("No username found, using '%s' instead\n") % user)
188 188 except KeyError:
189 189 pass
190 190 if not user:
191 191 raise util.Abort(_("Please specify a username."))
192 192 if "\n" in user:
193 193 raise util.Abort(_("username %s contains a newline\n") % repr(user))
194 194 return user
195 195
196 196 def shortuser(self, user):
197 197 """Return a short representation of a user name or email address."""
198 198 if not self.verbose: user = util.shortuser(user)
199 199 return user
200 200
201 201 def _path(self, loc):
202 202 p = self.config('paths', loc)
203 203 if p and '%%' in p:
204 204 ui.warn('(deprecated \'\%\%\' in path %s=%s from %s)\n' %
205 205 (loc, p, self.configsource('paths', loc)))
206 206 p = p.replace('%%', '%')
207 207 return p
208 208
209 209 def expandpath(self, loc, default=None):
210 210 """Return repository location relative to cwd or from [paths]"""
211 211 if "://" in loc or os.path.isdir(os.path.join(loc, '.hg')):
212 212 return loc
213 213
214 214 path = self._path(loc)
215 215 if not path and default is not None:
216 216 path = self._path(default)
217 217 return path or loc
218 218
219 219 def pushbuffer(self):
220 220 self._buffers.append([])
221 221
222 222 def popbuffer(self):
223 223 return "".join(self._buffers.pop())
224 224
225 225 def write(self, *args):
226 226 if self._buffers:
227 227 self._buffers[-1].extend([str(a) for a in args])
228 228 else:
229 229 for a in args:
230 230 sys.stdout.write(str(a))
231 231
232 232 def write_err(self, *args):
233 233 try:
234 234 if not sys.stdout.closed: sys.stdout.flush()
235 235 for a in args:
236 236 sys.stderr.write(str(a))
237 237 # stderr may be buffered under win32 when redirected to files,
238 238 # including stdout.
239 239 if not sys.stderr.closed: sys.stderr.flush()
240 240 except IOError, inst:
241 241 if inst.errno != errno.EPIPE:
242 242 raise
243 243
244 244 def flush(self):
245 245 try: sys.stdout.flush()
246 246 except: pass
247 247 try: sys.stderr.flush()
248 248 except: pass
249 249
250 250 def interactive(self):
251 251 return self.configbool("ui", "interactive") or sys.stdin.isatty()
252 252
253 253 def _readline(self, prompt=''):
254 254 if sys.stdin.isatty():
255 255 try:
256 256 # magically add command line editing support, where
257 257 # available
258 258 import readline
259 259 # force demandimport to really load the module
260 260 readline.read_history_file
261 261 # windows sometimes raises something other than ImportError
262 262 except Exception:
263 263 pass
264 264 line = raw_input(prompt)
265 265 # When stdin is in binary mode on Windows, it can cause
266 266 # raw_input() to emit an extra trailing carriage return
267 267 if os.linesep == '\r\n' and line and line[-1] == '\r':
268 268 line = line[:-1]
269 269 return line
270 270
271 271 def prompt(self, msg, choices=None, default="y"):
272 272 """Prompt user with msg, read response, and ensure it matches
273 273 one of the provided choices. choices is a sequence of acceptable
274 274 responses with the format: ('&None', 'E&xec', 'Sym&link')
275 275 No sequence implies no response checking. Responses are case
276 276 insensitive. If ui is not interactive, the default is returned.
277 277 """
278 278 if not self.interactive():
279 279 self.note(msg, ' ', default, "\n")
280 280 return default
281 281 while True:
282 282 try:
283 283 r = self._readline(msg + ' ')
284 284 if not r:
285 285 return default
286 286 if not choices:
287 287 return r
288 288 resps = [s[s.index('&')+1].lower() for s in choices]
289 289 if r.lower() in resps:
290 290 return r.lower()
291 291 else:
292 292 self.write(_("unrecognized response\n"))
293 293 except EOFError:
294 294 raise util.Abort(_('response expected'))
295 295
296 296 def getpass(self, prompt=None, default=None):
297 297 if not self.interactive(): return default
298 298 try:
299 299 return getpass.getpass(prompt or _('password: '))
300 300 except EOFError:
301 301 raise util.Abort(_('response expected'))
302 302 def status(self, *msg):
303 303 if not self.quiet: self.write(*msg)
304 304 def warn(self, *msg):
305 305 self.write_err(*msg)
306 306 def note(self, *msg):
307 307 if self.verbose: self.write(*msg)
308 308 def debug(self, *msg):
309 309 if self.debugflag: self.write(*msg)
310 310 def edit(self, text, user):
311 311 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
312 312 text=True)
313 313 try:
314 314 f = os.fdopen(fd, "w")
315 315 f.write(text)
316 316 f.close()
317 317
318 318 editor = self.geteditor()
319 319
320 320 util.system("%s \"%s\"" % (editor, name),
321 321 environ={'HGUSER': user},
322 322 onerr=util.Abort, errprefix=_("edit failed"))
323 323
324 324 f = open(name)
325 325 t = f.read()
326 326 f.close()
327 327 t = re.sub("(?m)^HG:.*\n", "", t)
328 328 finally:
329 329 os.unlink(name)
330 330
331 331 return t
332 332
333 333 def traceback(self):
334 334 '''print exception traceback if traceback printing enabled.
335 335 only to call in exception handler. returns true if traceback
336 336 printed.'''
337 337 if self._traceback:
338 338 traceback.print_exc()
339 339 return self._traceback
340 340
341 341 def geteditor(self):
342 342 '''return editor to use'''
343 343 return (os.environ.get("HGEDITOR") or
344 344 self.config("ui", "editor") or
345 345 os.environ.get("VISUAL") or
346 346 os.environ.get("EDITOR", "vi"))
General Comments 0
You need to be logged in to leave comments. Login now