##// END OF EJS Templates
config: getsource -> source
Matt Mackall -
r8198:cf9accff default
parent child Browse files
Show More
@@ -1,125 +1,125 b''
1 1 from i18n import _
2 2 import re, error, os
3 3
4 4 class sortdict(dict):
5 5 'a simple sorted dictionary'
6 6 def __init__(self, data=None):
7 7 self._list = []
8 8 if data:
9 9 self.update(data)
10 10 def copy(self):
11 11 return sortdict(self)
12 12 def __setitem__(self, key, val):
13 13 if key in self:
14 14 self._list.remove(key)
15 15 self._list.append(key)
16 16 dict.__setitem__(self, key, val)
17 17 def __iter__(self):
18 18 return self._list.__iter__()
19 19 def update(self, src):
20 20 for k in src:
21 21 self[k] = src[k]
22 22 def items(self):
23 23 return [(k,self[k]) for k in self._list]
24 24 def __delitem__(self, key):
25 25 dict.__delitem__(self, key)
26 26 self._list.remove(key)
27 27
28 28 class config(object):
29 29 def __init__(self, data=None):
30 30 self._data = {}
31 31 self._source = {}
32 32 if data:
33 33 for k in data._data:
34 34 self._data[k] = data[k].copy()
35 35 self._source = data._source.copy()
36 36 def copy(self):
37 37 return config(self)
38 38 def __contains__(self, section):
39 39 return section in self._data
40 40 def __getitem__(self, section):
41 41 return self._data.get(section, {})
42 42 def __iter__(self):
43 43 for d in self.sections():
44 44 yield d
45 45 def update(self, src):
46 46 for s in src:
47 47 if s not in self:
48 48 self._data[s] = sortdict()
49 49 self._data[s].update(src._data[s])
50 50 self._source.update(src._source)
51 51 def get(self, section, item, default=None):
52 52 return self._data.get(section, {}).get(item, default)
53 def getsource(self, section, item):
53 def source(self, section, item):
54 54 return self._source.get((section, item), "")
55 55 def sections(self):
56 56 return sorted(self._data.keys())
57 57 def items(self, section):
58 58 return self._data.get(section, {}).items()
59 59 def set(self, section, item, value, source=""):
60 60 if section not in self:
61 61 self._data[section] = sortdict()
62 62 self._data[section][item] = value
63 63 self._source[(section, item)] = source
64 64
65 65 def read(self, path, fp=None, sections=None):
66 66 sectionre = re.compile(r'\[([^\[]+)\]')
67 67 itemre = re.compile(r'([^=\s]+)\s*=\s*(.*\S|)')
68 68 contre = re.compile(r'\s+(\S.*\S)')
69 69 emptyre = re.compile(r'(;|#|\s*$)')
70 70 unsetre = re.compile(r'%unset\s+(\S+)')
71 71 includere = re.compile(r'%include\s+(\S.*\S)')
72 72 section = ""
73 73 item = None
74 74 line = 0
75 75 cont = 0
76 76
77 77 if not fp:
78 78 fp = open(path)
79 79
80 80 for l in fp:
81 81 line += 1
82 82 if cont:
83 83 m = contre.match(l)
84 84 if m:
85 85 if sections and section not in sections:
86 86 continue
87 87 v = self.get(section, item) + "\n" + m.group(1)
88 88 self.set(section, item, v, "%s:%d" % (path, line))
89 89 continue
90 90 item = None
91 91 m = includere.match(l)
92 92 if m:
93 93 inc = m.group(1)
94 94 base = os.path.dirname(path)
95 95 inc = os.path.normpath(os.path.join(base, inc))
96 96 incfp = open(inc)
97 97 self.read(inc, incfp)
98 98 continue
99 99 if emptyre.match(l):
100 100 continue
101 101 m = sectionre.match(l)
102 102 if m:
103 103 section = m.group(1)
104 104 if section not in self:
105 105 self._data[section] = sortdict()
106 106 continue
107 107 m = itemre.match(l)
108 108 if m:
109 109 item = m.group(1)
110 110 cont = 1
111 111 if sections and section not in sections:
112 112 continue
113 113 self.set(section, item, m.group(2), "%s:%d" % (path, line))
114 114 continue
115 115 m = unsetre.match(l)
116 116 if m:
117 117 name = m.group(1)
118 118 if sections and section not in sections:
119 119 continue
120 120 if self.get(section, name) != None:
121 121 del self._data[section][name]
122 122 continue
123 123
124 124 raise error.ConfigError(_('config error at %s:%d: \'%s\'')
125 125 % (path, line, l.rstrip()))
@@ -1,208 +1,208 b''
1 1 # templater.py - template expansion for output
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 _
9 9 import re, sys, os
10 10 from mercurial import util, config
11 11
12 12 path = ['templates', '../templates']
13 13
14 14 def parsestring(s, quoted=True):
15 15 '''parse a string using simple c-like syntax.
16 16 string must be in quotes if quoted is True.'''
17 17 if quoted:
18 18 if len(s) < 2 or s[0] != s[-1]:
19 19 raise SyntaxError(_('unmatched quotes'))
20 20 return s[1:-1].decode('string_escape')
21 21
22 22 return s.decode('string_escape')
23 23
24 24 class templater(object):
25 25 '''template expansion engine.
26 26
27 27 template expansion works like this. a map file contains key=value
28 28 pairs. if value is quoted, it is treated as string. otherwise, it
29 29 is treated as name of template file.
30 30
31 31 templater is asked to expand a key in map. it looks up key, and
32 32 looks for strings like this: {foo}. it expands {foo} by looking up
33 33 foo in map, and substituting it. expansion is recursive: it stops
34 34 when there is no more {foo} to replace.
35 35
36 36 expansion also allows formatting and filtering.
37 37
38 38 format uses key to expand each item in list. syntax is
39 39 {key%format}.
40 40
41 41 filter uses function to transform value. syntax is
42 42 {key|filter1|filter2|...}.'''
43 43
44 44 template_re = re.compile(r"(?:(?:#(?=[\w\|%]+#))|(?:{(?=[\w\|%]+})))"
45 45 r"(\w+)(?:(?:%(\w+))|((?:\|\w+)*))[#}]")
46 46
47 47 def __init__(self, mapfile, filters={}, defaults={}, cache={},
48 48 minchunk=1024, maxchunk=65536):
49 49 '''set up template engine.
50 50 mapfile is name of file to read map definitions from.
51 51 filters is dict of functions. each transforms a value into another.
52 52 defaults is dict of default map definitions.'''
53 53 self.mapfile = mapfile or 'template'
54 54 self.cache = cache.copy()
55 55 self.map = {}
56 56 self.base = (mapfile and os.path.dirname(mapfile)) or ''
57 57 self.filters = filters
58 58 self.defaults = defaults
59 59 self.minchunk, self.maxchunk = minchunk, maxchunk
60 60
61 61 if not mapfile:
62 62 return
63 63 if not os.path.exists(mapfile):
64 64 raise util.Abort(_('style not found: %s') % mapfile)
65 65
66 66 conf = config.config()
67 67 conf.read(mapfile)
68 68
69 69 for key, val in conf[''].items():
70 70 if val[0] in "'\"":
71 71 try:
72 72 self.cache[key] = parsestring(val)
73 73 except SyntaxError, inst:
74 74 raise SyntaxError('%s: %s' %
75 (conf.getsource('', key), inst.args[0]))
75 (conf.source('', key), inst.args[0]))
76 76 else:
77 77 self.map[key] = os.path.join(self.base, val)
78 78
79 79 def __contains__(self, key):
80 80 return key in self.cache or key in self.map
81 81
82 82 def _template(self, t):
83 83 '''Get the template for the given template name. Use a local cache.'''
84 84 if not t in self.cache:
85 85 try:
86 86 self.cache[t] = file(self.map[t]).read()
87 87 except IOError, inst:
88 88 raise IOError(inst.args[0], _('template file %s: %s') %
89 89 (self.map[t], inst.args[1]))
90 90 return self.cache[t]
91 91
92 92 def _process(self, tmpl, map):
93 93 '''Render a template. Returns a generator.'''
94 94 while tmpl:
95 95 m = self.template_re.search(tmpl)
96 96 if not m:
97 97 yield tmpl
98 98 break
99 99
100 100 start, end = m.span(0)
101 101 key, format, fl = m.groups()
102 102
103 103 if start:
104 104 yield tmpl[:start]
105 105 tmpl = tmpl[end:]
106 106
107 107 if key in map:
108 108 v = map[key]
109 109 else:
110 110 v = self.defaults.get(key, "")
111 111 if callable(v):
112 112 v = v(**map)
113 113 if format:
114 114 if not hasattr(v, '__iter__'):
115 115 raise SyntaxError(_("Error expanding '%s%%%s'")
116 116 % (key, format))
117 117 lm = map.copy()
118 118 for i in v:
119 119 lm.update(i)
120 120 t = self._template(format)
121 121 yield self._process(t, lm)
122 122 else:
123 123 if fl:
124 124 for f in fl.split("|")[1:]:
125 125 v = self.filters[f](v)
126 126 yield v
127 127
128 128 def __call__(self, t, **map):
129 129 stream = self.expand(t, **map)
130 130 if self.minchunk:
131 131 stream = util.increasingchunks(stream, min=self.minchunk,
132 132 max=self.maxchunk)
133 133 return stream
134 134
135 135 def expand(self, t, **map):
136 136 '''Perform expansion. t is name of map element to expand. map contains
137 137 added elements for use during expansion. Is a generator.'''
138 138 tmpl = self._template(t)
139 139 iters = [self._process(tmpl, map)]
140 140 while iters:
141 141 try:
142 142 item = iters[0].next()
143 143 except StopIteration:
144 144 iters.pop(0)
145 145 continue
146 146 if isinstance(item, str):
147 147 yield item
148 148 elif item is None:
149 149 yield ''
150 150 elif hasattr(item, '__iter__'):
151 151 iters.insert(0, iter(item))
152 152 else:
153 153 yield str(item)
154 154
155 155 def templatepath(name=None):
156 156 '''return location of template file or directory (if no name).
157 157 returns None if not found.'''
158 158 normpaths = []
159 159
160 160 # executable version (py2exe) doesn't support __file__
161 161 if hasattr(sys, 'frozen'):
162 162 module = sys.executable
163 163 else:
164 164 module = __file__
165 165 for f in path:
166 166 if f.startswith('/'):
167 167 p = f
168 168 else:
169 169 fl = f.split('/')
170 170 p = os.path.join(os.path.dirname(module), *fl)
171 171 if name:
172 172 p = os.path.join(p, name)
173 173 if name and os.path.exists(p):
174 174 return os.path.normpath(p)
175 175 elif os.path.isdir(p):
176 176 normpaths.append(os.path.normpath(p))
177 177
178 178 return normpaths
179 179
180 180 def stylemap(style, paths=None):
181 181 """Return path to mapfile for a given style.
182 182
183 183 Searches mapfile in the following locations:
184 184 1. templatepath/style/map
185 185 2. templatepath/map-style
186 186 3. templatepath/map
187 187 """
188 188
189 189 if paths is None:
190 190 paths = templatepath()
191 191 elif isinstance(paths, str):
192 192 paths = [templatepath]
193 193
194 194 locations = style and [os.path.join(style, "map"), "map-" + style] or []
195 195 locations.append("map")
196 196 for path in paths:
197 197 for location in locations:
198 198 mapfile = os.path.join(path, location)
199 199 if os.path.isfile(mapfile):
200 200 return mapfile
201 201
202 202 raise RuntimeError("No hgweb templates found in %r" % paths)
203 203
204 204 def stringify(thing):
205 205 '''turn nested template iterator into string.'''
206 206 if hasattr(thing, '__iter__') and not isinstance(thing, str):
207 207 return "".join([stringify(t) for t in thing if t is not None])
208 208 return str(thing)
@@ -1,353 +1,353 b''
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
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from i18n import _
9 9 import errno, getpass, os, re, socket, sys, tempfile
10 10 import config, traceback, 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.interactive = self.report_untrusted = True
20 20 self.overlay = config.config()
21 21 self.cdata = config.config()
22 22 self.ucdata = config.config()
23 23 self.trusted_users = {}
24 24 self.trusted_groups = {}
25 25
26 26 if src:
27 27 self.cdata = src.cdata.copy()
28 28 self.ucdata = src.ucdata.copy()
29 29 self.overlay = src.overlay.copy()
30 30 self.trusted_users = src.trusted_users.copy()
31 31 self.trusted_groups = src.trusted_groups.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, assumetrusted=True)
37 37 def copy(self):
38 38 return ui(self)
39 39
40 40 _isatty = None
41 41 def isatty(self):
42 42 if ui._isatty is None:
43 43 try:
44 44 ui._isatty = sys.stdin.isatty()
45 45 except AttributeError: # not a real file object
46 46 ui._isatty = False
47 47 except IOError:
48 48 # access to stdin is unsafe in a WSGI environment
49 49 ui._isatty = False
50 50 return ui._isatty
51 51
52 52 def _is_trusted(self, fp, f):
53 53 st = util.fstat(fp)
54 54 if util.isowner(fp, st):
55 55 return True
56 56
57 57 tusers = self.trusted_users
58 58 tgroups = self.trusted_groups
59 59 if '*' in tusers or '*' in tgroups:
60 60 return True
61 61
62 62 user = util.username(st.st_uid)
63 63 group = util.groupname(st.st_gid)
64 64 if user in tusers or group in tgroups or user == util.username():
65 65 return True
66 66
67 67 if self.report_untrusted:
68 68 self.warn(_('Not trusting file %s from untrusted '
69 69 'user %s, group %s\n') % (f, user, group))
70 70 return False
71 71
72 72 def readconfig(self, filename, root=None, assumetrusted=False,
73 73 sections = None):
74 74 try:
75 75 fp = open(filename)
76 76 except IOError:
77 77 if not sections: # ignore unless we were looking for something
78 78 return
79 79 raise
80 80
81 81 cdata = config.config()
82 82 trusted = sections or assumetrusted or self._is_trusted(fp, filename)
83 83
84 84 try:
85 85 cdata.read(filename, fp, sections=sections)
86 86 except error.ConfigError, inst:
87 87 if trusted:
88 88 raise
89 89 self.warn(_("Ignored: %s\n") % str(inst))
90 90
91 91 if trusted:
92 92 self.cdata.update(cdata)
93 93 self.cdata.update(self.overlay)
94 94 self.ucdata.update(cdata)
95 95 self.ucdata.update(self.overlay)
96 96
97 97 if root is None:
98 98 root = os.path.expanduser('~')
99 99 self.fixconfig(root=root)
100 100
101 101 def fixconfig(self, root=None):
102 102 # translate paths relative to root (or home) into absolute paths
103 103 root = root or os.getcwd()
104 104 for c in self.cdata, self.ucdata, self.overlay:
105 105 for n, p in c.items('paths'):
106 106 if p and "://" not in p and not os.path.isabs(p):
107 107 c.set("paths", n, os.path.normpath(os.path.join(root, p)))
108 108
109 109 # update ui options
110 110 self.debugflag = self.configbool('ui', 'debug')
111 111 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
112 112 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
113 113 if self.verbose and self.quiet:
114 114 self.quiet = self.verbose = False
115 115 self.report_untrusted = self.configbool("ui", "report_untrusted", True)
116 116 self.interactive = self.configbool("ui", "interactive", self.isatty())
117 117 self.traceback = self.configbool('ui', 'traceback', False)
118 118
119 119 # update trust information
120 120 for user in self.configlist('trusted', 'users'):
121 121 self.trusted_users[user] = 1
122 122 for group in self.configlist('trusted', 'groups'):
123 123 self.trusted_groups[group] = 1
124 124
125 125 def setconfig(self, section, name, value):
126 126 for cdata in (self.overlay, self.cdata, self.ucdata):
127 127 cdata.set(section, name, value)
128 128 self.fixconfig()
129 129
130 130 def _get_cdata(self, untrusted):
131 131 if untrusted:
132 132 return self.ucdata
133 133 return self.cdata
134 134
135 135 def configsource(self, section, name, untrusted=False):
136 return self._get_cdata(untrusted).getsource(section, name) or 'none'
136 return self._get_cdata(untrusted).source(section, name) or 'none'
137 137
138 138 def config(self, section, name, default=None, untrusted=False):
139 139 value = self._get_cdata(untrusted).get(section, name, default)
140 140 if self.debugflag and not untrusted:
141 141 uvalue = self.ucdata.get(section, name)
142 142 if uvalue is not None and uvalue != value:
143 143 self.warn(_("Ignoring untrusted configuration option "
144 144 "%s.%s = %s\n") % (section, name, uvalue))
145 145 return value
146 146
147 147 def configbool(self, section, name, default=False, untrusted=False):
148 148 v = self.config(section, name, None, untrusted)
149 149 if v == None:
150 150 return default
151 151 if v.lower() not in _booleans:
152 152 raise error.ConfigError(_("%s.%s not a boolean ('%s')")
153 153 % (section, name, v))
154 154 return _booleans[v.lower()]
155 155
156 156 def configlist(self, section, name, default=None, untrusted=False):
157 157 """Return a list of comma/space separated strings"""
158 158 result = self.config(section, name, untrusted=untrusted)
159 159 if result is None:
160 160 result = default or []
161 161 if isinstance(result, basestring):
162 162 result = result.replace(",", " ").split()
163 163 return result
164 164
165 165 def has_section(self, section, untrusted=False):
166 166 '''tell whether section exists in config.'''
167 167 return section in self._get_cdata(untrusted)
168 168
169 169 def configitems(self, section, untrusted=False):
170 170 items = self._get_cdata(untrusted).items(section)
171 171 if self.debugflag and not untrusted:
172 172 for k,v in self.ucdata.items(section):
173 173 if self.cdata.get(section, k) != v:
174 174 self.warn(_("Ignoring untrusted configuration option "
175 175 "%s.%s = %s\n") % (section, k, v))
176 176 return items
177 177
178 178 def walkconfig(self, untrusted=False):
179 179 cdata = self._get_cdata(untrusted)
180 180 for section in cdata.sections():
181 181 for name, value in self.configitems(section, untrusted):
182 182 yield section, name, str(value).replace('\n', '\\n')
183 183
184 184 def username(self):
185 185 """Return default username to be used in commits.
186 186
187 187 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
188 188 and stop searching if one of these is set.
189 189 If not found and ui.askusername is True, ask the user, else use
190 190 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
191 191 """
192 192 user = os.environ.get("HGUSER")
193 193 if user is None:
194 194 user = self.config("ui", "username")
195 195 if user is None:
196 196 user = os.environ.get("EMAIL")
197 197 if user is None and self.configbool("ui", "askusername"):
198 198 user = self.prompt(_("enter a commit username:"), default=None)
199 199 if user is None:
200 200 try:
201 201 user = '%s@%s' % (util.getuser(), socket.getfqdn())
202 202 self.warn(_("No username found, using '%s' instead\n") % user)
203 203 except KeyError:
204 204 pass
205 205 if not user:
206 206 raise util.Abort(_("Please specify a username."))
207 207 if "\n" in user:
208 208 raise util.Abort(_("username %s contains a newline\n") % repr(user))
209 209 return user
210 210
211 211 def shortuser(self, user):
212 212 """Return a short representation of a user name or email address."""
213 213 if not self.verbose: user = util.shortuser(user)
214 214 return user
215 215
216 216 def _path(self, loc):
217 217 p = self.config('paths', loc)
218 218 if p and '%%' in p:
219 219 ui.warn('(deprecated \'\%\%\' in path %s=%s from %s)\n' %
220 220 (loc, p, self.configsource('paths', loc)))
221 221 p = p.replace('%%', '%')
222 222 return p
223 223
224 224 def expandpath(self, loc, default=None):
225 225 """Return repository location relative to cwd or from [paths]"""
226 226 if "://" in loc or os.path.isdir(os.path.join(loc, '.hg')):
227 227 return loc
228 228
229 229 path = self._path(loc)
230 230 if not path and default is not None:
231 231 path = self._path(default)
232 232 return path or loc
233 233
234 234 def pushbuffer(self):
235 235 self.buffers.append([])
236 236
237 237 def popbuffer(self):
238 238 return "".join(self.buffers.pop())
239 239
240 240 def write(self, *args):
241 241 if self.buffers:
242 242 self.buffers[-1].extend([str(a) for a in args])
243 243 else:
244 244 for a in args:
245 245 sys.stdout.write(str(a))
246 246
247 247 def write_err(self, *args):
248 248 try:
249 249 if not sys.stdout.closed: sys.stdout.flush()
250 250 for a in args:
251 251 sys.stderr.write(str(a))
252 252 # stderr may be buffered under win32 when redirected to files,
253 253 # including stdout.
254 254 if not sys.stderr.closed: sys.stderr.flush()
255 255 except IOError, inst:
256 256 if inst.errno != errno.EPIPE:
257 257 raise
258 258
259 259 def flush(self):
260 260 try: sys.stdout.flush()
261 261 except: pass
262 262 try: sys.stderr.flush()
263 263 except: pass
264 264
265 265 def _readline(self, prompt=''):
266 266 if self.isatty():
267 267 try:
268 268 # magically add command line editing support, where
269 269 # available
270 270 import readline
271 271 # force demandimport to really load the module
272 272 readline.read_history_file
273 273 # windows sometimes raises something other than ImportError
274 274 except Exception:
275 275 pass
276 276 line = raw_input(prompt)
277 277 # When stdin is in binary mode on Windows, it can cause
278 278 # raw_input() to emit an extra trailing carriage return
279 279 if os.linesep == '\r\n' and line and line[-1] == '\r':
280 280 line = line[:-1]
281 281 return line
282 282
283 283 def prompt(self, msg, pat=None, default="y"):
284 284 """Prompt user with msg, read response, and ensure it matches pat
285 285
286 286 If not interactive -- the default is returned
287 287 """
288 288 if not self.interactive:
289 289 self.note(msg, ' ', default, "\n")
290 290 return default
291 291 while True:
292 292 try:
293 293 r = self._readline(msg + ' ')
294 294 if not r:
295 295 return default
296 296 if not pat or re.match(pat, r):
297 297 return r
298 298 else:
299 299 self.write(_("unrecognized response\n"))
300 300 except EOFError:
301 301 raise util.Abort(_('response expected'))
302 302
303 303 def getpass(self, prompt=None, default=None):
304 304 if not self.interactive: return default
305 305 try:
306 306 return getpass.getpass(prompt or _('password: '))
307 307 except EOFError:
308 308 raise util.Abort(_('response expected'))
309 309 def status(self, *msg):
310 310 if not self.quiet: self.write(*msg)
311 311 def warn(self, *msg):
312 312 self.write_err(*msg)
313 313 def note(self, *msg):
314 314 if self.verbose: self.write(*msg)
315 315 def debug(self, *msg):
316 316 if self.debugflag: self.write(*msg)
317 317 def edit(self, text, user):
318 318 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
319 319 text=True)
320 320 try:
321 321 f = os.fdopen(fd, "w")
322 322 f.write(text)
323 323 f.close()
324 324
325 325 editor = self.geteditor()
326 326
327 327 util.system("%s \"%s\"" % (editor, name),
328 328 environ={'HGUSER': user},
329 329 onerr=util.Abort, errprefix=_("edit failed"))
330 330
331 331 f = open(name)
332 332 t = f.read()
333 333 f.close()
334 334 t = re.sub("(?m)^HG:.*\n", "", t)
335 335 finally:
336 336 os.unlink(name)
337 337
338 338 return t
339 339
340 340 def print_exc(self):
341 341 '''print exception traceback if traceback printing enabled.
342 342 only to call in exception handler. returns true if traceback
343 343 printed.'''
344 344 if self.traceback:
345 345 traceback.print_exc()
346 346 return self.traceback
347 347
348 348 def geteditor(self):
349 349 '''return editor to use'''
350 350 return (os.environ.get("HGEDITOR") or
351 351 self.config("ui", "editor") or
352 352 os.environ.get("VISUAL") or
353 353 os.environ.get("EDITOR", "vi"))
General Comments 0
You need to be logged in to leave comments. Login now