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