| George Keishing | e7e9171 | 2021-09-03 11:28:44 -0500 | [diff] [blame] | 1 | #!/usr/bin/env python3 | 
| Michael Walsh | ced4eb0 | 2017-09-19 16:49:13 -0500 | [diff] [blame] | 2 |  | 
|  | 3 | r""" | 
|  | 4 | Define variable manipulation functions. | 
|  | 5 | """ | 
|  | 6 |  | 
|  | 7 | import os | 
| Michael Walsh | 05c68d9 | 2017-09-20 16:36:37 -0500 | [diff] [blame] | 8 | import re | 
| Michael Walsh | ced4eb0 | 2017-09-19 16:49:13 -0500 | [diff] [blame] | 9 |  | 
|  | 10 | try: | 
|  | 11 | from robot.utils import DotDict | 
|  | 12 | except ImportError: | 
|  | 13 | pass | 
|  | 14 |  | 
|  | 15 | import collections | 
|  | 16 |  | 
| George Keishing | e635ddc | 2022-12-08 07:38:02 -0600 | [diff] [blame] | 17 | import func_args as fa | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 18 | import gen_misc as gm | 
|  | 19 | import gen_print as gp | 
| Michael Walsh | ced4eb0 | 2017-09-19 16:49:13 -0500 | [diff] [blame] | 20 |  | 
|  | 21 |  | 
|  | 22 | def create_var_dict(*args): | 
| Michael Walsh | ced4eb0 | 2017-09-19 16:49:13 -0500 | [diff] [blame] | 23 | r""" | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 24 | Create a dictionary whose keys/values are the arg names/arg values passed to it and return it to the | 
|  | 25 | caller. | 
| Michael Walsh | ced4eb0 | 2017-09-19 16:49:13 -0500 | [diff] [blame] | 26 |  | 
|  | 27 | Note: The resulting dictionary will be ordered. | 
|  | 28 |  | 
|  | 29 | Description of argument(s): | 
|  | 30 | *args  An unlimited number of arguments to be processed. | 
|  | 31 |  | 
|  | 32 | Example use: | 
|  | 33 |  | 
|  | 34 | first_name = 'Steve' | 
|  | 35 | last_name = 'Smith' | 
|  | 36 | var_dict = create_var_dict(first_name, last_name) | 
|  | 37 |  | 
|  | 38 | gp.print_var(var_dict) | 
|  | 39 |  | 
|  | 40 | The print-out of the resulting var dictionary is: | 
|  | 41 | var_dict: | 
|  | 42 | var_dict[first_name]:                           Steve | 
|  | 43 | var_dict[last_name]:                            Smith | 
|  | 44 | """ | 
|  | 45 |  | 
|  | 46 | try: | 
|  | 47 | result_dict = collections.OrderedDict() | 
|  | 48 | except AttributeError: | 
|  | 49 | result_dict = DotDict() | 
|  | 50 |  | 
|  | 51 | arg_num = 1 | 
|  | 52 | for arg in args: | 
|  | 53 | arg_name = gp.get_arg_name(None, arg_num, stack_frame_ix=2) | 
|  | 54 | result_dict[arg_name] = arg | 
|  | 55 | arg_num += 1 | 
|  | 56 |  | 
|  | 57 | return result_dict | 
|  | 58 |  | 
|  | 59 |  | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 60 | default_record_delim = ":" | 
|  | 61 | default_key_val_delim = "." | 
| Michael Walsh | ced4eb0 | 2017-09-19 16:49:13 -0500 | [diff] [blame] | 62 |  | 
|  | 63 |  | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 64 | def join_dict( | 
|  | 65 | dict, | 
|  | 66 | record_delim=default_record_delim, | 
|  | 67 | key_val_delim=default_key_val_delim, | 
|  | 68 | ): | 
| Michael Walsh | ced4eb0 | 2017-09-19 16:49:13 -0500 | [diff] [blame] | 69 | r""" | 
|  | 70 | Join a dictionary's keys and values into a string and return the string. | 
|  | 71 |  | 
|  | 72 | Description of argument(s): | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 73 | dict                            The dictionary whose keys and values are to be joined. | 
|  | 74 | record_delim                    The delimiter to be used to separate dictionary pairs in the resulting | 
|  | 75 | string. | 
|  | 76 | key_val_delim                   The delimiter to be used to separate keys from values in the resulting | 
|  | 77 | string. | 
| Michael Walsh | ced4eb0 | 2017-09-19 16:49:13 -0500 | [diff] [blame] | 78 |  | 
|  | 79 | Example use: | 
|  | 80 |  | 
|  | 81 | gp.print_var(var_dict) | 
|  | 82 | str1 = join_dict(var_dict) | 
| Michael Walsh | c2762f6 | 2019-05-17 15:21:35 -0500 | [diff] [blame] | 83 | gp.print_var(str1) | 
| Michael Walsh | ced4eb0 | 2017-09-19 16:49:13 -0500 | [diff] [blame] | 84 |  | 
|  | 85 | Program output. | 
|  | 86 | var_dict: | 
|  | 87 | var_dict[first_name]:                           Steve | 
|  | 88 | var_dict[last_name]:                            Smith | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 89 | str1:                                             first_name.Steve:last_name.Smith | 
| Michael Walsh | ced4eb0 | 2017-09-19 16:49:13 -0500 | [diff] [blame] | 90 | """ | 
|  | 91 |  | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 92 | format_str = "%s" + key_val_delim + "%s" | 
|  | 93 | return record_delim.join( | 
|  | 94 | [format_str % (key, value) for (key, value) in dict.items()] | 
|  | 95 | ) | 
| Michael Walsh | ced4eb0 | 2017-09-19 16:49:13 -0500 | [diff] [blame] | 96 |  | 
|  | 97 |  | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 98 | def split_to_dict( | 
|  | 99 | string, | 
|  | 100 | record_delim=default_record_delim, | 
|  | 101 | key_val_delim=default_key_val_delim, | 
|  | 102 | ): | 
| Michael Walsh | ced4eb0 | 2017-09-19 16:49:13 -0500 | [diff] [blame] | 103 | r""" | 
|  | 104 | Split a string into a dictionary and return it. | 
|  | 105 |  | 
|  | 106 | This function is the complement to join_dict. | 
|  | 107 |  | 
|  | 108 | Description of argument(s): | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 109 | string                          The string to be split into a dictionary.  The string must have the | 
|  | 110 | proper delimiters in it.  A string created by join_dict would qualify. | 
|  | 111 | record_delim                    The delimiter to be used to separate dictionary pairs in the input string. | 
|  | 112 | key_val_delim                   The delimiter to be used to separate keys/values in the input string. | 
| Michael Walsh | ced4eb0 | 2017-09-19 16:49:13 -0500 | [diff] [blame] | 113 |  | 
|  | 114 | Example use: | 
|  | 115 |  | 
|  | 116 | gp.print_var(str1) | 
|  | 117 | new_dict = split_to_dict(str1) | 
|  | 118 | gp.print_var(new_dict) | 
|  | 119 |  | 
|  | 120 |  | 
|  | 121 | Program output. | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 122 | str1:                                             first_name.Steve:last_name.Smith | 
| Michael Walsh | ced4eb0 | 2017-09-19 16:49:13 -0500 | [diff] [blame] | 123 | new_dict: | 
|  | 124 | new_dict[first_name]:                           Steve | 
|  | 125 | new_dict[last_name]:                            Smith | 
|  | 126 | """ | 
|  | 127 |  | 
|  | 128 | try: | 
|  | 129 | result_dict = collections.OrderedDict() | 
|  | 130 | except AttributeError: | 
|  | 131 | result_dict = DotDict() | 
|  | 132 |  | 
|  | 133 | raw_keys_values = string.split(record_delim) | 
|  | 134 | for key_value in raw_keys_values: | 
|  | 135 | key_value_list = key_value.split(key_val_delim) | 
|  | 136 | try: | 
|  | 137 | result_dict[key_value_list[0]] = key_value_list[1] | 
|  | 138 | except IndexError: | 
|  | 139 | result_dict[key_value_list[0]] = "" | 
|  | 140 |  | 
|  | 141 | return result_dict | 
|  | 142 |  | 
|  | 143 |  | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 144 | def create_file_path(file_name_dict, dir_path="/tmp/", file_suffix=""): | 
| Michael Walsh | ced4eb0 | 2017-09-19 16:49:13 -0500 | [diff] [blame] | 145 | r""" | 
|  | 146 | Create a file path using the given parameters and return it. | 
|  | 147 |  | 
|  | 148 | Description of argument(s): | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 149 | file_name_dict                  A dictionary with keys/values which are to appear as part of the file | 
|  | 150 | name. | 
|  | 151 | dir_path                        The dir_path that is to appear as part of the file name. | 
|  | 152 | file_suffix                     A suffix to be included as part of the file name. | 
| Michael Walsh | ced4eb0 | 2017-09-19 16:49:13 -0500 | [diff] [blame] | 153 | """ | 
|  | 154 |  | 
|  | 155 | dir_path = gm.add_trailing_slash(dir_path) | 
|  | 156 | return dir_path + join_dict(file_name_dict) + file_suffix | 
|  | 157 |  | 
|  | 158 |  | 
|  | 159 | def parse_file_path(file_path): | 
| Michael Walsh | ced4eb0 | 2017-09-19 16:49:13 -0500 | [diff] [blame] | 160 | r""" | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 161 | Parse a file path created by create_file_path and return the result as a dictionary. | 
| Michael Walsh | ced4eb0 | 2017-09-19 16:49:13 -0500 | [diff] [blame] | 162 |  | 
|  | 163 | This function is the complement to create_file_path. | 
|  | 164 |  | 
|  | 165 | Description of argument(s): | 
|  | 166 | file_path                       The file_path. | 
|  | 167 |  | 
|  | 168 | Example use: | 
| Michael Walsh | c2762f6 | 2019-05-17 15:21:35 -0500 | [diff] [blame] | 169 | gp.print_var(boot_results_file_path) | 
| Michael Walsh | ced4eb0 | 2017-09-19 16:49:13 -0500 | [diff] [blame] | 170 | file_path_data = parse_file_path(boot_results_file_path) | 
| Michael Walsh | c2762f6 | 2019-05-17 15:21:35 -0500 | [diff] [blame] | 171 | gp.print_var(file_path_data) | 
| Michael Walsh | ced4eb0 | 2017-09-19 16:49:13 -0500 | [diff] [blame] | 172 |  | 
|  | 173 | Program output. | 
|  | 174 |  | 
|  | 175 | boot_results_file_path: | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 176 | /tmp/pgm_name.obmc_boot_test:openbmc_nickname.beye6:master_pid.2039:boot_results | 
| Michael Walsh | ced4eb0 | 2017-09-19 16:49:13 -0500 | [diff] [blame] | 177 | file_path_data: | 
|  | 178 | file_path_data[dir_path]:                       /tmp/ | 
|  | 179 | file_path_data[pgm_name]:                       obmc_boot_test | 
|  | 180 | file_path_data[openbmc_nickname]:               beye6 | 
|  | 181 | file_path_data[master_pid]:                     2039 | 
|  | 182 | file_path_data[boot_results]: | 
|  | 183 | """ | 
|  | 184 |  | 
|  | 185 | try: | 
|  | 186 | result_dict = collections.OrderedDict() | 
|  | 187 | except AttributeError: | 
|  | 188 | result_dict = DotDict() | 
|  | 189 |  | 
|  | 190 | dir_path = os.path.dirname(file_path) + os.sep | 
|  | 191 | file_path = os.path.basename(file_path) | 
|  | 192 |  | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 193 | result_dict["dir_path"] = dir_path | 
| Michael Walsh | ced4eb0 | 2017-09-19 16:49:13 -0500 | [diff] [blame] | 194 |  | 
|  | 195 | result_dict.update(split_to_dict(file_path)) | 
|  | 196 |  | 
|  | 197 | return result_dict | 
| Michael Walsh | 05c68d9 | 2017-09-20 16:36:37 -0500 | [diff] [blame] | 198 |  | 
|  | 199 |  | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 200 | def parse_key_value(string, delim=":", strip=" ", to_lower=1, underscores=1): | 
| Michael Walsh | 05c68d9 | 2017-09-20 16:36:37 -0500 | [diff] [blame] | 201 | r""" | 
|  | 202 | Parse a key/value string and return as a key/value tuple. | 
|  | 203 |  | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 204 | This function is useful for parsing a line of program output or data that is in the following form: | 
| Michael Walsh | 05c68d9 | 2017-09-20 16:36:37 -0500 | [diff] [blame] | 205 | <key or variable name><delimiter><value> | 
|  | 206 |  | 
|  | 207 | An example of a key/value string would be as follows: | 
|  | 208 |  | 
|  | 209 | Current Limit State: No Active Power Limit | 
|  | 210 |  | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 211 | In the example shown, the delimiter is ":".  The resulting key would be as follows: | 
| Michael Walsh | 05c68d9 | 2017-09-20 16:36:37 -0500 | [diff] [blame] | 212 | Current Limit State | 
|  | 213 |  | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 214 | Note: If one were to take the default values of to_lower=1 and underscores=1, the resulting key would be | 
|  | 215 | as follows: | 
| Michael Walsh | 05c68d9 | 2017-09-20 16:36:37 -0500 | [diff] [blame] | 216 | current_limit_state | 
|  | 217 |  | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 218 | The to_lower and underscores arguments are provided for those who wish to have their key names have the | 
|  | 219 | look and feel of python variable names. | 
| Michael Walsh | 05c68d9 | 2017-09-20 16:36:37 -0500 | [diff] [blame] | 220 |  | 
|  | 221 | The resulting value for the example above would be as follows: | 
|  | 222 | No Active Power Limit | 
|  | 223 |  | 
|  | 224 | Another example: | 
|  | 225 | name=Mike | 
|  | 226 |  | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 227 | In this case, the delim would be "=", the key is "name" and the value is "Mike". | 
| Michael Walsh | 05c68d9 | 2017-09-20 16:36:37 -0500 | [diff] [blame] | 228 |  | 
|  | 229 | Description of argument(s): | 
|  | 230 | string                          The string to be parsed. | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 231 | delim                           The delimiter which separates the key from the value. | 
|  | 232 | strip                           The characters (if any) to strip from the beginning and end of both the | 
|  | 233 | key and the value. | 
| Michael Walsh | 05c68d9 | 2017-09-20 16:36:37 -0500 | [diff] [blame] | 234 | to_lower                        Change the key name to lower case. | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 235 | underscores                     Change any blanks found in the key name to underscores. | 
| Michael Walsh | 05c68d9 | 2017-09-20 16:36:37 -0500 | [diff] [blame] | 236 | """ | 
|  | 237 |  | 
|  | 238 | pair = string.split(delim) | 
|  | 239 |  | 
|  | 240 | key = pair[0].strip(strip) | 
|  | 241 | if len(pair) == 0: | 
|  | 242 | value = "" | 
|  | 243 | else: | 
| MICHAEL J. WALSH | 9509a0f | 2018-02-08 11:08:52 -0600 | [diff] [blame] | 244 | value = delim.join(pair[1:]).strip(strip) | 
| Michael Walsh | 05c68d9 | 2017-09-20 16:36:37 -0500 | [diff] [blame] | 245 |  | 
|  | 246 | if to_lower: | 
|  | 247 | key = key.lower() | 
|  | 248 | if underscores: | 
|  | 249 | key = re.sub(r" ", "_", key) | 
|  | 250 |  | 
|  | 251 | return key, value | 
|  | 252 |  | 
|  | 253 |  | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 254 | def key_value_list_to_dict(key_value_list, process_indent=0, **args): | 
| Michael Walsh | 05c68d9 | 2017-09-20 16:36:37 -0500 | [diff] [blame] | 255 | r""" | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 256 | Convert a list containing key/value strings or tuples to a dictionary and return it. | 
| Michael Walsh | 05c68d9 | 2017-09-20 16:36:37 -0500 | [diff] [blame] | 257 |  | 
|  | 258 | See docstring of parse_key_value function for details on key/value strings. | 
|  | 259 |  | 
|  | 260 | Example usage: | 
|  | 261 |  | 
| Michael Walsh | 19df7aa | 2019-12-16 17:17:39 -0600 | [diff] [blame] | 262 | For the following value of key_value_list: | 
| Michael Walsh | 05c68d9 | 2017-09-20 16:36:37 -0500 | [diff] [blame] | 263 |  | 
| Michael Walsh | 19df7aa | 2019-12-16 17:17:39 -0600 | [diff] [blame] | 264 | key_value_list: | 
|  | 265 | [0]:          Current Limit State: No Active Power Limit | 
|  | 266 | [1]:          Exception actions:   Hard Power Off & Log Event to SEL | 
|  | 267 | [2]:          Power Limit:         0 Watts | 
|  | 268 | [3]:          Correction time:     0 milliseconds | 
|  | 269 | [4]:          Sampling period:     0 seconds | 
| Michael Walsh | 05c68d9 | 2017-09-20 16:36:37 -0500 | [diff] [blame] | 270 |  | 
|  | 271 | And the following call in python: | 
|  | 272 |  | 
| Michael Walsh | 19df7aa | 2019-12-16 17:17:39 -0600 | [diff] [blame] | 273 | power_limit = key_value_outbuf_to_dict(key_value_list) | 
| Michael Walsh | 05c68d9 | 2017-09-20 16:36:37 -0500 | [diff] [blame] | 274 |  | 
|  | 275 | The resulting power_limit directory would look like this: | 
|  | 276 |  | 
|  | 277 | power_limit: | 
|  | 278 | [current_limit_state]:        No Active Power Limit | 
|  | 279 | [exception_actions]:          Hard Power Off & Log Event to SEL | 
|  | 280 | [power_limit]:                0 Watts | 
|  | 281 | [correction_time]:            0 milliseconds | 
|  | 282 | [sampling_period]:            0 seconds | 
|  | 283 |  | 
| Michael Walsh | 1db8687 | 2019-04-16 11:48:25 -0500 | [diff] [blame] | 284 | For the following list: | 
|  | 285 |  | 
|  | 286 | headers: | 
|  | 287 | headers[0]: | 
|  | 288 | headers[0][0]:           content-length | 
|  | 289 | headers[0][1]:           559 | 
|  | 290 | headers[1]: | 
|  | 291 | headers[1][0]:           x-xss-protection | 
|  | 292 | headers[1][1]:           1; mode=block | 
|  | 293 |  | 
|  | 294 | And the following call in python: | 
|  | 295 |  | 
|  | 296 | headers_dict = key_value_list_to_dict(headers) | 
|  | 297 |  | 
|  | 298 | The resulting headers_dict would look like this: | 
|  | 299 |  | 
|  | 300 | headers_dict: | 
|  | 301 | [content-length]:          559 | 
|  | 302 | [x-xss-protection]:        1; mode=block | 
|  | 303 |  | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 304 | Another example containing a sub-list (see process_indent description below): | 
| Michael Walsh | cad0713 | 2018-02-19 17:28:01 -0600 | [diff] [blame] | 305 |  | 
|  | 306 | Provides Device SDRs      : yes | 
|  | 307 | Additional Device Support : | 
|  | 308 | Sensor Device | 
|  | 309 | SEL Device | 
|  | 310 | FRU Inventory Device | 
|  | 311 | Chassis Device | 
|  | 312 |  | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 313 | Note that the 2 qualifications for containing a sub-list are met: 1) 'Additional Device Support' has no | 
|  | 314 | value and 2) The entries below it are indented.  In this case those entries contain no delimiters (":") | 
|  | 315 | so they will be processed as a list rather than as a dictionary.  The result would be as follows: | 
| Michael Walsh | cad0713 | 2018-02-19 17:28:01 -0600 | [diff] [blame] | 316 |  | 
|  | 317 | mc_info: | 
|  | 318 | mc_info[provides_device_sdrs]:            yes | 
|  | 319 | mc_info[additional_device_support]: | 
|  | 320 | mc_info[additional_device_support][0]:  Sensor Device | 
|  | 321 | mc_info[additional_device_support][1]:  SEL Device | 
|  | 322 | mc_info[additional_device_support][2]:  FRU Inventory Device | 
|  | 323 | mc_info[additional_device_support][3]:  Chassis Device | 
|  | 324 |  | 
| Michael Walsh | 05c68d9 | 2017-09-20 16:36:37 -0500 | [diff] [blame] | 325 | Description of argument(s): | 
| Michael Walsh | 19df7aa | 2019-12-16 17:17:39 -0600 | [diff] [blame] | 326 | key_value_list                  A list of key/value strings.  (See docstring of parse_key_value function | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 327 | for details). | 
|  | 328 | process_indent                  This indicates that indented sub-dictionaries and sub-lists are to be | 
|  | 329 | processed as such.  An entry may have a sub-dict or sub-list if 1) It has | 
|  | 330 | no value other than blank 2) There are entries below it that are | 
|  | 331 | indented.  Note that process_indent is not allowed for a list of tuples | 
|  | 332 | (vs. a list of key/value strings). | 
|  | 333 | **args                          Arguments to be interpreted by parse_key_value.  (See docstring of | 
| Michael Walsh | 05c68d9 | 2017-09-20 16:36:37 -0500 | [diff] [blame] | 334 | parse_key_value function for details). | 
|  | 335 | """ | 
|  | 336 |  | 
|  | 337 | try: | 
|  | 338 | result_dict = collections.OrderedDict() | 
|  | 339 | except AttributeError: | 
|  | 340 | result_dict = DotDict() | 
|  | 341 |  | 
| Michael Walsh | cad0713 | 2018-02-19 17:28:01 -0600 | [diff] [blame] | 342 | if not process_indent: | 
| Michael Walsh | 19df7aa | 2019-12-16 17:17:39 -0600 | [diff] [blame] | 343 | for entry in key_value_list: | 
| Michael Walsh | 1db8687 | 2019-04-16 11:48:25 -0500 | [diff] [blame] | 344 | if type(entry) is tuple: | 
|  | 345 | key, value = entry | 
|  | 346 | else: | 
|  | 347 | key, value = parse_key_value(entry, **args) | 
| Michael Walsh | cad0713 | 2018-02-19 17:28:01 -0600 | [diff] [blame] | 348 | result_dict[key] = value | 
|  | 349 | return result_dict | 
|  | 350 |  | 
|  | 351 | # Process list while paying heed to indentation. | 
|  | 352 | delim = args.get("delim", ":") | 
|  | 353 | # Initialize "parent_" indentation level variables. | 
| Michael Walsh | 19df7aa | 2019-12-16 17:17:39 -0600 | [diff] [blame] | 354 | parent_indent = len(key_value_list[0]) - len(key_value_list[0].lstrip()) | 
| Michael Walsh | cad0713 | 2018-02-19 17:28:01 -0600 | [diff] [blame] | 355 | sub_list = [] | 
| Michael Walsh | 19df7aa | 2019-12-16 17:17:39 -0600 | [diff] [blame] | 356 | for entry in key_value_list: | 
| Michael Walsh | c1dfc78 | 2017-09-26 16:08:51 -0500 | [diff] [blame] | 357 | key, value = parse_key_value(entry, **args) | 
| Michael Walsh | cad0713 | 2018-02-19 17:28:01 -0600 | [diff] [blame] | 358 |  | 
|  | 359 | indent = len(entry) - len(entry.lstrip()) | 
|  | 360 |  | 
|  | 361 | if indent > parent_indent and parent_value == "": | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 362 | # This line is indented compared to the parent entry and the parent entry has no value. | 
| Michael Walsh | cad0713 | 2018-02-19 17:28:01 -0600 | [diff] [blame] | 363 | # Append the entry to sub_list for later processing. | 
|  | 364 | sub_list.append(str(entry)) | 
|  | 365 | continue | 
|  | 366 |  | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 367 | # Process any outstanding sub_list and add it to result_dict[parent_key]. | 
| Michael Walsh | cad0713 | 2018-02-19 17:28:01 -0600 | [diff] [blame] | 368 | if len(sub_list) > 0: | 
|  | 369 | if any(delim in word for word in sub_list): | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 370 | # If delim is found anywhere in the sub_list, we'll process as a sub-dictionary. | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 371 | result_dict[parent_key] = key_value_list_to_dict( | 
|  | 372 | sub_list, **args | 
|  | 373 | ) | 
| Michael Walsh | cad0713 | 2018-02-19 17:28:01 -0600 | [diff] [blame] | 374 | else: | 
| Michael Walsh | 19df7aa | 2019-12-16 17:17:39 -0600 | [diff] [blame] | 375 | result_dict[parent_key] = list(map(str.strip, sub_list)) | 
| Michael Walsh | cad0713 | 2018-02-19 17:28:01 -0600 | [diff] [blame] | 376 | del sub_list[:] | 
|  | 377 |  | 
| Michael Walsh | 05c68d9 | 2017-09-20 16:36:37 -0500 | [diff] [blame] | 378 | result_dict[key] = value | 
|  | 379 |  | 
| Michael Walsh | cad0713 | 2018-02-19 17:28:01 -0600 | [diff] [blame] | 380 | parent_key = key | 
|  | 381 | parent_value = value | 
|  | 382 | parent_indent = indent | 
|  | 383 |  | 
|  | 384 | # Any outstanding sub_list to be processed? | 
|  | 385 | if len(sub_list) > 0: | 
|  | 386 | if any(delim in word for word in sub_list): | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 387 | # If delim is found anywhere in the sub_list, we'll process as a sub-dictionary. | 
| Michael Walsh | cad0713 | 2018-02-19 17:28:01 -0600 | [diff] [blame] | 388 | result_dict[parent_key] = key_value_list_to_dict(sub_list, **args) | 
|  | 389 | else: | 
| Michael Walsh | 19df7aa | 2019-12-16 17:17:39 -0600 | [diff] [blame] | 390 | result_dict[parent_key] = list(map(str.strip, sub_list)) | 
| Michael Walsh | cad0713 | 2018-02-19 17:28:01 -0600 | [diff] [blame] | 391 |  | 
| Michael Walsh | 05c68d9 | 2017-09-20 16:36:37 -0500 | [diff] [blame] | 392 | return result_dict | 
|  | 393 |  | 
|  | 394 |  | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 395 | def key_value_outbuf_to_dict(out_buf, **args): | 
| Michael Walsh | 05c68d9 | 2017-09-20 16:36:37 -0500 | [diff] [blame] | 396 | r""" | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 397 | Convert a buffer with a key/value string on each line to a dictionary and return it. | 
| Michael Walsh | 05c68d9 | 2017-09-20 16:36:37 -0500 | [diff] [blame] | 398 |  | 
|  | 399 | Each line in the out_buf should end with a \n. | 
|  | 400 |  | 
|  | 401 | See docstring of parse_key_value function for details on key/value strings. | 
|  | 402 |  | 
|  | 403 | Example usage: | 
|  | 404 |  | 
|  | 405 | For the following value of out_buf: | 
|  | 406 |  | 
|  | 407 | Current Limit State: No Active Power Limit | 
|  | 408 | Exception actions:   Hard Power Off & Log Event to SEL | 
|  | 409 | Power Limit:         0 Watts | 
|  | 410 | Correction time:     0 milliseconds | 
|  | 411 | Sampling period:     0 seconds | 
|  | 412 |  | 
|  | 413 | And the following call in python: | 
|  | 414 |  | 
|  | 415 | power_limit = key_value_outbuf_to_dict(out_buf) | 
|  | 416 |  | 
|  | 417 | The resulting power_limit directory would look like this: | 
|  | 418 |  | 
|  | 419 | power_limit: | 
|  | 420 | [current_limit_state]:        No Active Power Limit | 
|  | 421 | [exception_actions]:          Hard Power Off & Log Event to SEL | 
|  | 422 | [power_limit]:                0 Watts | 
|  | 423 | [correction_time]:            0 milliseconds | 
|  | 424 | [sampling_period]:            0 seconds | 
|  | 425 |  | 
|  | 426 | Description of argument(s): | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 427 | out_buf                         A buffer with a key/value string on each line. (See docstring of | 
|  | 428 | parse_key_value function for details). | 
|  | 429 | **args                          Arguments to be interpreted by parse_key_value.  (See docstring of | 
| Michael Walsh | 05c68d9 | 2017-09-20 16:36:37 -0500 | [diff] [blame] | 430 | parse_key_value function for details). | 
|  | 431 | """ | 
|  | 432 |  | 
|  | 433 | # Create key_var_list and remove null entries. | 
|  | 434 | key_var_list = list(filter(None, out_buf.split("\n"))) | 
| Michael Walsh | c1dfc78 | 2017-09-26 16:08:51 -0500 | [diff] [blame] | 435 | return key_value_list_to_dict(key_var_list, **args) | 
| Michael Walsh | db560d4 | 2017-11-20 16:42:49 -0600 | [diff] [blame] | 436 |  | 
|  | 437 |  | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 438 | def key_value_outbuf_to_dicts(out_buf, **args): | 
| Michael Walsh | 79af439 | 2019-12-09 11:42:48 -0600 | [diff] [blame] | 439 | r""" | 
|  | 440 | Convert a buffer containing multiple sections with key/value strings on each line to a list of | 
|  | 441 | dictionaries and return it. | 
|  | 442 |  | 
|  | 443 | Sections in the output are delimited by blank lines. | 
|  | 444 |  | 
|  | 445 | Example usage: | 
|  | 446 |  | 
|  | 447 | For the following value of out_buf: | 
|  | 448 |  | 
|  | 449 | Maximum User IDs     : 15 | 
|  | 450 | Enabled User IDs     : 1 | 
|  | 451 |  | 
|  | 452 | User ID              : 1 | 
|  | 453 | User Name            : root | 
|  | 454 | Fixed Name           : No | 
|  | 455 | Access Available     : callback | 
|  | 456 | Link Authentication  : enabled | 
|  | 457 | IPMI Messaging       : enabled | 
|  | 458 | Privilege Level      : ADMINISTRATOR | 
|  | 459 | Enable Status        : enabled | 
|  | 460 |  | 
|  | 461 | User ID              : 2 | 
|  | 462 | User Name            : | 
|  | 463 | Fixed Name           : No | 
|  | 464 | Access Available     : call-in / callback | 
|  | 465 | Link Authentication  : disabled | 
|  | 466 | IPMI Messaging       : disabled | 
|  | 467 | Privilege Level      : NO ACCESS | 
|  | 468 | Enable Status        : disabled | 
|  | 469 |  | 
|  | 470 | And the following call in python: | 
|  | 471 |  | 
|  | 472 | user_info = key_value_outbuf_to_dicts(out_buf) | 
|  | 473 |  | 
|  | 474 | The resulting user_info list would look like this: | 
|  | 475 |  | 
|  | 476 | user_info: | 
|  | 477 | [0]: | 
|  | 478 | [maximum_user_ids]:      15 | 
|  | 479 | [enabled_user_ids]:      1 | 
|  | 480 | [1]: | 
|  | 481 | [user_id]:               1 | 
|  | 482 | [user_name]:             root | 
|  | 483 | [fixed_name]:            No | 
|  | 484 | [access_available]:      callback | 
|  | 485 | [link_authentication]:   enabled | 
|  | 486 | [ipmi_messaging]:        enabled | 
|  | 487 | [privilege_level]:       ADMINISTRATOR | 
|  | 488 | [enable_status]:         enabled | 
|  | 489 | [2]: | 
|  | 490 | [user_id]:               2 | 
|  | 491 | [user_name]: | 
|  | 492 | [fixed_name]:            No | 
|  | 493 | [access_available]:      call-in / callback | 
|  | 494 | [link_authentication]:   disabled | 
|  | 495 | [ipmi_messaging]:        disabled | 
|  | 496 | [privilege_level]:       NO ACCESS | 
|  | 497 | [enable_status]:         disabled | 
|  | 498 |  | 
|  | 499 | Description of argument(s): | 
|  | 500 | out_buf                         A buffer with multiple secionts of key/value strings on each line. | 
|  | 501 | Sections are delimited by one or more blank lines (i.e. line feeds). (See | 
|  | 502 | docstring of parse_key_value function for details). | 
|  | 503 | **args                          Arguments to be interpreted by parse_key_value.  (See docstring of | 
|  | 504 | parse_key_value function for details). | 
|  | 505 | """ | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 506 | return [ | 
|  | 507 | key_value_outbuf_to_dict(x, **args) | 
|  | 508 | for x in re.split("\n[\n]+", out_buf) | 
|  | 509 | ] | 
| Michael Walsh | 79af439 | 2019-12-09 11:42:48 -0600 | [diff] [blame] | 510 |  | 
|  | 511 |  | 
| Michael Walsh | dc97882 | 2018-07-12 15:34:13 -0500 | [diff] [blame] | 512 | def create_field_desc_regex(line): | 
| Michael Walsh | dc97882 | 2018-07-12 15:34:13 -0500 | [diff] [blame] | 513 | r""" | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 514 | Create a field descriptor regular expression based on the input line and return it. | 
| Michael Walsh | dc97882 | 2018-07-12 15:34:13 -0500 | [diff] [blame] | 515 |  | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 516 | This function is designed for use by the list_to_report function (defined below). | 
| Michael Walsh | dc97882 | 2018-07-12 15:34:13 -0500 | [diff] [blame] | 517 |  | 
|  | 518 | Example: | 
|  | 519 |  | 
|  | 520 | Given the following input line: | 
|  | 521 |  | 
|  | 522 | --------   ------------ ------------------ ------------------------ | 
|  | 523 |  | 
|  | 524 | This function will return this regular expression: | 
|  | 525 |  | 
|  | 526 | (.{8})   (.{12}) (.{18}) (.{24}) | 
|  | 527 |  | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 528 | This means that other report lines interpreted using the regular expression are expected to have: | 
| Michael Walsh | dc97882 | 2018-07-12 15:34:13 -0500 | [diff] [blame] | 529 | - An 8 character field | 
|  | 530 | - 3 spaces | 
|  | 531 | - A 12 character field | 
|  | 532 | - One space | 
|  | 533 | - An 18 character field | 
|  | 534 | - One space | 
|  | 535 | - A 24 character field | 
|  | 536 |  | 
|  | 537 | Description of argument(s): | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 538 | line                            A line consisting of dashes to represent fields and spaces to delimit | 
|  | 539 | fields. | 
| Michael Walsh | dc97882 | 2018-07-12 15:34:13 -0500 | [diff] [blame] | 540 | """ | 
|  | 541 |  | 
|  | 542 | # Split the line into a descriptors list.  Example: | 
|  | 543 | # descriptors: | 
|  | 544 | #  descriptors[0]:            -------- | 
|  | 545 | #  descriptors[1]: | 
|  | 546 | #  descriptors[2]: | 
|  | 547 | #  descriptors[3]:            ------------ | 
|  | 548 | #  descriptors[4]:            ------------------ | 
|  | 549 | #  descriptors[5]:            ------------------------ | 
|  | 550 | descriptors = line.split(" ") | 
|  | 551 |  | 
|  | 552 | # Create regexes list.  Example: | 
|  | 553 | # regexes: | 
|  | 554 | #  regexes[0]:                (.{8}) | 
|  | 555 | #  regexes[1]: | 
|  | 556 | #  regexes[2]: | 
|  | 557 | #  regexes[3]:                (.{12}) | 
|  | 558 | #  regexes[4]:                (.{18}) | 
|  | 559 | #  regexes[5]:                (.{24}) | 
|  | 560 | regexes = [] | 
|  | 561 | for descriptor in descriptors: | 
|  | 562 | if descriptor == "": | 
|  | 563 | regexes.append("") | 
|  | 564 | else: | 
|  | 565 | regexes.append("(.{" + str(len(descriptor)) + "})") | 
|  | 566 |  | 
|  | 567 | # Join the regexes list into a regex string. | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 568 | field_desc_regex = " ".join(regexes) | 
| Michael Walsh | dc97882 | 2018-07-12 15:34:13 -0500 | [diff] [blame] | 569 |  | 
|  | 570 | return field_desc_regex | 
|  | 571 |  | 
|  | 572 |  | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 573 | def list_to_report(report_list, to_lower=1, field_delim=None): | 
| Michael Walsh | db560d4 | 2017-11-20 16:42:49 -0600 | [diff] [blame] | 574 | r""" | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 575 | Convert a list containing report text lines to a report "object" and return it. | 
| Michael Walsh | db560d4 | 2017-11-20 16:42:49 -0600 | [diff] [blame] | 576 |  | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 577 | The first entry in report_list must be a header line consisting of column names delimited by white space. | 
|  | 578 | No column name may contain white space.  The remaining report_list entries should contain tabular data | 
|  | 579 | which corresponds to the column names. | 
| Michael Walsh | db560d4 | 2017-11-20 16:42:49 -0600 | [diff] [blame] | 580 |  | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 581 | A report object is a list where each entry is a dictionary whose keys are the field names from the first | 
|  | 582 | entry in report_list. | 
| Michael Walsh | db560d4 | 2017-11-20 16:42:49 -0600 | [diff] [blame] | 583 |  | 
|  | 584 | Example: | 
|  | 585 | Given the following report_list as input: | 
|  | 586 |  | 
|  | 587 | rl: | 
|  | 588 | rl[0]: Filesystem           1K-blocks      Used Available Use% Mounted on | 
|  | 589 | rl[1]: dev                     247120         0    247120   0% /dev | 
|  | 590 | rl[2]: tmpfs                   248408     79792    168616  32% /run | 
|  | 591 |  | 
|  | 592 | This function will return a list of dictionaries as shown below: | 
|  | 593 |  | 
|  | 594 | df_report: | 
|  | 595 | df_report[0]: | 
|  | 596 | [filesystem]:                  dev | 
|  | 597 | [1k-blocks]:                   247120 | 
|  | 598 | [used]:                        0 | 
|  | 599 | [available]:                   247120 | 
|  | 600 | [use%]:                        0% | 
|  | 601 | [mounted]:                     /dev | 
|  | 602 | df_report[1]: | 
|  | 603 | [filesystem]:                  dev | 
|  | 604 | [1k-blocks]:                   247120 | 
|  | 605 | [used]:                        0 | 
|  | 606 | [available]:                   247120 | 
|  | 607 | [use%]:                        0% | 
|  | 608 | [mounted]:                     /dev | 
|  | 609 |  | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 610 | Notice that because "Mounted on" contains a space, "on" would be considered the 7th field.  In this case, | 
|  | 611 | there is never any data in field 7 so things work out nicely.  A caller could do some pre-processing if | 
| Michael Walsh | db560d4 | 2017-11-20 16:42:49 -0600 | [diff] [blame] | 612 | desired (e.g. change "Mounted on" to "Mounted_on"). | 
|  | 613 |  | 
| Michael Walsh | dc97882 | 2018-07-12 15:34:13 -0500 | [diff] [blame] | 614 | Example 2: | 
|  | 615 |  | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 616 | If the 2nd line of report data is a series of dashes and spaces as in the following example, that line | 
|  | 617 | will serve to delineate columns. | 
| Michael Walsh | dc97882 | 2018-07-12 15:34:13 -0500 | [diff] [blame] | 618 |  | 
|  | 619 | The 2nd line of data is like this: | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 620 | ID                              status       size               tool,clientid,userid | 
| Michael Walsh | dc97882 | 2018-07-12 15:34:13 -0500 | [diff] [blame] | 621 | -------- ------------ ------------------ ------------------------ | 
|  | 622 | 20000001 in progress  0x7D0              ,, | 
|  | 623 |  | 
| Michael Walsh | db560d4 | 2017-11-20 16:42:49 -0600 | [diff] [blame] | 624 | Description of argument(s): | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 625 | report_list                     A list where each entry is one line of output from a report.  The first | 
|  | 626 | entry must be a header line which contains column names.  Column names | 
|  | 627 | may not contain spaces. | 
|  | 628 | to_lower                        Change the resulting key names to lower case. | 
|  | 629 | field_delim                     Indicates that there are field delimiters in report_list entries (which | 
|  | 630 | should be removed). | 
| Michael Walsh | db560d4 | 2017-11-20 16:42:49 -0600 | [diff] [blame] | 631 | """ | 
|  | 632 |  | 
| Michael Walsh | dc97882 | 2018-07-12 15:34:13 -0500 | [diff] [blame] | 633 | if len(report_list) <= 1: | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 634 | # If we don't have at least a descriptor line and one line of data, return an empty array. | 
| Michael Walsh | dc97882 | 2018-07-12 15:34:13 -0500 | [diff] [blame] | 635 | return [] | 
|  | 636 |  | 
| Michael Walsh | 64043d5 | 2018-09-21 16:40:44 -0500 | [diff] [blame] | 637 | if field_delim is not None: | 
|  | 638 | report_list = [re.sub("\\|", "", line) for line in report_list] | 
|  | 639 |  | 
| Michael Walsh | db560d4 | 2017-11-20 16:42:49 -0600 | [diff] [blame] | 640 | header_line = report_list[0] | 
|  | 641 | if to_lower: | 
|  | 642 | header_line = header_line.lower() | 
| Michael Walsh | dc97882 | 2018-07-12 15:34:13 -0500 | [diff] [blame] | 643 |  | 
|  | 644 | field_desc_regex = "" | 
|  | 645 | if re.match(r"^-[ -]*$", report_list[1]): | 
|  | 646 | # We have a field descriptor line (as shown in example 2 above). | 
|  | 647 | field_desc_regex = create_field_desc_regex(report_list[1]) | 
|  | 648 | field_desc_len = len(report_list[1]) | 
|  | 649 | pad_format_string = "%-" + str(field_desc_len) + "s" | 
|  | 650 | # The field descriptor line has served its purpose.  Deleting it. | 
|  | 651 | del report_list[1] | 
|  | 652 |  | 
|  | 653 | # Process the header line by creating a list of column names. | 
|  | 654 | if field_desc_regex == "": | 
|  | 655 | columns = header_line.split() | 
|  | 656 | else: | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 657 | # Pad the line with spaces on the right to facilitate processing with field_desc_regex. | 
| Michael Walsh | dc97882 | 2018-07-12 15:34:13 -0500 | [diff] [blame] | 658 | header_line = pad_format_string % header_line | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 659 | columns = list( | 
|  | 660 | map(str.strip, re.findall(field_desc_regex, header_line)[0]) | 
|  | 661 | ) | 
| Michael Walsh | db560d4 | 2017-11-20 16:42:49 -0600 | [diff] [blame] | 662 |  | 
|  | 663 | report_obj = [] | 
|  | 664 | for report_line in report_list[1:]: | 
| Michael Walsh | dc97882 | 2018-07-12 15:34:13 -0500 | [diff] [blame] | 665 | if field_desc_regex == "": | 
|  | 666 | line = report_line.split() | 
|  | 667 | else: | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 668 | # Pad the line with spaces on the right to facilitate processing with field_desc_regex. | 
| Michael Walsh | dc97882 | 2018-07-12 15:34:13 -0500 | [diff] [blame] | 669 | report_line = pad_format_string % report_line | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 670 | line = list( | 
|  | 671 | map(str.strip, re.findall(field_desc_regex, report_line)[0]) | 
|  | 672 | ) | 
| Michael Walsh | db560d4 | 2017-11-20 16:42:49 -0600 | [diff] [blame] | 673 | try: | 
|  | 674 | line_dict = collections.OrderedDict(zip(columns, line)) | 
|  | 675 | except AttributeError: | 
|  | 676 | line_dict = DotDict(zip(columns, line)) | 
|  | 677 | report_obj.append(line_dict) | 
|  | 678 |  | 
|  | 679 | return report_obj | 
|  | 680 |  | 
|  | 681 |  | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 682 | def outbuf_to_report(out_buf, **args): | 
| Michael Walsh | db560d4 | 2017-11-20 16:42:49 -0600 | [diff] [blame] | 683 | r""" | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 684 | Convert a text buffer containing report lines to a report "object" and return it. | 
| Michael Walsh | db560d4 | 2017-11-20 16:42:49 -0600 | [diff] [blame] | 685 |  | 
|  | 686 | Refer to list_to_report (above) for more details. | 
|  | 687 |  | 
|  | 688 | Example: | 
|  | 689 |  | 
|  | 690 | Given the following out_buf: | 
|  | 691 |  | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 692 | Filesystem                      1K-blocks      Used Available Use% Mounted on | 
| Michael Walsh | dc97882 | 2018-07-12 15:34:13 -0500 | [diff] [blame] | 693 | dev                             247120         0    247120   0% /dev | 
|  | 694 | tmpfs                           248408     79792    168616  32% /run | 
| Michael Walsh | db560d4 | 2017-11-20 16:42:49 -0600 | [diff] [blame] | 695 |  | 
|  | 696 | This function will return a list of dictionaries as shown below: | 
|  | 697 |  | 
|  | 698 | df_report: | 
|  | 699 | df_report[0]: | 
|  | 700 | [filesystem]:                  dev | 
|  | 701 | [1k-blocks]:                   247120 | 
|  | 702 | [used]:                        0 | 
|  | 703 | [available]:                   247120 | 
|  | 704 | [use%]:                        0% | 
|  | 705 | [mounted]:                     /dev | 
|  | 706 | df_report[1]: | 
|  | 707 | [filesystem]:                  dev | 
|  | 708 | [1k-blocks]:                   247120 | 
|  | 709 | [used]:                        0 | 
|  | 710 | [available]:                   247120 | 
|  | 711 | [use%]:                        0% | 
|  | 712 | [mounted]:                     /dev | 
|  | 713 |  | 
|  | 714 | Other possible uses: | 
|  | 715 | - Process the output of a ps command. | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 716 | - Process the output of an ls command (the caller would need to supply column names) | 
| Michael Walsh | db560d4 | 2017-11-20 16:42:49 -0600 | [diff] [blame] | 717 |  | 
|  | 718 | Description of argument(s): | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 719 | out_buf                         A text report.  The first line must be a header line which contains | 
|  | 720 | column names.  Column names may not contain spaces. | 
|  | 721 | **args                          Arguments to be interpreted by list_to_report.  (See docstring of | 
| Michael Walsh | db560d4 | 2017-11-20 16:42:49 -0600 | [diff] [blame] | 722 | list_to_report function for details). | 
|  | 723 | """ | 
|  | 724 |  | 
| Michael Walsh | 255181c | 2018-08-07 15:06:23 -0500 | [diff] [blame] | 725 | report_list = list(filter(None, out_buf.split("\n"))) | 
| Michael Walsh | db560d4 | 2017-11-20 16:42:49 -0600 | [diff] [blame] | 726 | return list_to_report(report_list, **args) | 
| Michael Walsh | 7822b9e | 2019-03-12 16:34:38 -0500 | [diff] [blame] | 727 |  | 
|  | 728 |  | 
| Michael Walsh | 46ef0a2 | 2019-06-11 15:44:49 -0500 | [diff] [blame] | 729 | def nested_get(key_name, structure): | 
| Michael Walsh | 7822b9e | 2019-03-12 16:34:38 -0500 | [diff] [blame] | 730 | r""" | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 731 | Return a list of all values from the nested structure that have the given key name. | 
| Michael Walsh | 7822b9e | 2019-03-12 16:34:38 -0500 | [diff] [blame] | 732 |  | 
|  | 733 | Example: | 
|  | 734 |  | 
| Michael Walsh | 46ef0a2 | 2019-06-11 15:44:49 -0500 | [diff] [blame] | 735 | Given a dictionary structure named "personnel" with the following contents: | 
| Michael Walsh | 7822b9e | 2019-03-12 16:34:38 -0500 | [diff] [blame] | 736 |  | 
|  | 737 | personnel: | 
|  | 738 | [manager]: | 
|  | 739 | [last_name]:             Doe | 
|  | 740 | [first_name]:            John | 
|  | 741 | [accountant]: | 
|  | 742 | [last_name]:             Smith | 
|  | 743 | [first_name]:            Will | 
|  | 744 |  | 
|  | 745 | The following code... | 
|  | 746 |  | 
|  | 747 | last_names = nested_get('last_name', personnel) | 
|  | 748 | print_var(last_names) | 
|  | 749 |  | 
| Michael Walsh | 46ef0a2 | 2019-06-11 15:44:49 -0500 | [diff] [blame] | 750 | Would result in the following data returned: | 
| Michael Walsh | 7822b9e | 2019-03-12 16:34:38 -0500 | [diff] [blame] | 751 |  | 
|  | 752 | last_names: | 
|  | 753 | last_names[0]:             Doe | 
|  | 754 | last_names[1]:             Smith | 
|  | 755 |  | 
|  | 756 | Description of argument(s): | 
| Michael Walsh | 46ef0a2 | 2019-06-11 15:44:49 -0500 | [diff] [blame] | 757 | key_name                        The key name (e.g. 'last_name'). | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 758 | structure                       Any nested combination of lists or dictionaries (e.g. a dictionary, a | 
|  | 759 | dictionary of dictionaries, a list of dictionaries, etc.).  This function | 
|  | 760 | will locate the given key at any level within the structure and include | 
|  | 761 | its value in the returned list. | 
| Michael Walsh | 7822b9e | 2019-03-12 16:34:38 -0500 | [diff] [blame] | 762 | """ | 
|  | 763 |  | 
|  | 764 | result = [] | 
| Michael Walsh | d882cdc | 2019-04-24 16:46:34 -0500 | [diff] [blame] | 765 | if type(structure) is list: | 
|  | 766 | for entry in structure: | 
| Michael Walsh | 46ef0a2 | 2019-06-11 15:44:49 -0500 | [diff] [blame] | 767 | result += nested_get(key_name, entry) | 
| Michael Walsh | d882cdc | 2019-04-24 16:46:34 -0500 | [diff] [blame] | 768 | return result | 
| Michael Walsh | 46ef0a2 | 2019-06-11 15:44:49 -0500 | [diff] [blame] | 769 | elif gp.is_dict(structure): | 
|  | 770 | for key, value in structure.items(): | 
|  | 771 | result += nested_get(key_name, value) | 
|  | 772 | if key == key_name: | 
|  | 773 | result.append(value) | 
| Michael Walsh | 7822b9e | 2019-03-12 16:34:38 -0500 | [diff] [blame] | 774 |  | 
|  | 775 | return result | 
| Michael Walsh | 074b765 | 2019-05-22 16:25:38 -0500 | [diff] [blame] | 776 |  | 
|  | 777 |  | 
| Michael Walsh | 46ef0a2 | 2019-06-11 15:44:49 -0500 | [diff] [blame] | 778 | def match_struct(structure, match_dict, regex=False): | 
|  | 779 | r""" | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 780 | Return True or False to indicate whether the structure matches the match dictionary. | 
| Michael Walsh | 46ef0a2 | 2019-06-11 15:44:49 -0500 | [diff] [blame] | 781 |  | 
|  | 782 | Example: | 
|  | 783 |  | 
|  | 784 | Given a dictionary structure named "personnel" with the following contents: | 
|  | 785 |  | 
|  | 786 | personnel: | 
|  | 787 | [manager]: | 
|  | 788 | [last_name]:             Doe | 
|  | 789 | [first_name]:            John | 
|  | 790 | [accountant]: | 
|  | 791 | [last_name]:             Smith | 
|  | 792 | [first_name]:            Will | 
|  | 793 |  | 
|  | 794 | The following call would return True. | 
|  | 795 |  | 
|  | 796 | match_struct(personnel, {'last_name': '^Doe$'}, regex=True) | 
|  | 797 |  | 
|  | 798 | Whereas the following call would return False. | 
|  | 799 |  | 
|  | 800 | match_struct(personnel, {'last_name': 'Johnson'}, regex=True) | 
|  | 801 |  | 
|  | 802 | Description of argument(s): | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 803 | structure                       Any nested combination of lists or dictionaries.  See the prolog of | 
| Michael Walsh | 46ef0a2 | 2019-06-11 15:44:49 -0500 | [diff] [blame] | 804 | get_nested() for details. | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 805 | match_dict                      Each key/value pair in match_dict must exist somewhere in the structure | 
|  | 806 | for the structure to be considered a match.  A match value of None is | 
|  | 807 | considered a special case where the structure would be considered a match | 
|  | 808 | only if the key in question is found nowhere in the structure. | 
|  | 809 | regex                           Indicates whether the values in the match_dict should be interpreted as | 
| Michael Walsh | 46ef0a2 | 2019-06-11 15:44:49 -0500 | [diff] [blame] | 810 | regular expressions. | 
|  | 811 | """ | 
|  | 812 |  | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 813 | # The structure must match for each match_dict entry to be considered a match.  Therefore, any failure | 
|  | 814 | # to match is grounds for returning False. | 
| Michael Walsh | 46ef0a2 | 2019-06-11 15:44:49 -0500 | [diff] [blame] | 815 | for match_key, match_value in match_dict.items(): | 
|  | 816 | struct_key_values = nested_get(match_key, structure) | 
|  | 817 | if match_value is None: | 
|  | 818 | # Handle this as special case. | 
|  | 819 | if len(struct_key_values) != 0: | 
|  | 820 | return False | 
|  | 821 | else: | 
|  | 822 | if len(struct_key_values) == 0: | 
|  | 823 | return False | 
|  | 824 | if regex: | 
| Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 825 | matches = [ | 
|  | 826 | x | 
|  | 827 | for x in struct_key_values | 
|  | 828 | if re.search(match_value, str(x)) | 
|  | 829 | ] | 
| Michael Walsh | 46ef0a2 | 2019-06-11 15:44:49 -0500 | [diff] [blame] | 830 | if not matches: | 
|  | 831 | return False | 
|  | 832 | elif match_value not in struct_key_values: | 
|  | 833 | return False | 
|  | 834 |  | 
|  | 835 | return True | 
|  | 836 |  | 
|  | 837 |  | 
| Michael Walsh | 399df5a | 2019-06-21 11:23:54 -0500 | [diff] [blame] | 838 | def filter_struct(structure, filter_dict, regex=False, invert=False): | 
| Michael Walsh | 074b765 | 2019-05-22 16:25:38 -0500 | [diff] [blame] | 839 | r""" | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 840 | Filter the structure by removing any entries that do NOT contain the keys/values specified in filter_dict | 
|  | 841 | and return the result. | 
| Michael Walsh | 074b765 | 2019-05-22 16:25:38 -0500 | [diff] [blame] | 842 |  | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 843 | The selection process is directed only at the first-level entries of the structure. | 
| Michael Walsh | 46ef0a2 | 2019-06-11 15:44:49 -0500 | [diff] [blame] | 844 |  | 
| Michael Walsh | 074b765 | 2019-05-22 16:25:38 -0500 | [diff] [blame] | 845 | Example: | 
|  | 846 |  | 
|  | 847 | Given a dictionary named "properties" that has the following structure: | 
|  | 848 |  | 
|  | 849 | properties: | 
|  | 850 | [/redfish/v1/Systems/system/Processors]: | 
|  | 851 | [Members]: | 
|  | 852 | [0]: | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 853 | [@odata.id]:                              /redfish/v1/Systems/system/Processors/cpu0 | 
| Michael Walsh | 074b765 | 2019-05-22 16:25:38 -0500 | [diff] [blame] | 854 | [1]: | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 855 | [@odata.id]:                              /redfish/v1/Systems/system/Processors/cpu1 | 
| Michael Walsh | 074b765 | 2019-05-22 16:25:38 -0500 | [diff] [blame] | 856 | [/redfish/v1/Systems/system/Processors/cpu0]: | 
|  | 857 | [Status]: | 
|  | 858 | [State]:                                    Enabled | 
|  | 859 | [Health]:                                   OK | 
|  | 860 | [/redfish/v1/Systems/system/Processors/cpu1]: | 
|  | 861 | [Status]: | 
|  | 862 | [State]:                                    Enabled | 
|  | 863 | [Health]:                                   Bad | 
|  | 864 |  | 
|  | 865 | The following call: | 
|  | 866 |  | 
|  | 867 | properties = filter_struct(properties, "[('Health', 'OK')]") | 
|  | 868 |  | 
|  | 869 | Would return a new properties dictionary that looks like this: | 
|  | 870 |  | 
|  | 871 | properties: | 
|  | 872 | [/redfish/v1/Systems/system/Processors/cpu0]: | 
|  | 873 | [Status]: | 
|  | 874 | [State]:                                    Enabled | 
|  | 875 | [Health]:                                   OK | 
|  | 876 |  | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 877 | Note that the first item in the original properties directory had no key anywhere in the structure named | 
|  | 878 | "Health".  Therefore, that item failed to make the cut.  The next item did have a key named "Health" | 
|  | 879 | whose value was "OK" so it was included in the new structure.  The third item had a key named "Health" | 
|  | 880 | but its value was not "OK" so it also failed to make the cut. | 
| Michael Walsh | 074b765 | 2019-05-22 16:25:38 -0500 | [diff] [blame] | 881 |  | 
|  | 882 | Description of argument(s): | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 883 | structure                       Any nested combination of lists or dictionaries.  See the prolog of | 
| Michael Walsh | 46ef0a2 | 2019-06-11 15:44:49 -0500 | [diff] [blame] | 884 | get_nested() for details. | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 885 | filter_dict                     For each key/value pair in filter_dict, each entry in structure must | 
|  | 886 | contain the same key/value pair at some level.  A filter_dict value of | 
|  | 887 | None is treated as a special case.  Taking the example shown above, | 
|  | 888 | [('State', None)] would mean that the result should only contain records | 
| Michael Walsh | 46ef0a2 | 2019-06-11 15:44:49 -0500 | [diff] [blame] | 889 | that have no State key at all. | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 890 | regex                           Indicates whether the values in the filter_dict should be interpreted as | 
| Michael Walsh | 46ef0a2 | 2019-06-11 15:44:49 -0500 | [diff] [blame] | 891 | regular expressions. | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 892 | invert                          Invert the results.  Instead of including only matching entries in the | 
|  | 893 | results, include only NON-matching entries in the results. | 
| Michael Walsh | 074b765 | 2019-05-22 16:25:38 -0500 | [diff] [blame] | 894 | """ | 
|  | 895 |  | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 896 | # Convert filter_dict from a string containing a python object definition to an actual python object (if | 
|  | 897 | # warranted). | 
| Michael Walsh | 074b765 | 2019-05-22 16:25:38 -0500 | [diff] [blame] | 898 | filter_dict = fa.source_to_object(filter_dict) | 
|  | 899 |  | 
| Michael Walsh | 410b178 | 2019-10-22 15:56:18 -0500 | [diff] [blame] | 900 | # Determine whether structure is a list or a dictionary and process accordingly.  The result returned | 
|  | 901 | # will be of the same type as the structure. | 
| Michael Walsh | 074b765 | 2019-05-22 16:25:38 -0500 | [diff] [blame] | 902 | if type(structure) is list: | 
|  | 903 | result = [] | 
| Michael Walsh | 46ef0a2 | 2019-06-11 15:44:49 -0500 | [diff] [blame] | 904 | for element in structure: | 
| Michael Walsh | 399df5a | 2019-06-21 11:23:54 -0500 | [diff] [blame] | 905 | if match_struct(element, filter_dict, regex) != invert: | 
| Michael Walsh | 46ef0a2 | 2019-06-11 15:44:49 -0500 | [diff] [blame] | 906 | result.append(element) | 
| Michael Walsh | 074b765 | 2019-05-22 16:25:38 -0500 | [diff] [blame] | 907 | else: | 
| Michael Walsh | 074b765 | 2019-05-22 16:25:38 -0500 | [diff] [blame] | 908 | try: | 
|  | 909 | result = collections.OrderedDict() | 
|  | 910 | except AttributeError: | 
|  | 911 | result = DotDict() | 
|  | 912 | for struct_key, struct_value in structure.items(): | 
| Michael Walsh | 399df5a | 2019-06-21 11:23:54 -0500 | [diff] [blame] | 913 | if match_struct(struct_value, filter_dict, regex) != invert: | 
| Michael Walsh | 074b765 | 2019-05-22 16:25:38 -0500 | [diff] [blame] | 914 | result[struct_key] = struct_value | 
|  | 915 |  | 
|  | 916 | return result | 
| Michael Walsh | f3a8ae1 | 2020-02-10 17:06:16 -0600 | [diff] [blame] | 917 |  | 
|  | 918 |  | 
|  | 919 | def split_dict_on_key(split_key, dictionary): | 
|  | 920 | r""" | 
|  | 921 | Split a dictionary into two dictionaries based on the first occurrence of the split key and return the | 
|  | 922 | resulting sub-dictionaries. | 
|  | 923 |  | 
|  | 924 | Example: | 
|  | 925 | dictionary = {'one': 1, 'two': 2, 'three':3, 'four':4} | 
|  | 926 | dict1, dict2 = split_dict_on_key('three', dictionary) | 
|  | 927 | pvars(dictionary, dict1, dict2) | 
|  | 928 |  | 
|  | 929 | Output: | 
|  | 930 | dictionary: | 
|  | 931 | [one]:                                          1 | 
|  | 932 | [two]:                                          2 | 
|  | 933 | [three]:                                        3 | 
|  | 934 | [four]:                                         4 | 
|  | 935 | dict1: | 
|  | 936 | [one]:                                          1 | 
|  | 937 | [two]:                                          2 | 
|  | 938 | dict2: | 
|  | 939 | [three]:                                        3 | 
|  | 940 | [four]:                                         4 | 
|  | 941 |  | 
|  | 942 | Description of argument(s): | 
|  | 943 | split_key                       The key value to be used to determine where the dictionary should be | 
|  | 944 | split. | 
|  | 945 | dictionary                      The dictionary to be split. | 
|  | 946 | """ | 
|  | 947 | dict1 = {} | 
|  | 948 | dict2 = {} | 
|  | 949 | found_split_key = False | 
|  | 950 | for key in list(dictionary.keys()): | 
|  | 951 | if key == split_key: | 
|  | 952 | found_split_key = True | 
|  | 953 | if found_split_key: | 
|  | 954 | dict2[key] = dictionary[key] | 
|  | 955 | else: | 
|  | 956 | dict1[key] = dictionary[key] | 
|  | 957 | return dict1, dict2 |