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
104    catch (i.e. TERM, INT).
105    """
106
107    dprint_executing()
108    dprint_var(signal_number)
109
110    qprint_pgm_footer()
111
112
113def signal_handler(signal_number,
114                   frame):
115    r"""
116    Handle signals.  Without a function to catch a SIGTERM or SIGINT, our
117    program would terminate immediately with return code 143 and without
118    calling our exit_function.
119    """
120
121    # Our convention is to set up exit_function with atexit.register() so
122    # there is no need to explicitly call exit_function from here.
123
124    dprint_executing()
125
126    # Calling exit prevents us from returning to the code that was running
127    # when we received the signal.
128    exit(0)
129
130
131def validate_parms():
132    r"""
133    Validate program parameters, etc.
134    """
135
136    global status_dir_path
137    global command_string
138
139    # Convert command_string from list to string.
140    command_string = " ".join(command_string)
141    set_pgm_arg(command_string)
142    valid_value(command_string)
143
144    if status_dir_path == "":
145        status_dir_path = \
146            os.environ.get("STATUS_DIR_PATH",
147                           os.environ.get("HOME") + "/status/")
148    status_dir_path = add_trailing_slash(status_dir_path)
149    set_pgm_arg(status_dir_path)
150    valid_dir_path(status_dir_path)
151
152    global prefix
153    global status_file_name
154    if status_file_name == "":
155        if prefix == "":
156            prefix = command_string.split(" ")[0]
157            # File extensions (e.g. ".sh", ".py", .etc), look clumsy in
158            # status file names.
159            extension_regex = "\\.[a-zA-Z0-9]{1,3}$"
160            prefix = re.sub(extension_regex, "", prefix)
161            set_pgm_arg(prefix)
162        status_file_name = prefix + "." + file_date_time_stamp() + ".status"
163        set_pgm_arg(status_file_name)
164
165    global status_file_path
166
167    status_file_path = status_dir_path + status_file_name
168    # Set environment variable for the benefit of child programs.
169    os.environ['AUTO_STATUS_FILE_PATH'] = status_file_path
170    # Set deprecated but still used AUTOSCRIPT_STATUS_FILE_PATH value.
171    os.environ['AUTOSCRIPT_STATUS_FILE_PATH'] = status_file_path
172
173    gen_post_validation(exit_function, signal_handler)
174
175
176def script_func(command_string, status_file_path):
177    r"""
178    Run the command string producing both stdout and file output via the
179    script command and return the shell_rc.
180
181    Description of argument(s):
182    command_string                  The command string to be run.
183    status_file_path                The path to the status file which is to
184                                    contain a copy of all stdout.
185    """
186
187    cmd_buf = "script -a -q -f " + status_file_path + " -c '" \
188        + escape_bash_quotes(command_string) + " ; printf \"\\n" \
189        + sprint_varx(ret_code_str, "${?}").rstrip("\n") + "\\n\"'"
190    qprint_issuing(cmd_buf)
191    sub_proc = subprocess.Popen(cmd_buf, shell=True)
192    sub_proc.communicate()
193    shell_rc = sub_proc.returncode
194
195    # Retrieve return code by examining ret_code_str output statement from
196    # status file.
197    # Example text to be analyzed.
198    # auto_status_file_ret_code:                        127
199    cmd_buf = "tail -n 10 " + status_file_path + " | egrep -a \"" \
200        + ret_code_str + ":[ ]+\""
201    rc, output = shell_cmd(cmd_buf)
202    key, value = parse_key_value(output)
203    shell_rc = int(value)
204
205    return shell_rc
206
207
208def tee_func(command_string, status_file_path):
209    r"""
210    Run the command string producing both stdout and file output via the tee
211    command and return the shell_rc.
212
213    Description of argument(s):
214    command_string                  The command string to be run.
215    status_file_path                The path to the status file which is to
216                                    contain a copy of all stdout.
217    """
218
219    cmd_buf = "set -o pipefail ; " + command_string + " 2>&1 | tee -a " \
220        + status_file_path
221    qprint_issuing(cmd_buf)
222    sub_proc = subprocess.Popen(cmd_buf, shell=True)
223    sub_proc.communicate()
224    shell_rc = sub_proc.returncode
225
226    print
227    print_varx(ret_code_str, shell_rc)
228    with open(status_file_path, "a") as status_file:
229        # Append ret code string and status_file_path to end of status file.
230        status_file.write("\n" + sprint_varx(ret_code_str, shell_rc))
231
232    return shell_rc
233
234
235def main():
236
237    gen_get_options(parser, stock_list)
238
239    validate_parms()
240
241    qprint_pgm_header()
242
243    global ret_code_str
244    ret_code_str = re.sub("\\.py$", "", pgm_name) + "_ret_code"
245
246    global show_url
247    if show_url:
248        shell_rc, output = shell_cmd("which get_file_path_url.py", show_err=0)
249        if shell_rc != 0:
250            show_url = 0
251            set_pgm_arg(show_url)
252        else:
253            shell_rc, status_file_url = shell_cmd("get_file_path_url.py "
254                                                  + status_file_path)
255            status_file_url = status_file_url.rstrip("\n")
256
257    # Print status file path/url to stdout and to status file.
258    with open(status_file_path, "w+") as status_file:
259        if show_url:
260            print_var(status_file_url)
261            status_file.write(sprint_var(status_file_url))
262        else:
263            print_var(status_file_path)
264            status_file.write(sprint_var(status_file_path))
265
266    if stdout:
267        if tee:
268            shell_rc = tee_func(command_string, status_file_path)
269        else:
270            shell_rc = script_func(command_string, status_file_path)
271        if show_url:
272            print_var(status_file_url)
273        else:
274            print_var(status_file_path)
275    else:
276        cmd_buf = command_string + " >> " + status_file_path + " 2>&1"
277        shell_rc, output = shell_cmd(cmd_buf, show_err=0)
278        with open(status_file_path, "a") as status_file:
279            # Append ret code string and status_file_path to end of status
280            # file.
281            status_file.write("\n" + sprint_varx(ret_code_str, shell_rc))
282
283    # Append status_file_path print statement to end of status file.
284    with open(status_file_path, "a") as status_file:
285        if show_url:
286            status_file.write(sprint_var(status_file_url))
287        else:
288            status_file.write(sprint_var(status_file_path))
289    exit(shell_rc)
290
291
292main()
293