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