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"""
24__author__ = "Andr\xe9 Malo"
25__docformat__ = "restructuredtext en"
26
27import errno as _errno
28import fnmatch as _fnmatch
29import os as _os
30import shutil as _shutil
31import subprocess as _subprocess
32import sys as _sys
33import tempfile as _tempfile
34
35cwd = _os.path.dirname(_os.path.abspath(_sys.argv[0]))
36
37class 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
45class 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
60def 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
66def cp(src, dest):
67    """ Copy src to dest """
68    _shutil.copy2(native(src), native(dest))
69
70
71def cp_r(src, dest):
72    """ Copy -r src to dest """
73    _shutil.copytree(native(src), native(dest))
74
75
76def 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
84def 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
93mkstemp = _tempfile.mkstemp
94
95
96def _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"""
103import os
104import pickle
105import subprocess
106import sys
107
108argv = pickle.loads(%(argv)s)
109env = pickle.loads(%(env)s)
110if 'X_JYTHON_WA_PATH' in env:
111    env['PATH'] = env['X_JYTHON_WA_PATH']
112
113p = subprocess.Popen(argv, env=env)
114result = p.wait()
115if result < 0:
116    print("\n%%d 1" %% (-result))
117    sys.exit(2)
118if result == 0:
119    sys.exit(0)
120print("\n%%d" %% (result & 7,))
121sys.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
175def _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, (("""
181import os
182import pickle
183import sys
184
185infile = pickle.loads(%(infile)s)
186outfile = pickle.loads(%(outfile)s)
187argv = pickle.loads(%(argv)s)
188env = pickle.loads(%(env)s)
189
190if infile is not None:
191    infile = open(infile, 'rb')
192    os.dup2(infile.fileno(), 0)
193    infile.close()
194if outfile is not None:
195    outfile = open(outfile, 'wb')
196    os.dup2(outfile.fileno(), 1)
197    outfile.close()
198
199pid = os.spawnve(os.P_NOWAIT, argv[0], argv, env)
200result = os.waitpid(pid, 0)[1]
201sys.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
235def 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
272walk = _os.walk
273
274
275def 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
301def 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
326def 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