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 22 Shell utilities. 23 """ 24 __author__ = "Andr\xe9 Malo" 25 __docformat__ = "restructuredtext en" 26 27 import errno as _errno 28 import fnmatch as _fnmatch 29 import os as _os 30 import shutil as _shutil 31 import subprocess as _subprocess 32 import sys as _sys 33 import tempfile as _tempfile 34 35 cwd = _os.path.dirname(_os.path.abspath(_sys.argv[0])) 36 37 class ExitError(RuntimeError): 38 """ Exit error """ 39 def __init__(self, code): 40 RuntimeError.__init__(self, code) 41 self.code = code 42 self.signal = None 43 44 45 class SignalError(ExitError): 46 """ Signal error """ 47 def __init__(self, code, signal): 48 ExitError.__init__(self, code) 49 import signal as _signal 50 self.signal = signal 51 for key, val in vars(_signal).items(): 52 if key.startswith('SIG') and not key.startswith('SIG_'): 53 if val == signal: 54 self.signalstr = key[3:] 55 break 56 else: 57 self.signalstr = '%04d' % signal 58 59 60 def native(path): 61 """ Convert slash path to native """ 62 path = _os.path.sep.join(path.split('/')) 63 return _os.path.normpath(_os.path.join(cwd, path)) 64 65 66 def cp(src, dest): 67 """ Copy src to dest """ 68 _shutil.copy2(native(src), native(dest)) 69 70 71 def cp_r(src, dest): 72 """ Copy -r src to dest """ 73 _shutil.copytree(native(src), native(dest)) 74 75 76 def rm(dest): 77 """ Remove a file """ 78 try: 79 _os.unlink(native(dest)) 80 except OSError as e: 81 if _errno.ENOENT != e.errno: 82 raise 83 84 def rm_rf(dest): 85 """ Remove a tree """ 86 dest = native(dest) 87 if _os.path.exists(dest): 88 for path in files(dest, '*'): 89 _os.chmod(native(path), 0o644) 90 _shutil.rmtree(dest) 91 92 93 mkstemp = _tempfile.mkstemp 94 95 96 def _pipespawn(argv, env): 97 """ Pipe spawn """ 98 # pylint: disable = R0912 99 import pickle as _pickle 100 fd, name = mkstemp('.py') 101 try: 102 _os.write(fd, ((r""" 103 import os 104 import pickle 105 import subprocess 106 import sys 107 108 argv = pickle.loads(%(argv)s) 109 env = pickle.loads(%(env)s) 110 if 'X_JYTHON_WA_PATH' in env: 111 env['PATH'] = env['X_JYTHON_WA_PATH'] 112 113 p = subprocess.Popen(argv, env=env) 114 result = p.wait() 115 if result < 0: 116 print("\n%%d 1" %% (-result)) 117 sys.exit(2) 118 if result == 0: 119 sys.exit(0) 120 print("\n%%d" %% (result & 7,)) 121 sys.exit(3) 122 """.strip() + "\n") % { 123 'argv': repr(_pickle.dumps(argv)), 124 'env': repr(_pickle.dumps(dict(env))), 125 }).encode('utf-8')) 126 fd, _ = None, _os.close(fd) 127 if _sys.platform == 'win32': 128 argv = [] 129 for arg in [_sys.executable, name]: 130 if ' ' in arg or arg.startswith('"'): 131 arg = '"%s"' % arg.replace('"', '\\"') 132 argv.append(arg) 133 argv = ' '.join(argv) 134 shell = True 135 close_fds = False 136 else: 137 argv = [_sys.executable, name] 138 shell = False 139 close_fds = True 140 141 res = 0 142 if 'X_JYTHON_WA_PATH' in env: 143 env['PATH'] = env['X_JYTHON_WA_PATH'] 144 145 proc = _subprocess.Popen(argv, 146 shell=shell, 147 stdin=_subprocess.PIPE, 148 stdout=_subprocess.PIPE, 149 close_fds=close_fds, 150 env=env, 151 ) 152 try: 153 proc.stdin.close() 154 result = proc.stdout.read() 155 finally: 156 res = proc.wait() 157 if res != 0: 158 if res == 2: 159 signal, code = list(map(int, result.splitlines()[-1].split())) 160 raise SignalError(code, signal) 161 elif res == 3: 162 code = int(result.splitlines()[-1].strip()) 163 raise ExitError(code) 164 raise ExitError(res) 165 166 return result.decode('latin-1') 167 finally: 168 try: 169 if fd is not None: 170 _os.close(fd) 171 finally: 172 _os.unlink(name) 173 174 175 def _filepipespawn(infile, outfile, argv, env): 176 """ File Pipe spawn """ 177 import pickle as _pickle 178 fd, name = mkstemp('.py') 179 try: 180 _os.write(fd, ((""" 181 import os 182 import pickle 183 import sys 184 185 infile = pickle.loads(%(infile)s) 186 outfile = pickle.loads(%(outfile)s) 187 argv = pickle.loads(%(argv)s) 188 env = pickle.loads(%(env)s) 189 190 if infile is not None: 191 infile = open(infile, 'rb') 192 os.dup2(infile.fileno(), 0) 193 infile.close() 194 if outfile is not None: 195 outfile = open(outfile, 'wb') 196 os.dup2(outfile.fileno(), 1) 197 outfile.close() 198 199 pid = os.spawnve(os.P_NOWAIT, argv[0], argv, env) 200 result = os.waitpid(pid, 0)[1] 201 sys.exit(result & 7) 202 """.strip() + "\n") % { 203 'infile': repr(_pickle.dumps(_os.path.abspath(infile))), 204 'outfile': repr(_pickle.dumps(_os.path.abspath(outfile))), 205 'argv': repr(_pickle.dumps(argv)), 206 'env': repr(_pickle.dumps(env)), 207 }).encode('utf-8')) 208 fd, _ = None, _os.close(fd) 209 if _sys.platform == 'win32': 210 argv = [] 211 for arg in [_sys.executable, name]: 212 if ' ' in arg or arg.startswith('"'): 213 arg = '"%s"' % arg.replace('"', '\\"') 214 argv.append(arg) 215 argv = ' '.join(argv) 216 close_fds = False 217 shell = True 218 else: 219 argv = [_sys.executable, name] 220 close_fds = True 221 shell = False 222 223 p = _subprocess.Popen( 224 argv, env=env, shell=shell, close_fds=close_fds 225 ) 226 return p.wait() 227 finally: 228 try: 229 if fd is not None: 230 _os.close(fd) 231 finally: 232 _os.unlink(name) 233 234 235 def spawn(*argv, **kwargs): 236 """ Spawn a process """ 237 if _sys.platform == 'win32': 238 newargv = [] 239 for arg in argv: 240 if not arg or ' ' in arg or arg.startswith('"'): 241 arg = '"%s"' % arg.replace('"', '\\"') 242 newargv.append(arg) 243 argv = newargv 244 close_fds = False 245 shell = True 246 else: 247 close_fds = True 248 shell = False 249 250 env = kwargs.get('env') 251 if env is None: 252 env = dict(_os.environ) 253 if 'X_JYTHON_WA_PATH' in env: 254 env['PATH'] = env['X_JYTHON_WA_PATH'] 255 256 echo = kwargs.get('echo') 257 if echo: 258 print(' '.join(argv)) 259 filepipe = kwargs.get('filepipe') 260 if filepipe: 261 return _filepipespawn( 262 kwargs.get('stdin'), kwargs.get('stdout'), argv, env 263 ) 264 pipe = kwargs.get('stdout') 265 if pipe: 266 return _pipespawn(argv, env) 267 268 p = _subprocess.Popen(argv, env=env, shell=shell, close_fds=close_fds) 269 return p.wait() 270 271 272 walk = _os.walk 273 274 275 def files(base, wildcard='[!.]*', recursive=1, prune=('.git', '.svn', 'CVS')): 276 """ Determine a filelist """ 277 for dirpath, dirnames, filenames in walk(native(base)): 278 for item in prune: 279 if item in dirnames: 280 dirnames.remove(item) 281 282 filenames.sort() 283 for name in _fnmatch.filter(filenames, wildcard): 284 dest = _os.path.join(dirpath, name) 285 if dest.startswith(cwd): 286 dest = dest.replace(cwd, '', 1) 287 aslist = [] 288 head, tail = _os.path.split(dest) 289 while tail: 290 aslist.append(tail) 291 head, tail = _os.path.split(head) 292 aslist.reverse() 293 dest = '/'.join(aslist) 294 yield dest 295 296 if not recursive: 297 break 298 dirnames.sort() 299 300 301 def dirs(base, wildcard='[!.]*', recursive=1, prune=('.git', '.svn', 'CVS')): 302 """ Determine a filelist """ 303 for dirpath, dirnames, filenames in walk(native(base)): 304 for item in prune: 305 if item in dirnames: 306 dirnames.remove(item) 307 308 dirnames.sort() 309 for name in _fnmatch.filter(dirnames, wildcard): 310 dest = _os.path.join(dirpath, name) 311 if dest.startswith(cwd): 312 dest = dest.replace(cwd, '', 1) 313 aslist = [] 314 head, tail = _os.path.split(dest) 315 while tail: 316 aslist.append(tail) 317 head, tail = _os.path.split(head) 318 aslist.reverse() 319 dest = '/'.join(aslist) 320 yield dest 321 322 if not recursive: 323 break 324 325 326 def frompath(executable): 327 """ Find executable in PATH """ 328 # Based on distutils.spawn.find_executable. 329 path = _os.environ.get('PATH', '') 330 paths = [ 331 _os.path.expanduser(item) 332 for item in path.split(_os.pathsep) 333 ] 334 ext = _os.path.splitext(executable)[1] 335 exts = [''] 336 if _sys.platform == 'win32' or _os.name == 'os2': 337 eext = ['.exe', '.bat', '.py'] 338 if ext not in eext: 339 exts.extend(eext) 340 341 for ext in exts: 342 if not _os.path.isfile(executable + ext): 343 for path in paths: 344 fname = _os.path.join(path, executable + ext) 345 if _os.path.isfile(fname): 346 # the file exists, we have a shot at spawn working 347 return fname 348 else: 349 return executable + ext 350 351 return None 352