1#!/usr/bin/env python3
2
3r"""
4See help text for details.
5"""
6
7import re
8import subprocess
9import sys
10
11save_dir_path = sys.path.pop(0)
12
13modules = [
14    "gen_arg",
15    "gen_print",
16    "gen_valid",
17    "gen_misc",
18    "gen_cmd",
19    "var_funcs",
20]
21for module in modules:
22    exec("from " + module + " import *")
23
24sys.path.insert(0, save_dir_path)
25
26parser = argparse.ArgumentParser(
27    usage="%(prog)s [OPTIONS]",
28    description="%(prog)s will create a status file path name adhering to the"
29    + " following pattern: <status dir path>/<prefix>.yymmdd."
30    + "hhmmss.status.  It will then run the command string and"
31    + " direct its stdout/stderr to the status file and optionally"
32    + " to stdout.  This dual output streaming will be"
33    + ' accomplished using either the "script" or the "tee"'
34    + " program.  %(prog)s will also set and export environment"
35    + ' variable "AUTO_STATUS_FILE_PATH" for the benefit of'
36    + " child programs.",
37    formatter_class=argparse.ArgumentDefaultsHelpFormatter,
38    prefix_chars="-+",
39)
40
41parser.add_argument(
42    "--status_dir_path",
43    default="",
44    help="The path to the directory where the status file will be created."
45    + "%(default)s The default value is obtained from environment"
46    + ' variable "${STATUS_DIR_PATH}", if set or from "${HOME}/'
47    + 'status/".',
48)
49
50parser.add_argument(
51    "--prefix",
52    default="",
53    help="The prefix for the generated file name.%(default)s The default value"
54    + " is the command portion (i.e. the first token) of the command"
55    + " string.",
56)
57
58parser.add_argument(
59    "--status_file_name",
60    default="",
61    help="This allows the user to explicitly specify the status file name.  If"
62    + " this argument is not used, %(prog)s composes a status file name."
63    + '  If this argument is specified, the "--prefix" argument is'
64    + " ignored.",
65)
66
67parser.add_argument(
68    "--stdout",
69    default=1,
70    type=int,
71    choices=[1, 0],
72    help="Indicates that stdout/stderr from the command string execution"
73    + " should be written to stdout as well as to the status file.",
74)
75
76parser.add_argument(
77    "--tee",
78    default=1,
79    type=int,
80    choices=[1, 0],
81    help='Indicates that "tee" rather than "script" should be used.',
82)
83
84parser.add_argument(
85    "--show_url",
86    default=0,
87    type=int,
88    choices=[1, 0],
89    help="Indicates that the status file path shown should be shown in the"
90    + " form of a url.  If the output is to be viewed from a browser,"
91    + " this may well become a clickable link.  Note that the"
92    + ' get_file_path_url.py program must be found in the "PATH"'
93    + " environment variable for this argument to be effective.",
94)
95
96parser.add_argument(
97    "command_string",
98    default="",
99    nargs="*",
100    help="The command string to be run.%(default)s",
101)
102
103# Populate stock_list with options we want.
104stock_list = [("test_mode", 0), ("quiet", 1), ("debug", 0)]
105
106
107def validate_parms():
108    r"""
109    Validate program parameters, etc.
110    """
111
112    global status_dir_path
113    global command_string
114
115    # Convert command_string from list to string.
116    command_string = " ".join(command_string)
117    set_pgm_arg(command_string)
118    valid_value(command_string)
119
120    if status_dir_path == "":
121        status_dir_path = os.environ.get(
122            "STATUS_DIR_PATH", os.environ.get("HOME") + "/status/"
123        )
124    status_dir_path = add_trailing_slash(status_dir_path)
125    set_pgm_arg(status_dir_path)
126    valid_dir_path(status_dir_path)
127
128    global prefix
129    global status_file_name
130    if status_file_name == "":
131        if prefix == "":
132            prefix = command_string.split(" ")[0]
133            # File extensions (e.g. ".sh", ".py", .etc), look clumsy in status file names.
134            extension_regex = "\\.[a-zA-Z0-9]{1,3}$"
135            prefix = re.sub(extension_regex, "", prefix)
136            set_pgm_arg(prefix)
137        status_file_name = prefix + "." + file_date_time_stamp() + ".status"
138        set_pgm_arg(status_file_name)
139
140    global status_file_path
141
142    status_file_path = status_dir_path + status_file_name
143    # Set environment variable for the benefit of child programs.
144    os.environ["AUTO_STATUS_FILE_PATH"] = status_file_path
145    # Set deprecated but still used AUTOSCRIPT_STATUS_FILE_PATH value.
146    os.environ["AUTOSCRIPT_STATUS_FILE_PATH"] = status_file_path
147
148
149def script_func(command_string, status_file_path):
150    r"""
151    Run the command string producing both stdout and file output via the script command and return the
152    shell_rc.
153
154    Description of argument(s):
155    command_string                  The command string to be run.
156    status_file_path                The path to the status file which is to contain a copy of all stdout.
157    """
158
159    cmd_buf = (
160        "script -a -q -f "
161        + status_file_path
162        + " -c '"
163        + escape_bash_quotes(command_string)
164        + ' ; printf "\\n'
165        + sprint_varx(ret_code_str, "${?}").rstrip("\n")
166        + "\\n\"'"
167    )
168    qprint_issuing(cmd_buf)
169    sub_proc = subprocess.Popen(cmd_buf, shell=True)
170    sub_proc.communicate()
171    shell_rc = sub_proc.returncode
172
173    # Retrieve return code by examining ret_code_str output statement from status file.
174    # Example text to be analyzed.
175    # auto_status_file_ret_code:                        127
176    cmd_buf = (
177        "tail -n 10 "
178        + status_file_path
179        + ' | egrep -a "'
180        + ret_code_str
181        + ':[ ]+"'
182    )
183    rc, output = shell_cmd(cmd_buf)
184    key, value = parse_key_value(output)
185    shell_rc = int(value)
186
187    return shell_rc
188
189
190def tee_func(command_string, status_file_path):
191    r"""
192    Run the command string producing both stdout and file output via the tee command and return the shell_rc.
193
194    Description of argument(s):
195    command_string                  The command string to be run.
196    status_file_path                The path to the status file which is to contain a copy of all stdout.
197    """
198
199    cmd_buf = (
200        "set -o pipefail ; "
201        + command_string
202        + " 2>&1 | tee -a "
203        + status_file_path
204    )
205    qprint_issuing(cmd_buf)
206    sub_proc = subprocess.Popen(cmd_buf, shell=True)
207    sub_proc.communicate()
208    shell_rc = sub_proc.returncode
209
210    print
211    print_varx(ret_code_str, shell_rc)
212    with open(status_file_path, "a") as status_file:
213        # Append ret code string and status_file_path to end of status file.
214        status_file.write("\n" + sprint_varx(ret_code_str, shell_rc))
215
216    return shell_rc
217
218
219def main():
220    gen_setup()
221
222    set_term_options(
223        term_requests={"pgm_names": [command_string.split(" ")[0]]}
224    )
225
226    global ret_code_str
227    ret_code_str = re.sub("\\.py$", "", pgm_name) + "_ret_code"
228
229    global show_url
230    if show_url:
231        shell_rc, output = shell_cmd("which get_file_path_url.py", show_err=0)
232        if shell_rc != 0:
233            show_url = 0
234            set_pgm_arg(show_url)
235        else:
236            shell_rc, status_file_url = shell_cmd(
237                "get_file_path_url.py " + status_file_path
238            )
239            status_file_url = status_file_url.rstrip("\n")
240
241    # Print status file path/url to stdout and to status file.
242    with open(status_file_path, "w+") as status_file:
243        if show_url:
244            print_var(status_file_url)
245            status_file.write(sprint_var(status_file_url))
246        else:
247            print_var(status_file_path)
248            status_file.write(sprint_var(status_file_path))
249
250    if stdout:
251        if tee:
252            shell_rc = tee_func(command_string, status_file_path)
253        else:
254            shell_rc = script_func(command_string, status_file_path)
255        if show_url:
256            print_var(status_file_url)
257        else:
258            print_var(status_file_path)
259    else:
260        cmd_buf = command_string + " >> " + status_file_path + " 2>&1"
261        shell_rc, output = shell_cmd(cmd_buf, show_err=0)
262        with open(status_file_path, "a") as status_file:
263            # Append ret code string and status_file_path to end of status
264            # file.
265            status_file.write("\n" + sprint_varx(ret_code_str, shell_rc))
266
267    # Append status_file_path print statement to end of status file.
268    with open(status_file_path, "a") as status_file:
269        if show_url:
270            status_file.write(sprint_var(status_file_url))
271        else:
272            status_file.write(sprint_var(status_file_path))
273    exit(shell_rc)
274
275
276main()
277