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