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