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