Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | |
| 3 | r""" |
Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 4 | This module provides valuable argument processing functions like gen_get_options and sprint_args. |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 5 | """ |
| 6 | |
| 7 | import sys |
George Keishing | 3b7115a | 2018-08-02 10:48:17 -0500 | [diff] [blame] | 8 | try: |
| 9 | import __builtin__ |
| 10 | except ImportError: |
| 11 | import builtins as __builtin__ |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 12 | import atexit |
| 13 | import signal |
| 14 | import argparse |
| 15 | |
| 16 | import gen_print as gp |
Michael Walsh | 69d58ae | 2018-06-01 15:18:57 -0500 | [diff] [blame] | 17 | import gen_valid as gv |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 18 | |
| 19 | default_string = ' The default value is "%(default)s".' |
| 20 | |
| 21 | |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 22 | def gen_get_options(parser, |
| 23 | stock_list=[]): |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 24 | r""" |
Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 25 | Parse the command line arguments using the parser object passed and return True/False (i.e. pass/fail). |
| 26 | However, if gv.exit_on_error is set, simply exit the program on failure. Also set the following built in |
| 27 | values: |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 28 | |
| 29 | __builtin__.quiet This value is used by the qprint functions. |
| 30 | __builtin__.test_mode This value is used by command processing functions. |
| 31 | __builtin__.debug This value is used by the dprint functions. |
| 32 | __builtin__.arg_obj This value is used by print_program_header, etc. |
| 33 | __builtin__.parser This value is used by print_program_header, etc. |
| 34 | |
| 35 | Description of arguments: |
Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 36 | parser A parser object. See argparse module documentation for details. |
| 37 | stock_list The caller can use this parameter to request certain stock parameters |
| 38 | offered by this function. For example, this function will define a |
| 39 | "quiet" option upon request. This includes stop help text and parm |
| 40 | checking. The stock_list is a list of tuples each of which consists of |
| 41 | an arg_name and a default value. Example: stock_list = [("test_mode", |
| 42 | 0), ("quiet", 1), ("debug", 0)] |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 43 | """ |
| 44 | |
| 45 | # This is a list of stock parms that we support. |
| 46 | master_stock_list = ["quiet", "test_mode", "debug", "loglevel"] |
| 47 | |
| 48 | # Process stock_list. |
| 49 | for ix in range(0, len(stock_list)): |
| 50 | if len(stock_list[ix]) < 1: |
Michael Walsh | 69d58ae | 2018-06-01 15:18:57 -0500 | [diff] [blame] | 51 | error_message = "Programmer error - stock_list[" + str(ix) +\ |
| 52 | "] is supposed to be a tuple containing at" +\ |
| 53 | " least one element which is the name of" +\ |
| 54 | " the desired stock parameter:\n" +\ |
| 55 | gp.sprint_var(stock_list) |
| 56 | return gv.process_error_message(error_message) |
Joy Onyerikwu | 004ad3c | 2018-06-11 16:29:56 -0500 | [diff] [blame] | 57 | if isinstance(stock_list[ix], tuple): |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 58 | arg_name = stock_list[ix][0] |
| 59 | default = stock_list[ix][1] |
| 60 | else: |
| 61 | arg_name = stock_list[ix] |
| 62 | default = None |
| 63 | |
| 64 | if arg_name not in master_stock_list: |
Michael Walsh | 69d58ae | 2018-06-01 15:18:57 -0500 | [diff] [blame] | 65 | error_message = "Programmer error - arg_name \"" + arg_name +\ |
| 66 | "\" not found found in stock list:\n" +\ |
| 67 | gp.sprint_var(master_stock_list) |
| 68 | return gv.process_error_message(error_message) |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 69 | |
| 70 | if arg_name == "quiet": |
| 71 | if default is None: |
| 72 | default = 0 |
| 73 | parser.add_argument( |
| 74 | '--quiet', |
| 75 | default=default, |
| 76 | type=int, |
| 77 | choices=[1, 0], |
Joy Onyerikwu | 004ad3c | 2018-06-11 16:29:56 -0500 | [diff] [blame] | 78 | help='If this parameter is set to "1", %(prog)s' |
| 79 | + ' will print only essential information, i.e. it will' |
| 80 | + ' not echo parameters, echo commands, print the total' |
| 81 | + ' run time, etc.' + default_string) |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 82 | elif arg_name == "test_mode": |
| 83 | if default is None: |
| 84 | default = 0 |
| 85 | parser.add_argument( |
| 86 | '--test_mode', |
| 87 | default=default, |
| 88 | type=int, |
| 89 | choices=[1, 0], |
Joy Onyerikwu | 004ad3c | 2018-06-11 16:29:56 -0500 | [diff] [blame] | 90 | help='This means that %(prog)s should go through all the' |
| 91 | + ' motions but not actually do anything substantial.' |
| 92 | + ' This is mainly to be used by the developer of' |
| 93 | + ' %(prog)s.' + default_string) |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 94 | elif arg_name == "debug": |
| 95 | if default is None: |
| 96 | default = 0 |
| 97 | parser.add_argument( |
| 98 | '--debug', |
| 99 | default=default, |
| 100 | type=int, |
| 101 | choices=[1, 0], |
Joy Onyerikwu | 004ad3c | 2018-06-11 16:29:56 -0500 | [diff] [blame] | 102 | help='If this parameter is set to "1", %(prog)s will print' |
| 103 | + ' additional debug information. This is mainly to be' |
| 104 | + ' used by the developer of %(prog)s.' + default_string) |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 105 | elif arg_name == "loglevel": |
| 106 | if default is None: |
| 107 | default = "info" |
| 108 | parser.add_argument( |
| 109 | '--loglevel', |
| 110 | default=default, |
| 111 | type=str, |
| 112 | choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL', |
| 113 | 'debug', 'info', 'warning', 'error', 'critical'], |
Joy Onyerikwu | 004ad3c | 2018-06-11 16:29:56 -0500 | [diff] [blame] | 114 | help='If this parameter is set to "1", %(prog)s will print' |
| 115 | + ' additional debug information. This is mainly to be' |
| 116 | + ' used by the developer of %(prog)s.' + default_string) |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 117 | |
| 118 | arg_obj = parser.parse_args() |
| 119 | |
| 120 | __builtin__.quiet = 0 |
| 121 | __builtin__.test_mode = 0 |
| 122 | __builtin__.debug = 0 |
| 123 | __builtin__.loglevel = 'WARNING' |
| 124 | for ix in range(0, len(stock_list)): |
Joy Onyerikwu | 004ad3c | 2018-06-11 16:29:56 -0500 | [diff] [blame] | 125 | if isinstance(stock_list[ix], tuple): |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 126 | arg_name = stock_list[ix][0] |
| 127 | default = stock_list[ix][1] |
| 128 | else: |
| 129 | arg_name = stock_list[ix] |
| 130 | default = None |
| 131 | if arg_name == "quiet": |
| 132 | __builtin__.quiet = arg_obj.quiet |
| 133 | elif arg_name == "test_mode": |
| 134 | __builtin__.test_mode = arg_obj.test_mode |
| 135 | elif arg_name == "debug": |
| 136 | __builtin__.debug = arg_obj.debug |
| 137 | elif arg_name == "loglevel": |
| 138 | __builtin__.loglevel = arg_obj.loglevel |
| 139 | |
| 140 | __builtin__.arg_obj = arg_obj |
| 141 | __builtin__.parser = parser |
| 142 | |
Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 143 | # For each command line parameter, create a corresponding global variable and assign it the appropriate |
| 144 | # value. For example, if the command line contained "--last_name='Smith', we'll create a global variable |
| 145 | # named "last_name" with the value "Smith". |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 146 | module = sys.modules['__main__'] |
| 147 | for key in arg_obj.__dict__: |
| 148 | setattr(module, key, getattr(__builtin__.arg_obj, key)) |
| 149 | |
| 150 | return True |
| 151 | |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 152 | |
Michael Walsh | c33ef37 | 2017-01-10 11:46:29 -0600 | [diff] [blame] | 153 | def set_pgm_arg(var_value, |
| 154 | var_name=None): |
Michael Walsh | c33ef37 | 2017-01-10 11:46:29 -0600 | [diff] [blame] | 155 | r""" |
Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 156 | Set the value of the arg_obj.__dict__ entry named in var_name with the var_value provided. Also, set |
| 157 | corresponding global variable. |
Michael Walsh | c33ef37 | 2017-01-10 11:46:29 -0600 | [diff] [blame] | 158 | |
| 159 | Description of arguments: |
| 160 | var_value The value to set in the variable. |
Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 161 | var_name The name of the variable to set. This defaults to the name of the |
| 162 | variable used for var_value when calling this function. |
Michael Walsh | c33ef37 | 2017-01-10 11:46:29 -0600 | [diff] [blame] | 163 | """ |
| 164 | |
| 165 | if var_name is None: |
| 166 | var_name = gp.get_arg_name(None, 1, 2) |
| 167 | |
| 168 | arg_obj.__dict__[var_name] = var_value |
| 169 | module = sys.modules['__main__'] |
| 170 | setattr(module, var_name, var_value) |
| 171 | if var_name == "quiet": |
| 172 | __builtin__.quiet = var_value |
| 173 | elif var_name == "debug": |
| 174 | __builtin__.debug = var_value |
| 175 | elif var_name == "test_mode": |
| 176 | __builtin__.test_mode = var_value |
| 177 | |
Michael Walsh | c33ef37 | 2017-01-10 11:46:29 -0600 | [diff] [blame] | 178 | |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 179 | def sprint_args(arg_obj, |
| 180 | indent=0): |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 181 | r""" |
Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 182 | sprint_var all of the arguments found in arg_obj and return the result as a string. |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 183 | |
| 184 | Description of arguments: |
Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 185 | arg_obj An argument object such as is returned by the argparse parse_args() |
| 186 | method. |
| 187 | indent The number of spaces to indent each line of output. |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 188 | """ |
| 189 | |
Michael Walsh | 0d5f96a | 2019-05-20 10:09:57 -0500 | [diff] [blame] | 190 | col1_width = gp.dft_col1_width + indent |
Michael Walsh | bec416d | 2016-11-10 08:54:52 -0600 | [diff] [blame] | 191 | |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 192 | buffer = "" |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 193 | for key in arg_obj.__dict__: |
Michael Walsh | bec416d | 2016-11-10 08:54:52 -0600 | [diff] [blame] | 194 | buffer += gp.sprint_varx(key, getattr(arg_obj, key), 0, indent, |
Michael Walsh | 0d5f96a | 2019-05-20 10:09:57 -0500 | [diff] [blame] | 195 | col1_width) |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 196 | return buffer |
| 197 | |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 198 | |
Michael Walsh | 5328f38 | 2019-09-13 14:18:55 -0500 | [diff] [blame] | 199 | module = sys.modules["__main__"] |
| 200 | |
| 201 | |
| 202 | def gen_exit_function(signal_number=0, |
| 203 | frame=None): |
| 204 | r""" |
Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 205 | Execute whenever the program ends normally or with the signals that we catch (i.e. TERM, INT). |
Michael Walsh | 5328f38 | 2019-09-13 14:18:55 -0500 | [diff] [blame] | 206 | """ |
| 207 | |
| 208 | gp.dprint_executing() |
| 209 | gp.dprint_var(signal_number) |
| 210 | |
Michael Walsh | e15e7e9 | 2019-11-04 10:40:53 -0600 | [diff] [blame] | 211 | # ignore_err influences the way shell_cmd processes errors. Since we're doing exit processing, we don't |
| 212 | # want to stop the program due to a shell_cmd failure. |
| 213 | ignore_err = 1 |
| 214 | |
Michael Walsh | 5328f38 | 2019-09-13 14:18:55 -0500 | [diff] [blame] | 215 | # Call the main module's exit_function if it is defined. |
| 216 | exit_function = getattr(module, "exit_function", None) |
| 217 | if exit_function: |
| 218 | exit_function(signal_number, frame) |
| 219 | |
| 220 | gp.qprint_pgm_footer() |
| 221 | |
| 222 | |
| 223 | def gen_signal_handler(signal_number, |
| 224 | frame): |
| 225 | r""" |
Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 226 | Handle signals. Without a function to catch a SIGTERM or SIGINT, the program would terminate immediately |
| 227 | with return code 143 and without calling the exit_function. |
Michael Walsh | 5328f38 | 2019-09-13 14:18:55 -0500 | [diff] [blame] | 228 | """ |
| 229 | |
Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 230 | # The convention is to set up exit_function with atexit.register() so there is no need to explicitly |
| 231 | # call exit_function from here. |
Michael Walsh | 5328f38 | 2019-09-13 14:18:55 -0500 | [diff] [blame] | 232 | |
| 233 | gp.dprint_executing() |
| 234 | |
Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 235 | # Calling exit prevents control from returning to the code that was running when the signal was received. |
Michael Walsh | 5328f38 | 2019-09-13 14:18:55 -0500 | [diff] [blame] | 236 | exit(0) |
| 237 | |
| 238 | |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 239 | def gen_post_validation(exit_function=None, |
| 240 | signal_handler=None): |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 241 | r""" |
Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 242 | Do generic post-validation processing. By "post", we mean that this is to be called from a validation |
| 243 | function after the caller has done any validation desired. If the calling program passes exit_function |
| 244 | and signal_handler parms, this function will register them. In other words, it will make the |
| 245 | signal_handler functions get called for SIGINT and SIGTERM and will make the exit_function function run |
| 246 | prior to the termination of the program. |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 247 | |
| 248 | Description of arguments: |
Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 249 | exit_function A function object pointing to the caller's exit function. This defaults |
| 250 | to this module's gen_exit_function. |
| 251 | signal_handler A function object pointing to the caller's signal_handler function. This |
| 252 | defaults to this module's gen_signal_handler. |
Michael Walsh | 7423c01 | 2016-10-04 10:27:21 -0500 | [diff] [blame] | 253 | """ |
| 254 | |
Michael Walsh | 5328f38 | 2019-09-13 14:18:55 -0500 | [diff] [blame] | 255 | # Get defaults. |
| 256 | exit_function = exit_function or gen_exit_function |
| 257 | signal_handler = signal_handler or gen_signal_handler |
| 258 | |
| 259 | atexit.register(exit_function) |
| 260 | signal.signal(signal.SIGINT, signal_handler) |
| 261 | signal.signal(signal.SIGTERM, signal_handler) |
| 262 | |
| 263 | |
| 264 | def gen_setup(): |
| 265 | r""" |
| 266 | Do general setup for a program. |
| 267 | """ |
| 268 | |
| 269 | # Set exit_on_error for gen_valid functions. |
| 270 | gv.set_exit_on_error(True) |
| 271 | |
| 272 | # Get main module variable values. |
| 273 | parser = getattr(module, "parser") |
| 274 | stock_list = getattr(module, "stock_list") |
| 275 | validate_parms = getattr(module, "validate_parms", None) |
| 276 | |
| 277 | gen_get_options(parser, stock_list) |
| 278 | |
| 279 | if validate_parms: |
| 280 | validate_parms() |
| 281 | gen_post_validation() |
| 282 | |
| 283 | gp.qprint_pgm_header() |