1#!/usr/bin/env python 2 3r""" 4Define variable manipulation functions. 5""" 6 7import os 8import re 9 10try: 11 from robot.utils import DotDict 12except ImportError: 13 pass 14 15import collections 16 17import gen_print as gp 18import gen_misc as gm 19 20 21def create_var_dict(*args): 22 23 r""" 24 Create a dictionary whose keys/values are the arg names/arg values passed 25 to it and return it to the caller. 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 60default_record_delim = ':' 61default_key_val_delim = '.' 62 63 64def join_dict(dict, 65 record_delim=default_record_delim, 66 key_val_delim=default_key_val_delim): 67 68 r""" 69 Join a dictionary's keys and values into a string and return the string. 70 71 Description of argument(s): 72 dict The dictionary whose keys and values are 73 to be joined. 74 record_delim The delimiter to be used to separate 75 dictionary pairs in the resulting string. 76 key_val_delim The delimiter to be used to separate keys 77 from values in the resulting string. 78 79 Example use: 80 81 gp.print_var(var_dict) 82 str1 = join_dict(var_dict) 83 gp.pvar(str1) 84 85 Program output. 86 var_dict: 87 var_dict[first_name]: Steve 88 var_dict[last_name]: Smith 89 str1: 90 first_name.Steve:last_name.Smith 91 """ 92 93 format_str = '%s' + key_val_delim + '%s' 94 return record_delim.join([format_str % (key, value) for (key, value) in 95 dict.items()]) 96 97 98def split_to_dict(string, 99 record_delim=default_record_delim, 100 key_val_delim=default_key_val_delim): 101 102 r""" 103 Split a string into a dictionary and return it. 104 105 This function is the complement to join_dict. 106 107 Description of argument(s): 108 string The string to be split into a dictionary. 109 The string must have the proper delimiters 110 in it. A string created by join_dict 111 would qualify. 112 record_delim The delimiter to be used to separate 113 dictionary pairs in the input string. 114 key_val_delim The delimiter to be used to separate 115 keys/values in the input string. 116 117 Example use: 118 119 gp.print_var(str1) 120 new_dict = split_to_dict(str1) 121 gp.print_var(new_dict) 122 123 124 Program output. 125 str1: 126 first_name.Steve:last_name.Smith 127 new_dict: 128 new_dict[first_name]: Steve 129 new_dict[last_name]: Smith 130 """ 131 132 try: 133 result_dict = collections.OrderedDict() 134 except AttributeError: 135 result_dict = DotDict() 136 137 raw_keys_values = string.split(record_delim) 138 for key_value in raw_keys_values: 139 key_value_list = key_value.split(key_val_delim) 140 try: 141 result_dict[key_value_list[0]] = key_value_list[1] 142 except IndexError: 143 result_dict[key_value_list[0]] = "" 144 145 return result_dict 146 147 148def create_file_path(file_name_dict, 149 dir_path="/tmp/", 150 file_suffix=""): 151 152 r""" 153 Create a file path using the given parameters and return it. 154 155 Description of argument(s): 156 file_name_dict A dictionary with keys/values which are to 157 appear as part of the file name. 158 dir_path The dir_path that is to appear as part of 159 the file name. 160 file_suffix A suffix to be included as part of the 161 file name. 162 """ 163 164 dir_path = gm.add_trailing_slash(dir_path) 165 return dir_path + join_dict(file_name_dict) + file_suffix 166 167 168def parse_file_path(file_path): 169 170 r""" 171 Parse a file path created by create_file_path and return the result as a 172 dictionary. 173 174 This function is the complement to create_file_path. 175 176 Description of argument(s): 177 file_path The file_path. 178 179 Example use: 180 gp.pvar(boot_results_file_path) 181 file_path_data = parse_file_path(boot_results_file_path) 182 gp.pvar(file_path_data) 183 184 Program output. 185 186 boot_results_file_path: 187 /tmp/pgm_name.obmc_boot_test:openbmc_nickname.beye6:master_pid.2039:boot_re 188 sults 189 file_path_data: 190 file_path_data[dir_path]: /tmp/ 191 file_path_data[pgm_name]: obmc_boot_test 192 file_path_data[openbmc_nickname]: beye6 193 file_path_data[master_pid]: 2039 194 file_path_data[boot_results]: 195 """ 196 197 try: 198 result_dict = collections.OrderedDict() 199 except AttributeError: 200 result_dict = DotDict() 201 202 dir_path = os.path.dirname(file_path) + os.sep 203 file_path = os.path.basename(file_path) 204 205 result_dict['dir_path'] = dir_path 206 207 result_dict.update(split_to_dict(file_path)) 208 209 return result_dict 210 211 212def parse_key_value(string, 213 delim=":", 214 strip=" ", 215 to_lower=1, 216 underscores=1): 217 218 r""" 219 Parse a key/value string and return as a key/value tuple. 220 221 This function is useful for parsing a line of program output or data that 222 is in the following form: 223 <key or variable name><delimiter><value> 224 225 An example of a key/value string would be as follows: 226 227 Current Limit State: No Active Power Limit 228 229 In the example shown, the delimiter is ":". The resulting key would be as 230 follows: 231 Current Limit State 232 233 Note: If one were to take the default values of to_lower=1 and 234 underscores=1, the resulting key would be as follows: 235 current_limit_state 236 237 The to_lower and underscores arguments are provided for those who wish to 238 have their key names have the look and feel of python variable names. 239 240 The resulting value for the example above would be as follows: 241 No Active Power Limit 242 243 Another example: 244 name=Mike 245 246 In this case, the delim would be "=", the key is "name" and the value is 247 "Mike". 248 249 Description of argument(s): 250 string The string to be parsed. 251 delim The delimiter which separates the key from 252 the value. 253 strip The characters (if any) to strip from the 254 beginning and end of both the key and the 255 value. 256 to_lower Change the key name to lower case. 257 underscores Change any blanks found in the key name to 258 underscores. 259 """ 260 261 pair = string.split(delim) 262 263 key = pair[0].strip(strip) 264 if len(pair) == 0: 265 value = "" 266 else: 267 value = delim.join(pair[1:]).strip(strip) 268 269 if to_lower: 270 key = key.lower() 271 if underscores: 272 key = re.sub(r" ", "_", key) 273 274 return key, value 275 276 277def key_value_list_to_dict(list, 278 process_indent=0, 279 **args): 280 281 r""" 282 Convert a list containing key/value strings to a dictionary and return it. 283 284 See docstring of parse_key_value function for details on key/value strings. 285 286 Example usage: 287 288 For the following value of list: 289 290 list: 291 list[0]: Current Limit State: No Active Power Limit 292 list[1]: Exception actions: Hard Power Off & Log Event to SEL 293 list[2]: Power Limit: 0 Watts 294 list[3]: Correction time: 0 milliseconds 295 list[4]: Sampling period: 0 seconds 296 297 And the following call in python: 298 299 power_limit = key_value_outbuf_to_dict(list) 300 301 The resulting power_limit directory would look like this: 302 303 power_limit: 304 [current_limit_state]: No Active Power Limit 305 [exception_actions]: Hard Power Off & Log Event to SEL 306 [power_limit]: 0 Watts 307 [correction_time]: 0 milliseconds 308 [sampling_period]: 0 seconds 309 310 Another example containing a sub-list (see process_indent description 311 below): 312 313 Provides Device SDRs : yes 314 Additional Device Support : 315 Sensor Device 316 SEL Device 317 FRU Inventory Device 318 Chassis Device 319 320 Note that the 2 qualifications for containing a sub-list are met: 1) 321 'Additional Device Support' has no value and 2) The entries below it are 322 indented. In this case those entries contain no delimiters (":") so they 323 will be processed as a list rather than as a dictionary. The result would 324 be as follows: 325 326 mc_info: 327 mc_info[provides_device_sdrs]: yes 328 mc_info[additional_device_support]: 329 mc_info[additional_device_support][0]: Sensor Device 330 mc_info[additional_device_support][1]: SEL Device 331 mc_info[additional_device_support][2]: FRU Inventory Device 332 mc_info[additional_device_support][3]: Chassis Device 333 334 Description of argument(s): 335 list A list of key/value strings. (See 336 docstring of parse_key_value function for 337 details). 338 process_indent This indicates that indented 339 sub-dictionaries and sub-lists are to be 340 processed as such. An entry may have a 341 sub-dict or sub-list if 1) It has no value 342 other than blank 2) There are entries 343 below it that are indented. 344 **args Arguments to be interpreted by 345 parse_key_value. (See docstring of 346 parse_key_value function for details). 347 """ 348 349 try: 350 result_dict = collections.OrderedDict() 351 except AttributeError: 352 result_dict = DotDict() 353 354 if not process_indent: 355 for entry in list: 356 key, value = parse_key_value(entry, **args) 357 result_dict[key] = value 358 return result_dict 359 360 # Process list while paying heed to indentation. 361 delim = args.get("delim", ":") 362 # Initialize "parent_" indentation level variables. 363 parent_indent = len(list[0]) - len(list[0].lstrip()) 364 sub_list = [] 365 for entry in list: 366 key, value = parse_key_value(entry, **args) 367 368 indent = len(entry) - len(entry.lstrip()) 369 370 if indent > parent_indent and parent_value == "": 371 # This line is indented compared to the parent entry and the 372 # parent entry has no value. 373 # Append the entry to sub_list for later processing. 374 sub_list.append(str(entry)) 375 continue 376 377 # Process any outstanding sub_list and add it to 378 # result_dict[parent_key]. 379 if len(sub_list) > 0: 380 if any(delim in word for word in sub_list): 381 # If delim is found anywhere in the sub_list, we'll process 382 # as a sub-dictionary. 383 result_dict[parent_key] = key_value_list_to_dict(sub_list, 384 **args) 385 else: 386 result_dict[parent_key] = map(str.strip, sub_list) 387 del sub_list[:] 388 389 result_dict[key] = value 390 391 parent_key = key 392 parent_value = value 393 parent_indent = indent 394 395 # Any outstanding sub_list to be processed? 396 if len(sub_list) > 0: 397 if any(delim in word for word in sub_list): 398 # If delim is found anywhere in the sub_list, we'll process as a 399 # sub-dictionary. 400 result_dict[parent_key] = key_value_list_to_dict(sub_list, **args) 401 else: 402 result_dict[parent_key] = map(str.strip, sub_list) 403 404 return result_dict 405 406 407def key_value_outbuf_to_dict(out_buf, 408 **args): 409 410 r""" 411 Convert a buffer with a key/value string on each line to a dictionary and 412 return it. 413 414 Each line in the out_buf should end with a \n. 415 416 See docstring of parse_key_value function for details on key/value strings. 417 418 Example usage: 419 420 For the following value of out_buf: 421 422 Current Limit State: No Active Power Limit 423 Exception actions: Hard Power Off & Log Event to SEL 424 Power Limit: 0 Watts 425 Correction time: 0 milliseconds 426 Sampling period: 0 seconds 427 428 And the following call in python: 429 430 power_limit = key_value_outbuf_to_dict(out_buf) 431 432 The resulting power_limit directory would look like this: 433 434 power_limit: 435 [current_limit_state]: No Active Power Limit 436 [exception_actions]: Hard Power Off & Log Event to SEL 437 [power_limit]: 0 Watts 438 [correction_time]: 0 milliseconds 439 [sampling_period]: 0 seconds 440 441 Description of argument(s): 442 out_buf A buffer with a key/value string on each 443 line. (See docstring of parse_key_value 444 function for details). 445 **args Arguments to be interpreted by 446 parse_key_value. (See docstring of 447 parse_key_value function for details). 448 """ 449 450 # Create key_var_list and remove null entries. 451 key_var_list = list(filter(None, out_buf.split("\n"))) 452 return key_value_list_to_dict(key_var_list, **args) 453 454 455def list_to_report(report_list, 456 to_lower=1): 457 458 r""" 459 Convert a list containing report text lines to a report "object" and 460 return it. 461 462 The first entry in report_list must be a header line consisting of column 463 names delimited by white space. No column name may contain white space. 464 The remaining report_list entries should contain tabular data which 465 corresponds to the column names. 466 467 A report object is a list where each entry is a dictionary whose keys are 468 the field names from the first entry in report_list. 469 470 Example: 471 Given the following report_list as input: 472 473 rl: 474 rl[0]: Filesystem 1K-blocks Used Available Use% Mounted on 475 rl[1]: dev 247120 0 247120 0% /dev 476 rl[2]: tmpfs 248408 79792 168616 32% /run 477 478 This function will return a list of dictionaries as shown below: 479 480 df_report: 481 df_report[0]: 482 [filesystem]: dev 483 [1k-blocks]: 247120 484 [used]: 0 485 [available]: 247120 486 [use%]: 0% 487 [mounted]: /dev 488 df_report[1]: 489 [filesystem]: dev 490 [1k-blocks]: 247120 491 [used]: 0 492 [available]: 247120 493 [use%]: 0% 494 [mounted]: /dev 495 496 Notice that because "Mounted on" contains a space, "on" would be 497 considered the 7th field. In this case, there is never any data in field 498 7 so things work out nicely. A caller could do some pre-processing if 499 desired (e.g. change "Mounted on" to "Mounted_on"). 500 501 Description of argument(s): 502 report_list A list where each entry is one line of 503 output from a report. The first entry 504 must be a header line which contains 505 column names. Column names may not 506 contain spaces. 507 to_lower Change the resulting key names to lower 508 case. 509 """ 510 511 # Process header line. 512 header_line = report_list[0] 513 if to_lower: 514 header_line = header_line.lower() 515 columns = header_line.split() 516 517 report_obj = [] 518 for report_line in report_list[1:]: 519 line = report_list[1].split() 520 try: 521 line_dict = collections.OrderedDict(zip(columns, line)) 522 except AttributeError: 523 line_dict = DotDict(zip(columns, line)) 524 report_obj.append(line_dict) 525 526 return report_obj 527 528 529def outbuf_to_report(out_buf, 530 **args): 531 532 r""" 533 Convert a text buffer containing report lines to a report "object" and 534 return it. 535 536 Refer to list_to_report (above) for more details. 537 538 Example: 539 540 Given the following out_buf: 541 542 Filesystem 1K-blocks Used Available Use% Mounted on 543 dev 247120 0 247120 0% /dev 544 tmpfs 248408 79792 168616 32% /run 545 546 This function will return a list of dictionaries as shown below: 547 548 df_report: 549 df_report[0]: 550 [filesystem]: dev 551 [1k-blocks]: 247120 552 [used]: 0 553 [available]: 247120 554 [use%]: 0% 555 [mounted]: /dev 556 df_report[1]: 557 [filesystem]: dev 558 [1k-blocks]: 247120 559 [used]: 0 560 [available]: 247120 561 [use%]: 0% 562 [mounted]: /dev 563 564 Other possible uses: 565 - Process the output of a ps command. 566 - Process the output of an ls command (the caller would need to supply 567 column names) 568 569 Description of argument(s): 570 out_buf A text report The first line must be a 571 header line which contains column names. 572 Column names may not contain spaces. 573 **args Arguments to be interpreted by 574 list_to_report. (See docstring of 575 list_to_report function for details). 576 """ 577 578 report_list = filter(None, out_buf.split("\n")) 579 return list_to_report(report_list, **args) 580