1# -*- coding: ascii -*- 2# 3# Copyright 2007 - 2013 4# Andr\xe9 Malo or his licensors, as applicable 5# 6# Licensed under the Apache License, Version 2.0 (the "License"); 7# you may not use this file except in compliance with the License. 8# You may obtain a copy of the License at 9# 10# http://www.apache.org/licenses/LICENSE-2.0 11# 12# Unless required by applicable law or agreed to in writing, software 13# distributed under the License is distributed on an "AS IS" BASIS, 14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15# See the License for the specific language governing permissions and 16# limitations under the License. 17""" 18================= 19 Shell utilities 20================= 21 22Shell utilities. 23""" 24from __future__ import generators 25 26__author__ = u"Andr\xe9 Malo" 27__docformat__ = "restructuredtext en" 28 29import errno as _errno 30import fnmatch as _fnmatch 31import os as _os 32import shutil as _shutil 33import sys as _sys 34import tempfile as _tempfile 35 36cwd = _os.path.dirname(_os.path.abspath(_sys.argv[0])) 37 38class ExitError(RuntimeError): 39 """ Exit error """ 40 def __init__(self, code): 41 RuntimeError.__init__(self, code) 42 self.code = code 43 self.signal = None 44 45 46class SignalError(ExitError): 47 """ Signal error """ 48 def __init__(self, code, signal): 49 ExitError.__init__(self, code) 50 import signal as _signal 51 self.signal = signal 52 for key, val in vars(_signal).iteritems(): 53 if key.startswith('SIG') and not key.startswith('SIG_'): 54 if val == signal: 55 self.signalstr = key[3:] 56 break 57 else: 58 self.signalstr = '%04d' % signal 59 60 61def native(path): 62 """ Convert slash path to native """ 63 path = _os.path.sep.join(path.split('/')) 64 return _os.path.normpath(_os.path.join(cwd, path)) 65 66 67def cp(src, dest): 68 """ Copy src to dest """ 69 _shutil.copy2(native(src), native(dest)) 70 71 72def cp_r(src, dest): 73 """ Copy -r src to dest """ 74 _shutil.copytree(native(src), native(dest)) 75 76 77def rm(dest): 78 """ Remove a file """ 79 try: 80 _os.unlink(native(dest)) 81 except OSError, e: 82 if _errno.ENOENT != e.errno: 83 raise 84 85def rm_rf(dest): 86 """ Remove a tree """ 87 dest = native(dest) 88 if _os.path.exists(dest): 89 for path in files(dest, '*'): 90 _os.chmod(native(path), 0644) 91 _shutil.rmtree(dest) 92 93 94try: 95 mkstemp = _tempfile.mkstemp 96except AttributeError: 97 # helpers stolen from 2.4 tempfile module 98 try: 99 import fcntl as _fcntl 100 except ImportError: 101 def _set_cloexec(fd): 102 """ Set close-on-exec (not implemented, but not an error) """ 103 # pylint: disable = W0613 104 pass 105 else: 106 def _set_cloexec(fd): 107 """ Set close-on-exec """ 108 try: 109 flags = _fcntl.fcntl(fd, _fcntl.F_GETFD, 0) 110 except IOError: 111 pass 112 else: 113 # flags read successfully, modify 114 flags |= _fcntl.FD_CLOEXEC 115 _fcntl.fcntl(fd, _fcntl.F_SETFD, flags) 116 117 _text_openflags = _os.O_RDWR | _os.O_CREAT | _os.O_EXCL 118 _text_openflags |= getattr(_os, 'O_NOINHERIT', 0) 119 _text_openflags |= getattr(_os, 'O_NOFOLLOW', 0) 120 121 _bin_openflags = _text_openflags 122 _bin_openflags |= getattr(_os, 'O_BINARY', 0) 123 124 def mkstemp(suffix="", prefix=_tempfile.gettempprefix(), dir=None, 125 text=False): 126 """ Create secure temp file """ 127 # pylint: disable = W0622 128 if dir is None: 129 dir = _tempfile.gettempdir() 130 if text: 131 flags = _text_openflags 132 else: 133 flags = _bin_openflags 134 count = 100 135 while count > 0: 136 j = _tempfile._counter.get_next() # pylint: disable = E1101, W0212 137 fname = _os.path.join(dir, prefix + str(j) + suffix) 138 try: 139 fd = _os.open(fname, flags, 0600) 140 except OSError, e: 141 if e.errno == _errno.EEXIST: 142 count -= 1 143 continue 144 raise 145 _set_cloexec(fd) 146 return fd, _os.path.abspath(fname) 147 raise IOError, (_errno.EEXIST, "No usable temporary file name found") 148 149 150def _pipespawn(argv, env): 151 """ Pipe spawn """ 152 # pylint: disable = R0912 153 import pickle as _pickle 154 fd, name = mkstemp('.py') 155 try: 156 _os.write(fd, (r""" 157import os 158import pickle 159try: 160 import subprocess 161except ImportError: 162 subprocess = None 163import sys 164 165argv = pickle.loads(%(argv)s) 166env = pickle.loads(%(env)s) 167if 'X_JYTHON_WA_PATH' in env: 168 env['PATH'] = env['X_JYTHON_WA_PATH'] 169 170if subprocess is None: 171 pid = os.spawnve(os.P_NOWAIT, argv[0], argv, env) 172 result = os.waitpid(pid, 0)[1] 173else: 174 p = subprocess.Popen(argv, env=env) 175 result = p.wait() 176 if result < 0: 177 print "\n%%d 1" %% (-result) 178 sys.exit(2) 179 180if result == 0: 181 sys.exit(0) 182signalled = getattr(os, 'WIFSIGNALED', None) 183if signalled is not None: 184 if signalled(result): 185 print "\n%%d %%d" %% (os.WTERMSIG(result), result & 7) 186 sys.exit(2) 187print "\n%%d" %% (result & 7,) 188sys.exit(3) 189 """.strip() + "\n") % { 190 'argv': repr(_pickle.dumps(argv)), 191 'env': repr(_pickle.dumps(env)), 192 }) 193 fd, _ = None, _os.close(fd) 194 if _sys.platform == 'win32': 195 argv = [] 196 for arg in [_sys.executable, name]: 197 if ' ' in arg or arg.startswith('"'): 198 arg = '"%s"' % arg.replace('"', '\\"') 199 argv.append(arg) 200 argv = ' '.join(argv) 201 shell = True 202 close_fds = False 203 else: 204 argv = [_sys.executable, name] 205 shell = False 206 close_fds = True 207 208 res = 0 209 try: 210 import subprocess 211 except ImportError: 212 import popen2 as _popen2 213 proc = _popen2.Popen3(argv, False) 214 try: 215 proc.tochild.close() 216 result = proc.fromchild.read() 217 finally: 218 res = proc.wait() 219 else: 220 if 'X_JYTHON_WA_PATH' in env: 221 env['PATH'] = env['X_JYTHON_WA_PATH'] 222 223 proc = subprocess.Popen(argv, 224 shell=shell, 225 stdin=subprocess.PIPE, 226 stdout=subprocess.PIPE, 227 close_fds=close_fds, 228 env=env, 229 ) 230 try: 231 proc.stdin.close() 232 result = proc.stdout.read() 233 finally: 234 res = proc.wait() 235 if res != 0: 236 if res == 2: 237 signal, code = map(int, result.splitlines()[-1].split()) 238 raise SignalError(code, signal) 239 elif res == 3: 240 code = int(result.splitlines()[-1].strip()) 241 raise ExitError(code) 242 raise ExitError(res) 243 244 return result 245 finally: 246 try: 247 if fd is not None: 248 _os.close(fd) 249 finally: 250 _os.unlink(name) 251 252 253def _filepipespawn(infile, outfile, argv, env): 254 """ File Pipe spawn """ 255 try: 256 import subprocess 257 except ImportError: 258 subprocess = None 259 import pickle as _pickle 260 fd, name = mkstemp('.py') 261 try: 262 _os.write(fd, (""" 263import os 264import pickle 265import sys 266 267infile = pickle.loads(%(infile)s) 268outfile = pickle.loads(%(outfile)s) 269argv = pickle.loads(%(argv)s) 270env = pickle.loads(%(env)s) 271 272if infile is not None: 273 infile = open(infile, 'rb') 274 os.dup2(infile.fileno(), 0) 275 infile.close() 276if outfile is not None: 277 outfile = open(outfile, 'wb') 278 os.dup2(outfile.fileno(), 1) 279 outfile.close() 280 281pid = os.spawnve(os.P_NOWAIT, argv[0], argv, env) 282result = os.waitpid(pid, 0)[1] 283sys.exit(result & 7) 284 """.strip() + "\n") % { 285 'infile': repr(_pickle.dumps(_os.path.abspath(infile))), 286 'outfile': repr(_pickle.dumps(_os.path.abspath(outfile))), 287 'argv': repr(_pickle.dumps(argv)), 288 'env': repr(_pickle.dumps(env)), 289 }) 290 fd, _ = None, _os.close(fd) 291 if _sys.platform == 'win32': 292 argv = [] 293 for arg in [_sys.executable, name]: 294 if ' ' in arg or arg.startswith('"'): 295 arg = '"%s"' % arg.replace('"', '\\"') 296 argv.append(arg) 297 argv = ' '.join(argv) 298 close_fds = False 299 shell = True 300 else: 301 argv = [_sys.executable, name] 302 close_fds = True 303 shell = False 304 305 if subprocess is None: 306 pid = _os.spawnve(_os.P_NOWAIT, argv[0], argv, env) 307 return _os.waitpid(pid, 0)[1] 308 else: 309 p = subprocess.Popen( 310 argv, env=env, shell=shell, close_fds=close_fds 311 ) 312 return p.wait() 313 finally: 314 try: 315 if fd is not None: 316 _os.close(fd) 317 finally: 318 _os.unlink(name) 319 320 321def spawn(*argv, **kwargs): 322 """ Spawn a process """ 323 try: 324 import subprocess 325 except ImportError: 326 subprocess = None 327 328 if _sys.platform == 'win32': 329 newargv = [] 330 for arg in argv: 331 if not arg or ' ' in arg or arg.startswith('"'): 332 arg = '"%s"' % arg.replace('"', '\\"') 333 newargv.append(arg) 334 argv = newargv 335 close_fds = False 336 shell = True 337 else: 338 close_fds = True 339 shell = False 340 341 env = kwargs.get('env') 342 if env is None: 343 env = dict(_os.environ) 344 if 'X_JYTHON_WA_PATH' in env: 345 env['PATH'] = env['X_JYTHON_WA_PATH'] 346 347 echo = kwargs.get('echo') 348 if echo: 349 print ' '.join(argv) 350 filepipe = kwargs.get('filepipe') 351 if filepipe: 352 return _filepipespawn( 353 kwargs.get('stdin'), kwargs.get('stdout'), argv, env 354 ) 355 pipe = kwargs.get('stdout') 356 if pipe: 357 return _pipespawn(argv, env) 358 359 if subprocess is None: 360 pid = _os.spawnve(_os.P_NOWAIT, argv[0], argv, env) 361 return _os.waitpid(pid, 0)[1] 362 else: 363 p = subprocess.Popen(argv, env=env, shell=shell, close_fds=close_fds) 364 return p.wait() 365 366 367try: 368 walk = _os.walk 369except AttributeError: 370 # copy from python 2.4 sources (modulo docs and comments) 371 def walk(top, topdown=True, onerror=None): 372 """ directory tree walker """ 373 # pylint: disable = C0103 374 join, isdir, islink = _os.path.join, _os.path.isdir, _os.path.islink 375 listdir, error = _os.listdir, _os.error 376 377 try: 378 names = listdir(top) 379 except error, err: 380 if onerror is not None: 381 onerror(err) 382 return 383 384 dirs, nondirs = [], [] 385 for name in names: 386 if isdir(join(top, name)): 387 dirs.append(name) 388 else: 389 nondirs.append(name) 390 391 if topdown: 392 yield top, dirs, nondirs 393 for name in dirs: 394 path = join(top, name) 395 if not islink(path): 396 for x in walk(path, topdown, onerror): 397 yield x 398 if not topdown: 399 yield top, dirs, nondirs 400 401 402def files(base, wildcard='[!.]*', recursive=1, prune=('.git', '.svn', 'CVS')): 403 """ Determine a filelist """ 404 for dirpath, dirnames, filenames in walk(native(base)): 405 for item in prune: 406 if item in dirnames: 407 dirnames.remove(item) 408 409 filenames.sort() 410 for name in _fnmatch.filter(filenames, wildcard): 411 dest = _os.path.join(dirpath, name) 412 if dest.startswith(cwd): 413 dest = dest.replace(cwd, '', 1) 414 aslist = [] 415 head, tail = _os.path.split(dest) 416 while tail: 417 aslist.append(tail) 418 head, tail = _os.path.split(head) 419 aslist.reverse() 420 dest = '/'.join(aslist) 421 yield dest 422 423 if not recursive: 424 break 425 dirnames.sort() 426 427 428def dirs(base, wildcard='[!.]*', recursive=1, prune=('.git', '.svn', 'CVS')): 429 """ Determine a filelist """ 430 for dirpath, dirnames, filenames in walk(native(base)): 431 for item in prune: 432 if item in dirnames: 433 dirnames.remove(item) 434 435 dirnames.sort() 436 for name in _fnmatch.filter(dirnames, wildcard): 437 dest = _os.path.join(dirpath, name) 438 if dest.startswith(cwd): 439 dest = dest.replace(cwd, '', 1) 440 aslist = [] 441 head, tail = _os.path.split(dest) 442 while tail: 443 aslist.append(tail) 444 head, tail = _os.path.split(head) 445 aslist.reverse() 446 dest = '/'.join(aslist) 447 yield dest 448 449 if not recursive: 450 break 451 452 453def frompath(executable): 454 """ Find executable in PATH """ 455 # Based on distutils.spawn.find_executable. 456 path = _os.environ.get('PATH', '') 457 paths = [ 458 _os.path.expanduser(item) 459 for item in path.split(_os.pathsep) 460 ] 461 ext = _os.path.splitext(executable)[1] 462 exts = [''] 463 if _sys.platform == 'win32' or _os.name == 'os2': 464 eext = ['.exe', '.bat', '.py'] 465 if ext not in eext: 466 exts.extend(eext) 467 468 for ext in exts: 469 if not _os.path.isfile(executable + ext): 470 for path in paths: 471 fname = _os.path.join(path, executable + ext) 472 if _os.path.isfile(fname): 473 # the file exists, we have a shot at spawn working 474 return fname 475 else: 476 return executable + ext 477 478 return None 479