Home | History | Annotate | Download | only in py3
      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