Completed
Pull Request — master (#1228)
by Mischa
07:43 queued 01:48
created

coalib.misc.run_shell_command()   A

Complexity

Conditions 2

Size

Total Lines 17

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 2
dl 0
loc 17
rs 9.4285
1
from contextlib import contextmanager
2
import platform
3
from subprocess import Popen, PIPE
4
5
from coalib.parsing.StringProcessing import escape
6
7
8
def prepare_string_argument(string, os=platform.system()):
9
    """
10
    Prepares a string argument for being passed as a parameter on shell.
11
12
    On Linux this function effectively encloses the given string with quotes
13
    (either '' or "", depending on content). On Windows it also escapes \\n
14
    with a caret (`^`).
15
16
    :param string: The string to prepare for shell.
17
    :param os:     The shell platform to prepare string argument for. Possible
18
                   values are "Windows" and "Linux" (others will be ignored and
19
                   return the given string without modification).
20
    :return:       The shell-prepared string.
21
    """
22
    if os == "Windows":
23
        return '"' + escape(escape(string, '\n', '^\n'), '"', '^') + '"'
24
    elif os == "Linux":
25
        return '"' + escape(string, '"') + '"'
26
    else:
27
        return string
28
29
30
def escape_path_argument(path, os=platform.system()):
31
    """
32
    Makes a raw path ready for using as parameter in a shell command (escapes
33
    illegal characters, surrounds with quotes etc.).
34
35
    :param path: The path to make ready for shell.
36
    :param os:   The shell platform to escape the path argument for. Possible
37
                 values are "Windows" and "Linux" (others will be ignored and
38
                 return the given path without modification).
39
    :return:     The escaped path argument.
40
    """
41
    if os == "Windows":
42
        # If a quote (") occurs in path (which is illegal for NTFS file
43
        # systems, but maybe for others), escape it by preceding it with
44
        # a caret (^).
45
        return '"' + escape(path, '"', '^') + '"'
46
    elif os == "Linux":
47
        return escape(path, " ")
48
    else:
49
        # Any other non-supported system doesn't get a path escape.
50
        return path
51
52
53
@contextmanager
54
def run_interactive_shell_command(command, **kwargs):
55
    """
56
    Runs a command in shell and provides stdout, stderr and stdin streams.
57
58
    This function creates a context manager that sets up the process, returns
59
    to caller, closes streams and waits for process to exit on leaving.
60
61
    The process is opened in `universal_newlines` mode.
62
63
    :param command: The command to run on shell.
64
    :param kwargs:  Additional keyword arguments to pass to `subprocess.Popen`
65
                    that is used to spawn the process (except `shell`,
66
                    `stdout`, `stderr`, `stdin` and `universal_newlines`, a
67
                    `TypeError` is raised then).
68
    :return:        A context manager yielding the process started from the
69
                    command.
70
    """
71
    process = Popen(command,
72
                    shell=True,
73
                    stdout=PIPE,
74
                    stderr=PIPE,
75
                    stdin=PIPE,
76
                    universal_newlines=True,
77
                    **kwargs)
78
    try:
79
        yield process
80
    finally:
81
        process.stdout.close()
82
        process.stderr.close()
83
        process.stdin.close()
84
        process.wait()
85
86
87
def run_shell_command(command, input=None, **kwargs):
88
    """
89
    Runs a command in shell and returns the read stdout and stderr data.
90
91
    This function waits for the process to exit.
92
93
    :param command: The command to run on shell.
94
    :param input:   Initial input to send to the process.
95
    :param kwargs:  Additional keyword arguments to pass to `subprocess.Popen`
96
                    that is used to spawn the process (except `shell`,
97
                    `stdout`, `stderr`, `stdin` and `universal_newlines`, a
98
                    `TypeError` is raised then).
99
    :return:        A tuple with `(stdoutstring, stderrstring)`.
100
    """
101
    with run_interactive_shell_command(command, **kwargs) as p:
102
        ret = p.communicate(input)
103
    return ret
104