1#!/usr/bin/env python3
2
3"""Spawn sub processes."""
4
5import subprocess
6import logging
7
8logger = logging.getLogger()
9
10from chart.common.path import Path
11
12
13class SubprocessError(Exception):
14 """Error from subprocess."""
15 pass
16
17# codec for converting command line output buffers to displayable text
18ASCII_CODEC = 'latin-1'
19
20
21def spawn(command,
22 stdin=None,
23 capture_stdout=False,
24 capture_stderr=False,
25 timeout=None,
26 echo=True,
27 linger=False):
28 """Run a process in a subshell.
29
30 Args:
31 `command` (str, Path or list): Command to run
32 `stdin` (str): String to pass to standard input
33 `capture_stdout' (handle): File-like object to store standard output in
34 `capture_stderr' (handle): File-like object to store standard error
35 `timeout` (int): Terminate process after this number of seconds
36 `echo` (bool): Duplicate outputs to terminal as the process runs
37 `linger` (bool): Leave the subprocess running after this function ends
38
39 Return:
40 Return code, unless `linger` if specified in which case a subprocess object.
41 """
42 if isinstance(command, str) or isinstance(command, Path):
43 command = [command]
44
45 # if any((isinstance(i, basestring) for i in command)):
46 # get rid of any Paths or ints
47 command = [str(i) for i in command]
48
49 if echo:
50 logger.info('> {c}'.format(c=' '.join(command)))
51
52 if capture_stdout or capture_stderr or echo:
53 stdout = subprocess.PIPE
54
55 else:
56 stdout = None
57
58 if capture_stderr or echo:
59 stderr = subprocess.PIPE
60
61 else:
62 stderr = None
63
64 # import os
65 # import sys
66 # sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
67 # sys.stderr = os.fdopen(sys.stderr.fileno(), 'w', 0)
68 # print(command)
69 try:
70 child = subprocess.Popen(command,
71 stdout=stdout,
72 stderr=stderr)
73 except OSError as e:
74 raise SubprocessError('Cannot run "{command}" ({e})'.format(
75 command=' '.join(command), e=e))
76
77 if linger:
78 return child
79
80 if echo:
81 for line in iter(child.stdout.readline, ''):
82 mess = line.decode('latin-1').rstrip()
83 if len(mess) == 0:
84 break
85
86 logger.info('< ' + mess)
87
88 res = child.communicate()
89
90 ret_code = child.returncode
91
92 if capture_stdout or capture_stderr:
93 # if echo and not isinstance(capture_stdout, Path):
94 # for line in res[0].decode(ASCII_CODEC).split('\n'):
95 # if len(line) == 0:
96 # continue
97
98 # logger.info('OUT: {line}'.format(line=line))
99
100 # for line in res[1].decode(ASCII_CODEC).split('\n'):
101 # if len(line) == 0:
102 # continue
103
104 # logger.warn('ERR: {line}'.format(line=line))
105
106 if isinstance(capture_stdout, Path):
107 capture_stdout.open('w').write(str(res[0], ASCII_CODEC))
108 logger.info('Wrote {cc} bytes to {dest}'.format(cc=len(res[0]), dest=capture_stdout))
109
110 # if child.returncod != 0:
111 # raise SubprocessError('Processes returned {exit}'.format(exit=child.returncode))
112
113 return ret_code