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