##// END OF EJS Templates
add a simple nested buffering scheme to ui
Matt Mackall -
r3737:9f5c46c5 default
parent child Browse files
Show More
@@ -1,442 +1,452 b''
1 1 # ui.py - user interface bits for mercurial
2 2 #
3 3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from i18n import gettext as _
9 9 from demandload import *
10 10 demandload(globals(), "errno getpass os re socket sys tempfile")
11 11 demandload(globals(), "ConfigParser traceback util")
12 12
13 13 def dupconfig(orig):
14 14 new = util.configparser(orig.defaults())
15 15 updateconfig(orig, new)
16 16 return new
17 17
18 18 def updateconfig(source, dest, sections=None):
19 19 if not sections:
20 20 sections = source.sections()
21 21 for section in sections:
22 22 if not dest.has_section(section):
23 23 dest.add_section(section)
24 24 for name, value in source.items(section, raw=True):
25 25 dest.set(section, name, value)
26 26
27 27 class ui(object):
28 28 def __init__(self, verbose=False, debug=False, quiet=False,
29 29 interactive=True, traceback=False, report_untrusted=True,
30 30 parentui=None):
31 31 self.overlay = None
32 self.buffers = []
32 33 if parentui is None:
33 34 # this is the parent of all ui children
34 35 self.parentui = None
35 36 self.readhooks = []
36 37 self.quiet = quiet
37 38 self.verbose = verbose
38 39 self.debugflag = debug
39 40 self.interactive = interactive
40 41 self.traceback = traceback
41 42 self.report_untrusted = report_untrusted
42 43 self.trusted_users = {}
43 44 self.trusted_groups = {}
44 45 # if ucdata is not None, its keys must be a superset of cdata's
45 46 self.cdata = util.configparser()
46 47 self.ucdata = None
47 48 # we always trust global config files
48 49 self.check_trusted = False
49 50 self.readconfig(util.rcpath())
50 51 self.check_trusted = True
51 52 self.updateopts(verbose, debug, quiet, interactive)
52 53 else:
53 54 # parentui may point to an ui object which is already a child
54 55 self.parentui = parentui.parentui or parentui
55 56 self.readhooks = self.parentui.readhooks[:]
56 57 self.trusted_users = parentui.trusted_users.copy()
57 58 self.trusted_groups = parentui.trusted_groups.copy()
58 59 self.cdata = dupconfig(self.parentui.cdata)
59 60 if self.parentui.ucdata:
60 61 self.ucdata = dupconfig(self.parentui.ucdata)
61 62 if self.parentui.overlay:
62 63 self.overlay = dupconfig(self.parentui.overlay)
63 64
64 65 def __getattr__(self, key):
65 66 return getattr(self.parentui, key)
66 67
67 68 def updateopts(self, verbose=False, debug=False, quiet=False,
68 69 interactive=True, traceback=False, config=[]):
69 70 for section, name, value in config:
70 71 self.setconfig(section, name, value)
71 72
72 73 if quiet or verbose or debug:
73 74 self.setconfig('ui', 'quiet', str(bool(quiet)))
74 75 self.setconfig('ui', 'verbose', str(bool(verbose)))
75 76 self.setconfig('ui', 'debug', str(bool(debug)))
76 77
77 78 self.verbosity_constraints()
78 79
79 80 if not interactive:
80 81 self.setconfig('ui', 'interactive', 'False')
81 82 self.interactive = False
82 83
83 84 self.traceback = self.traceback or traceback
84 85
85 86 def verbosity_constraints(self):
86 87 self.quiet = self.configbool('ui', 'quiet')
87 88 self.verbose = self.configbool('ui', 'verbose')
88 89 self.debugflag = self.configbool('ui', 'debug')
89 90
90 91 if self.debugflag:
91 92 self.verbose = True
92 93 self.quiet = False
93 94 elif self.verbose and self.quiet:
94 95 self.quiet = self.verbose = False
95 96
96 97 def _is_trusted(self, fp, f, warn=True):
97 98 if not self.check_trusted:
98 99 return True
99 100 st = util.fstat(fp)
100 101 if util.isowner(fp, st):
101 102 return True
102 103 tusers = self.trusted_users
103 104 tgroups = self.trusted_groups
104 105 if not tusers:
105 106 user = util.username()
106 107 if user is not None:
107 108 self.trusted_users[user] = 1
108 109 self.fixconfig(section='trusted')
109 110 if (tusers or tgroups) and '*' not in tusers and '*' not in tgroups:
110 111 user = util.username(st.st_uid)
111 112 group = util.groupname(st.st_gid)
112 113 if user not in tusers and group not in tgroups:
113 114 if warn and self.report_untrusted:
114 115 self.warn(_('Not trusting file %s from untrusted '
115 116 'user %s, group %s\n') % (f, user, group))
116 117 return False
117 118 return True
118 119
119 120 def readconfig(self, fn, root=None):
120 121 if isinstance(fn, basestring):
121 122 fn = [fn]
122 123 for f in fn:
123 124 try:
124 125 fp = open(f)
125 126 except IOError:
126 127 continue
127 128 cdata = self.cdata
128 129 trusted = self._is_trusted(fp, f)
129 130 if not trusted:
130 131 if self.ucdata is None:
131 132 self.ucdata = dupconfig(self.cdata)
132 133 cdata = self.ucdata
133 134 elif self.ucdata is not None:
134 135 # use a separate configparser, so that we don't accidentally
135 136 # override ucdata settings later on.
136 137 cdata = util.configparser()
137 138
138 139 try:
139 140 cdata.readfp(fp, f)
140 141 except ConfigParser.ParsingError, inst:
141 142 msg = _("Failed to parse %s\n%s") % (f, inst)
142 143 if trusted:
143 144 raise util.Abort(msg)
144 145 self.warn(_("Ignored: %s\n") % msg)
145 146
146 147 if trusted:
147 148 if cdata != self.cdata:
148 149 updateconfig(cdata, self.cdata)
149 150 if self.ucdata is not None:
150 151 updateconfig(cdata, self.ucdata)
151 152 # override data from config files with data set with ui.setconfig
152 153 if self.overlay:
153 154 updateconfig(self.overlay, self.cdata)
154 155 if root is None:
155 156 root = os.path.expanduser('~')
156 157 self.fixconfig(root=root)
157 158 for hook in self.readhooks:
158 159 hook(self)
159 160
160 161 def addreadhook(self, hook):
161 162 self.readhooks.append(hook)
162 163
163 164 def readsections(self, filename, *sections):
164 165 """Read filename and add only the specified sections to the config data
165 166
166 167 The settings are added to the trusted config data.
167 168 """
168 169 if not sections:
169 170 return
170 171
171 172 cdata = util.configparser()
172 173 try:
173 174 cdata.read(filename)
174 175 except ConfigParser.ParsingError, inst:
175 176 raise util.Abort(_("failed to parse %s\n%s") % (filename,
176 177 inst))
177 178
178 179 for section in sections:
179 180 if not cdata.has_section(section):
180 181 cdata.add_section(section)
181 182
182 183 updateconfig(cdata, self.cdata, sections)
183 184 if self.ucdata:
184 185 updateconfig(cdata, self.ucdata, sections)
185 186
186 187 def fixconfig(self, section=None, name=None, value=None, root=None):
187 188 # translate paths relative to root (or home) into absolute paths
188 189 if section is None or section == 'paths':
189 190 if root is None:
190 191 root = os.getcwd()
191 192 items = section and [(name, value)] or []
192 193 for cdata in self.cdata, self.ucdata, self.overlay:
193 194 if not cdata: continue
194 195 if not items and cdata.has_section('paths'):
195 196 pathsitems = cdata.items('paths')
196 197 else:
197 198 pathsitems = items
198 199 for n, path in pathsitems:
199 200 if path and "://" not in path and not os.path.isabs(path):
200 201 cdata.set("paths", n, os.path.join(root, path))
201 202
202 203 # update quiet/verbose/debug and interactive status
203 204 if section is None or section == 'ui':
204 205 if name is None or name in ('quiet', 'verbose', 'debug'):
205 206 self.verbosity_constraints()
206 207
207 208 if name is None or name == 'interactive':
208 209 self.interactive = self.configbool("ui", "interactive", True)
209 210
210 211 # update trust information
211 212 if (section is None or section == 'trusted') and self.trusted_users:
212 213 for user in self.configlist('trusted', 'users'):
213 214 self.trusted_users[user] = 1
214 215 for group in self.configlist('trusted', 'groups'):
215 216 self.trusted_groups[group] = 1
216 217
217 218 def setconfig(self, section, name, value):
218 219 if not self.overlay:
219 220 self.overlay = util.configparser()
220 221 for cdata in (self.overlay, self.cdata, self.ucdata):
221 222 if not cdata: continue
222 223 if not cdata.has_section(section):
223 224 cdata.add_section(section)
224 225 cdata.set(section, name, value)
225 226 self.fixconfig(section, name, value)
226 227
227 228 def _get_cdata(self, untrusted):
228 229 if untrusted and self.ucdata:
229 230 return self.ucdata
230 231 return self.cdata
231 232
232 233 def _config(self, section, name, default, funcname, untrusted, abort):
233 234 cdata = self._get_cdata(untrusted)
234 235 if cdata.has_option(section, name):
235 236 try:
236 237 func = getattr(cdata, funcname)
237 238 return func(section, name)
238 239 except ConfigParser.InterpolationError, inst:
239 240 msg = _("Error in configuration section [%s] "
240 241 "parameter '%s':\n%s") % (section, name, inst)
241 242 if abort:
242 243 raise util.Abort(msg)
243 244 self.warn(_("Ignored: %s\n") % msg)
244 245 return default
245 246
246 247 def _configcommon(self, section, name, default, funcname, untrusted):
247 248 value = self._config(section, name, default, funcname,
248 249 untrusted, abort=True)
249 250 if self.debugflag and not untrusted and self.ucdata:
250 251 uvalue = self._config(section, name, None, funcname,
251 252 untrusted=True, abort=False)
252 253 if uvalue is not None and uvalue != value:
253 254 self.warn(_("Ignoring untrusted configuration option "
254 255 "%s.%s = %s\n") % (section, name, uvalue))
255 256 return value
256 257
257 258 def config(self, section, name, default=None, untrusted=False):
258 259 return self._configcommon(section, name, default, 'get', untrusted)
259 260
260 261 def configbool(self, section, name, default=False, untrusted=False):
261 262 return self._configcommon(section, name, default, 'getboolean',
262 263 untrusted)
263 264
264 265 def configlist(self, section, name, default=None, untrusted=False):
265 266 """Return a list of comma/space separated strings"""
266 267 result = self.config(section, name, untrusted=untrusted)
267 268 if result is None:
268 269 result = default or []
269 270 if isinstance(result, basestring):
270 271 result = result.replace(",", " ").split()
271 272 return result
272 273
273 274 def has_config(self, section, untrusted=False):
274 275 '''tell whether section exists in config.'''
275 276 cdata = self._get_cdata(untrusted)
276 277 return cdata.has_section(section)
277 278
278 279 def _configitems(self, section, untrusted, abort):
279 280 items = {}
280 281 cdata = self._get_cdata(untrusted)
281 282 if cdata.has_section(section):
282 283 try:
283 284 items.update(dict(cdata.items(section)))
284 285 except ConfigParser.InterpolationError, inst:
285 286 msg = _("Error in configuration section [%s]:\n"
286 287 "%s") % (section, inst)
287 288 if abort:
288 289 raise util.Abort(msg)
289 290 self.warn(_("Ignored: %s\n") % msg)
290 291 return items
291 292
292 293 def configitems(self, section, untrusted=False):
293 294 items = self._configitems(section, untrusted=untrusted, abort=True)
294 295 if self.debugflag and not untrusted and self.ucdata:
295 296 uitems = self._configitems(section, untrusted=True, abort=False)
296 297 keys = uitems.keys()
297 298 keys.sort()
298 299 for k in keys:
299 300 if uitems[k] != items.get(k):
300 301 self.warn(_("Ignoring untrusted configuration option "
301 302 "%s.%s = %s\n") % (section, k, uitems[k]))
302 303 x = items.items()
303 304 x.sort()
304 305 return x
305 306
306 307 def walkconfig(self, untrusted=False):
307 308 cdata = self._get_cdata(untrusted)
308 309 sections = cdata.sections()
309 310 sections.sort()
310 311 for section in sections:
311 312 for name, value in self.configitems(section, untrusted):
312 313 yield section, name, value.replace('\n', '\\n')
313 314
314 315 def extensions(self):
315 316 result = self.configitems("extensions")
316 317 for i, (key, value) in enumerate(result):
317 318 if value:
318 319 result[i] = (key, os.path.expanduser(value))
319 320 return result
320 321
321 322 def hgignorefiles(self):
322 323 result = []
323 324 for key, value in self.configitems("ui"):
324 325 if key == 'ignore' or key.startswith('ignore.'):
325 326 result.append(os.path.expanduser(value))
326 327 return result
327 328
328 329 def configrevlog(self):
329 330 result = {}
330 331 for key, value in self.configitems("revlog"):
331 332 result[key.lower()] = value
332 333 return result
333 334
334 335 def username(self):
335 336 """Return default username to be used in commits.
336 337
337 338 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
338 339 and stop searching if one of these is set.
339 340 If not found, use ($LOGNAME or $USER or $LNAME or
340 341 $USERNAME) +"@full.hostname".
341 342 """
342 343 user = os.environ.get("HGUSER")
343 344 if user is None:
344 345 user = self.config("ui", "username")
345 346 if user is None:
346 347 user = os.environ.get("EMAIL")
347 348 if not user:
348 349 try:
349 350 user = '%s@%s' % (util.getuser(), socket.getfqdn())
350 351 except KeyError:
351 352 raise util.Abort(_("Please specify a username."))
352 353 self.warn(_("No username found, using '%s' instead\n" % user))
353 354 return user
354 355
355 356 def shortuser(self, user):
356 357 """Return a short representation of a user name or email address."""
357 358 if not self.verbose: user = util.shortuser(user)
358 359 return user
359 360
360 361 def expandpath(self, loc, default=None):
361 362 """Return repository location relative to cwd or from [paths]"""
362 363 if "://" in loc or os.path.isdir(loc):
363 364 return loc
364 365
365 366 path = self.config("paths", loc)
366 367 if not path and default is not None:
367 368 path = self.config("paths", default)
368 369 return path or loc
369 370
371 def pushbuffer(self):
372 self.buffers.append([])
373
374 def popbuffer(self):
375 return "".join(self.buffers.pop())
376
370 377 def write(self, *args):
378 if self.buffers:
379 self.buffers[-1].extend([str(a) for a in args])
380 else:
371 381 for a in args:
372 382 sys.stdout.write(str(a))
373 383
374 384 def write_err(self, *args):
375 385 try:
376 386 if not sys.stdout.closed: sys.stdout.flush()
377 387 for a in args:
378 388 sys.stderr.write(str(a))
379 389 except IOError, inst:
380 390 if inst.errno != errno.EPIPE:
381 391 raise
382 392
383 393 def flush(self):
384 394 try: sys.stdout.flush()
385 395 except: pass
386 396 try: sys.stderr.flush()
387 397 except: pass
388 398
389 399 def readline(self):
390 400 return sys.stdin.readline()[:-1]
391 401 def prompt(self, msg, pat=None, default="y"):
392 402 if not self.interactive: return default
393 403 while 1:
394 404 self.write(msg, " ")
395 405 r = self.readline()
396 406 if not pat or re.match(pat, r):
397 407 return r
398 408 else:
399 409 self.write(_("unrecognized response\n"))
400 410 def getpass(self, prompt=None, default=None):
401 411 if not self.interactive: return default
402 412 return getpass.getpass(prompt or _('password: '))
403 413 def status(self, *msg):
404 414 if not self.quiet: self.write(*msg)
405 415 def warn(self, *msg):
406 416 self.write_err(*msg)
407 417 def note(self, *msg):
408 418 if self.verbose: self.write(*msg)
409 419 def debug(self, *msg):
410 420 if self.debugflag: self.write(*msg)
411 421 def edit(self, text, user):
412 422 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
413 423 text=True)
414 424 try:
415 425 f = os.fdopen(fd, "w")
416 426 f.write(text)
417 427 f.close()
418 428
419 429 editor = (os.environ.get("HGEDITOR") or
420 430 self.config("ui", "editor") or
421 431 os.environ.get("EDITOR", "vi"))
422 432
423 433 util.system("%s \"%s\"" % (editor, name),
424 434 environ={'HGUSER': user},
425 435 onerr=util.Abort, errprefix=_("edit failed"))
426 436
427 437 f = open(name)
428 438 t = f.read()
429 439 f.close()
430 440 t = re.sub("(?m)^HG:.*\n", "", t)
431 441 finally:
432 442 os.unlink(name)
433 443
434 444 return t
435 445
436 446 def print_exc(self):
437 447 '''print exception traceback if traceback printing enabled.
438 448 only to call in exception handler. returns true if traceback
439 449 printed.'''
440 450 if self.traceback:
441 451 traceback.print_exc()
442 452 return self.traceback
General Comments 0
You need to be logged in to leave comments. Login now