##// END OF EJS Templates
py3: fix str vs bytes in enough places to run `hg version` on Windows...
Matt Harbison -
r39680:3b421154 default
parent child Browse files
Show More
@@ -1,537 +1,537
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 import re
11 11
12 12 from .i18n import _
13 13
14 14 from . import (
15 15 encoding,
16 16 pycompat,
17 17 )
18 18
19 19 from .utils import (
20 20 stringutil,
21 21 )
22 22
23 23 try:
24 24 import curses
25 25 # Mapping from effect name to terminfo attribute name (or raw code) or
26 26 # color number. This will also force-load the curses module.
27 27 _baseterminfoparams = {
28 28 'none': (True, 'sgr0', ''),
29 29 'standout': (True, 'smso', ''),
30 30 'underline': (True, 'smul', ''),
31 31 'reverse': (True, 'rev', ''),
32 32 'inverse': (True, 'rev', ''),
33 33 'blink': (True, 'blink', ''),
34 34 'dim': (True, 'dim', ''),
35 35 'bold': (True, 'bold', ''),
36 36 'invisible': (True, 'invis', ''),
37 37 'italic': (True, 'sitm', ''),
38 38 'black': (False, curses.COLOR_BLACK, ''),
39 39 'red': (False, curses.COLOR_RED, ''),
40 40 'green': (False, curses.COLOR_GREEN, ''),
41 41 'yellow': (False, curses.COLOR_YELLOW, ''),
42 42 'blue': (False, curses.COLOR_BLUE, ''),
43 43 'magenta': (False, curses.COLOR_MAGENTA, ''),
44 44 'cyan': (False, curses.COLOR_CYAN, ''),
45 45 'white': (False, curses.COLOR_WHITE, ''),
46 46 }
47 47 except ImportError:
48 48 curses = None
49 49 _baseterminfoparams = {}
50 50
51 51 # start and stop parameters for effects
52 52 _effects = {
53 53 'none': 0,
54 54 'black': 30,
55 55 'red': 31,
56 56 'green': 32,
57 57 'yellow': 33,
58 58 'blue': 34,
59 59 'magenta': 35,
60 60 'cyan': 36,
61 61 'white': 37,
62 62 'bold': 1,
63 63 'italic': 3,
64 64 'underline': 4,
65 65 'inverse': 7,
66 66 'dim': 2,
67 67 'black_background': 40,
68 68 'red_background': 41,
69 69 'green_background': 42,
70 70 'yellow_background': 43,
71 71 'blue_background': 44,
72 72 'purple_background': 45,
73 73 'cyan_background': 46,
74 74 'white_background': 47,
75 75 }
76 76
77 77 _defaultstyles = {
78 78 'grep.match': 'red bold',
79 79 'grep.linenumber': 'green',
80 80 'grep.rev': 'green',
81 81 'grep.change': 'green',
82 82 'grep.sep': 'cyan',
83 83 'grep.filename': 'magenta',
84 84 'grep.user': 'magenta',
85 85 'grep.date': 'magenta',
86 86 'addremove.added': 'green',
87 87 'addremove.removed': 'red',
88 88 'bookmarks.active': 'green',
89 89 'branches.active': 'none',
90 90 'branches.closed': 'black bold',
91 91 'branches.current': 'green',
92 92 'branches.inactive': 'none',
93 93 'diff.changed': 'white',
94 94 'diff.deleted': 'red',
95 95 'diff.deleted.changed': 'red bold underline',
96 96 'diff.deleted.unchanged': 'red',
97 97 'diff.diffline': 'bold',
98 98 'diff.extended': 'cyan bold',
99 99 'diff.file_a': 'red bold',
100 100 'diff.file_b': 'green bold',
101 101 'diff.hunk': 'magenta',
102 102 'diff.inserted': 'green',
103 103 'diff.inserted.changed': 'green bold underline',
104 104 'diff.inserted.unchanged': 'green',
105 105 'diff.tab': '',
106 106 'diff.trailingwhitespace': 'bold red_background',
107 107 'changeset.public': '',
108 108 'changeset.draft': '',
109 109 'changeset.secret': '',
110 110 'diffstat.deleted': 'red',
111 111 'diffstat.inserted': 'green',
112 112 'formatvariant.name.mismatchconfig': 'red',
113 113 'formatvariant.name.mismatchdefault': 'yellow',
114 114 'formatvariant.name.uptodate': 'green',
115 115 'formatvariant.repo.mismatchconfig': 'red',
116 116 'formatvariant.repo.mismatchdefault': 'yellow',
117 117 'formatvariant.repo.uptodate': 'green',
118 118 'formatvariant.config.special': 'yellow',
119 119 'formatvariant.config.default': 'green',
120 120 'formatvariant.default': '',
121 121 'histedit.remaining': 'red bold',
122 122 'ui.error': 'red',
123 123 'ui.prompt': 'yellow',
124 124 'log.changeset': 'yellow',
125 125 'patchbomb.finalsummary': '',
126 126 'patchbomb.from': 'magenta',
127 127 'patchbomb.to': 'cyan',
128 128 'patchbomb.subject': 'green',
129 129 'patchbomb.diffstats': '',
130 130 'rebase.rebased': 'blue',
131 131 'rebase.remaining': 'red bold',
132 132 'resolve.resolved': 'green bold',
133 133 'resolve.unresolved': 'red bold',
134 134 'shelve.age': 'cyan',
135 135 'shelve.newest': 'green bold',
136 136 'shelve.name': 'blue bold',
137 137 'status.added': 'green bold',
138 138 'status.clean': 'none',
139 139 'status.copied': 'none',
140 140 'status.deleted': 'cyan bold underline',
141 141 'status.ignored': 'black bold',
142 142 'status.modified': 'blue bold',
143 143 'status.removed': 'red bold',
144 144 'status.unknown': 'magenta bold underline',
145 145 'tags.normal': 'green',
146 146 'tags.local': 'black bold',
147 147 }
148 148
149 149 def loadcolortable(ui, extname, colortable):
150 150 _defaultstyles.update(colortable)
151 151
152 152 def _terminfosetup(ui, mode, formatted):
153 153 '''Initialize terminfo data and the terminal if we're in terminfo mode.'''
154 154
155 155 # If we failed to load curses, we go ahead and return.
156 156 if curses is None:
157 157 return
158 158 # Otherwise, see what the config file says.
159 159 if mode not in ('auto', 'terminfo'):
160 160 return
161 161 ui._terminfoparams.update(_baseterminfoparams)
162 162
163 163 for key, val in ui.configitems('color'):
164 164 if key.startswith('color.'):
165 165 newval = (False, int(val), '')
166 166 ui._terminfoparams[key[6:]] = newval
167 167 elif key.startswith('terminfo.'):
168 168 newval = (True, '', val.replace('\\E', '\x1b'))
169 169 ui._terminfoparams[key[9:]] = newval
170 170 try:
171 171 curses.setupterm()
172 172 except curses.error as e:
173 173 ui._terminfoparams.clear()
174 174 return
175 175
176 176 for key, (b, e, c) in ui._terminfoparams.copy().items():
177 177 if not b:
178 178 continue
179 179 if not c and not curses.tigetstr(pycompat.sysstr(e)):
180 180 # Most terminals don't support dim, invis, etc, so don't be
181 181 # noisy and use ui.debug().
182 182 ui.debug("no terminfo entry for %s\n" % e)
183 183 del ui._terminfoparams[key]
184 184 if not curses.tigetstr(r'setaf') or not curses.tigetstr(r'setab'):
185 185 # Only warn about missing terminfo entries if we explicitly asked for
186 186 # terminfo mode and we're in a formatted terminal.
187 187 if mode == "terminfo" and formatted:
188 188 ui.warn(_("no terminfo entry for setab/setaf: reverting to "
189 189 "ECMA-48 color\n"))
190 190 ui._terminfoparams.clear()
191 191
192 192 def setup(ui):
193 193 """configure color on a ui
194 194
195 195 That function both set the colormode for the ui object and read
196 196 the configuration looking for custom colors and effect definitions."""
197 197 mode = _modesetup(ui)
198 198 ui._colormode = mode
199 199 if mode and mode != 'debug':
200 200 configstyles(ui)
201 201
202 202 def _modesetup(ui):
203 203 if ui.plain('color'):
204 204 return None
205 205 config = ui.config('ui', 'color')
206 206 if config == 'debug':
207 207 return 'debug'
208 208
209 209 auto = (config == 'auto')
210 210 always = False
211 211 if not auto and stringutil.parsebool(config):
212 212 # We want the config to behave like a boolean, "on" is actually auto,
213 213 # but "always" value is treated as a special case to reduce confusion.
214 214 if ui.configsource('ui', 'color') == '--color' or config == 'always':
215 215 always = True
216 216 else:
217 217 auto = True
218 218
219 219 if not always and not auto:
220 220 return None
221 221
222 222 formatted = (always or (encoding.environ.get('TERM') != 'dumb'
223 223 and ui.formatted()))
224 224
225 225 mode = ui.config('color', 'mode')
226 226
227 227 # If pager is active, color.pagermode overrides color.mode.
228 228 if getattr(ui, 'pageractive', False):
229 229 mode = ui.config('color', 'pagermode', mode)
230 230
231 231 realmode = mode
232 232 if pycompat.iswindows:
233 233 from . import win32
234 234
235 235 term = encoding.environ.get('TERM')
236 236 # TERM won't be defined in a vanilla cmd.exe environment.
237 237
238 238 # UNIX-like environments on Windows such as Cygwin and MSYS will
239 239 # set TERM. They appear to make a best effort attempt at setting it
240 240 # to something appropriate. However, not all environments with TERM
241 241 # defined support ANSI.
242 242 ansienviron = term and 'xterm' in term
243 243
244 244 if mode == 'auto':
245 245 # Since "ansi" could result in terminal gibberish, we error on the
246 246 # side of selecting "win32". However, if w32effects is not defined,
247 247 # we almost certainly don't support "win32", so don't even try.
248 248 # w32ffects is not populated when stdout is redirected, so checking
249 249 # it first avoids win32 calls in a state known to error out.
250 250 if ansienviron or not w32effects or win32.enablevtmode():
251 251 realmode = 'ansi'
252 252 else:
253 253 realmode = 'win32'
254 254 # An empty w32effects is a clue that stdout is redirected, and thus
255 255 # cannot enable VT mode.
256 256 elif mode == 'ansi' and w32effects and not ansienviron:
257 257 win32.enablevtmode()
258 258 elif mode == 'auto':
259 259 realmode = 'ansi'
260 260
261 261 def modewarn():
262 262 # only warn if color.mode was explicitly set and we're in
263 263 # a formatted terminal
264 264 if mode == realmode and formatted:
265 265 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
266 266
267 267 if realmode == 'win32':
268 268 ui._terminfoparams.clear()
269 269 if not w32effects:
270 270 modewarn()
271 271 return None
272 272 elif realmode == 'ansi':
273 273 ui._terminfoparams.clear()
274 274 elif realmode == 'terminfo':
275 275 _terminfosetup(ui, mode, formatted)
276 276 if not ui._terminfoparams:
277 277 ## FIXME Shouldn't we return None in this case too?
278 278 modewarn()
279 279 realmode = 'ansi'
280 280 else:
281 281 return None
282 282
283 283 if always or (auto and formatted):
284 284 return realmode
285 285 return None
286 286
287 287 def configstyles(ui):
288 288 ui._styles.update(_defaultstyles)
289 289 for status, cfgeffects in ui.configitems('color'):
290 290 if '.' not in status or status.startswith(('color.', 'terminfo.')):
291 291 continue
292 292 cfgeffects = ui.configlist('color', status)
293 293 if cfgeffects:
294 294 good = []
295 295 for e in cfgeffects:
296 296 if valideffect(ui, e):
297 297 good.append(e)
298 298 else:
299 299 ui.warn(_("ignoring unknown color/effect %r "
300 300 "(configured in color.%s)\n")
301 301 % (e, status))
302 302 ui._styles[status] = ' '.join(good)
303 303
304 304 def _activeeffects(ui):
305 305 '''Return the effects map for the color mode set on the ui.'''
306 306 if ui._colormode == 'win32':
307 307 return w32effects
308 308 elif ui._colormode is not None:
309 309 return _effects
310 310 return {}
311 311
312 312 def valideffect(ui, effect):
313 313 'Determine if the effect is valid or not.'
314 314 return ((not ui._terminfoparams and effect in _activeeffects(ui))
315 315 or (effect in ui._terminfoparams
316 316 or effect[:-11] in ui._terminfoparams))
317 317
318 318 def _effect_str(ui, effect):
319 319 '''Helper function for render_effects().'''
320 320
321 321 bg = False
322 322 if effect.endswith('_background'):
323 323 bg = True
324 324 effect = effect[:-11]
325 325 try:
326 326 attr, val, termcode = ui._terminfoparams[effect]
327 327 except KeyError:
328 328 return ''
329 329 if attr:
330 330 if termcode:
331 331 return termcode
332 332 else:
333 333 return curses.tigetstr(pycompat.sysstr(val))
334 334 elif bg:
335 335 return curses.tparm(curses.tigetstr(r'setab'), val)
336 336 else:
337 337 return curses.tparm(curses.tigetstr(r'setaf'), val)
338 338
339 339 def _mergeeffects(text, start, stop):
340 340 """Insert start sequence at every occurrence of stop sequence
341 341
342 342 >>> s = _mergeeffects(b'cyan', b'[C]', b'|')
343 343 >>> s = _mergeeffects(s + b'yellow', b'[Y]', b'|')
344 344 >>> s = _mergeeffects(b'ma' + s + b'genta', b'[M]', b'|')
345 345 >>> s = _mergeeffects(b'red' + s, b'[R]', b'|')
346 346 >>> s
347 347 '[R]red[M]ma[Y][C]cyan|[R][M][Y]yellow|[R][M]genta|'
348 348 """
349 349 parts = []
350 350 for t in text.split(stop):
351 351 if not t:
352 352 continue
353 353 parts.extend([start, t, stop])
354 354 return ''.join(parts)
355 355
356 356 def _render_effects(ui, text, effects):
357 357 'Wrap text in commands to turn on each effect.'
358 358 if not text:
359 359 return text
360 360 if ui._terminfoparams:
361 361 start = ''.join(_effect_str(ui, effect)
362 362 for effect in ['none'] + effects.split())
363 363 stop = _effect_str(ui, 'none')
364 364 else:
365 365 activeeffects = _activeeffects(ui)
366 366 start = [pycompat.bytestr(activeeffects[e])
367 367 for e in ['none'] + effects.split()]
368 368 start = '\033[' + ';'.join(start) + 'm'
369 369 stop = '\033[' + pycompat.bytestr(activeeffects['none']) + 'm'
370 370 return _mergeeffects(text, start, stop)
371 371
372 372 _ansieffectre = re.compile(br'\x1b\[[0-9;]*m')
373 373
374 374 def stripeffects(text):
375 375 """Strip ANSI control codes which could be inserted by colorlabel()"""
376 376 return _ansieffectre.sub('', text)
377 377
378 378 def colorlabel(ui, msg, label):
379 379 """add color control code according to the mode"""
380 380 if ui._colormode == 'debug':
381 381 if label and msg:
382 382 if msg.endswith('\n'):
383 383 msg = "[%s|%s]\n" % (label, msg[:-1])
384 384 else:
385 385 msg = "[%s|%s]" % (label, msg)
386 386 elif ui._colormode is not None:
387 387 effects = []
388 388 for l in label.split():
389 389 s = ui._styles.get(l, '')
390 390 if s:
391 391 effects.append(s)
392 392 elif valideffect(ui, l):
393 393 effects.append(l)
394 394 effects = ' '.join(effects)
395 395 if effects:
396 396 msg = '\n'.join([_render_effects(ui, line, effects)
397 397 for line in msg.split('\n')])
398 398 return msg
399 399
400 400 w32effects = None
401 401 if pycompat.iswindows:
402 402 import ctypes
403 403
404 404 _kernel32 = ctypes.windll.kernel32
405 405
406 406 _WORD = ctypes.c_ushort
407 407
408 408 _INVALID_HANDLE_VALUE = -1
409 409
410 410 class _COORD(ctypes.Structure):
411 _fields_ = [('X', ctypes.c_short),
412 ('Y', ctypes.c_short)]
411 _fields_ = [(r'X', ctypes.c_short),
412 (r'Y', ctypes.c_short)]
413 413
414 414 class _SMALL_RECT(ctypes.Structure):
415 _fields_ = [('Left', ctypes.c_short),
416 ('Top', ctypes.c_short),
417 ('Right', ctypes.c_short),
418 ('Bottom', ctypes.c_short)]
415 _fields_ = [(r'Left', ctypes.c_short),
416 (r'Top', ctypes.c_short),
417 (r'Right', ctypes.c_short),
418 (r'Bottom', ctypes.c_short)]
419 419
420 420 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
421 _fields_ = [('dwSize', _COORD),
422 ('dwCursorPosition', _COORD),
423 ('wAttributes', _WORD),
424 ('srWindow', _SMALL_RECT),
425 ('dwMaximumWindowSize', _COORD)]
421 _fields_ = [(r'dwSize', _COORD),
422 (r'dwCursorPosition', _COORD),
423 (r'wAttributes', _WORD),
424 (r'srWindow', _SMALL_RECT),
425 (r'dwMaximumWindowSize', _COORD)]
426 426
427 427 _STD_OUTPUT_HANDLE = 0xfffffff5 # (DWORD)-11
428 428 _STD_ERROR_HANDLE = 0xfffffff4 # (DWORD)-12
429 429
430 430 _FOREGROUND_BLUE = 0x0001
431 431 _FOREGROUND_GREEN = 0x0002
432 432 _FOREGROUND_RED = 0x0004
433 433 _FOREGROUND_INTENSITY = 0x0008
434 434
435 435 _BACKGROUND_BLUE = 0x0010
436 436 _BACKGROUND_GREEN = 0x0020
437 437 _BACKGROUND_RED = 0x0040
438 438 _BACKGROUND_INTENSITY = 0x0080
439 439
440 440 _COMMON_LVB_REVERSE_VIDEO = 0x4000
441 441 _COMMON_LVB_UNDERSCORE = 0x8000
442 442
443 443 # http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx
444 444 w32effects = {
445 445 'none': -1,
446 446 'black': 0,
447 447 'red': _FOREGROUND_RED,
448 448 'green': _FOREGROUND_GREEN,
449 449 'yellow': _FOREGROUND_RED | _FOREGROUND_GREEN,
450 450 'blue': _FOREGROUND_BLUE,
451 451 'magenta': _FOREGROUND_BLUE | _FOREGROUND_RED,
452 452 'cyan': _FOREGROUND_BLUE | _FOREGROUND_GREEN,
453 453 'white': _FOREGROUND_RED | _FOREGROUND_GREEN | _FOREGROUND_BLUE,
454 454 'bold': _FOREGROUND_INTENSITY,
455 455 'black_background': 0x100, # unused value > 0x0f
456 456 'red_background': _BACKGROUND_RED,
457 457 'green_background': _BACKGROUND_GREEN,
458 458 'yellow_background': _BACKGROUND_RED | _BACKGROUND_GREEN,
459 459 'blue_background': _BACKGROUND_BLUE,
460 460 'purple_background': _BACKGROUND_BLUE | _BACKGROUND_RED,
461 461 'cyan_background': _BACKGROUND_BLUE | _BACKGROUND_GREEN,
462 462 'white_background': (_BACKGROUND_RED | _BACKGROUND_GREEN |
463 463 _BACKGROUND_BLUE),
464 464 'bold_background': _BACKGROUND_INTENSITY,
465 465 'underline': _COMMON_LVB_UNDERSCORE, # double-byte charsets only
466 466 'inverse': _COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only
467 467 }
468 468
469 469 passthrough = {_FOREGROUND_INTENSITY,
470 470 _BACKGROUND_INTENSITY,
471 471 _COMMON_LVB_UNDERSCORE,
472 472 _COMMON_LVB_REVERSE_VIDEO}
473 473
474 474 stdout = _kernel32.GetStdHandle(
475 475 _STD_OUTPUT_HANDLE) # don't close the handle returned
476 476 if stdout is None or stdout == _INVALID_HANDLE_VALUE:
477 477 w32effects = None
478 478 else:
479 479 csbi = _CONSOLE_SCREEN_BUFFER_INFO()
480 480 if not _kernel32.GetConsoleScreenBufferInfo(
481 481 stdout, ctypes.byref(csbi)):
482 482 # stdout may not support GetConsoleScreenBufferInfo()
483 483 # when called from subprocess or redirected
484 484 w32effects = None
485 485 else:
486 486 origattr = csbi.wAttributes
487 ansire = re.compile('\033\[([^m]*)m([^\033]*)(.*)',
487 ansire = re.compile(b'\033\[([^m]*)m([^\033]*)(.*)',
488 488 re.MULTILINE | re.DOTALL)
489 489
490 490 def win32print(ui, writefunc, *msgs, **opts):
491 491 for text in msgs:
492 492 _win32print(ui, text, writefunc, **opts)
493 493
494 494 def _win32print(ui, text, writefunc, **opts):
495 495 label = opts.get(r'label', '')
496 496 attr = origattr
497 497
498 498 def mapcolor(val, attr):
499 499 if val == -1:
500 500 return origattr
501 501 elif val in passthrough:
502 502 return attr | val
503 503 elif val > 0x0f:
504 504 return (val & 0x70) | (attr & 0x8f)
505 505 else:
506 506 return (val & 0x07) | (attr & 0xf8)
507 507
508 508 # determine console attributes based on labels
509 509 for l in label.split():
510 510 style = ui._styles.get(l, '')
511 511 for effect in style.split():
512 512 try:
513 513 attr = mapcolor(w32effects[effect], attr)
514 514 except KeyError:
515 515 # w32effects could not have certain attributes so we skip
516 516 # them if not found
517 517 pass
518 518 # hack to ensure regexp finds data
519 if not text.startswith('\033['):
520 text = '\033[m' + text
519 if not text.startswith(b'\033['):
520 text = b'\033[m' + text
521 521
522 522 # Look for ANSI-like codes embedded in text
523 523 m = re.match(ansire, text)
524 524
525 525 try:
526 526 while m:
527 for sattr in m.group(1).split(';'):
527 for sattr in m.group(1).split(b';'):
528 528 if sattr:
529 529 attr = mapcolor(int(sattr), attr)
530 530 ui.flush()
531 531 _kernel32.SetConsoleTextAttribute(stdout, attr)
532 532 writefunc(m.group(2), **opts)
533 533 m = re.match(ansire, m.group(3))
534 534 finally:
535 535 # Explicitly reset original attributes
536 536 ui.flush()
537 537 _kernel32.SetConsoleTextAttribute(stdout, origattr)
@@ -1,271 +1,273
1 1 # osutil.py - pure Python version of osutil.c
2 2 #
3 3 # Copyright 2009 Matt Mackall <mpm@selenic.com> and others
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 ctypes
11 11 import ctypes.util
12 12 import os
13 13 import socket
14 14 import stat as statmod
15 15
16 16 from .. import (
17 encoding,
17 18 pycompat,
18 19 )
19 20
20 21 def _mode_to_kind(mode):
21 22 if statmod.S_ISREG(mode):
22 23 return statmod.S_IFREG
23 24 if statmod.S_ISDIR(mode):
24 25 return statmod.S_IFDIR
25 26 if statmod.S_ISLNK(mode):
26 27 return statmod.S_IFLNK
27 28 if statmod.S_ISBLK(mode):
28 29 return statmod.S_IFBLK
29 30 if statmod.S_ISCHR(mode):
30 31 return statmod.S_IFCHR
31 32 if statmod.S_ISFIFO(mode):
32 33 return statmod.S_IFIFO
33 34 if statmod.S_ISSOCK(mode):
34 35 return statmod.S_IFSOCK
35 36 return mode
36 37
37 38 def listdir(path, stat=False, skip=None):
38 39 '''listdir(path, stat=False) -> list_of_tuples
39 40
40 41 Return a sorted list containing information about the entries
41 42 in the directory.
42 43
43 44 If stat is True, each element is a 3-tuple:
44 45
45 46 (name, type, stat object)
46 47
47 48 Otherwise, each element is a 2-tuple:
48 49
49 50 (name, type)
50 51 '''
51 52 result = []
52 53 prefix = path
53 54 if not prefix.endswith(pycompat.ossep):
54 55 prefix += pycompat.ossep
55 56 names = os.listdir(path)
56 57 names.sort()
57 58 for fn in names:
58 59 st = os.lstat(prefix + fn)
59 60 if fn == skip and statmod.S_ISDIR(st.st_mode):
60 61 return []
61 62 if stat:
62 63 result.append((fn, _mode_to_kind(st.st_mode), st))
63 64 else:
64 65 result.append((fn, _mode_to_kind(st.st_mode)))
65 66 return result
66 67
67 68 if not pycompat.iswindows:
68 69 posixfile = open
69 70
70 71 _SCM_RIGHTS = 0x01
71 72 _socklen_t = ctypes.c_uint
72 73
73 74 if pycompat.sysplatform.startswith('linux'):
74 75 # socket.h says "the type should be socklen_t but the definition of
75 76 # the kernel is incompatible with this."
76 77 _cmsg_len_t = ctypes.c_size_t
77 78 _msg_controllen_t = ctypes.c_size_t
78 79 _msg_iovlen_t = ctypes.c_size_t
79 80 else:
80 81 _cmsg_len_t = _socklen_t
81 82 _msg_controllen_t = _socklen_t
82 83 _msg_iovlen_t = ctypes.c_int
83 84
84 85 class _iovec(ctypes.Structure):
85 86 _fields_ = [
86 87 (u'iov_base', ctypes.c_void_p),
87 88 (u'iov_len', ctypes.c_size_t),
88 89 ]
89 90
90 91 class _msghdr(ctypes.Structure):
91 92 _fields_ = [
92 93 (u'msg_name', ctypes.c_void_p),
93 94 (u'msg_namelen', _socklen_t),
94 95 (u'msg_iov', ctypes.POINTER(_iovec)),
95 96 (u'msg_iovlen', _msg_iovlen_t),
96 97 (u'msg_control', ctypes.c_void_p),
97 98 (u'msg_controllen', _msg_controllen_t),
98 99 (u'msg_flags', ctypes.c_int),
99 100 ]
100 101
101 102 class _cmsghdr(ctypes.Structure):
102 103 _fields_ = [
103 104 (u'cmsg_len', _cmsg_len_t),
104 105 (u'cmsg_level', ctypes.c_int),
105 106 (u'cmsg_type', ctypes.c_int),
106 107 (u'cmsg_data', ctypes.c_ubyte * 0),
107 108 ]
108 109
109 110 _libc = ctypes.CDLL(ctypes.util.find_library(u'c'), use_errno=True)
110 111 _recvmsg = getattr(_libc, 'recvmsg', None)
111 112 if _recvmsg:
112 113 _recvmsg.restype = getattr(ctypes, 'c_ssize_t', ctypes.c_long)
113 114 _recvmsg.argtypes = (ctypes.c_int, ctypes.POINTER(_msghdr),
114 115 ctypes.c_int)
115 116 else:
116 117 # recvmsg isn't always provided by libc; such systems are unsupported
117 118 def _recvmsg(sockfd, msg, flags):
118 119 raise NotImplementedError('unsupported platform')
119 120
120 121 def _CMSG_FIRSTHDR(msgh):
121 122 if msgh.msg_controllen < ctypes.sizeof(_cmsghdr):
122 123 return
123 124 cmsgptr = ctypes.cast(msgh.msg_control, ctypes.POINTER(_cmsghdr))
124 125 return cmsgptr.contents
125 126
126 127 # The pure version is less portable than the native version because the
127 128 # handling of socket ancillary data heavily depends on C preprocessor.
128 129 # Also, some length fields are wrongly typed in Linux kernel.
129 130 def recvfds(sockfd):
130 131 """receive list of file descriptors via socket"""
131 132 dummy = (ctypes.c_ubyte * 1)()
132 133 iov = _iovec(ctypes.cast(dummy, ctypes.c_void_p), ctypes.sizeof(dummy))
133 134 cbuf = ctypes.create_string_buffer(256)
134 135 msgh = _msghdr(None, 0,
135 136 ctypes.pointer(iov), 1,
136 137 ctypes.cast(cbuf, ctypes.c_void_p), ctypes.sizeof(cbuf),
137 138 0)
138 139 r = _recvmsg(sockfd, ctypes.byref(msgh), 0)
139 140 if r < 0:
140 141 e = ctypes.get_errno()
141 142 raise OSError(e, os.strerror(e))
142 143 # assumes that the first cmsg has fds because it isn't easy to write
143 144 # portable CMSG_NXTHDR() with ctypes.
144 145 cmsg = _CMSG_FIRSTHDR(msgh)
145 146 if not cmsg:
146 147 return []
147 148 if (cmsg.cmsg_level != socket.SOL_SOCKET or
148 149 cmsg.cmsg_type != _SCM_RIGHTS):
149 150 return []
150 151 rfds = ctypes.cast(cmsg.cmsg_data, ctypes.POINTER(ctypes.c_int))
151 152 rfdscount = ((cmsg.cmsg_len - _cmsghdr.cmsg_data.offset) /
152 153 ctypes.sizeof(ctypes.c_int))
153 154 return [rfds[i] for i in pycompat.xrange(rfdscount)]
154 155
155 156 else:
156 157 import msvcrt
157 158
158 159 _kernel32 = ctypes.windll.kernel32
159 160
160 161 _DWORD = ctypes.c_ulong
161 162 _LPCSTR = _LPSTR = ctypes.c_char_p
162 163 _HANDLE = ctypes.c_void_p
163 164
164 165 _INVALID_HANDLE_VALUE = _HANDLE(-1).value
165 166
166 167 # CreateFile
167 168 _FILE_SHARE_READ = 0x00000001
168 169 _FILE_SHARE_WRITE = 0x00000002
169 170 _FILE_SHARE_DELETE = 0x00000004
170 171
171 172 _CREATE_ALWAYS = 2
172 173 _OPEN_EXISTING = 3
173 174 _OPEN_ALWAYS = 4
174 175
175 176 _GENERIC_READ = 0x80000000
176 177 _GENERIC_WRITE = 0x40000000
177 178
178 179 _FILE_ATTRIBUTE_NORMAL = 0x80
179 180
180 181 # open_osfhandle flags
181 182 _O_RDONLY = 0x0000
182 183 _O_RDWR = 0x0002
183 184 _O_APPEND = 0x0008
184 185
185 186 _O_TEXT = 0x4000
186 187 _O_BINARY = 0x8000
187 188
188 189 # types of parameters of C functions used (required by pypy)
189 190
190 191 _kernel32.CreateFileA.argtypes = [_LPCSTR, _DWORD, _DWORD, ctypes.c_void_p,
191 192 _DWORD, _DWORD, _HANDLE]
192 193 _kernel32.CreateFileA.restype = _HANDLE
193 194
194 195 def _raiseioerror(name):
195 196 err = ctypes.WinError()
196 raise IOError(err.errno, '%s: %s' % (name, err.strerror))
197 raise IOError(err.errno, r'%s: %s' % (encoding.strfromlocal(name),
198 err.strerror))
197 199
198 200 class posixfile(object):
199 201 '''a file object aiming for POSIX-like semantics
200 202
201 203 CPython's open() returns a file that was opened *without* setting the
202 204 _FILE_SHARE_DELETE flag, which causes rename and unlink to abort.
203 205 This even happens if any hardlinked copy of the file is in open state.
204 206 We set _FILE_SHARE_DELETE here, so files opened with posixfile can be
205 207 renamed and deleted while they are held open.
206 208 Note that if a file opened with posixfile is unlinked, the file
207 209 remains but cannot be opened again or be recreated under the same name,
208 210 until all reading processes have closed the file.'''
209 211
210 def __init__(self, name, mode='r', bufsize=-1):
211 if 'b' in mode:
212 def __init__(self, name, mode=b'r', bufsize=-1):
213 if b'b' in mode:
212 214 flags = _O_BINARY
213 215 else:
214 216 flags = _O_TEXT
215 217
216 m0 = mode[0]
217 if m0 == 'r' and '+' not in mode:
218 m0 = mode[0:1]
219 if m0 == b'r' and b'+' not in mode:
218 220 flags |= _O_RDONLY
219 221 access = _GENERIC_READ
220 222 else:
221 223 # work around http://support.microsoft.com/kb/899149 and
222 224 # set _O_RDWR for 'w' and 'a', even if mode has no '+'
223 225 flags |= _O_RDWR
224 226 access = _GENERIC_READ | _GENERIC_WRITE
225 227
226 if m0 == 'r':
228 if m0 == b'r':
227 229 creation = _OPEN_EXISTING
228 elif m0 == 'w':
230 elif m0 == b'w':
229 231 creation = _CREATE_ALWAYS
230 elif m0 == 'a':
232 elif m0 == b'a':
231 233 creation = _OPEN_ALWAYS
232 234 flags |= _O_APPEND
233 235 else:
234 raise ValueError("invalid mode: %s" % mode)
236 raise ValueError(r"invalid mode: %s" % pycompat.sysstr(mode))
235 237
236 238 fh = _kernel32.CreateFileA(name, access,
237 239 _FILE_SHARE_READ | _FILE_SHARE_WRITE | _FILE_SHARE_DELETE,
238 240 None, creation, _FILE_ATTRIBUTE_NORMAL, None)
239 241 if fh == _INVALID_HANDLE_VALUE:
240 242 _raiseioerror(name)
241 243
242 244 fd = msvcrt.open_osfhandle(fh, flags)
243 245 if fd == -1:
244 246 _kernel32.CloseHandle(fh)
245 247 _raiseioerror(name)
246 248
247 249 f = os.fdopen(fd, pycompat.sysstr(mode), bufsize)
248 250 # unfortunately, f.name is '<fdopen>' at this point -- so we store
249 251 # the name on this wrapper. We cannot just assign to f.name,
250 252 # because that attribute is read-only.
251 253 object.__setattr__(self, r'name', name)
252 254 object.__setattr__(self, r'_file', f)
253 255
254 256 def __iter__(self):
255 257 return self._file
256 258
257 259 def __getattr__(self, name):
258 260 return getattr(self._file, name)
259 261
260 262 def __setattr__(self, name, value):
261 263 '''mimics the read-only attributes of Python file objects
262 264 by raising 'TypeError: readonly attribute' if someone tries:
263 265 f = posixfile('foo.txt')
264 266 f.name = 'bla' '''
265 267 return self._file.__setattr__(name, value)
266 268
267 269 def __enter__(self):
268 270 return self._file.__enter__()
269 271
270 272 def __exit__(self, exc_type, exc_value, exc_tb):
271 273 return self._file.__exit__(exc_type, exc_value, exc_tb)
@@ -1,594 +1,594
1 1 # windows.py - Windows utility function implementations for Mercurial
2 2 #
3 3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
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 errno
11 11 import msvcrt
12 12 import os
13 13 import re
14 14 import stat
15 15 import string
16 16 import sys
17 17
18 18 from .i18n import _
19 19 from . import (
20 20 encoding,
21 21 error,
22 22 policy,
23 23 pycompat,
24 24 win32,
25 25 )
26 26
27 27 try:
28 28 import _winreg as winreg
29 29 winreg.CloseKey
30 30 except ImportError:
31 31 import winreg
32 32
33 33 osutil = policy.importmod(r'osutil')
34 34
35 35 getfsmountpoint = win32.getvolumename
36 36 getfstype = win32.getfstype
37 37 getuser = win32.getuser
38 38 hidewindow = win32.hidewindow
39 39 makedir = win32.makedir
40 40 nlinks = win32.nlinks
41 41 oslink = win32.oslink
42 42 samedevice = win32.samedevice
43 43 samefile = win32.samefile
44 44 setsignalhandler = win32.setsignalhandler
45 45 spawndetached = win32.spawndetached
46 46 split = os.path.split
47 47 testpid = win32.testpid
48 48 unlink = win32.unlink
49 49
50 50 umask = 0o022
51 51
52 52 class mixedfilemodewrapper(object):
53 53 """Wraps a file handle when it is opened in read/write mode.
54 54
55 55 fopen() and fdopen() on Windows have a specific-to-Windows requirement
56 56 that files opened with mode r+, w+, or a+ make a call to a file positioning
57 57 function when switching between reads and writes. Without this extra call,
58 58 Python will raise a not very intuitive "IOError: [Errno 0] Error."
59 59
60 60 This class wraps posixfile instances when the file is opened in read/write
61 61 mode and automatically adds checks or inserts appropriate file positioning
62 62 calls when necessary.
63 63 """
64 64 OPNONE = 0
65 65 OPREAD = 1
66 66 OPWRITE = 2
67 67
68 68 def __init__(self, fp):
69 69 object.__setattr__(self, r'_fp', fp)
70 70 object.__setattr__(self, r'_lastop', 0)
71 71
72 72 def __enter__(self):
73 73 return self._fp.__enter__()
74 74
75 75 def __exit__(self, exc_type, exc_val, exc_tb):
76 76 self._fp.__exit__(exc_type, exc_val, exc_tb)
77 77
78 78 def __getattr__(self, name):
79 79 return getattr(self._fp, name)
80 80
81 81 def __setattr__(self, name, value):
82 82 return self._fp.__setattr__(name, value)
83 83
84 84 def _noopseek(self):
85 85 self._fp.seek(0, os.SEEK_CUR)
86 86
87 87 def seek(self, *args, **kwargs):
88 88 object.__setattr__(self, r'_lastop', self.OPNONE)
89 89 return self._fp.seek(*args, **kwargs)
90 90
91 91 def write(self, d):
92 92 if self._lastop == self.OPREAD:
93 93 self._noopseek()
94 94
95 95 object.__setattr__(self, r'_lastop', self.OPWRITE)
96 96 return self._fp.write(d)
97 97
98 98 def writelines(self, *args, **kwargs):
99 99 if self._lastop == self.OPREAD:
100 100 self._noopeseek()
101 101
102 102 object.__setattr__(self, r'_lastop', self.OPWRITE)
103 103 return self._fp.writelines(*args, **kwargs)
104 104
105 105 def read(self, *args, **kwargs):
106 106 if self._lastop == self.OPWRITE:
107 107 self._noopseek()
108 108
109 109 object.__setattr__(self, r'_lastop', self.OPREAD)
110 110 return self._fp.read(*args, **kwargs)
111 111
112 112 def readline(self, *args, **kwargs):
113 113 if self._lastop == self.OPWRITE:
114 114 self._noopseek()
115 115
116 116 object.__setattr__(self, r'_lastop', self.OPREAD)
117 117 return self._fp.readline(*args, **kwargs)
118 118
119 119 def readlines(self, *args, **kwargs):
120 120 if self._lastop == self.OPWRITE:
121 121 self._noopseek()
122 122
123 123 object.__setattr__(self, r'_lastop', self.OPREAD)
124 124 return self._fp.readlines(*args, **kwargs)
125 125
126 126 def posixfile(name, mode='r', buffering=-1):
127 127 '''Open a file with even more POSIX-like semantics'''
128 128 try:
129 129 fp = osutil.posixfile(name, mode, buffering) # may raise WindowsError
130 130
131 131 # The position when opening in append mode is implementation defined, so
132 132 # make it consistent with other platforms, which position at EOF.
133 133 if 'a' in mode:
134 134 fp.seek(0, os.SEEK_END)
135 135
136 136 if '+' in mode:
137 137 return mixedfilemodewrapper(fp)
138 138
139 139 return fp
140 140 except WindowsError as err:
141 141 # convert to a friendlier exception
142 142 raise IOError(err.errno, '%s: %s' % (
143 143 name, encoding.strtolocal(err.strerror)))
144 144
145 145 # may be wrapped by win32mbcs extension
146 146 listdir = osutil.listdir
147 147
148 148 class winstdout(object):
149 149 '''stdout on windows misbehaves if sent through a pipe'''
150 150
151 151 def __init__(self, fp):
152 152 self.fp = fp
153 153
154 154 def __getattr__(self, key):
155 155 return getattr(self.fp, key)
156 156
157 157 def close(self):
158 158 try:
159 159 self.fp.close()
160 160 except IOError:
161 161 pass
162 162
163 163 def write(self, s):
164 164 try:
165 165 # This is workaround for "Not enough space" error on
166 166 # writing large size of data to console.
167 167 limit = 16000
168 168 l = len(s)
169 169 start = 0
170 170 self.softspace = 0
171 171 while start < l:
172 172 end = start + limit
173 173 self.fp.write(s[start:end])
174 174 start = end
175 175 except IOError as inst:
176 176 if inst.errno != 0 and not win32.lasterrorwaspipeerror(inst):
177 177 raise
178 178 self.close()
179 179 raise IOError(errno.EPIPE, 'Broken pipe')
180 180
181 181 def flush(self):
182 182 try:
183 183 return self.fp.flush()
184 184 except IOError as inst:
185 185 if not win32.lasterrorwaspipeerror(inst):
186 186 raise
187 187 raise IOError(errno.EPIPE, 'Broken pipe')
188 188
189 189 def _is_win_9x():
190 190 '''return true if run on windows 95, 98 or me.'''
191 191 try:
192 192 return sys.getwindowsversion()[3] == 1
193 193 except AttributeError:
194 194 return 'command' in encoding.environ.get('comspec', '')
195 195
196 196 def openhardlinks():
197 197 return not _is_win_9x()
198 198
199 199 def parsepatchoutput(output_line):
200 200 """parses the output produced by patch and returns the filename"""
201 201 pf = output_line[14:]
202 202 if pf[0] == '`':
203 203 pf = pf[1:-1] # Remove the quotes
204 204 return pf
205 205
206 206 def sshargs(sshcmd, host, user, port):
207 207 '''Build argument list for ssh or Plink'''
208 208 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
209 209 args = user and ("%s@%s" % (user, host)) or host
210 210 if args.startswith('-') or args.startswith('/'):
211 211 raise error.Abort(
212 212 _('illegal ssh hostname or username starting with - or /: %s') %
213 213 args)
214 214 args = shellquote(args)
215 215 if port:
216 216 args = '%s %s %s' % (pflag, shellquote(port), args)
217 217 return args
218 218
219 219 def setflags(f, l, x):
220 220 pass
221 221
222 222 def copymode(src, dst, mode=None):
223 223 pass
224 224
225 225 def checkexec(path):
226 226 return False
227 227
228 228 def checklink(path):
229 229 return False
230 230
231 231 def setbinary(fd):
232 232 # When run without console, pipes may expose invalid
233 233 # fileno(), usually set to -1.
234 234 fno = getattr(fd, 'fileno', None)
235 235 if fno is not None and fno() >= 0:
236 236 msvcrt.setmode(fno(), os.O_BINARY)
237 237
238 238 def pconvert(path):
239 239 return path.replace(pycompat.ossep, '/')
240 240
241 241 def localpath(path):
242 242 return path.replace('/', '\\')
243 243
244 244 def normpath(path):
245 245 return pconvert(os.path.normpath(path))
246 246
247 247 def normcase(path):
248 248 return encoding.upper(path) # NTFS compares via upper()
249 249
250 250 # see posix.py for definitions
251 251 normcasespec = encoding.normcasespecs.upper
252 252 normcasefallback = encoding.upperfallback
253 253
254 254 def samestat(s1, s2):
255 255 return False
256 256
257 257 def shelltocmdexe(path, env):
258 258 r"""Convert shell variables in the form $var and ${var} inside ``path``
259 259 to %var% form. Existing Windows style variables are left unchanged.
260 260
261 261 The variables are limited to the given environment. Unknown variables are
262 262 left unchanged.
263 263
264 264 >>> e = {b'var1': b'v1', b'var2': b'v2', b'var3': b'v3'}
265 265 >>> # Only valid values are expanded
266 266 >>> shelltocmdexe(b'cmd $var1 ${var2} %var3% $missing ${missing} %missing%',
267 267 ... e)
268 268 'cmd %var1% %var2% %var3% $missing ${missing} %missing%'
269 269 >>> # Single quote prevents expansion, as does \$ escaping
270 270 >>> shelltocmdexe(b"cmd '$var1 ${var2} %var3%' \$var1 \${var2} \\", e)
271 271 'cmd "$var1 ${var2} %var3%" $var1 ${var2} \\'
272 272 >>> # $$ is not special. %% is not special either, but can be the end and
273 273 >>> # start of consecutive variables
274 274 >>> shelltocmdexe(b"cmd $$ %% %var1%%var2%", e)
275 275 'cmd $$ %% %var1%%var2%'
276 276 >>> # No double substitution
277 277 >>> shelltocmdexe(b"$var1 %var1%", {b'var1': b'%var2%', b'var2': b'boom'})
278 278 '%var1% %var1%'
279 279 >>> # Tilde expansion
280 280 >>> shelltocmdexe(b"~/dir ~\dir2 ~tmpfile \~/", {})
281 281 '%USERPROFILE%/dir %USERPROFILE%\\dir2 ~tmpfile ~/'
282 282 """
283 283 if not any(c in path for c in b"$'~"):
284 284 return path
285 285
286 286 varchars = pycompat.sysbytes(string.ascii_letters + string.digits) + b'_-'
287 287
288 288 res = b''
289 289 index = 0
290 290 pathlen = len(path)
291 291 while index < pathlen:
292 292 c = path[index]
293 293 if c == b'\'': # no expansion within single quotes
294 294 path = path[index + 1:]
295 295 pathlen = len(path)
296 296 try:
297 297 index = path.index(b'\'')
298 298 res += b'"' + path[:index] + b'"'
299 299 except ValueError:
300 300 res += c + path
301 301 index = pathlen - 1
302 302 elif c == b'%': # variable
303 303 path = path[index + 1:]
304 304 pathlen = len(path)
305 305 try:
306 306 index = path.index(b'%')
307 307 except ValueError:
308 308 res += b'%' + path
309 309 index = pathlen - 1
310 310 else:
311 311 var = path[:index]
312 312 res += b'%' + var + b'%'
313 313 elif c == b'$': # variable
314 314 if path[index + 1:index + 2] == b'{':
315 315 path = path[index + 2:]
316 316 pathlen = len(path)
317 317 try:
318 318 index = path.index(b'}')
319 319 var = path[:index]
320 320
321 321 # See below for why empty variables are handled specially
322 322 if env.get(var, '') != '':
323 323 res += b'%' + var + b'%'
324 324 else:
325 325 res += b'${' + var + b'}'
326 326 except ValueError:
327 327 res += b'${' + path
328 328 index = pathlen - 1
329 329 else:
330 330 var = b''
331 331 index += 1
332 332 c = path[index:index + 1]
333 333 while c != b'' and c in varchars:
334 334 var += c
335 335 index += 1
336 336 c = path[index:index + 1]
337 337 # Some variables (like HG_OLDNODE) may be defined, but have an
338 338 # empty value. Those need to be skipped because when spawning
339 339 # cmd.exe to run the hook, it doesn't replace %VAR% for an empty
340 340 # VAR, and that really confuses things like revset expressions.
341 341 # OTOH, if it's left in Unix format and the hook runs sh.exe, it
342 342 # will substitute to an empty string, and everything is happy.
343 343 if env.get(var, '') != '':
344 344 res += b'%' + var + b'%'
345 345 else:
346 346 res += b'$' + var
347 347
348 348 if c != '':
349 349 index -= 1
350 350 elif (c == b'~' and index + 1 < pathlen
351 351 and path[index + 1] in (b'\\', b'/')):
352 352 res += "%USERPROFILE%"
353 353 elif (c == b'\\' and index + 1 < pathlen
354 354 and path[index + 1] in (b'$', b'~')):
355 355 # Skip '\', but only if it is escaping $ or ~
356 356 res += path[index + 1]
357 357 index += 1
358 358 else:
359 359 res += c
360 360
361 361 index += 1
362 362 return res
363 363
364 364 # A sequence of backslashes is special iff it precedes a double quote:
365 365 # - if there's an even number of backslashes, the double quote is not
366 366 # quoted (i.e. it ends the quoted region)
367 367 # - if there's an odd number of backslashes, the double quote is quoted
368 368 # - in both cases, every pair of backslashes is unquoted into a single
369 369 # backslash
370 370 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
371 371 # So, to quote a string, we must surround it in double quotes, double
372 372 # the number of backslashes that precede double quotes and add another
373 373 # backslash before every double quote (being careful with the double
374 374 # quote we've appended to the end)
375 375 _quotere = None
376 376 _needsshellquote = None
377 377 def shellquote(s):
378 378 r"""
379 379 >>> shellquote(br'C:\Users\xyz')
380 380 '"C:\\Users\\xyz"'
381 381 >>> shellquote(br'C:\Users\xyz/mixed')
382 382 '"C:\\Users\\xyz/mixed"'
383 383 >>> # Would be safe not to quote too, since it is all double backslashes
384 384 >>> shellquote(br'C:\\Users\\xyz')
385 385 '"C:\\\\Users\\\\xyz"'
386 386 >>> # But this must be quoted
387 387 >>> shellquote(br'C:\\Users\\xyz/abc')
388 388 '"C:\\\\Users\\\\xyz/abc"'
389 389 """
390 390 global _quotere
391 391 if _quotere is None:
392 _quotere = re.compile(r'(\\*)("|\\$)')
392 _quotere = re.compile(br'(\\*)("|\\$)')
393 393 global _needsshellquote
394 394 if _needsshellquote is None:
395 395 # ":" is also treated as "safe character", because it is used as a part
396 396 # of path name on Windows. "\" is also part of a path name, but isn't
397 397 # safe because shlex.split() (kind of) treats it as an escape char and
398 398 # drops it. It will leave the next character, even if it is another
399 399 # "\".
400 _needsshellquote = re.compile(r'[^a-zA-Z0-9._:/-]').search
400 _needsshellquote = re.compile(br'[^a-zA-Z0-9._:/-]').search
401 401 if s and not _needsshellquote(s) and not _quotere.search(s):
402 402 # "s" shouldn't have to be quoted
403 403 return s
404 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
404 return b'"%s"' % _quotere.sub(br'\1\1\\\2', s)
405 405
406 406 def _unquote(s):
407 407 if s.startswith(b'"') and s.endswith(b'"'):
408 408 return s[1:-1]
409 409 return s
410 410
411 411 def shellsplit(s):
412 412 """Parse a command string in cmd.exe way (best-effort)"""
413 413 return pycompat.maplist(_unquote, pycompat.shlexsplit(s, posix=False))
414 414
415 415 def quotecommand(cmd):
416 416 """Build a command string suitable for os.popen* calls."""
417 417 if sys.version_info < (2, 7, 1):
418 418 # Python versions since 2.7.1 do this extra quoting themselves
419 419 return '"' + cmd + '"'
420 420 return cmd
421 421
422 422 # if you change this stub into a real check, please try to implement the
423 423 # username and groupname functions above, too.
424 424 def isowner(st):
425 425 return True
426 426
427 427 def findexe(command):
428 428 '''Find executable for command searching like cmd.exe does.
429 429 If command is a basename then PATH is searched for command.
430 430 PATH isn't searched if command is an absolute or relative path.
431 431 An extension from PATHEXT is found and added if not present.
432 432 If command isn't found None is returned.'''
433 433 pathext = encoding.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
434 434 pathexts = [ext for ext in pathext.lower().split(pycompat.ospathsep)]
435 435 if os.path.splitext(command)[1].lower() in pathexts:
436 436 pathexts = ['']
437 437
438 438 def findexisting(pathcommand):
439 439 'Will append extension (if needed) and return existing file'
440 440 for ext in pathexts:
441 441 executable = pathcommand + ext
442 442 if os.path.exists(executable):
443 443 return executable
444 444 return None
445 445
446 446 if pycompat.ossep in command:
447 447 return findexisting(command)
448 448
449 449 for path in encoding.environ.get('PATH', '').split(pycompat.ospathsep):
450 450 executable = findexisting(os.path.join(path, command))
451 451 if executable is not None:
452 452 return executable
453 453 return findexisting(os.path.expanduser(os.path.expandvars(command)))
454 454
455 455 _wantedkinds = {stat.S_IFREG, stat.S_IFLNK}
456 456
457 457 def statfiles(files):
458 458 '''Stat each file in files. Yield each stat, or None if a file
459 459 does not exist or has a type we don't care about.
460 460
461 461 Cluster and cache stat per directory to minimize number of OS stat calls.'''
462 462 dircache = {} # dirname -> filename -> status | None if file does not exist
463 463 getkind = stat.S_IFMT
464 464 for nf in files:
465 465 nf = normcase(nf)
466 466 dir, base = os.path.split(nf)
467 467 if not dir:
468 468 dir = '.'
469 469 cache = dircache.get(dir, None)
470 470 if cache is None:
471 471 try:
472 472 dmap = dict([(normcase(n), s)
473 473 for n, k, s in listdir(dir, True)
474 474 if getkind(s.st_mode) in _wantedkinds])
475 475 except OSError as err:
476 476 # Python >= 2.5 returns ENOENT and adds winerror field
477 477 # EINVAL is raised if dir is not a directory.
478 478 if err.errno not in (errno.ENOENT, errno.EINVAL,
479 479 errno.ENOTDIR):
480 480 raise
481 481 dmap = {}
482 482 cache = dircache.setdefault(dir, dmap)
483 483 yield cache.get(base, None)
484 484
485 485 def username(uid=None):
486 486 """Return the name of the user with the given uid.
487 487
488 488 If uid is None, return the name of the current user."""
489 489 return None
490 490
491 491 def groupname(gid=None):
492 492 """Return the name of the group with the given gid.
493 493
494 494 If gid is None, return the name of the current group."""
495 495 return None
496 496
497 497 def removedirs(name):
498 498 """special version of os.removedirs that does not remove symlinked
499 499 directories or junction points if they actually contain files"""
500 500 if listdir(name):
501 501 return
502 502 os.rmdir(name)
503 503 head, tail = os.path.split(name)
504 504 if not tail:
505 505 head, tail = os.path.split(head)
506 506 while head and tail:
507 507 try:
508 508 if listdir(head):
509 509 return
510 510 os.rmdir(head)
511 511 except (ValueError, OSError):
512 512 break
513 513 head, tail = os.path.split(head)
514 514
515 515 def rename(src, dst):
516 516 '''atomically rename file src to dst, replacing dst if it exists'''
517 517 try:
518 518 os.rename(src, dst)
519 519 except OSError as e:
520 520 if e.errno != errno.EEXIST:
521 521 raise
522 522 unlink(dst)
523 523 os.rename(src, dst)
524 524
525 525 def gethgcmd():
526 526 return [sys.executable] + sys.argv[:1]
527 527
528 528 def groupmembers(name):
529 529 # Don't support groups on Windows for now
530 530 raise KeyError
531 531
532 532 def isexec(f):
533 533 return False
534 534
535 535 class cachestat(object):
536 536 def __init__(self, path):
537 537 pass
538 538
539 539 def cacheable(self):
540 540 return False
541 541
542 542 def lookupreg(key, valname=None, scope=None):
543 543 ''' Look up a key/value name in the Windows registry.
544 544
545 545 valname: value name. If unspecified, the default value for the key
546 546 is used.
547 547 scope: optionally specify scope for registry lookup, this can be
548 548 a sequence of scopes to look up in order. Default (CURRENT_USER,
549 549 LOCAL_MACHINE).
550 550 '''
551 551 if scope is None:
552 552 scope = (winreg.HKEY_CURRENT_USER, winreg.HKEY_LOCAL_MACHINE)
553 553 elif not isinstance(scope, (list, tuple)):
554 554 scope = (scope,)
555 555 for s in scope:
556 556 try:
557 557 with winreg.OpenKey(s, encoding.strfromlocal(key)) as hkey:
558 558 val = winreg.QueryValueEx(hkey, valname)[0]
559 559 # never let a Unicode string escape into the wild
560 560 return encoding.unitolocal(val)
561 561 except EnvironmentError:
562 562 pass
563 563
564 564 expandglobs = True
565 565
566 566 def statislink(st):
567 567 '''check whether a stat result is a symlink'''
568 568 return False
569 569
570 570 def statisexec(st):
571 571 '''check whether a stat result is an executable file'''
572 572 return False
573 573
574 574 def poll(fds):
575 575 # see posix.py for description
576 576 raise NotImplementedError()
577 577
578 578 def readpipe(pipe):
579 579 """Read all available data from a pipe."""
580 580 chunks = []
581 581 while True:
582 582 size = win32.peekpipe(pipe)
583 583 if not size:
584 584 break
585 585
586 586 s = pipe.read(size)
587 587 if not s:
588 588 break
589 589 chunks.append(s)
590 590
591 591 return ''.join(chunks)
592 592
593 593 def bindunixsocket(sock, path):
594 594 raise NotImplementedError('unsupported platform')
General Comments 0
You need to be logged in to leave comments. Login now