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