blob: d1a4b31a45312bd91d539bc8f23eb34ec05e2942 [file] [log] [blame]
Michael Walshced4eb02017-09-19 16:49:13 -05001#!/usr/bin/env python
2
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
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
Michael Walsh05c68d92017-09-20 16:36:37 -0500210
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:
MICHAEL J. WALSH9509a0f2018-02-08 11:08:52 -0600267 value = delim.join(pair[1:]).strip(strip)
Michael Walsh05c68d92017-09-20 16:36:37 -0500268
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,
Michael Walshcad07132018-02-19 17:28:01 -0600278 process_indent=0,
Michael Walsh05c68d92017-09-20 16:36:37 -0500279 **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
Michael Walshcad07132018-02-19 17:28:01 -0600310 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
Michael Walsh05c68d92017-09-20 16:36:37 -0500334 Description of argument(s):
335 list A list of key/value strings. (See
336 docstring of parse_key_value function for
337 details).
Michael Walshcad07132018-02-19 17:28:01 -0600338 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.
Michael Walsh05c68d92017-09-20 16:36:37 -0500344 **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
Michael Walshcad07132018-02-19 17:28:01 -0600354 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 = []
Michael Walsh05c68d92017-09-20 16:36:37 -0500365 for entry in list:
Michael Walshc1dfc782017-09-26 16:08:51 -0500366 key, value = parse_key_value(entry, **args)
Michael Walshcad07132018-02-19 17:28:01 -0600367
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
Michael Walsh05c68d92017-09-20 16:36:37 -0500389 result_dict[key] = value
390
Michael Walshcad07132018-02-19 17:28:01 -0600391 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
Michael Walsh05c68d92017-09-20 16:36:37 -0500404 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")))
Michael Walshc1dfc782017-09-26 16:08:51 -0500452 return key_value_list_to_dict(key_var_list, **args)
Michael Walshdb560d42017-11-20 16:42:49 -0600453
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)