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