##// END OF EJS Templates
color: move the dict with terminfo parameters on the ui object...
Pierre-Yves David -
r31113:268caf97 default
parent child Browse files
Show More
@@ -1,241 +1,241 b''
1 1 # color.py color output for Mercurial commands
2 2 #
3 3 # Copyright (C) 2007 Kevin Christen <kevin.christen@gmail.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 or any later version.
7 7
8 8 '''colorize output from some commands
9 9
10 10 The color extension colorizes output from several Mercurial commands.
11 11 For example, the diff command shows additions in green and deletions
12 12 in red, while the status command shows modified files in magenta. Many
13 13 other commands have analogous colors. It is possible to customize
14 14 these colors.
15 15
16 16 Effects
17 17 -------
18 18
19 19 Other effects in addition to color, like bold and underlined text, are
20 20 also available. By default, the terminfo database is used to find the
21 21 terminal codes used to change color and effect. If terminfo is not
22 22 available, then effects are rendered with the ECMA-48 SGR control
23 23 function (aka ANSI escape codes).
24 24
25 25 The available effects in terminfo mode are 'blink', 'bold', 'dim',
26 26 'inverse', 'invisible', 'italic', 'standout', and 'underline'; in
27 27 ECMA-48 mode, the options are 'bold', 'inverse', 'italic', and
28 28 'underline'. How each is rendered depends on the terminal emulator.
29 29 Some may not be available for a given terminal type, and will be
30 30 silently ignored.
31 31
32 32 If the terminfo entry for your terminal is missing codes for an effect
33 33 or has the wrong codes, you can add or override those codes in your
34 34 configuration::
35 35
36 36 [color]
37 37 terminfo.dim = \E[2m
38 38
39 39 where '\E' is substituted with an escape character.
40 40
41 41 Labels
42 42 ------
43 43
44 44 Text receives color effects depending on the labels that it has. Many
45 45 default Mercurial commands emit labelled text. You can also define
46 46 your own labels in templates using the label function, see :hg:`help
47 47 templates`. A single portion of text may have more than one label. In
48 48 that case, effects given to the last label will override any other
49 49 effects. This includes the special "none" effect, which nullifies
50 50 other effects.
51 51
52 52 Labels are normally invisible. In order to see these labels and their
53 53 position in the text, use the global --color=debug option. The same
54 54 anchor text may be associated to multiple labels, e.g.
55 55
56 56 [log.changeset changeset.secret|changeset: 22611:6f0a53c8f587]
57 57
58 58 The following are the default effects for some default labels. Default
59 59 effects may be overridden from your configuration file::
60 60
61 61 [color]
62 62 status.modified = blue bold underline red_background
63 63 status.added = green bold
64 64 status.removed = red bold blue_background
65 65 status.deleted = cyan bold underline
66 66 status.unknown = magenta bold underline
67 67 status.ignored = black bold
68 68
69 69 # 'none' turns off all effects
70 70 status.clean = none
71 71 status.copied = none
72 72
73 73 qseries.applied = blue bold underline
74 74 qseries.unapplied = black bold
75 75 qseries.missing = red bold
76 76
77 77 diff.diffline = bold
78 78 diff.extended = cyan bold
79 79 diff.file_a = red bold
80 80 diff.file_b = green bold
81 81 diff.hunk = magenta
82 82 diff.deleted = red
83 83 diff.inserted = green
84 84 diff.changed = white
85 85 diff.tab =
86 86 diff.trailingwhitespace = bold red_background
87 87
88 88 # Blank so it inherits the style of the surrounding label
89 89 changeset.public =
90 90 changeset.draft =
91 91 changeset.secret =
92 92
93 93 resolve.unresolved = red bold
94 94 resolve.resolved = green bold
95 95
96 96 bookmarks.active = green
97 97
98 98 branches.active = none
99 99 branches.closed = black bold
100 100 branches.current = green
101 101 branches.inactive = none
102 102
103 103 tags.normal = green
104 104 tags.local = black bold
105 105
106 106 rebase.rebased = blue
107 107 rebase.remaining = red bold
108 108
109 109 shelve.age = cyan
110 110 shelve.newest = green bold
111 111 shelve.name = blue bold
112 112
113 113 histedit.remaining = red bold
114 114
115 115 Custom colors
116 116 -------------
117 117
118 118 Because there are only eight standard colors, this module allows you
119 119 to define color names for other color slots which might be available
120 120 for your terminal type, assuming terminfo mode. For instance::
121 121
122 122 color.brightblue = 12
123 123 color.pink = 207
124 124 color.orange = 202
125 125
126 126 to set 'brightblue' to color slot 12 (useful for 16 color terminals
127 127 that have brighter colors defined in the upper eight) and, 'pink' and
128 128 'orange' to colors in 256-color xterm's default color cube. These
129 129 defined colors may then be used as any of the pre-defined eight,
130 130 including appending '_background' to set the background to that color.
131 131
132 132 Modes
133 133 -----
134 134
135 135 By default, the color extension will use ANSI mode (or win32 mode on
136 136 Windows) if it detects a terminal. To override auto mode (to enable
137 137 terminfo mode, for example), set the following configuration option::
138 138
139 139 [color]
140 140 mode = terminfo
141 141
142 142 Any value other than 'ansi', 'win32', 'terminfo', or 'auto' will
143 143 disable color.
144 144
145 145 Note that on some systems, terminfo mode may cause problems when using
146 146 color with the pager extension and less -R. less with the -R option
147 147 will only display ECMA-48 color codes, and terminfo mode may sometimes
148 148 emit codes that less doesn't understand. You can work around this by
149 149 either using ansi mode (or auto mode), or by using less -r (which will
150 150 pass through all terminal control codes, not just color control
151 151 codes).
152 152
153 153 On some systems (such as MSYS in Windows), the terminal may support
154 154 a different color mode than the pager (activated via the "pager"
155 155 extension). It is possible to define separate modes depending on whether
156 156 the pager is active::
157 157
158 158 [color]
159 159 mode = auto
160 160 pagermode = ansi
161 161
162 162 If ``pagermode`` is not defined, the ``mode`` will be used.
163 163 '''
164 164
165 165 from __future__ import absolute_import
166 166
167 167 try:
168 168 import curses
169 169 curses.COLOR_BLACK # force import
170 170 except ImportError:
171 171 curses = None
172 172
173 173 from mercurial.i18n import _
174 174 from mercurial import (
175 175 cmdutil,
176 176 color,
177 177 commands,
178 178 )
179 179
180 180 cmdtable = {}
181 181 command = cmdutil.command(cmdtable)
182 182 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
183 183 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
184 184 # be specifying the version(s) of Mercurial they are tested with, or
185 185 # leave the attribute unspecified.
186 186 testedwith = 'ships-with-hg-core'
187 187
188 188 def extsetup(ui):
189 189 # change default color config
190 190 color._enabledbydefault = True
191 191 for idx, entry in enumerate(commands.globalopts):
192 192 if entry[1] == 'color':
193 193 patch = (entry[3].replace(' (EXPERIMENTAL)', ''),)
194 194 new = entry[:3] + patch + entry[4:]
195 195 commands.globalopts[idx] = new
196 196 break
197 197
198 198 @command('debugcolor',
199 199 [('', 'style', None, _('show all configured styles'))],
200 200 'hg debugcolor')
201 201 def debugcolor(ui, repo, **opts):
202 202 """show available color, effects or style"""
203 203 ui.write(('color mode: %s\n') % ui._colormode)
204 204 if opts.get('style'):
205 205 return _debugdisplaystyle(ui)
206 206 else:
207 207 return _debugdisplaycolor(ui)
208 208
209 209 def _debugdisplaycolor(ui):
210 210 oldstyle = color._styles.copy()
211 211 try:
212 212 color._styles.clear()
213 213 for effect in color._effects.keys():
214 214 color._styles[effect] = effect
215 if color._terminfo_params:
215 if ui._terminfoparams:
216 216 for k, v in ui.configitems('color'):
217 217 if k.startswith('color.'):
218 218 color._styles[k] = k[6:]
219 219 elif k.startswith('terminfo.'):
220 220 color._styles[k] = k[9:]
221 221 ui.write(_('available colors:\n'))
222 222 # sort label with a '_' after the other to group '_background' entry.
223 223 items = sorted(color._styles.items(),
224 224 key=lambda i: ('_' in i[0], i[0], i[1]))
225 225 for colorname, label in items:
226 226 ui.write(('%s\n') % colorname, label=label)
227 227 finally:
228 228 color._styles.clear()
229 229 color._styles.update(oldstyle)
230 230
231 231 def _debugdisplaystyle(ui):
232 232 ui.write(_('available style:\n'))
233 233 width = max(len(s) for s in color._styles)
234 234 for label, effects in sorted(color._styles.items()):
235 235 ui.write('%s' % label, label=label)
236 236 if effects:
237 237 # 50
238 238 ui.write(': ')
239 239 ui.write(' ' * (max(0, width - len(label))))
240 240 ui.write(', '.join(ui.label(e, e) for e in effects.split()))
241 241 ui.write('\n')
@@ -1,470 +1,471 b''
1 1 # utility for color output for Mercurial commands
2 2 #
3 3 # Copyright (C) 2007 Kevin Christen <kevin.christen@gmail.com> and other
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 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 from .i18n import _
11 11
12 12 from . import (
13 13 encoding,
14 14 pycompat,
15 15 util
16 16 )
17 17
18 18 try:
19 19 import curses
20 20 # Mapping from effect name to terminfo attribute name (or raw code) or
21 21 # color number. This will also force-load the curses module.
22 _terminfo_params = {
22 _baseterminfoparams = {
23 23 'none': (True, 'sgr0', ''),
24 24 'standout': (True, 'smso', ''),
25 25 'underline': (True, 'smul', ''),
26 26 'reverse': (True, 'rev', ''),
27 27 'inverse': (True, 'rev', ''),
28 28 'blink': (True, 'blink', ''),
29 29 'dim': (True, 'dim', ''),
30 30 'bold': (True, 'bold', ''),
31 31 'invisible': (True, 'invis', ''),
32 32 'italic': (True, 'sitm', ''),
33 33 'black': (False, curses.COLOR_BLACK, ''),
34 34 'red': (False, curses.COLOR_RED, ''),
35 35 'green': (False, curses.COLOR_GREEN, ''),
36 36 'yellow': (False, curses.COLOR_YELLOW, ''),
37 37 'blue': (False, curses.COLOR_BLUE, ''),
38 38 'magenta': (False, curses.COLOR_MAGENTA, ''),
39 39 'cyan': (False, curses.COLOR_CYAN, ''),
40 40 'white': (False, curses.COLOR_WHITE, ''),
41 41 }
42 42 except ImportError:
43 43 curses = None
44 _terminfo_params = {}
44 _baseterminfoparams = {}
45 45
46 46 # allow the extensions to change the default
47 47 _enabledbydefault = False
48 48
49 49 # start and stop parameters for effects
50 50 _effects = {
51 51 'none': 0,
52 52 'black': 30,
53 53 'red': 31,
54 54 'green': 32,
55 55 'yellow': 33,
56 56 'blue': 34,
57 57 'magenta': 35,
58 58 'cyan': 36,
59 59 'white': 37,
60 60 'bold': 1,
61 61 'italic': 3,
62 62 'underline': 4,
63 63 'inverse': 7,
64 64 'dim': 2,
65 65 'black_background': 40,
66 66 'red_background': 41,
67 67 'green_background': 42,
68 68 'yellow_background': 43,
69 69 'blue_background': 44,
70 70 'purple_background': 45,
71 71 'cyan_background': 46,
72 72 'white_background': 47,
73 73 }
74 74
75 75 _styles = {
76 76 'grep.match': 'red bold',
77 77 'grep.linenumber': 'green',
78 78 'grep.rev': 'green',
79 79 'grep.change': 'green',
80 80 'grep.sep': 'cyan',
81 81 'grep.filename': 'magenta',
82 82 'grep.user': 'magenta',
83 83 'grep.date': 'magenta',
84 84 'bookmarks.active': 'green',
85 85 'branches.active': 'none',
86 86 'branches.closed': 'black bold',
87 87 'branches.current': 'green',
88 88 'branches.inactive': 'none',
89 89 'diff.changed': 'white',
90 90 'diff.deleted': 'red',
91 91 'diff.diffline': 'bold',
92 92 'diff.extended': 'cyan bold',
93 93 'diff.file_a': 'red bold',
94 94 'diff.file_b': 'green bold',
95 95 'diff.hunk': 'magenta',
96 96 'diff.inserted': 'green',
97 97 'diff.tab': '',
98 98 'diff.trailingwhitespace': 'bold red_background',
99 99 'changeset.public' : '',
100 100 'changeset.draft' : '',
101 101 'changeset.secret' : '',
102 102 'diffstat.deleted': 'red',
103 103 'diffstat.inserted': 'green',
104 104 'histedit.remaining': 'red bold',
105 105 'ui.prompt': 'yellow',
106 106 'log.changeset': 'yellow',
107 107 'patchbomb.finalsummary': '',
108 108 'patchbomb.from': 'magenta',
109 109 'patchbomb.to': 'cyan',
110 110 'patchbomb.subject': 'green',
111 111 'patchbomb.diffstats': '',
112 112 'rebase.rebased': 'blue',
113 113 'rebase.remaining': 'red bold',
114 114 'resolve.resolved': 'green bold',
115 115 'resolve.unresolved': 'red bold',
116 116 'shelve.age': 'cyan',
117 117 'shelve.newest': 'green bold',
118 118 'shelve.name': 'blue bold',
119 119 'status.added': 'green bold',
120 120 'status.clean': 'none',
121 121 'status.copied': 'none',
122 122 'status.deleted': 'cyan bold underline',
123 123 'status.ignored': 'black bold',
124 124 'status.modified': 'blue bold',
125 125 'status.removed': 'red bold',
126 126 'status.unknown': 'magenta bold underline',
127 127 'tags.normal': 'green',
128 128 'tags.local': 'black bold',
129 129 }
130 130
131 131 def loadcolortable(ui, extname, colortable):
132 132 _styles.update(colortable)
133 133
134 134 def _terminfosetup(ui, mode):
135 135 '''Initialize terminfo data and the terminal if we're in terminfo mode.'''
136 136
137 137 # If we failed to load curses, we go ahead and return.
138 138 if curses is None:
139 139 return
140 140 # Otherwise, see what the config file says.
141 141 if mode not in ('auto', 'terminfo'):
142 142 return
143 ui._terminfoparams.update(_baseterminfoparams)
143 144
144 145 for key, val in ui.configitems('color'):
145 146 if key.startswith('color.'):
146 147 newval = (False, int(val), '')
147 _terminfo_params[key[6:]] = newval
148 ui._terminfoparams[key[6:]] = newval
148 149 elif key.startswith('terminfo.'):
149 150 newval = (True, '', val.replace('\\E', '\x1b'))
150 _terminfo_params[key[9:]] = newval
151 ui._terminfoparams[key[9:]] = newval
151 152 try:
152 153 curses.setupterm()
153 154 except curses.error as e:
154 _terminfo_params.clear()
155 ui._terminfoparams.clear()
155 156 return
156 157
157 for key, (b, e, c) in _terminfo_params.items():
158 for key, (b, e, c) in ui._terminfoparams.items():
158 159 if not b:
159 160 continue
160 161 if not c and not curses.tigetstr(e):
161 162 # Most terminals don't support dim, invis, etc, so don't be
162 163 # noisy and use ui.debug().
163 164 ui.debug("no terminfo entry for %s\n" % e)
164 del _terminfo_params[key]
165 del ui._terminfoparams[key]
165 166 if not curses.tigetstr('setaf') or not curses.tigetstr('setab'):
166 167 # Only warn about missing terminfo entries if we explicitly asked for
167 168 # terminfo mode.
168 169 if mode == "terminfo":
169 170 ui.warn(_("no terminfo entry for setab/setaf: reverting to "
170 171 "ECMA-48 color\n"))
171 _terminfo_params.clear()
172 ui._terminfoparams.clear()
172 173
173 174 def setup(ui):
174 175 """configure color on a ui
175 176
176 177 That function both set the colormode for the ui object and read
177 178 the configuration looking for custom colors and effect definitions."""
178 179 mode = _modesetup(ui)
179 180 ui._colormode = mode
180 181 if mode and mode != 'debug':
181 182 configstyles(ui)
182 183
183 184 def _modesetup(ui):
184 185 if ui.plain():
185 186 return None
186 187 default = 'never'
187 188 if _enabledbydefault:
188 189 default = 'auto'
189 190 # experimental config: ui.color
190 191 config = ui.config('ui', 'color', default)
191 192 if config == 'debug':
192 193 return 'debug'
193 194
194 195 auto = (config == 'auto')
195 196 always = not auto and util.parsebool(config)
196 197 if not always and not auto:
197 198 return None
198 199
199 200 formatted = (always or (encoding.environ.get('TERM') != 'dumb'
200 201 and ui.formatted()))
201 202
202 203 mode = ui.config('color', 'mode', 'auto')
203 204
204 205 # If pager is active, color.pagermode overrides color.mode.
205 206 if getattr(ui, 'pageractive', False):
206 207 mode = ui.config('color', 'pagermode', mode)
207 208
208 209 realmode = mode
209 210 if mode == 'auto':
210 211 if pycompat.osname == 'nt':
211 212 term = encoding.environ.get('TERM')
212 213 # TERM won't be defined in a vanilla cmd.exe environment.
213 214
214 215 # UNIX-like environments on Windows such as Cygwin and MSYS will
215 216 # set TERM. They appear to make a best effort attempt at setting it
216 217 # to something appropriate. However, not all environments with TERM
217 218 # defined support ANSI. Since "ansi" could result in terminal
218 219 # gibberish, we error on the side of selecting "win32". However, if
219 220 # w32effects is not defined, we almost certainly don't support
220 221 # "win32", so don't even try.
221 222 if (term and 'xterm' in term) or not w32effects:
222 223 realmode = 'ansi'
223 224 else:
224 225 realmode = 'win32'
225 226 else:
226 227 realmode = 'ansi'
227 228
228 229 def modewarn():
229 230 # only warn if color.mode was explicitly set and we're in
230 231 # a formatted terminal
231 232 if mode == realmode and ui.formatted():
232 233 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
233 234
234 235 if realmode == 'win32':
235 _terminfo_params.clear()
236 ui._terminfoparams.clear()
236 237 if not w32effects:
237 238 modewarn()
238 239 return None
239 240 _effects.update(w32effects)
240 241 elif realmode == 'ansi':
241 _terminfo_params.clear()
242 ui._terminfoparams.clear()
242 243 elif realmode == 'terminfo':
243 244 _terminfosetup(ui, mode)
244 if not _terminfo_params:
245 if not ui._terminfoparams:
245 246 ## FIXME Shouldn't we return None in this case too?
246 247 modewarn()
247 248 realmode = 'ansi'
248 249 else:
249 250 return None
250 251
251 252 if always or (auto and formatted):
252 253 return realmode
253 254 return None
254 255
255 256 def configstyles(ui):
256 257 for status, cfgeffects in ui.configitems('color'):
257 258 if '.' not in status or status.startswith(('color.', 'terminfo.')):
258 259 continue
259 260 cfgeffects = ui.configlist('color', status)
260 261 if cfgeffects:
261 262 good = []
262 263 for e in cfgeffects:
263 264 if valideffect(ui, e):
264 265 good.append(e)
265 266 else:
266 267 ui.warn(_("ignoring unknown color/effect %r "
267 268 "(configured in color.%s)\n")
268 269 % (e, status))
269 270 _styles[status] = ' '.join(good)
270 271
271 272 def valideffect(ui, effect):
272 273 'Determine if the effect is valid or not.'
273 return ((not _terminfo_params and effect in _effects)
274 or (effect in _terminfo_params
275 or effect[:-11] in _terminfo_params))
274 return ((not ui._terminfoparams and effect in _effects)
275 or (effect in ui._terminfoparams
276 or effect[:-11] in ui._terminfoparams))
276 277
277 278 def _effect_str(ui, effect):
278 279 '''Helper function for render_effects().'''
279 280
280 281 bg = False
281 282 if effect.endswith('_background'):
282 283 bg = True
283 284 effect = effect[:-11]
284 285 try:
285 attr, val, termcode = _terminfo_params[effect]
286 attr, val, termcode = ui._terminfoparams[effect]
286 287 except KeyError:
287 288 return ''
288 289 if attr:
289 290 if termcode:
290 291 return termcode
291 292 else:
292 293 return curses.tigetstr(val)
293 294 elif bg:
294 295 return curses.tparm(curses.tigetstr('setab'), val)
295 296 else:
296 297 return curses.tparm(curses.tigetstr('setaf'), val)
297 298
298 299 def _render_effects(ui, text, effects):
299 300 'Wrap text in commands to turn on each effect.'
300 301 if not text:
301 302 return text
302 if _terminfo_params:
303 if ui._terminfoparams:
303 304 start = ''.join(_effect_str(ui, effect)
304 305 for effect in ['none'] + effects.split())
305 306 stop = _effect_str(ui, 'none')
306 307 else:
307 308 start = [str(_effects[e]) for e in ['none'] + effects.split()]
308 309 start = '\033[' + ';'.join(start) + 'm'
309 310 stop = '\033[' + str(_effects['none']) + 'm'
310 311 return ''.join([start, text, stop])
311 312
312 313 def colorlabel(ui, msg, label):
313 314 """add color control code according to the mode"""
314 315 if ui._colormode == 'debug':
315 316 if label and msg:
316 317 if msg[-1] == '\n':
317 318 msg = "[%s|%s]\n" % (label, msg[:-1])
318 319 else:
319 320 msg = "[%s|%s]" % (label, msg)
320 321 elif ui._colormode is not None:
321 322 effects = []
322 323 for l in label.split():
323 324 s = _styles.get(l, '')
324 325 if s:
325 326 effects.append(s)
326 327 elif valideffect(ui, l):
327 328 effects.append(l)
328 329 effects = ' '.join(effects)
329 330 if effects:
330 331 msg = '\n'.join([_render_effects(ui, line, effects)
331 332 for line in msg.split('\n')])
332 333 return msg
333 334
334 335 w32effects = None
335 336 if pycompat.osname == 'nt':
336 337 import ctypes
337 338 import re
338 339
339 340 _kernel32 = ctypes.windll.kernel32
340 341
341 342 _WORD = ctypes.c_ushort
342 343
343 344 _INVALID_HANDLE_VALUE = -1
344 345
345 346 class _COORD(ctypes.Structure):
346 347 _fields_ = [('X', ctypes.c_short),
347 348 ('Y', ctypes.c_short)]
348 349
349 350 class _SMALL_RECT(ctypes.Structure):
350 351 _fields_ = [('Left', ctypes.c_short),
351 352 ('Top', ctypes.c_short),
352 353 ('Right', ctypes.c_short),
353 354 ('Bottom', ctypes.c_short)]
354 355
355 356 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
356 357 _fields_ = [('dwSize', _COORD),
357 358 ('dwCursorPosition', _COORD),
358 359 ('wAttributes', _WORD),
359 360 ('srWindow', _SMALL_RECT),
360 361 ('dwMaximumWindowSize', _COORD)]
361 362
362 363 _STD_OUTPUT_HANDLE = 0xfffffff5 # (DWORD)-11
363 364 _STD_ERROR_HANDLE = 0xfffffff4 # (DWORD)-12
364 365
365 366 _FOREGROUND_BLUE = 0x0001
366 367 _FOREGROUND_GREEN = 0x0002
367 368 _FOREGROUND_RED = 0x0004
368 369 _FOREGROUND_INTENSITY = 0x0008
369 370
370 371 _BACKGROUND_BLUE = 0x0010
371 372 _BACKGROUND_GREEN = 0x0020
372 373 _BACKGROUND_RED = 0x0040
373 374 _BACKGROUND_INTENSITY = 0x0080
374 375
375 376 _COMMON_LVB_REVERSE_VIDEO = 0x4000
376 377 _COMMON_LVB_UNDERSCORE = 0x8000
377 378
378 379 # http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx
379 380 w32effects = {
380 381 'none': -1,
381 382 'black': 0,
382 383 'red': _FOREGROUND_RED,
383 384 'green': _FOREGROUND_GREEN,
384 385 'yellow': _FOREGROUND_RED | _FOREGROUND_GREEN,
385 386 'blue': _FOREGROUND_BLUE,
386 387 'magenta': _FOREGROUND_BLUE | _FOREGROUND_RED,
387 388 'cyan': _FOREGROUND_BLUE | _FOREGROUND_GREEN,
388 389 'white': _FOREGROUND_RED | _FOREGROUND_GREEN | _FOREGROUND_BLUE,
389 390 'bold': _FOREGROUND_INTENSITY,
390 391 'black_background': 0x100, # unused value > 0x0f
391 392 'red_background': _BACKGROUND_RED,
392 393 'green_background': _BACKGROUND_GREEN,
393 394 'yellow_background': _BACKGROUND_RED | _BACKGROUND_GREEN,
394 395 'blue_background': _BACKGROUND_BLUE,
395 396 'purple_background': _BACKGROUND_BLUE | _BACKGROUND_RED,
396 397 'cyan_background': _BACKGROUND_BLUE | _BACKGROUND_GREEN,
397 398 'white_background': (_BACKGROUND_RED | _BACKGROUND_GREEN |
398 399 _BACKGROUND_BLUE),
399 400 'bold_background': _BACKGROUND_INTENSITY,
400 401 'underline': _COMMON_LVB_UNDERSCORE, # double-byte charsets only
401 402 'inverse': _COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only
402 403 }
403 404
404 405 passthrough = set([_FOREGROUND_INTENSITY,
405 406 _BACKGROUND_INTENSITY,
406 407 _COMMON_LVB_UNDERSCORE,
407 408 _COMMON_LVB_REVERSE_VIDEO])
408 409
409 410 stdout = _kernel32.GetStdHandle(
410 411 _STD_OUTPUT_HANDLE) # don't close the handle returned
411 412 if stdout is None or stdout == _INVALID_HANDLE_VALUE:
412 413 w32effects = None
413 414 else:
414 415 csbi = _CONSOLE_SCREEN_BUFFER_INFO()
415 416 if not _kernel32.GetConsoleScreenBufferInfo(
416 417 stdout, ctypes.byref(csbi)):
417 418 # stdout may not support GetConsoleScreenBufferInfo()
418 419 # when called from subprocess or redirected
419 420 w32effects = None
420 421 else:
421 422 origattr = csbi.wAttributes
422 423 ansire = re.compile('\033\[([^m]*)m([^\033]*)(.*)',
423 424 re.MULTILINE | re.DOTALL)
424 425
425 426 def win32print(writefunc, *msgs, **opts):
426 427 for text in msgs:
427 428 _win32print(text, writefunc, **opts)
428 429
429 430 def _win32print(text, writefunc, **opts):
430 431 label = opts.get('label', '')
431 432 attr = origattr
432 433
433 434 def mapcolor(val, attr):
434 435 if val == -1:
435 436 return origattr
436 437 elif val in passthrough:
437 438 return attr | val
438 439 elif val > 0x0f:
439 440 return (val & 0x70) | (attr & 0x8f)
440 441 else:
441 442 return (val & 0x07) | (attr & 0xf8)
442 443
443 444 # determine console attributes based on labels
444 445 for l in label.split():
445 446 style = _styles.get(l, '')
446 447 for effect in style.split():
447 448 try:
448 449 attr = mapcolor(w32effects[effect], attr)
449 450 except KeyError:
450 451 # w32effects could not have certain attributes so we skip
451 452 # them if not found
452 453 pass
453 454 # hack to ensure regexp finds data
454 455 if not text.startswith('\033['):
455 456 text = '\033[m' + text
456 457
457 458 # Look for ANSI-like codes embedded in text
458 459 m = re.match(ansire, text)
459 460
460 461 try:
461 462 while m:
462 463 for sattr in m.group(1).split(';'):
463 464 if sattr:
464 465 attr = mapcolor(int(sattr), attr)
465 466 _kernel32.SetConsoleTextAttribute(stdout, attr)
466 467 writefunc(m.group(2), **opts)
467 468 m = re.match(ansire, m.group(3))
468 469 finally:
469 470 # Explicitly reset original attributes
470 471 _kernel32.SetConsoleTextAttribute(stdout, origattr)
@@ -1,1660 +1,1662 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 of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import atexit
11 11 import collections
12 12 import contextlib
13 13 import errno
14 14 import getpass
15 15 import inspect
16 16 import os
17 17 import re
18 18 import signal
19 19 import socket
20 20 import subprocess
21 21 import sys
22 22 import tempfile
23 23 import traceback
24 24
25 25 from .i18n import _
26 26 from .node import hex
27 27
28 28 from . import (
29 29 color,
30 30 config,
31 31 encoding,
32 32 error,
33 33 formatter,
34 34 progress,
35 35 pycompat,
36 36 scmutil,
37 37 util,
38 38 )
39 39
40 40 urlreq = util.urlreq
41 41
42 42 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
43 43 if pycompat.ispy3:
44 44 _bytes = [bytes([c]) for c in range(256)]
45 45 _notalnum = [s for s in _bytes if not s.isalnum()]
46 46 else:
47 47 _notalnum = [c for c in map(chr, range(256)) if not c.isalnum()]
48 48 _keepalnum = ''.join(_notalnum)
49 49
50 50 samplehgrcs = {
51 51 'user':
52 52 """# example user config (see 'hg help config' for more info)
53 53 [ui]
54 54 # name and email, e.g.
55 55 # username = Jane Doe <jdoe@example.com>
56 56 username =
57 57
58 58 [extensions]
59 59 # uncomment these lines to enable some popular extensions
60 60 # (see 'hg help extensions' for more info)
61 61 #
62 62 # pager =
63 63 # color =""",
64 64
65 65 'cloned':
66 66 """# example repository config (see 'hg help config' for more info)
67 67 [paths]
68 68 default = %s
69 69
70 70 # path aliases to other clones of this repo in URLs or filesystem paths
71 71 # (see 'hg help config.paths' for more info)
72 72 #
73 73 # default-push = ssh://jdoe@example.net/hg/jdoes-fork
74 74 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
75 75 # my-clone = /home/jdoe/jdoes-clone
76 76
77 77 [ui]
78 78 # name and email (local to this repository, optional), e.g.
79 79 # username = Jane Doe <jdoe@example.com>
80 80 """,
81 81
82 82 'local':
83 83 """# example repository config (see 'hg help config' for more info)
84 84 [paths]
85 85 # path aliases to other clones of this repo in URLs or filesystem paths
86 86 # (see 'hg help config.paths' for more info)
87 87 #
88 88 # default = http://example.com/hg/example-repo
89 89 # default-push = ssh://jdoe@example.net/hg/jdoes-fork
90 90 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
91 91 # my-clone = /home/jdoe/jdoes-clone
92 92
93 93 [ui]
94 94 # name and email (local to this repository, optional), e.g.
95 95 # username = Jane Doe <jdoe@example.com>
96 96 """,
97 97
98 98 'global':
99 99 """# example system-wide hg config (see 'hg help config' for more info)
100 100
101 101 [extensions]
102 102 # uncomment these lines to enable some popular extensions
103 103 # (see 'hg help extensions' for more info)
104 104 #
105 105 # blackbox =
106 106 # color =
107 107 # pager =""",
108 108 }
109 109
110 110
111 111 class httppasswordmgrdbproxy(object):
112 112 """Delays loading urllib2 until it's needed."""
113 113 def __init__(self):
114 114 self._mgr = None
115 115
116 116 def _get_mgr(self):
117 117 if self._mgr is None:
118 118 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
119 119 return self._mgr
120 120
121 121 def add_password(self, *args, **kwargs):
122 122 return self._get_mgr().add_password(*args, **kwargs)
123 123
124 124 def find_user_password(self, *args, **kwargs):
125 125 return self._get_mgr().find_user_password(*args, **kwargs)
126 126
127 127 def _catchterm(*args):
128 128 raise error.SignalInterrupt
129 129
130 130 class ui(object):
131 131 def __init__(self, src=None):
132 132 """Create a fresh new ui object if no src given
133 133
134 134 Use uimod.ui.load() to create a ui which knows global and user configs.
135 135 In most cases, you should use ui.copy() to create a copy of an existing
136 136 ui object.
137 137 """
138 138 # _buffers: used for temporary capture of output
139 139 self._buffers = []
140 140 # 3-tuple describing how each buffer in the stack behaves.
141 141 # Values are (capture stderr, capture subprocesses, apply labels).
142 142 self._bufferstates = []
143 143 # When a buffer is active, defines whether we are expanding labels.
144 144 # This exists to prevent an extra list lookup.
145 145 self._bufferapplylabels = None
146 146 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
147 147 self._reportuntrusted = True
148 148 self._ocfg = config.config() # overlay
149 149 self._tcfg = config.config() # trusted
150 150 self._ucfg = config.config() # untrusted
151 151 self._trustusers = set()
152 152 self._trustgroups = set()
153 153 self.callhooks = True
154 154 # Insecure server connections requested.
155 155 self.insecureconnections = False
156 156 # Blocked time
157 157 self.logblockedtimes = False
158 158 # color mode: see mercurial/color.py for possible value
159 159 self._colormode = None
160 self._terminfoparams = {}
160 161
161 162 if src:
162 163 self.fout = src.fout
163 164 self.ferr = src.ferr
164 165 self.fin = src.fin
165 166 self.pageractive = src.pageractive
166 167 self._disablepager = src._disablepager
167 168
168 169 self._tcfg = src._tcfg.copy()
169 170 self._ucfg = src._ucfg.copy()
170 171 self._ocfg = src._ocfg.copy()
171 172 self._trustusers = src._trustusers.copy()
172 173 self._trustgroups = src._trustgroups.copy()
173 174 self.environ = src.environ
174 175 self.callhooks = src.callhooks
175 176 self.insecureconnections = src.insecureconnections
176 177 self._colormode = src._colormode
178 self._terminfoparams = src._terminfoparams.copy()
177 179
178 180 self.fixconfig()
179 181
180 182 self.httppasswordmgrdb = src.httppasswordmgrdb
181 183 self._blockedtimes = src._blockedtimes
182 184 else:
183 185 self.fout = util.stdout
184 186 self.ferr = util.stderr
185 187 self.fin = util.stdin
186 188 self.pageractive = False
187 189 self._disablepager = False
188 190
189 191 # shared read-only environment
190 192 self.environ = encoding.environ
191 193
192 194 self.httppasswordmgrdb = httppasswordmgrdbproxy()
193 195 self._blockedtimes = collections.defaultdict(int)
194 196
195 197 allowed = self.configlist('experimental', 'exportableenviron')
196 198 if '*' in allowed:
197 199 self._exportableenviron = self.environ
198 200 else:
199 201 self._exportableenviron = {}
200 202 for k in allowed:
201 203 if k in self.environ:
202 204 self._exportableenviron[k] = self.environ[k]
203 205
204 206 @classmethod
205 207 def load(cls):
206 208 """Create a ui and load global and user configs"""
207 209 u = cls()
208 210 # we always trust global config files
209 211 for f in scmutil.rcpath():
210 212 u.readconfig(f, trust=True)
211 213 return u
212 214
213 215 def copy(self):
214 216 return self.__class__(self)
215 217
216 218 def resetstate(self):
217 219 """Clear internal state that shouldn't persist across commands"""
218 220 if self._progbar:
219 221 self._progbar.resetstate() # reset last-print time of progress bar
220 222 self.httppasswordmgrdb = httppasswordmgrdbproxy()
221 223
222 224 @contextlib.contextmanager
223 225 def timeblockedsection(self, key):
224 226 # this is open-coded below - search for timeblockedsection to find them
225 227 starttime = util.timer()
226 228 try:
227 229 yield
228 230 finally:
229 231 self._blockedtimes[key + '_blocked'] += \
230 232 (util.timer() - starttime) * 1000
231 233
232 234 def formatter(self, topic, opts):
233 235 return formatter.formatter(self, topic, opts)
234 236
235 237 def _trusted(self, fp, f):
236 238 st = util.fstat(fp)
237 239 if util.isowner(st):
238 240 return True
239 241
240 242 tusers, tgroups = self._trustusers, self._trustgroups
241 243 if '*' in tusers or '*' in tgroups:
242 244 return True
243 245
244 246 user = util.username(st.st_uid)
245 247 group = util.groupname(st.st_gid)
246 248 if user in tusers or group in tgroups or user == util.username():
247 249 return True
248 250
249 251 if self._reportuntrusted:
250 252 self.warn(_('not trusting file %s from untrusted '
251 253 'user %s, group %s\n') % (f, user, group))
252 254 return False
253 255
254 256 def readconfig(self, filename, root=None, trust=False,
255 257 sections=None, remap=None):
256 258 try:
257 259 fp = open(filename, u'rb')
258 260 except IOError:
259 261 if not sections: # ignore unless we were looking for something
260 262 return
261 263 raise
262 264
263 265 cfg = config.config()
264 266 trusted = sections or trust or self._trusted(fp, filename)
265 267
266 268 try:
267 269 cfg.read(filename, fp, sections=sections, remap=remap)
268 270 fp.close()
269 271 except error.ConfigError as inst:
270 272 if trusted:
271 273 raise
272 274 self.warn(_("ignored: %s\n") % str(inst))
273 275
274 276 if self.plain():
275 277 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
276 278 'logtemplate', 'statuscopies', 'style',
277 279 'traceback', 'verbose'):
278 280 if k in cfg['ui']:
279 281 del cfg['ui'][k]
280 282 for k, v in cfg.items('defaults'):
281 283 del cfg['defaults'][k]
282 284 # Don't remove aliases from the configuration if in the exceptionlist
283 285 if self.plain('alias'):
284 286 for k, v in cfg.items('alias'):
285 287 del cfg['alias'][k]
286 288 if self.plain('revsetalias'):
287 289 for k, v in cfg.items('revsetalias'):
288 290 del cfg['revsetalias'][k]
289 291 if self.plain('templatealias'):
290 292 for k, v in cfg.items('templatealias'):
291 293 del cfg['templatealias'][k]
292 294
293 295 if trusted:
294 296 self._tcfg.update(cfg)
295 297 self._tcfg.update(self._ocfg)
296 298 self._ucfg.update(cfg)
297 299 self._ucfg.update(self._ocfg)
298 300
299 301 if root is None:
300 302 root = os.path.expanduser('~')
301 303 self.fixconfig(root=root)
302 304
303 305 def fixconfig(self, root=None, section=None):
304 306 if section in (None, 'paths'):
305 307 # expand vars and ~
306 308 # translate paths relative to root (or home) into absolute paths
307 309 root = root or pycompat.getcwd()
308 310 for c in self._tcfg, self._ucfg, self._ocfg:
309 311 for n, p in c.items('paths'):
310 312 # Ignore sub-options.
311 313 if ':' in n:
312 314 continue
313 315 if not p:
314 316 continue
315 317 if '%%' in p:
316 318 s = self.configsource('paths', n) or 'none'
317 319 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
318 320 % (n, p, s))
319 321 p = p.replace('%%', '%')
320 322 p = util.expandpath(p)
321 323 if not util.hasscheme(p) and not os.path.isabs(p):
322 324 p = os.path.normpath(os.path.join(root, p))
323 325 c.set("paths", n, p)
324 326
325 327 if section in (None, 'ui'):
326 328 # update ui options
327 329 self.debugflag = self.configbool('ui', 'debug')
328 330 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
329 331 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
330 332 if self.verbose and self.quiet:
331 333 self.quiet = self.verbose = False
332 334 self._reportuntrusted = self.debugflag or self.configbool("ui",
333 335 "report_untrusted", True)
334 336 self.tracebackflag = self.configbool('ui', 'traceback', False)
335 337 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
336 338
337 339 if section in (None, 'trusted'):
338 340 # update trust information
339 341 self._trustusers.update(self.configlist('trusted', 'users'))
340 342 self._trustgroups.update(self.configlist('trusted', 'groups'))
341 343
342 344 def backupconfig(self, section, item):
343 345 return (self._ocfg.backup(section, item),
344 346 self._tcfg.backup(section, item),
345 347 self._ucfg.backup(section, item),)
346 348 def restoreconfig(self, data):
347 349 self._ocfg.restore(data[0])
348 350 self._tcfg.restore(data[1])
349 351 self._ucfg.restore(data[2])
350 352
351 353 def setconfig(self, section, name, value, source=''):
352 354 for cfg in (self._ocfg, self._tcfg, self._ucfg):
353 355 cfg.set(section, name, value, source)
354 356 self.fixconfig(section=section)
355 357
356 358 def _data(self, untrusted):
357 359 return untrusted and self._ucfg or self._tcfg
358 360
359 361 def configsource(self, section, name, untrusted=False):
360 362 return self._data(untrusted).source(section, name)
361 363
362 364 def config(self, section, name, default=None, untrusted=False):
363 365 if isinstance(name, list):
364 366 alternates = name
365 367 else:
366 368 alternates = [name]
367 369
368 370 for n in alternates:
369 371 value = self._data(untrusted).get(section, n, None)
370 372 if value is not None:
371 373 name = n
372 374 break
373 375 else:
374 376 value = default
375 377
376 378 if self.debugflag and not untrusted and self._reportuntrusted:
377 379 for n in alternates:
378 380 uvalue = self._ucfg.get(section, n)
379 381 if uvalue is not None and uvalue != value:
380 382 self.debug("ignoring untrusted configuration option "
381 383 "%s.%s = %s\n" % (section, n, uvalue))
382 384 return value
383 385
384 386 def configsuboptions(self, section, name, default=None, untrusted=False):
385 387 """Get a config option and all sub-options.
386 388
387 389 Some config options have sub-options that are declared with the
388 390 format "key:opt = value". This method is used to return the main
389 391 option and all its declared sub-options.
390 392
391 393 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
392 394 is a dict of defined sub-options where keys and values are strings.
393 395 """
394 396 data = self._data(untrusted)
395 397 main = data.get(section, name, default)
396 398 if self.debugflag and not untrusted and self._reportuntrusted:
397 399 uvalue = self._ucfg.get(section, name)
398 400 if uvalue is not None and uvalue != main:
399 401 self.debug('ignoring untrusted configuration option '
400 402 '%s.%s = %s\n' % (section, name, uvalue))
401 403
402 404 sub = {}
403 405 prefix = '%s:' % name
404 406 for k, v in data.items(section):
405 407 if k.startswith(prefix):
406 408 sub[k[len(prefix):]] = v
407 409
408 410 if self.debugflag and not untrusted and self._reportuntrusted:
409 411 for k, v in sub.items():
410 412 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
411 413 if uvalue is not None and uvalue != v:
412 414 self.debug('ignoring untrusted configuration option '
413 415 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
414 416
415 417 return main, sub
416 418
417 419 def configpath(self, section, name, default=None, untrusted=False):
418 420 'get a path config item, expanded relative to repo root or config file'
419 421 v = self.config(section, name, default, untrusted)
420 422 if v is None:
421 423 return None
422 424 if not os.path.isabs(v) or "://" not in v:
423 425 src = self.configsource(section, name, untrusted)
424 426 if ':' in src:
425 427 base = os.path.dirname(src.rsplit(':')[0])
426 428 v = os.path.join(base, os.path.expanduser(v))
427 429 return v
428 430
429 431 def configbool(self, section, name, default=False, untrusted=False):
430 432 """parse a configuration element as a boolean
431 433
432 434 >>> u = ui(); s = 'foo'
433 435 >>> u.setconfig(s, 'true', 'yes')
434 436 >>> u.configbool(s, 'true')
435 437 True
436 438 >>> u.setconfig(s, 'false', 'no')
437 439 >>> u.configbool(s, 'false')
438 440 False
439 441 >>> u.configbool(s, 'unknown')
440 442 False
441 443 >>> u.configbool(s, 'unknown', True)
442 444 True
443 445 >>> u.setconfig(s, 'invalid', 'somevalue')
444 446 >>> u.configbool(s, 'invalid')
445 447 Traceback (most recent call last):
446 448 ...
447 449 ConfigError: foo.invalid is not a boolean ('somevalue')
448 450 """
449 451
450 452 v = self.config(section, name, None, untrusted)
451 453 if v is None:
452 454 return default
453 455 if isinstance(v, bool):
454 456 return v
455 457 b = util.parsebool(v)
456 458 if b is None:
457 459 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
458 460 % (section, name, v))
459 461 return b
460 462
461 463 def configwith(self, convert, section, name, default=None,
462 464 desc=None, untrusted=False):
463 465 """parse a configuration element with a conversion function
464 466
465 467 >>> u = ui(); s = 'foo'
466 468 >>> u.setconfig(s, 'float1', '42')
467 469 >>> u.configwith(float, s, 'float1')
468 470 42.0
469 471 >>> u.setconfig(s, 'float2', '-4.25')
470 472 >>> u.configwith(float, s, 'float2')
471 473 -4.25
472 474 >>> u.configwith(float, s, 'unknown', 7)
473 475 7
474 476 >>> u.setconfig(s, 'invalid', 'somevalue')
475 477 >>> u.configwith(float, s, 'invalid')
476 478 Traceback (most recent call last):
477 479 ...
478 480 ConfigError: foo.invalid is not a valid float ('somevalue')
479 481 >>> u.configwith(float, s, 'invalid', desc='womble')
480 482 Traceback (most recent call last):
481 483 ...
482 484 ConfigError: foo.invalid is not a valid womble ('somevalue')
483 485 """
484 486
485 487 v = self.config(section, name, None, untrusted)
486 488 if v is None:
487 489 return default
488 490 try:
489 491 return convert(v)
490 492 except ValueError:
491 493 if desc is None:
492 494 desc = convert.__name__
493 495 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
494 496 % (section, name, desc, v))
495 497
496 498 def configint(self, section, name, default=None, untrusted=False):
497 499 """parse a configuration element as an integer
498 500
499 501 >>> u = ui(); s = 'foo'
500 502 >>> u.setconfig(s, 'int1', '42')
501 503 >>> u.configint(s, 'int1')
502 504 42
503 505 >>> u.setconfig(s, 'int2', '-42')
504 506 >>> u.configint(s, 'int2')
505 507 -42
506 508 >>> u.configint(s, 'unknown', 7)
507 509 7
508 510 >>> u.setconfig(s, 'invalid', 'somevalue')
509 511 >>> u.configint(s, 'invalid')
510 512 Traceback (most recent call last):
511 513 ...
512 514 ConfigError: foo.invalid is not a valid integer ('somevalue')
513 515 """
514 516
515 517 return self.configwith(int, section, name, default, 'integer',
516 518 untrusted)
517 519
518 520 def configbytes(self, section, name, default=0, untrusted=False):
519 521 """parse a configuration element as a quantity in bytes
520 522
521 523 Units can be specified as b (bytes), k or kb (kilobytes), m or
522 524 mb (megabytes), g or gb (gigabytes).
523 525
524 526 >>> u = ui(); s = 'foo'
525 527 >>> u.setconfig(s, 'val1', '42')
526 528 >>> u.configbytes(s, 'val1')
527 529 42
528 530 >>> u.setconfig(s, 'val2', '42.5 kb')
529 531 >>> u.configbytes(s, 'val2')
530 532 43520
531 533 >>> u.configbytes(s, 'unknown', '7 MB')
532 534 7340032
533 535 >>> u.setconfig(s, 'invalid', 'somevalue')
534 536 >>> u.configbytes(s, 'invalid')
535 537 Traceback (most recent call last):
536 538 ...
537 539 ConfigError: foo.invalid is not a byte quantity ('somevalue')
538 540 """
539 541
540 542 value = self.config(section, name)
541 543 if value is None:
542 544 if not isinstance(default, str):
543 545 return default
544 546 value = default
545 547 try:
546 548 return util.sizetoint(value)
547 549 except error.ParseError:
548 550 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
549 551 % (section, name, value))
550 552
551 553 def configlist(self, section, name, default=None, untrusted=False):
552 554 """parse a configuration element as a list of comma/space separated
553 555 strings
554 556
555 557 >>> u = ui(); s = 'foo'
556 558 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
557 559 >>> u.configlist(s, 'list1')
558 560 ['this', 'is', 'a small', 'test']
559 561 """
560 562
561 563 def _parse_plain(parts, s, offset):
562 564 whitespace = False
563 565 while offset < len(s) and (s[offset].isspace() or s[offset] == ','):
564 566 whitespace = True
565 567 offset += 1
566 568 if offset >= len(s):
567 569 return None, parts, offset
568 570 if whitespace:
569 571 parts.append('')
570 572 if s[offset] == '"' and not parts[-1]:
571 573 return _parse_quote, parts, offset + 1
572 574 elif s[offset] == '"' and parts[-1][-1] == '\\':
573 575 parts[-1] = parts[-1][:-1] + s[offset]
574 576 return _parse_plain, parts, offset + 1
575 577 parts[-1] += s[offset]
576 578 return _parse_plain, parts, offset + 1
577 579
578 580 def _parse_quote(parts, s, offset):
579 581 if offset < len(s) and s[offset] == '"': # ""
580 582 parts.append('')
581 583 offset += 1
582 584 while offset < len(s) and (s[offset].isspace() or
583 585 s[offset] == ','):
584 586 offset += 1
585 587 return _parse_plain, parts, offset
586 588
587 589 while offset < len(s) and s[offset] != '"':
588 590 if (s[offset] == '\\' and offset + 1 < len(s)
589 591 and s[offset + 1] == '"'):
590 592 offset += 1
591 593 parts[-1] += '"'
592 594 else:
593 595 parts[-1] += s[offset]
594 596 offset += 1
595 597
596 598 if offset >= len(s):
597 599 real_parts = _configlist(parts[-1])
598 600 if not real_parts:
599 601 parts[-1] = '"'
600 602 else:
601 603 real_parts[0] = '"' + real_parts[0]
602 604 parts = parts[:-1]
603 605 parts.extend(real_parts)
604 606 return None, parts, offset
605 607
606 608 offset += 1
607 609 while offset < len(s) and s[offset] in [' ', ',']:
608 610 offset += 1
609 611
610 612 if offset < len(s):
611 613 if offset + 1 == len(s) and s[offset] == '"':
612 614 parts[-1] += '"'
613 615 offset += 1
614 616 else:
615 617 parts.append('')
616 618 else:
617 619 return None, parts, offset
618 620
619 621 return _parse_plain, parts, offset
620 622
621 623 def _configlist(s):
622 624 s = s.rstrip(' ,')
623 625 if not s:
624 626 return []
625 627 parser, parts, offset = _parse_plain, [''], 0
626 628 while parser:
627 629 parser, parts, offset = parser(parts, s, offset)
628 630 return parts
629 631
630 632 result = self.config(section, name, untrusted=untrusted)
631 633 if result is None:
632 634 result = default or []
633 635 if isinstance(result, bytes):
634 636 result = _configlist(result.lstrip(' ,\n'))
635 637 if result is None:
636 638 result = default or []
637 639 return result
638 640
639 641 def hasconfig(self, section, name, untrusted=False):
640 642 return self._data(untrusted).hasitem(section, name)
641 643
642 644 def has_section(self, section, untrusted=False):
643 645 '''tell whether section exists in config.'''
644 646 return section in self._data(untrusted)
645 647
646 648 def configitems(self, section, untrusted=False, ignoresub=False):
647 649 items = self._data(untrusted).items(section)
648 650 if ignoresub:
649 651 newitems = {}
650 652 for k, v in items:
651 653 if ':' not in k:
652 654 newitems[k] = v
653 655 items = newitems.items()
654 656 if self.debugflag and not untrusted and self._reportuntrusted:
655 657 for k, v in self._ucfg.items(section):
656 658 if self._tcfg.get(section, k) != v:
657 659 self.debug("ignoring untrusted configuration option "
658 660 "%s.%s = %s\n" % (section, k, v))
659 661 return items
660 662
661 663 def walkconfig(self, untrusted=False):
662 664 cfg = self._data(untrusted)
663 665 for section in cfg.sections():
664 666 for name, value in self.configitems(section, untrusted):
665 667 yield section, name, value
666 668
667 669 def plain(self, feature=None):
668 670 '''is plain mode active?
669 671
670 672 Plain mode means that all configuration variables which affect
671 673 the behavior and output of Mercurial should be
672 674 ignored. Additionally, the output should be stable,
673 675 reproducible and suitable for use in scripts or applications.
674 676
675 677 The only way to trigger plain mode is by setting either the
676 678 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
677 679
678 680 The return value can either be
679 681 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
680 682 - True otherwise
681 683 '''
682 684 if ('HGPLAIN' not in encoding.environ and
683 685 'HGPLAINEXCEPT' not in encoding.environ):
684 686 return False
685 687 exceptions = encoding.environ.get('HGPLAINEXCEPT',
686 688 '').strip().split(',')
687 689 if feature and exceptions:
688 690 return feature not in exceptions
689 691 return True
690 692
691 693 def username(self):
692 694 """Return default username to be used in commits.
693 695
694 696 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
695 697 and stop searching if one of these is set.
696 698 If not found and ui.askusername is True, ask the user, else use
697 699 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
698 700 """
699 701 user = encoding.environ.get("HGUSER")
700 702 if user is None:
701 703 user = self.config("ui", ["username", "user"])
702 704 if user is not None:
703 705 user = os.path.expandvars(user)
704 706 if user is None:
705 707 user = encoding.environ.get("EMAIL")
706 708 if user is None and self.configbool("ui", "askusername"):
707 709 user = self.prompt(_("enter a commit username:"), default=None)
708 710 if user is None and not self.interactive():
709 711 try:
710 712 user = '%s@%s' % (util.getuser(), socket.getfqdn())
711 713 self.warn(_("no username found, using '%s' instead\n") % user)
712 714 except KeyError:
713 715 pass
714 716 if not user:
715 717 raise error.Abort(_('no username supplied'),
716 718 hint=_("use 'hg config --edit' "
717 719 'to set your username'))
718 720 if "\n" in user:
719 721 raise error.Abort(_("username %s contains a newline\n")
720 722 % repr(user))
721 723 return user
722 724
723 725 def shortuser(self, user):
724 726 """Return a short representation of a user name or email address."""
725 727 if not self.verbose:
726 728 user = util.shortuser(user)
727 729 return user
728 730
729 731 def expandpath(self, loc, default=None):
730 732 """Return repository location relative to cwd or from [paths]"""
731 733 try:
732 734 p = self.paths.getpath(loc)
733 735 if p:
734 736 return p.rawloc
735 737 except error.RepoError:
736 738 pass
737 739
738 740 if default:
739 741 try:
740 742 p = self.paths.getpath(default)
741 743 if p:
742 744 return p.rawloc
743 745 except error.RepoError:
744 746 pass
745 747
746 748 return loc
747 749
748 750 @util.propertycache
749 751 def paths(self):
750 752 return paths(self)
751 753
752 754 def pushbuffer(self, error=False, subproc=False, labeled=False):
753 755 """install a buffer to capture standard output of the ui object
754 756
755 757 If error is True, the error output will be captured too.
756 758
757 759 If subproc is True, output from subprocesses (typically hooks) will be
758 760 captured too.
759 761
760 762 If labeled is True, any labels associated with buffered
761 763 output will be handled. By default, this has no effect
762 764 on the output returned, but extensions and GUI tools may
763 765 handle this argument and returned styled output. If output
764 766 is being buffered so it can be captured and parsed or
765 767 processed, labeled should not be set to True.
766 768 """
767 769 self._buffers.append([])
768 770 self._bufferstates.append((error, subproc, labeled))
769 771 self._bufferapplylabels = labeled
770 772
771 773 def popbuffer(self):
772 774 '''pop the last buffer and return the buffered output'''
773 775 self._bufferstates.pop()
774 776 if self._bufferstates:
775 777 self._bufferapplylabels = self._bufferstates[-1][2]
776 778 else:
777 779 self._bufferapplylabels = None
778 780
779 781 return "".join(self._buffers.pop())
780 782
781 783 def write(self, *args, **opts):
782 784 '''write args to output
783 785
784 786 By default, this method simply writes to the buffer or stdout.
785 787 Color mode can be set on the UI class to have the output decorated
786 788 with color modifier before being written to stdout.
787 789
788 790 The color used is controlled by an optional keyword argument, "label".
789 791 This should be a string containing label names separated by space.
790 792 Label names take the form of "topic.type". For example, ui.debug()
791 793 issues a label of "ui.debug".
792 794
793 795 When labeling output for a specific command, a label of
794 796 "cmdname.type" is recommended. For example, status issues
795 797 a label of "status.modified" for modified files.
796 798 '''
797 799 if self._buffers and not opts.get('prompt', False):
798 800 if self._bufferapplylabels:
799 801 label = opts.get('label', '')
800 802 self._buffers[-1].extend(self.label(a, label) for a in args)
801 803 else:
802 804 self._buffers[-1].extend(args)
803 805 elif self._colormode == 'win32':
804 806 # windows color printing is its own can of crab, defer to
805 807 # the color module and that is it.
806 808 color.win32print(self._write, *args, **opts)
807 809 else:
808 810 msgs = args
809 811 if self._colormode is not None:
810 812 label = opts.get('label', '')
811 813 msgs = [self.label(a, label) for a in args]
812 814 self._write(*msgs, **opts)
813 815
814 816 def _write(self, *msgs, **opts):
815 817 self._progclear()
816 818 # opencode timeblockedsection because this is a critical path
817 819 starttime = util.timer()
818 820 try:
819 821 for a in msgs:
820 822 self.fout.write(a)
821 823 finally:
822 824 self._blockedtimes['stdio_blocked'] += \
823 825 (util.timer() - starttime) * 1000
824 826
825 827 def write_err(self, *args, **opts):
826 828 self._progclear()
827 829 if self._bufferstates and self._bufferstates[-1][0]:
828 830 self.write(*args, **opts)
829 831 elif self._colormode == 'win32':
830 832 # windows color printing is its own can of crab, defer to
831 833 # the color module and that is it.
832 834 color.win32print(self._write_err, *args, **opts)
833 835 else:
834 836 msgs = args
835 837 if self._colormode is not None:
836 838 label = opts.get('label', '')
837 839 msgs = [self.label(a, label) for a in args]
838 840 self._write_err(*msgs, **opts)
839 841
840 842 def _write_err(self, *msgs, **opts):
841 843 try:
842 844 with self.timeblockedsection('stdio'):
843 845 if not getattr(self.fout, 'closed', False):
844 846 self.fout.flush()
845 847 for a in msgs:
846 848 self.ferr.write(a)
847 849 # stderr may be buffered under win32 when redirected to files,
848 850 # including stdout.
849 851 if not getattr(self.ferr, 'closed', False):
850 852 self.ferr.flush()
851 853 except IOError as inst:
852 854 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
853 855 raise
854 856
855 857 def flush(self):
856 858 # opencode timeblockedsection because this is a critical path
857 859 starttime = util.timer()
858 860 try:
859 861 try: self.fout.flush()
860 862 except (IOError, ValueError): pass
861 863 try: self.ferr.flush()
862 864 except (IOError, ValueError): pass
863 865 finally:
864 866 self._blockedtimes['stdio_blocked'] += \
865 867 (util.timer() - starttime) * 1000
866 868
867 869 def _isatty(self, fh):
868 870 if self.configbool('ui', 'nontty', False):
869 871 return False
870 872 return util.isatty(fh)
871 873
872 874 def disablepager(self):
873 875 self._disablepager = True
874 876
875 877 def pager(self, command):
876 878 """Start a pager for subsequent command output.
877 879
878 880 Commands which produce a long stream of output should call
879 881 this function to activate the user's preferred pagination
880 882 mechanism (which may be no pager). Calling this function
881 883 precludes any future use of interactive functionality, such as
882 884 prompting the user or activating curses.
883 885
884 886 Args:
885 887 command: The full, non-aliased name of the command. That is, "log"
886 888 not "history, "summary" not "summ", etc.
887 889 """
888 890 if (self._disablepager
889 891 or self.pageractive
890 892 or command in self.configlist('pager', 'ignore')
891 893 or not self.configbool('pager', 'enable', True)
892 894 or not self.configbool('pager', 'attend-' + command, True)
893 895 # TODO: if we want to allow HGPLAINEXCEPT=pager,
894 896 # formatted() will need some adjustment.
895 897 or not self.formatted()
896 898 or self.plain()
897 899 # TODO: expose debugger-enabled on the UI object
898 900 or '--debugger' in sys.argv):
899 901 # We only want to paginate if the ui appears to be
900 902 # interactive, the user didn't say HGPLAIN or
901 903 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
902 904 return
903 905
904 906 # TODO: add a "system defaults" config section so this default
905 907 # of more(1) can be easily replaced with a global
906 908 # configuration file. For example, on OS X the sane default is
907 909 # less(1), not more(1), and on debian it's
908 910 # sensible-pager(1). We should probably also give the system
909 911 # default editor command similar treatment.
910 912 envpager = encoding.environ.get('PAGER', 'more')
911 913 pagercmd = self.config('pager', 'pager', envpager)
912 914 if not pagercmd:
913 915 return
914 916
915 917 self.debug('starting pager for command %r\n' % command)
916 918 self.pageractive = True
917 919 # Preserve the formatted-ness of the UI. This is important
918 920 # because we mess with stdout, which might confuse
919 921 # auto-detection of things being formatted.
920 922 self.setconfig('ui', 'formatted', self.formatted(), 'pager')
921 923 self.setconfig('ui', 'interactive', False, 'pager')
922 924 if util.safehasattr(signal, "SIGPIPE"):
923 925 signal.signal(signal.SIGPIPE, _catchterm)
924 926 self._runpager(pagercmd)
925 927
926 928 def _runpager(self, command):
927 929 """Actually start the pager and set up file descriptors.
928 930
929 931 This is separate in part so that extensions (like chg) can
930 932 override how a pager is invoked.
931 933 """
932 934 pager = subprocess.Popen(command, shell=True, bufsize=-1,
933 935 close_fds=util.closefds, stdin=subprocess.PIPE,
934 936 stdout=util.stdout, stderr=util.stderr)
935 937
936 938 # back up original file descriptors
937 939 stdoutfd = os.dup(util.stdout.fileno())
938 940 stderrfd = os.dup(util.stderr.fileno())
939 941
940 942 os.dup2(pager.stdin.fileno(), util.stdout.fileno())
941 943 if self._isatty(util.stderr):
942 944 os.dup2(pager.stdin.fileno(), util.stderr.fileno())
943 945
944 946 @atexit.register
945 947 def killpager():
946 948 if util.safehasattr(signal, "SIGINT"):
947 949 signal.signal(signal.SIGINT, signal.SIG_IGN)
948 950 # restore original fds, closing pager.stdin copies in the process
949 951 os.dup2(stdoutfd, util.stdout.fileno())
950 952 os.dup2(stderrfd, util.stderr.fileno())
951 953 pager.stdin.close()
952 954 pager.wait()
953 955
954 956 def interface(self, feature):
955 957 """what interface to use for interactive console features?
956 958
957 959 The interface is controlled by the value of `ui.interface` but also by
958 960 the value of feature-specific configuration. For example:
959 961
960 962 ui.interface.histedit = text
961 963 ui.interface.chunkselector = curses
962 964
963 965 Here the features are "histedit" and "chunkselector".
964 966
965 967 The configuration above means that the default interfaces for commands
966 968 is curses, the interface for histedit is text and the interface for
967 969 selecting chunk is crecord (the best curses interface available).
968 970
969 971 Consider the following example:
970 972 ui.interface = curses
971 973 ui.interface.histedit = text
972 974
973 975 Then histedit will use the text interface and chunkselector will use
974 976 the default curses interface (crecord at the moment).
975 977 """
976 978 alldefaults = frozenset(["text", "curses"])
977 979
978 980 featureinterfaces = {
979 981 "chunkselector": [
980 982 "text",
981 983 "curses",
982 984 ]
983 985 }
984 986
985 987 # Feature-specific interface
986 988 if feature not in featureinterfaces.keys():
987 989 # Programming error, not user error
988 990 raise ValueError("Unknown feature requested %s" % feature)
989 991
990 992 availableinterfaces = frozenset(featureinterfaces[feature])
991 993 if alldefaults > availableinterfaces:
992 994 # Programming error, not user error. We need a use case to
993 995 # define the right thing to do here.
994 996 raise ValueError(
995 997 "Feature %s does not handle all default interfaces" %
996 998 feature)
997 999
998 1000 if self.plain():
999 1001 return "text"
1000 1002
1001 1003 # Default interface for all the features
1002 1004 defaultinterface = "text"
1003 1005 i = self.config("ui", "interface", None)
1004 1006 if i in alldefaults:
1005 1007 defaultinterface = i
1006 1008
1007 1009 choseninterface = defaultinterface
1008 1010 f = self.config("ui", "interface.%s" % feature, None)
1009 1011 if f in availableinterfaces:
1010 1012 choseninterface = f
1011 1013
1012 1014 if i is not None and defaultinterface != i:
1013 1015 if f is not None:
1014 1016 self.warn(_("invalid value for ui.interface: %s\n") %
1015 1017 (i,))
1016 1018 else:
1017 1019 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1018 1020 (i, choseninterface))
1019 1021 if f is not None and choseninterface != f:
1020 1022 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1021 1023 (feature, f, choseninterface))
1022 1024
1023 1025 return choseninterface
1024 1026
1025 1027 def interactive(self):
1026 1028 '''is interactive input allowed?
1027 1029
1028 1030 An interactive session is a session where input can be reasonably read
1029 1031 from `sys.stdin'. If this function returns false, any attempt to read
1030 1032 from stdin should fail with an error, unless a sensible default has been
1031 1033 specified.
1032 1034
1033 1035 Interactiveness is triggered by the value of the `ui.interactive'
1034 1036 configuration variable or - if it is unset - when `sys.stdin' points
1035 1037 to a terminal device.
1036 1038
1037 1039 This function refers to input only; for output, see `ui.formatted()'.
1038 1040 '''
1039 1041 i = self.configbool("ui", "interactive", None)
1040 1042 if i is None:
1041 1043 # some environments replace stdin without implementing isatty
1042 1044 # usually those are non-interactive
1043 1045 return self._isatty(self.fin)
1044 1046
1045 1047 return i
1046 1048
1047 1049 def termwidth(self):
1048 1050 '''how wide is the terminal in columns?
1049 1051 '''
1050 1052 if 'COLUMNS' in encoding.environ:
1051 1053 try:
1052 1054 return int(encoding.environ['COLUMNS'])
1053 1055 except ValueError:
1054 1056 pass
1055 1057 return scmutil.termsize(self)[0]
1056 1058
1057 1059 def formatted(self):
1058 1060 '''should formatted output be used?
1059 1061
1060 1062 It is often desirable to format the output to suite the output medium.
1061 1063 Examples of this are truncating long lines or colorizing messages.
1062 1064 However, this is not often not desirable when piping output into other
1063 1065 utilities, e.g. `grep'.
1064 1066
1065 1067 Formatted output is triggered by the value of the `ui.formatted'
1066 1068 configuration variable or - if it is unset - when `sys.stdout' points
1067 1069 to a terminal device. Please note that `ui.formatted' should be
1068 1070 considered an implementation detail; it is not intended for use outside
1069 1071 Mercurial or its extensions.
1070 1072
1071 1073 This function refers to output only; for input, see `ui.interactive()'.
1072 1074 This function always returns false when in plain mode, see `ui.plain()'.
1073 1075 '''
1074 1076 if self.plain():
1075 1077 return False
1076 1078
1077 1079 i = self.configbool("ui", "formatted", None)
1078 1080 if i is None:
1079 1081 # some environments replace stdout without implementing isatty
1080 1082 # usually those are non-interactive
1081 1083 return self._isatty(self.fout)
1082 1084
1083 1085 return i
1084 1086
1085 1087 def _readline(self, prompt=''):
1086 1088 if self._isatty(self.fin):
1087 1089 try:
1088 1090 # magically add command line editing support, where
1089 1091 # available
1090 1092 import readline
1091 1093 # force demandimport to really load the module
1092 1094 readline.read_history_file
1093 1095 # windows sometimes raises something other than ImportError
1094 1096 except Exception:
1095 1097 pass
1096 1098
1097 1099 # call write() so output goes through subclassed implementation
1098 1100 # e.g. color extension on Windows
1099 1101 self.write(prompt, prompt=True)
1100 1102
1101 1103 # instead of trying to emulate raw_input, swap (self.fin,
1102 1104 # self.fout) with (sys.stdin, sys.stdout)
1103 1105 oldin = sys.stdin
1104 1106 oldout = sys.stdout
1105 1107 sys.stdin = self.fin
1106 1108 sys.stdout = self.fout
1107 1109 # prompt ' ' must exist; otherwise readline may delete entire line
1108 1110 # - http://bugs.python.org/issue12833
1109 1111 with self.timeblockedsection('stdio'):
1110 1112 line = raw_input(' ')
1111 1113 sys.stdin = oldin
1112 1114 sys.stdout = oldout
1113 1115
1114 1116 # When stdin is in binary mode on Windows, it can cause
1115 1117 # raw_input() to emit an extra trailing carriage return
1116 1118 if os.linesep == '\r\n' and line and line[-1] == '\r':
1117 1119 line = line[:-1]
1118 1120 return line
1119 1121
1120 1122 def prompt(self, msg, default="y"):
1121 1123 """Prompt user with msg, read response.
1122 1124 If ui is not interactive, the default is returned.
1123 1125 """
1124 1126 if not self.interactive():
1125 1127 self.write(msg, ' ', default or '', "\n")
1126 1128 return default
1127 1129 try:
1128 1130 r = self._readline(self.label(msg, 'ui.prompt'))
1129 1131 if not r:
1130 1132 r = default
1131 1133 if self.configbool('ui', 'promptecho'):
1132 1134 self.write(r, "\n")
1133 1135 return r
1134 1136 except EOFError:
1135 1137 raise error.ResponseExpected()
1136 1138
1137 1139 @staticmethod
1138 1140 def extractchoices(prompt):
1139 1141 """Extract prompt message and list of choices from specified prompt.
1140 1142
1141 1143 This returns tuple "(message, choices)", and "choices" is the
1142 1144 list of tuple "(response character, text without &)".
1143 1145
1144 1146 >>> ui.extractchoices("awake? $$ &Yes $$ &No")
1145 1147 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1146 1148 >>> ui.extractchoices("line\\nbreak? $$ &Yes $$ &No")
1147 1149 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1148 1150 >>> ui.extractchoices("want lots of $$money$$?$$Ye&s$$N&o")
1149 1151 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1150 1152 """
1151 1153
1152 1154 # Sadly, the prompt string may have been built with a filename
1153 1155 # containing "$$" so let's try to find the first valid-looking
1154 1156 # prompt to start parsing. Sadly, we also can't rely on
1155 1157 # choices containing spaces, ASCII, or basically anything
1156 1158 # except an ampersand followed by a character.
1157 1159 m = re.match(r'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1158 1160 msg = m.group(1)
1159 1161 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1160 1162 return (msg,
1161 1163 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
1162 1164 for s in choices])
1163 1165
1164 1166 def promptchoice(self, prompt, default=0):
1165 1167 """Prompt user with a message, read response, and ensure it matches
1166 1168 one of the provided choices. The prompt is formatted as follows:
1167 1169
1168 1170 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1169 1171
1170 1172 The index of the choice is returned. Responses are case
1171 1173 insensitive. If ui is not interactive, the default is
1172 1174 returned.
1173 1175 """
1174 1176
1175 1177 msg, choices = self.extractchoices(prompt)
1176 1178 resps = [r for r, t in choices]
1177 1179 while True:
1178 1180 r = self.prompt(msg, resps[default])
1179 1181 if r.lower() in resps:
1180 1182 return resps.index(r.lower())
1181 1183 self.write(_("unrecognized response\n"))
1182 1184
1183 1185 def getpass(self, prompt=None, default=None):
1184 1186 if not self.interactive():
1185 1187 return default
1186 1188 try:
1187 1189 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1188 1190 # disable getpass() only if explicitly specified. it's still valid
1189 1191 # to interact with tty even if fin is not a tty.
1190 1192 with self.timeblockedsection('stdio'):
1191 1193 if self.configbool('ui', 'nontty'):
1192 1194 l = self.fin.readline()
1193 1195 if not l:
1194 1196 raise EOFError
1195 1197 return l.rstrip('\n')
1196 1198 else:
1197 1199 return getpass.getpass('')
1198 1200 except EOFError:
1199 1201 raise error.ResponseExpected()
1200 1202 def status(self, *msg, **opts):
1201 1203 '''write status message to output (if ui.quiet is False)
1202 1204
1203 1205 This adds an output label of "ui.status".
1204 1206 '''
1205 1207 if not self.quiet:
1206 1208 opts['label'] = opts.get('label', '') + ' ui.status'
1207 1209 self.write(*msg, **opts)
1208 1210 def warn(self, *msg, **opts):
1209 1211 '''write warning message to output (stderr)
1210 1212
1211 1213 This adds an output label of "ui.warning".
1212 1214 '''
1213 1215 opts['label'] = opts.get('label', '') + ' ui.warning'
1214 1216 self.write_err(*msg, **opts)
1215 1217 def note(self, *msg, **opts):
1216 1218 '''write note to output (if ui.verbose is True)
1217 1219
1218 1220 This adds an output label of "ui.note".
1219 1221 '''
1220 1222 if self.verbose:
1221 1223 opts['label'] = opts.get('label', '') + ' ui.note'
1222 1224 self.write(*msg, **opts)
1223 1225 def debug(self, *msg, **opts):
1224 1226 '''write debug message to output (if ui.debugflag is True)
1225 1227
1226 1228 This adds an output label of "ui.debug".
1227 1229 '''
1228 1230 if self.debugflag:
1229 1231 opts['label'] = opts.get('label', '') + ' ui.debug'
1230 1232 self.write(*msg, **opts)
1231 1233
1232 1234 def edit(self, text, user, extra=None, editform=None, pending=None,
1233 1235 repopath=None):
1234 1236 extra_defaults = {
1235 1237 'prefix': 'editor',
1236 1238 'suffix': '.txt',
1237 1239 }
1238 1240 if extra is not None:
1239 1241 extra_defaults.update(extra)
1240 1242 extra = extra_defaults
1241 1243
1242 1244 rdir = None
1243 1245 if self.configbool('experimental', 'editortmpinhg'):
1244 1246 rdir = repopath
1245 1247 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1246 1248 suffix=extra['suffix'], text=True,
1247 1249 dir=rdir)
1248 1250 try:
1249 1251 f = os.fdopen(fd, pycompat.sysstr("w"))
1250 1252 f.write(text)
1251 1253 f.close()
1252 1254
1253 1255 environ = {'HGUSER': user}
1254 1256 if 'transplant_source' in extra:
1255 1257 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1256 1258 for label in ('intermediate-source', 'source', 'rebase_source'):
1257 1259 if label in extra:
1258 1260 environ.update({'HGREVISION': extra[label]})
1259 1261 break
1260 1262 if editform:
1261 1263 environ.update({'HGEDITFORM': editform})
1262 1264 if pending:
1263 1265 environ.update({'HG_PENDING': pending})
1264 1266
1265 1267 editor = self.geteditor()
1266 1268
1267 1269 self.system("%s \"%s\"" % (editor, name),
1268 1270 environ=environ,
1269 1271 onerr=error.Abort, errprefix=_("edit failed"),
1270 1272 blockedtag='editor')
1271 1273
1272 1274 f = open(name)
1273 1275 t = f.read()
1274 1276 f.close()
1275 1277 finally:
1276 1278 os.unlink(name)
1277 1279
1278 1280 return t
1279 1281
1280 1282 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1281 1283 blockedtag=None):
1282 1284 '''execute shell command with appropriate output stream. command
1283 1285 output will be redirected if fout is not stdout.
1284 1286
1285 1287 if command fails and onerr is None, return status, else raise onerr
1286 1288 object as exception.
1287 1289 '''
1288 1290 if blockedtag is None:
1289 1291 blockedtag = 'unknown_system_' + cmd.translate(None, _keepalnum)
1290 1292 out = self.fout
1291 1293 if any(s[1] for s in self._bufferstates):
1292 1294 out = self
1293 1295 with self.timeblockedsection(blockedtag):
1294 1296 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1295 1297 if rc and onerr:
1296 1298 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1297 1299 util.explainexit(rc)[0])
1298 1300 if errprefix:
1299 1301 errmsg = '%s: %s' % (errprefix, errmsg)
1300 1302 raise onerr(errmsg)
1301 1303 return rc
1302 1304
1303 1305 def _runsystem(self, cmd, environ, cwd, out):
1304 1306 """actually execute the given shell command (can be overridden by
1305 1307 extensions like chg)"""
1306 1308 return util.system(cmd, environ=environ, cwd=cwd, out=out)
1307 1309
1308 1310 def traceback(self, exc=None, force=False):
1309 1311 '''print exception traceback if traceback printing enabled or forced.
1310 1312 only to call in exception handler. returns true if traceback
1311 1313 printed.'''
1312 1314 if self.tracebackflag or force:
1313 1315 if exc is None:
1314 1316 exc = sys.exc_info()
1315 1317 cause = getattr(exc[1], 'cause', None)
1316 1318
1317 1319 if cause is not None:
1318 1320 causetb = traceback.format_tb(cause[2])
1319 1321 exctb = traceback.format_tb(exc[2])
1320 1322 exconly = traceback.format_exception_only(cause[0], cause[1])
1321 1323
1322 1324 # exclude frame where 'exc' was chained and rethrown from exctb
1323 1325 self.write_err('Traceback (most recent call last):\n',
1324 1326 ''.join(exctb[:-1]),
1325 1327 ''.join(causetb),
1326 1328 ''.join(exconly))
1327 1329 else:
1328 1330 output = traceback.format_exception(exc[0], exc[1], exc[2])
1329 1331 self.write_err(''.join(output))
1330 1332 return self.tracebackflag or force
1331 1333
1332 1334 def geteditor(self):
1333 1335 '''return editor to use'''
1334 1336 if pycompat.sysplatform == 'plan9':
1335 1337 # vi is the MIPS instruction simulator on Plan 9. We
1336 1338 # instead default to E to plumb commit messages to
1337 1339 # avoid confusion.
1338 1340 editor = 'E'
1339 1341 else:
1340 1342 editor = 'vi'
1341 1343 return (encoding.environ.get("HGEDITOR") or
1342 1344 self.config("ui", "editor") or
1343 1345 encoding.environ.get("VISUAL") or
1344 1346 encoding.environ.get("EDITOR", editor))
1345 1347
1346 1348 @util.propertycache
1347 1349 def _progbar(self):
1348 1350 """setup the progbar singleton to the ui object"""
1349 1351 if (self.quiet or self.debugflag
1350 1352 or self.configbool('progress', 'disable', False)
1351 1353 or not progress.shouldprint(self)):
1352 1354 return None
1353 1355 return getprogbar(self)
1354 1356
1355 1357 def _progclear(self):
1356 1358 """clear progress bar output if any. use it before any output"""
1357 1359 if '_progbar' not in vars(self): # nothing loaded yet
1358 1360 return
1359 1361 if self._progbar is not None and self._progbar.printed:
1360 1362 self._progbar.clear()
1361 1363
1362 1364 def progress(self, topic, pos, item="", unit="", total=None):
1363 1365 '''show a progress message
1364 1366
1365 1367 By default a textual progress bar will be displayed if an operation
1366 1368 takes too long. 'topic' is the current operation, 'item' is a
1367 1369 non-numeric marker of the current position (i.e. the currently
1368 1370 in-process file), 'pos' is the current numeric position (i.e.
1369 1371 revision, bytes, etc.), unit is a corresponding unit label,
1370 1372 and total is the highest expected pos.
1371 1373
1372 1374 Multiple nested topics may be active at a time.
1373 1375
1374 1376 All topics should be marked closed by setting pos to None at
1375 1377 termination.
1376 1378 '''
1377 1379 if self._progbar is not None:
1378 1380 self._progbar.progress(topic, pos, item=item, unit=unit,
1379 1381 total=total)
1380 1382 if pos is None or not self.configbool('progress', 'debug'):
1381 1383 return
1382 1384
1383 1385 if unit:
1384 1386 unit = ' ' + unit
1385 1387 if item:
1386 1388 item = ' ' + item
1387 1389
1388 1390 if total:
1389 1391 pct = 100.0 * pos / total
1390 1392 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
1391 1393 % (topic, item, pos, total, unit, pct))
1392 1394 else:
1393 1395 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
1394 1396
1395 1397 def log(self, service, *msg, **opts):
1396 1398 '''hook for logging facility extensions
1397 1399
1398 1400 service should be a readily-identifiable subsystem, which will
1399 1401 allow filtering.
1400 1402
1401 1403 *msg should be a newline-terminated format string to log, and
1402 1404 then any values to %-format into that format string.
1403 1405
1404 1406 **opts currently has no defined meanings.
1405 1407 '''
1406 1408
1407 1409 def label(self, msg, label):
1408 1410 '''style msg based on supplied label
1409 1411
1410 1412 If some color mode is enabled, this will add the necessary control
1411 1413 characters to apply such color. In addition, 'debug' color mode adds
1412 1414 markup showing which label affects a piece of text.
1413 1415
1414 1416 ui.write(s, 'label') is equivalent to
1415 1417 ui.write(ui.label(s, 'label')).
1416 1418 '''
1417 1419 if self._colormode is not None:
1418 1420 return color.colorlabel(self, msg, label)
1419 1421 return msg
1420 1422
1421 1423 def develwarn(self, msg, stacklevel=1, config=None):
1422 1424 """issue a developer warning message
1423 1425
1424 1426 Use 'stacklevel' to report the offender some layers further up in the
1425 1427 stack.
1426 1428 """
1427 1429 if not self.configbool('devel', 'all-warnings'):
1428 1430 if config is not None and not self.configbool('devel', config):
1429 1431 return
1430 1432 msg = 'devel-warn: ' + msg
1431 1433 stacklevel += 1 # get in develwarn
1432 1434 if self.tracebackflag:
1433 1435 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1434 1436 self.log('develwarn', '%s at:\n%s' %
1435 1437 (msg, ''.join(util.getstackframes(stacklevel))))
1436 1438 else:
1437 1439 curframe = inspect.currentframe()
1438 1440 calframe = inspect.getouterframes(curframe, 2)
1439 1441 self.write_err('%s at: %s:%s (%s)\n'
1440 1442 % ((msg,) + calframe[stacklevel][1:4]))
1441 1443 self.log('develwarn', '%s at: %s:%s (%s)\n',
1442 1444 msg, *calframe[stacklevel][1:4])
1443 1445 curframe = calframe = None # avoid cycles
1444 1446
1445 1447 def deprecwarn(self, msg, version):
1446 1448 """issue a deprecation warning
1447 1449
1448 1450 - msg: message explaining what is deprecated and how to upgrade,
1449 1451 - version: last version where the API will be supported,
1450 1452 """
1451 1453 if not (self.configbool('devel', 'all-warnings')
1452 1454 or self.configbool('devel', 'deprec-warn')):
1453 1455 return
1454 1456 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1455 1457 " update your code.)") % version
1456 1458 self.develwarn(msg, stacklevel=2, config='deprec-warn')
1457 1459
1458 1460 def exportableenviron(self):
1459 1461 """The environment variables that are safe to export, e.g. through
1460 1462 hgweb.
1461 1463 """
1462 1464 return self._exportableenviron
1463 1465
1464 1466 @contextlib.contextmanager
1465 1467 def configoverride(self, overrides, source=""):
1466 1468 """Context manager for temporary config overrides
1467 1469 `overrides` must be a dict of the following structure:
1468 1470 {(section, name) : value}"""
1469 1471 backups = {}
1470 1472 try:
1471 1473 for (section, name), value in overrides.items():
1472 1474 backups[(section, name)] = self.backupconfig(section, name)
1473 1475 self.setconfig(section, name, value, source)
1474 1476 yield
1475 1477 finally:
1476 1478 for __, backup in backups.items():
1477 1479 self.restoreconfig(backup)
1478 1480 # just restoring ui.quiet config to the previous value is not enough
1479 1481 # as it does not update ui.quiet class member
1480 1482 if ('ui', 'quiet') in overrides:
1481 1483 self.fixconfig(section='ui')
1482 1484
1483 1485 class paths(dict):
1484 1486 """Represents a collection of paths and their configs.
1485 1487
1486 1488 Data is initially derived from ui instances and the config files they have
1487 1489 loaded.
1488 1490 """
1489 1491 def __init__(self, ui):
1490 1492 dict.__init__(self)
1491 1493
1492 1494 for name, loc in ui.configitems('paths', ignoresub=True):
1493 1495 # No location is the same as not existing.
1494 1496 if not loc:
1495 1497 continue
1496 1498 loc, sub = ui.configsuboptions('paths', name)
1497 1499 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1498 1500
1499 1501 def getpath(self, name, default=None):
1500 1502 """Return a ``path`` from a string, falling back to default.
1501 1503
1502 1504 ``name`` can be a named path or locations. Locations are filesystem
1503 1505 paths or URIs.
1504 1506
1505 1507 Returns None if ``name`` is not a registered path, a URI, or a local
1506 1508 path to a repo.
1507 1509 """
1508 1510 # Only fall back to default if no path was requested.
1509 1511 if name is None:
1510 1512 if not default:
1511 1513 default = ()
1512 1514 elif not isinstance(default, (tuple, list)):
1513 1515 default = (default,)
1514 1516 for k in default:
1515 1517 try:
1516 1518 return self[k]
1517 1519 except KeyError:
1518 1520 continue
1519 1521 return None
1520 1522
1521 1523 # Most likely empty string.
1522 1524 # This may need to raise in the future.
1523 1525 if not name:
1524 1526 return None
1525 1527
1526 1528 try:
1527 1529 return self[name]
1528 1530 except KeyError:
1529 1531 # Try to resolve as a local path or URI.
1530 1532 try:
1531 1533 # We don't pass sub-options in, so no need to pass ui instance.
1532 1534 return path(None, None, rawloc=name)
1533 1535 except ValueError:
1534 1536 raise error.RepoError(_('repository %s does not exist') %
1535 1537 name)
1536 1538
1537 1539 _pathsuboptions = {}
1538 1540
1539 1541 def pathsuboption(option, attr):
1540 1542 """Decorator used to declare a path sub-option.
1541 1543
1542 1544 Arguments are the sub-option name and the attribute it should set on
1543 1545 ``path`` instances.
1544 1546
1545 1547 The decorated function will receive as arguments a ``ui`` instance,
1546 1548 ``path`` instance, and the string value of this option from the config.
1547 1549 The function should return the value that will be set on the ``path``
1548 1550 instance.
1549 1551
1550 1552 This decorator can be used to perform additional verification of
1551 1553 sub-options and to change the type of sub-options.
1552 1554 """
1553 1555 def register(func):
1554 1556 _pathsuboptions[option] = (attr, func)
1555 1557 return func
1556 1558 return register
1557 1559
1558 1560 @pathsuboption('pushurl', 'pushloc')
1559 1561 def pushurlpathoption(ui, path, value):
1560 1562 u = util.url(value)
1561 1563 # Actually require a URL.
1562 1564 if not u.scheme:
1563 1565 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1564 1566 return None
1565 1567
1566 1568 # Don't support the #foo syntax in the push URL to declare branch to
1567 1569 # push.
1568 1570 if u.fragment:
1569 1571 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1570 1572 'ignoring)\n') % path.name)
1571 1573 u.fragment = None
1572 1574
1573 1575 return str(u)
1574 1576
1575 1577 @pathsuboption('pushrev', 'pushrev')
1576 1578 def pushrevpathoption(ui, path, value):
1577 1579 return value
1578 1580
1579 1581 class path(object):
1580 1582 """Represents an individual path and its configuration."""
1581 1583
1582 1584 def __init__(self, ui, name, rawloc=None, suboptions=None):
1583 1585 """Construct a path from its config options.
1584 1586
1585 1587 ``ui`` is the ``ui`` instance the path is coming from.
1586 1588 ``name`` is the symbolic name of the path.
1587 1589 ``rawloc`` is the raw location, as defined in the config.
1588 1590 ``pushloc`` is the raw locations pushes should be made to.
1589 1591
1590 1592 If ``name`` is not defined, we require that the location be a) a local
1591 1593 filesystem path with a .hg directory or b) a URL. If not,
1592 1594 ``ValueError`` is raised.
1593 1595 """
1594 1596 if not rawloc:
1595 1597 raise ValueError('rawloc must be defined')
1596 1598
1597 1599 # Locations may define branches via syntax <base>#<branch>.
1598 1600 u = util.url(rawloc)
1599 1601 branch = None
1600 1602 if u.fragment:
1601 1603 branch = u.fragment
1602 1604 u.fragment = None
1603 1605
1604 1606 self.url = u
1605 1607 self.branch = branch
1606 1608
1607 1609 self.name = name
1608 1610 self.rawloc = rawloc
1609 1611 self.loc = str(u)
1610 1612
1611 1613 # When given a raw location but not a symbolic name, validate the
1612 1614 # location is valid.
1613 1615 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1614 1616 raise ValueError('location is not a URL or path to a local '
1615 1617 'repo: %s' % rawloc)
1616 1618
1617 1619 suboptions = suboptions or {}
1618 1620
1619 1621 # Now process the sub-options. If a sub-option is registered, its
1620 1622 # attribute will always be present. The value will be None if there
1621 1623 # was no valid sub-option.
1622 1624 for suboption, (attr, func) in _pathsuboptions.iteritems():
1623 1625 if suboption not in suboptions:
1624 1626 setattr(self, attr, None)
1625 1627 continue
1626 1628
1627 1629 value = func(ui, self, suboptions[suboption])
1628 1630 setattr(self, attr, value)
1629 1631
1630 1632 def _isvalidlocalpath(self, path):
1631 1633 """Returns True if the given path is a potentially valid repository.
1632 1634 This is its own function so that extensions can change the definition of
1633 1635 'valid' in this case (like when pulling from a git repo into a hg
1634 1636 one)."""
1635 1637 return os.path.isdir(os.path.join(path, '.hg'))
1636 1638
1637 1639 @property
1638 1640 def suboptions(self):
1639 1641 """Return sub-options and their values for this path.
1640 1642
1641 1643 This is intended to be used for presentation purposes.
1642 1644 """
1643 1645 d = {}
1644 1646 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1645 1647 value = getattr(self, attr)
1646 1648 if value is not None:
1647 1649 d[subopt] = value
1648 1650 return d
1649 1651
1650 1652 # we instantiate one globally shared progress bar to avoid
1651 1653 # competing progress bars when multiple UI objects get created
1652 1654 _progresssingleton = None
1653 1655
1654 1656 def getprogbar(ui):
1655 1657 global _progresssingleton
1656 1658 if _progresssingleton is None:
1657 1659 # passing 'ui' object to the singleton is fishy,
1658 1660 # this is how the extension used to work but feel free to rework it.
1659 1661 _progresssingleton = progress.progbar(ui)
1660 1662 return _progresssingleton
General Comments 0
You need to be logged in to leave comments. Login now