blob: 0009b5429280381ea635fef048378599b5eef183 [file] [log] [blame]
George Keishinge7e91712021-09-03 11:28:44 -05001#!/usr/bin/env python3
Michael Walshced4eb02017-09-19 16:49:13 -05002
3r"""
4Define variable manipulation functions.
5"""
6
7import os
Michael Walsh05c68d92017-09-20 16:36:37 -05008import re
Michael Walshced4eb02017-09-19 16:49:13 -05009
10try:
11 from robot.utils import DotDict
12except ImportError:
13 pass
14
15import collections
16
George Keishinge635ddc2022-12-08 07:38:02 -060017import func_args as fa
Patrick Williams20f38712022-12-08 06:18:26 -060018import gen_misc as gm
19import gen_print as gp
Michael Walshced4eb02017-09-19 16:49:13 -050020
21
22def create_var_dict(*args):
Michael Walshced4eb02017-09-19 16:49:13 -050023 r"""
Michael Walsh410b1782019-10-22 15:56:18 -050024 Create a dictionary whose keys/values are the arg names/arg values passed to it and return it to the
25 caller.
Michael Walshced4eb02017-09-19 16:49:13 -050026
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 Williams20f38712022-12-08 06:18:26 -060060default_record_delim = ":"
61default_key_val_delim = "."
Michael Walshced4eb02017-09-19 16:49:13 -050062
63
Patrick Williams20f38712022-12-08 06:18:26 -060064def join_dict(
65 dict,
66 record_delim=default_record_delim,
67 key_val_delim=default_key_val_delim,
68):
Michael Walshced4eb02017-09-19 16:49:13 -050069 r"""
70 Join a dictionary's keys and values into a string and return the string.
71
72 Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -050073 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 Walshced4eb02017-09-19 16:49:13 -050078
79 Example use:
80
81 gp.print_var(var_dict)
82 str1 = join_dict(var_dict)
Michael Walshc2762f62019-05-17 15:21:35 -050083 gp.print_var(str1)
Michael Walshced4eb02017-09-19 16:49:13 -050084
85 Program output.
86 var_dict:
87 var_dict[first_name]: Steve
88 var_dict[last_name]: Smith
Michael Walsh410b1782019-10-22 15:56:18 -050089 str1: first_name.Steve:last_name.Smith
Michael Walshced4eb02017-09-19 16:49:13 -050090 """
91
Patrick Williams20f38712022-12-08 06:18:26 -060092 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 Walshced4eb02017-09-19 16:49:13 -050096
97
Patrick Williams20f38712022-12-08 06:18:26 -060098def split_to_dict(
99 string,
100 record_delim=default_record_delim,
101 key_val_delim=default_key_val_delim,
102):
Michael Walshced4eb02017-09-19 16:49:13 -0500103 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 Walsh410b1782019-10-22 15:56:18 -0500109 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 Walshced4eb02017-09-19 16:49:13 -0500113
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 Walsh410b1782019-10-22 15:56:18 -0500122 str1: first_name.Steve:last_name.Smith
Michael Walshced4eb02017-09-19 16:49:13 -0500123 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 Williams20f38712022-12-08 06:18:26 -0600144def create_file_path(file_name_dict, dir_path="/tmp/", file_suffix=""):
Michael Walshced4eb02017-09-19 16:49:13 -0500145 r"""
146 Create a file path using the given parameters and return it.
147
148 Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500149 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 Walshced4eb02017-09-19 16:49:13 -0500153 """
154
155 dir_path = gm.add_trailing_slash(dir_path)
156 return dir_path + join_dict(file_name_dict) + file_suffix
157
158
159def parse_file_path(file_path):
Michael Walshced4eb02017-09-19 16:49:13 -0500160 r"""
Michael Walsh410b1782019-10-22 15:56:18 -0500161 Parse a file path created by create_file_path and return the result as a dictionary.
Michael Walshced4eb02017-09-19 16:49:13 -0500162
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 Walshc2762f62019-05-17 15:21:35 -0500169 gp.print_var(boot_results_file_path)
Michael Walshced4eb02017-09-19 16:49:13 -0500170 file_path_data = parse_file_path(boot_results_file_path)
Michael Walshc2762f62019-05-17 15:21:35 -0500171 gp.print_var(file_path_data)
Michael Walshced4eb02017-09-19 16:49:13 -0500172
173 Program output.
174
175 boot_results_file_path:
Michael Walsh410b1782019-10-22 15:56:18 -0500176 /tmp/pgm_name.obmc_boot_test:openbmc_nickname.beye6:master_pid.2039:boot_results
Michael Walshced4eb02017-09-19 16:49:13 -0500177 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 Williams20f38712022-12-08 06:18:26 -0600193 result_dict["dir_path"] = dir_path
Michael Walshced4eb02017-09-19 16:49:13 -0500194
195 result_dict.update(split_to_dict(file_path))
196
197 return result_dict
Michael Walsh05c68d92017-09-20 16:36:37 -0500198
199
Patrick Williams20f38712022-12-08 06:18:26 -0600200def parse_key_value(string, delim=":", strip=" ", to_lower=1, underscores=1):
Michael Walsh05c68d92017-09-20 16:36:37 -0500201 r"""
202 Parse a key/value string and return as a key/value tuple.
203
Michael Walsh410b1782019-10-22 15:56:18 -0500204 This function is useful for parsing a line of program output or data that is in the following form:
Michael Walsh05c68d92017-09-20 16:36:37 -0500205 <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 Walsh410b1782019-10-22 15:56:18 -0500211 In the example shown, the delimiter is ":". The resulting key would be as follows:
Michael Walsh05c68d92017-09-20 16:36:37 -0500212 Current Limit State
213
Michael Walsh410b1782019-10-22 15:56:18 -0500214 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 Walsh05c68d92017-09-20 16:36:37 -0500216 current_limit_state
217
Michael Walsh410b1782019-10-22 15:56:18 -0500218 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 Walsh05c68d92017-09-20 16:36:37 -0500220
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 Walsh410b1782019-10-22 15:56:18 -0500227 In this case, the delim would be "=", the key is "name" and the value is "Mike".
Michael Walsh05c68d92017-09-20 16:36:37 -0500228
229 Description of argument(s):
230 string The string to be parsed.
Michael Walsh410b1782019-10-22 15:56:18 -0500231 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 Walsh05c68d92017-09-20 16:36:37 -0500234 to_lower Change the key name to lower case.
Michael Walsh410b1782019-10-22 15:56:18 -0500235 underscores Change any blanks found in the key name to underscores.
Michael Walsh05c68d92017-09-20 16:36:37 -0500236 """
237
238 pair = string.split(delim)
239
240 key = pair[0].strip(strip)
241 if len(pair) == 0:
242 value = ""
243 else:
MICHAEL J. WALSH9509a0f2018-02-08 11:08:52 -0600244 value = delim.join(pair[1:]).strip(strip)
Michael Walsh05c68d92017-09-20 16:36:37 -0500245
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 Williams20f38712022-12-08 06:18:26 -0600254def key_value_list_to_dict(key_value_list, process_indent=0, **args):
Michael Walsh05c68d92017-09-20 16:36:37 -0500255 r"""
Michael Walsh410b1782019-10-22 15:56:18 -0500256 Convert a list containing key/value strings or tuples to a dictionary and return it.
Michael Walsh05c68d92017-09-20 16:36:37 -0500257
258 See docstring of parse_key_value function for details on key/value strings.
259
260 Example usage:
261
Michael Walsh19df7aa2019-12-16 17:17:39 -0600262 For the following value of key_value_list:
Michael Walsh05c68d92017-09-20 16:36:37 -0500263
Michael Walsh19df7aa2019-12-16 17:17:39 -0600264 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 Walsh05c68d92017-09-20 16:36:37 -0500270
271 And the following call in python:
272
Michael Walsh19df7aa2019-12-16 17:17:39 -0600273 power_limit = key_value_outbuf_to_dict(key_value_list)
Michael Walsh05c68d92017-09-20 16:36:37 -0500274
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 Walsh1db86872019-04-16 11:48:25 -0500284 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 Walsh410b1782019-10-22 15:56:18 -0500304 Another example containing a sub-list (see process_indent description below):
Michael Walshcad07132018-02-19 17:28:01 -0600305
306 Provides Device SDRs : yes
307 Additional Device Support :
308 Sensor Device
309 SEL Device
310 FRU Inventory Device
311 Chassis Device
312
Michael Walsh410b1782019-10-22 15:56:18 -0500313 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 Walshcad07132018-02-19 17:28:01 -0600316
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 Walsh05c68d92017-09-20 16:36:37 -0500325 Description of argument(s):
Michael Walsh19df7aa2019-12-16 17:17:39 -0600326 key_value_list A list of key/value strings. (See docstring of parse_key_value function
Michael Walsh410b1782019-10-22 15:56:18 -0500327 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 Walsh05c68d92017-09-20 16:36:37 -0500334 parse_key_value function for details).
335 """
336
337 try:
338 result_dict = collections.OrderedDict()
339 except AttributeError:
340 result_dict = DotDict()
341
Michael Walshcad07132018-02-19 17:28:01 -0600342 if not process_indent:
Michael Walsh19df7aa2019-12-16 17:17:39 -0600343 for entry in key_value_list:
Michael Walsh1db86872019-04-16 11:48:25 -0500344 if type(entry) is tuple:
345 key, value = entry
346 else:
347 key, value = parse_key_value(entry, **args)
Michael Walshcad07132018-02-19 17:28:01 -0600348 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 Walsh19df7aa2019-12-16 17:17:39 -0600354 parent_indent = len(key_value_list[0]) - len(key_value_list[0].lstrip())
Michael Walshcad07132018-02-19 17:28:01 -0600355 sub_list = []
Michael Walsh19df7aa2019-12-16 17:17:39 -0600356 for entry in key_value_list:
Michael Walshc1dfc782017-09-26 16:08:51 -0500357 key, value = parse_key_value(entry, **args)
Michael Walshcad07132018-02-19 17:28:01 -0600358
359 indent = len(entry) - len(entry.lstrip())
360
361 if indent > parent_indent and parent_value == "":
Michael Walsh410b1782019-10-22 15:56:18 -0500362 # This line is indented compared to the parent entry and the parent entry has no value.
Michael Walshcad07132018-02-19 17:28:01 -0600363 # Append the entry to sub_list for later processing.
364 sub_list.append(str(entry))
365 continue
366
Michael Walsh410b1782019-10-22 15:56:18 -0500367 # Process any outstanding sub_list and add it to result_dict[parent_key].
Michael Walshcad07132018-02-19 17:28:01 -0600368 if len(sub_list) > 0:
369 if any(delim in word for word in sub_list):
Michael Walsh410b1782019-10-22 15:56:18 -0500370 # If delim is found anywhere in the sub_list, we'll process as a sub-dictionary.
Patrick Williams20f38712022-12-08 06:18:26 -0600371 result_dict[parent_key] = key_value_list_to_dict(
372 sub_list, **args
373 )
Michael Walshcad07132018-02-19 17:28:01 -0600374 else:
Michael Walsh19df7aa2019-12-16 17:17:39 -0600375 result_dict[parent_key] = list(map(str.strip, sub_list))
Michael Walshcad07132018-02-19 17:28:01 -0600376 del sub_list[:]
377
Michael Walsh05c68d92017-09-20 16:36:37 -0500378 result_dict[key] = value
379
Michael Walshcad07132018-02-19 17:28:01 -0600380 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 Walsh410b1782019-10-22 15:56:18 -0500387 # If delim is found anywhere in the sub_list, we'll process as a sub-dictionary.
Michael Walshcad07132018-02-19 17:28:01 -0600388 result_dict[parent_key] = key_value_list_to_dict(sub_list, **args)
389 else:
Michael Walsh19df7aa2019-12-16 17:17:39 -0600390 result_dict[parent_key] = list(map(str.strip, sub_list))
Michael Walshcad07132018-02-19 17:28:01 -0600391
Michael Walsh05c68d92017-09-20 16:36:37 -0500392 return result_dict
393
394
Patrick Williams20f38712022-12-08 06:18:26 -0600395def key_value_outbuf_to_dict(out_buf, **args):
Michael Walsh05c68d92017-09-20 16:36:37 -0500396 r"""
Michael Walsh410b1782019-10-22 15:56:18 -0500397 Convert a buffer with a key/value string on each line to a dictionary and return it.
Michael Walsh05c68d92017-09-20 16:36:37 -0500398
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 Walsh410b1782019-10-22 15:56:18 -0500427 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 Walsh05c68d92017-09-20 16:36:37 -0500430 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 Walshc1dfc782017-09-26 16:08:51 -0500435 return key_value_list_to_dict(key_var_list, **args)
Michael Walshdb560d42017-11-20 16:42:49 -0600436
437
Patrick Williams20f38712022-12-08 06:18:26 -0600438def key_value_outbuf_to_dicts(out_buf, **args):
Michael Walsh79af4392019-12-09 11:42:48 -0600439 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 Williams20f38712022-12-08 06:18:26 -0600506 return [
507 key_value_outbuf_to_dict(x, **args)
508 for x in re.split("\n[\n]+", out_buf)
509 ]
Michael Walsh79af4392019-12-09 11:42:48 -0600510
511
Michael Walshdc978822018-07-12 15:34:13 -0500512def create_field_desc_regex(line):
Michael Walshdc978822018-07-12 15:34:13 -0500513 r"""
Michael Walsh410b1782019-10-22 15:56:18 -0500514 Create a field descriptor regular expression based on the input line and return it.
Michael Walshdc978822018-07-12 15:34:13 -0500515
Michael Walsh410b1782019-10-22 15:56:18 -0500516 This function is designed for use by the list_to_report function (defined below).
Michael Walshdc978822018-07-12 15:34:13 -0500517
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 Walsh410b1782019-10-22 15:56:18 -0500528 This means that other report lines interpreted using the regular expression are expected to have:
Michael Walshdc978822018-07-12 15:34:13 -0500529 - 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 Walsh410b1782019-10-22 15:56:18 -0500538 line A line consisting of dashes to represent fields and spaces to delimit
539 fields.
Michael Walshdc978822018-07-12 15:34:13 -0500540 """
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 Williams20f38712022-12-08 06:18:26 -0600568 field_desc_regex = " ".join(regexes)
Michael Walshdc978822018-07-12 15:34:13 -0500569
570 return field_desc_regex
571
572
Patrick Williams20f38712022-12-08 06:18:26 -0600573def list_to_report(report_list, to_lower=1, field_delim=None):
Michael Walshdb560d42017-11-20 16:42:49 -0600574 r"""
Michael Walsh410b1782019-10-22 15:56:18 -0500575 Convert a list containing report text lines to a report "object" and return it.
Michael Walshdb560d42017-11-20 16:42:49 -0600576
Michael Walsh410b1782019-10-22 15:56:18 -0500577 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 Walshdb560d42017-11-20 16:42:49 -0600580
Michael Walsh410b1782019-10-22 15:56:18 -0500581 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 Walshdb560d42017-11-20 16:42:49 -0600583
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 Walsh410b1782019-10-22 15:56:18 -0500610 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 Walshdb560d42017-11-20 16:42:49 -0600612 desired (e.g. change "Mounted on" to "Mounted_on").
613
Michael Walshdc978822018-07-12 15:34:13 -0500614 Example 2:
615
Michael Walsh410b1782019-10-22 15:56:18 -0500616 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 Walshdc978822018-07-12 15:34:13 -0500618
619 The 2nd line of data is like this:
Michael Walsh410b1782019-10-22 15:56:18 -0500620 ID status size tool,clientid,userid
Michael Walshdc978822018-07-12 15:34:13 -0500621 -------- ------------ ------------------ ------------------------
622 20000001 in progress 0x7D0 ,,
623
Michael Walshdb560d42017-11-20 16:42:49 -0600624 Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500625 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 Walshdb560d42017-11-20 16:42:49 -0600631 """
632
Michael Walshdc978822018-07-12 15:34:13 -0500633 if len(report_list) <= 1:
Michael Walsh410b1782019-10-22 15:56:18 -0500634 # If we don't have at least a descriptor line and one line of data, return an empty array.
Michael Walshdc978822018-07-12 15:34:13 -0500635 return []
636
Michael Walsh64043d52018-09-21 16:40:44 -0500637 if field_delim is not None:
638 report_list = [re.sub("\\|", "", line) for line in report_list]
639
Michael Walshdb560d42017-11-20 16:42:49 -0600640 header_line = report_list[0]
641 if to_lower:
642 header_line = header_line.lower()
Michael Walshdc978822018-07-12 15:34:13 -0500643
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 Walsh410b1782019-10-22 15:56:18 -0500657 # Pad the line with spaces on the right to facilitate processing with field_desc_regex.
Michael Walshdc978822018-07-12 15:34:13 -0500658 header_line = pad_format_string % header_line
Patrick Williams20f38712022-12-08 06:18:26 -0600659 columns = list(
660 map(str.strip, re.findall(field_desc_regex, header_line)[0])
661 )
Michael Walshdb560d42017-11-20 16:42:49 -0600662
663 report_obj = []
664 for report_line in report_list[1:]:
Michael Walshdc978822018-07-12 15:34:13 -0500665 if field_desc_regex == "":
666 line = report_line.split()
667 else:
Michael Walsh410b1782019-10-22 15:56:18 -0500668 # Pad the line with spaces on the right to facilitate processing with field_desc_regex.
Michael Walshdc978822018-07-12 15:34:13 -0500669 report_line = pad_format_string % report_line
Patrick Williams20f38712022-12-08 06:18:26 -0600670 line = list(
671 map(str.strip, re.findall(field_desc_regex, report_line)[0])
672 )
Michael Walshdb560d42017-11-20 16:42:49 -0600673 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 Williams20f38712022-12-08 06:18:26 -0600682def outbuf_to_report(out_buf, **args):
Michael Walshdb560d42017-11-20 16:42:49 -0600683 r"""
Michael Walsh410b1782019-10-22 15:56:18 -0500684 Convert a text buffer containing report lines to a report "object" and return it.
Michael Walshdb560d42017-11-20 16:42:49 -0600685
686 Refer to list_to_report (above) for more details.
687
688 Example:
689
690 Given the following out_buf:
691
Michael Walsh410b1782019-10-22 15:56:18 -0500692 Filesystem 1K-blocks Used Available Use% Mounted on
Michael Walshdc978822018-07-12 15:34:13 -0500693 dev 247120 0 247120 0% /dev
694 tmpfs 248408 79792 168616 32% /run
Michael Walshdb560d42017-11-20 16:42:49 -0600695
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 Walsh410b1782019-10-22 15:56:18 -0500716 - Process the output of an ls command (the caller would need to supply column names)
Michael Walshdb560d42017-11-20 16:42:49 -0600717
718 Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500719 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 Walshdb560d42017-11-20 16:42:49 -0600722 list_to_report function for details).
723 """
724
Michael Walsh255181c2018-08-07 15:06:23 -0500725 report_list = list(filter(None, out_buf.split("\n")))
Michael Walshdb560d42017-11-20 16:42:49 -0600726 return list_to_report(report_list, **args)
Michael Walsh7822b9e2019-03-12 16:34:38 -0500727
728
Michael Walsh46ef0a22019-06-11 15:44:49 -0500729def nested_get(key_name, structure):
Michael Walsh7822b9e2019-03-12 16:34:38 -0500730 r"""
Michael Walsh410b1782019-10-22 15:56:18 -0500731 Return a list of all values from the nested structure that have the given key name.
Michael Walsh7822b9e2019-03-12 16:34:38 -0500732
733 Example:
734
Michael Walsh46ef0a22019-06-11 15:44:49 -0500735 Given a dictionary structure named "personnel" with the following contents:
Michael Walsh7822b9e2019-03-12 16:34:38 -0500736
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 Walsh46ef0a22019-06-11 15:44:49 -0500750 Would result in the following data returned:
Michael Walsh7822b9e2019-03-12 16:34:38 -0500751
752 last_names:
753 last_names[0]: Doe
754 last_names[1]: Smith
755
756 Description of argument(s):
Michael Walsh46ef0a22019-06-11 15:44:49 -0500757 key_name The key name (e.g. 'last_name').
Michael Walsh410b1782019-10-22 15:56:18 -0500758 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 Walsh7822b9e2019-03-12 16:34:38 -0500762 """
763
764 result = []
Michael Walshd882cdc2019-04-24 16:46:34 -0500765 if type(structure) is list:
766 for entry in structure:
Michael Walsh46ef0a22019-06-11 15:44:49 -0500767 result += nested_get(key_name, entry)
Michael Walshd882cdc2019-04-24 16:46:34 -0500768 return result
Michael Walsh46ef0a22019-06-11 15:44:49 -0500769 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 Walsh7822b9e2019-03-12 16:34:38 -0500774
775 return result
Michael Walsh074b7652019-05-22 16:25:38 -0500776
777
Michael Walsh46ef0a22019-06-11 15:44:49 -0500778def match_struct(structure, match_dict, regex=False):
779 r"""
Michael Walsh410b1782019-10-22 15:56:18 -0500780 Return True or False to indicate whether the structure matches the match dictionary.
Michael Walsh46ef0a22019-06-11 15:44:49 -0500781
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 Walsh410b1782019-10-22 15:56:18 -0500803 structure Any nested combination of lists or dictionaries. See the prolog of
Michael Walsh46ef0a22019-06-11 15:44:49 -0500804 get_nested() for details.
Michael Walsh410b1782019-10-22 15:56:18 -0500805 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 Walsh46ef0a22019-06-11 15:44:49 -0500810 regular expressions.
811 """
812
Michael Walsh410b1782019-10-22 15:56:18 -0500813 # 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 Walsh46ef0a22019-06-11 15:44:49 -0500815 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 Williams20f38712022-12-08 06:18:26 -0600825 matches = [
826 x
827 for x in struct_key_values
828 if re.search(match_value, str(x))
829 ]
Michael Walsh46ef0a22019-06-11 15:44:49 -0500830 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 Walsh399df5a2019-06-21 11:23:54 -0500838def filter_struct(structure, filter_dict, regex=False, invert=False):
Michael Walsh074b7652019-05-22 16:25:38 -0500839 r"""
Michael Walsh410b1782019-10-22 15:56:18 -0500840 Filter the structure by removing any entries that do NOT contain the keys/values specified in filter_dict
841 and return the result.
Michael Walsh074b7652019-05-22 16:25:38 -0500842
Michael Walsh410b1782019-10-22 15:56:18 -0500843 The selection process is directed only at the first-level entries of the structure.
Michael Walsh46ef0a22019-06-11 15:44:49 -0500844
Michael Walsh074b7652019-05-22 16:25:38 -0500845 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 Walsh410b1782019-10-22 15:56:18 -0500853 [@odata.id]: /redfish/v1/Systems/system/Processors/cpu0
Michael Walsh074b7652019-05-22 16:25:38 -0500854 [1]:
Michael Walsh410b1782019-10-22 15:56:18 -0500855 [@odata.id]: /redfish/v1/Systems/system/Processors/cpu1
Michael Walsh074b7652019-05-22 16:25:38 -0500856 [/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 Walsh410b1782019-10-22 15:56:18 -0500877 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 Walsh074b7652019-05-22 16:25:38 -0500881
882 Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500883 structure Any nested combination of lists or dictionaries. See the prolog of
Michael Walsh46ef0a22019-06-11 15:44:49 -0500884 get_nested() for details.
Michael Walsh410b1782019-10-22 15:56:18 -0500885 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 Walsh46ef0a22019-06-11 15:44:49 -0500889 that have no State key at all.
Michael Walsh410b1782019-10-22 15:56:18 -0500890 regex Indicates whether the values in the filter_dict should be interpreted as
Michael Walsh46ef0a22019-06-11 15:44:49 -0500891 regular expressions.
Michael Walsh410b1782019-10-22 15:56:18 -0500892 invert Invert the results. Instead of including only matching entries in the
893 results, include only NON-matching entries in the results.
Michael Walsh074b7652019-05-22 16:25:38 -0500894 """
895
Michael Walsh410b1782019-10-22 15:56:18 -0500896 # Convert filter_dict from a string containing a python object definition to an actual python object (if
897 # warranted).
Michael Walsh074b7652019-05-22 16:25:38 -0500898 filter_dict = fa.source_to_object(filter_dict)
899
Michael Walsh410b1782019-10-22 15:56:18 -0500900 # 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 Walsh074b7652019-05-22 16:25:38 -0500902 if type(structure) is list:
903 result = []
Michael Walsh46ef0a22019-06-11 15:44:49 -0500904 for element in structure:
Michael Walsh399df5a2019-06-21 11:23:54 -0500905 if match_struct(element, filter_dict, regex) != invert:
Michael Walsh46ef0a22019-06-11 15:44:49 -0500906 result.append(element)
Michael Walsh074b7652019-05-22 16:25:38 -0500907 else:
Michael Walsh074b7652019-05-22 16:25:38 -0500908 try:
909 result = collections.OrderedDict()
910 except AttributeError:
911 result = DotDict()
912 for struct_key, struct_value in structure.items():
Michael Walsh399df5a2019-06-21 11:23:54 -0500913 if match_struct(struct_value, filter_dict, regex) != invert:
Michael Walsh074b7652019-05-22 16:25:38 -0500914 result[struct_key] = struct_value
915
916 return result
Michael Walshf3a8ae12020-02-10 17:06:16 -0600917
918
919def 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