xref: /openbmc/openbmc-test-automation/lib/var_funcs.py (revision 20f38712b324e61a94e174017c487a0af4b373e1)
1e7e9171eSGeorge Keishing#!/usr/bin/env python3
2ced4eb0aSMichael Walsh
3ced4eb0aSMichael Walshr"""
4ced4eb0aSMichael WalshDefine variable manipulation functions.
5ced4eb0aSMichael Walsh"""
6ced4eb0aSMichael Walsh
7ced4eb0aSMichael Walshimport os
805c68d98SMichael Walshimport re
9ced4eb0aSMichael Walsh
10ced4eb0aSMichael Walshtry:
11ced4eb0aSMichael Walsh    from robot.utils import DotDict
12ced4eb0aSMichael Walshexcept ImportError:
13ced4eb0aSMichael Walsh    pass
14ced4eb0aSMichael Walsh
15ced4eb0aSMichael Walshimport collections
16ced4eb0aSMichael Walsh
17e635ddc0SGeorge Keishingimport func_args as fa
18*20f38712SPatrick Williamsimport gen_misc as gm
19*20f38712SPatrick Williamsimport gen_print as gp
20ced4eb0aSMichael Walsh
21ced4eb0aSMichael Walsh
22ced4eb0aSMichael Walshdef create_var_dict(*args):
23ced4eb0aSMichael Walsh    r"""
24410b1787SMichael Walsh    Create a dictionary whose keys/values are the arg names/arg values passed to it and return it to the
25410b1787SMichael Walsh    caller.
26ced4eb0aSMichael Walsh
27ced4eb0aSMichael Walsh    Note: The resulting dictionary will be ordered.
28ced4eb0aSMichael Walsh
29ced4eb0aSMichael Walsh    Description of argument(s):
30ced4eb0aSMichael Walsh    *args  An unlimited number of arguments to be processed.
31ced4eb0aSMichael Walsh
32ced4eb0aSMichael Walsh    Example use:
33ced4eb0aSMichael Walsh
34ced4eb0aSMichael Walsh    first_name = 'Steve'
35ced4eb0aSMichael Walsh    last_name = 'Smith'
36ced4eb0aSMichael Walsh    var_dict = create_var_dict(first_name, last_name)
37ced4eb0aSMichael Walsh
38ced4eb0aSMichael Walsh    gp.print_var(var_dict)
39ced4eb0aSMichael Walsh
40ced4eb0aSMichael Walsh    The print-out of the resulting var dictionary is:
41ced4eb0aSMichael Walsh    var_dict:
42ced4eb0aSMichael Walsh      var_dict[first_name]:                           Steve
43ced4eb0aSMichael Walsh      var_dict[last_name]:                            Smith
44ced4eb0aSMichael Walsh    """
45ced4eb0aSMichael Walsh
46ced4eb0aSMichael Walsh    try:
47ced4eb0aSMichael Walsh        result_dict = collections.OrderedDict()
48ced4eb0aSMichael Walsh    except AttributeError:
49ced4eb0aSMichael Walsh        result_dict = DotDict()
50ced4eb0aSMichael Walsh
51ced4eb0aSMichael Walsh    arg_num = 1
52ced4eb0aSMichael Walsh    for arg in args:
53ced4eb0aSMichael Walsh        arg_name = gp.get_arg_name(None, arg_num, stack_frame_ix=2)
54ced4eb0aSMichael Walsh        result_dict[arg_name] = arg
55ced4eb0aSMichael Walsh        arg_num += 1
56ced4eb0aSMichael Walsh
57ced4eb0aSMichael Walsh    return result_dict
58ced4eb0aSMichael Walsh
59ced4eb0aSMichael Walsh
60*20f38712SPatrick Williamsdefault_record_delim = ":"
61*20f38712SPatrick Williamsdefault_key_val_delim = "."
62ced4eb0aSMichael Walsh
63ced4eb0aSMichael Walsh
64*20f38712SPatrick Williamsdef join_dict(
65*20f38712SPatrick Williams    dict,
66ced4eb0aSMichael Walsh    record_delim=default_record_delim,
67*20f38712SPatrick Williams    key_val_delim=default_key_val_delim,
68*20f38712SPatrick Williams):
69ced4eb0aSMichael Walsh    r"""
70ced4eb0aSMichael Walsh    Join a dictionary's keys and values into a string and return the string.
71ced4eb0aSMichael Walsh
72ced4eb0aSMichael Walsh    Description of argument(s):
73410b1787SMichael Walsh    dict                            The dictionary whose keys and values are to be joined.
74410b1787SMichael Walsh    record_delim                    The delimiter to be used to separate dictionary pairs in the resulting
75410b1787SMichael Walsh                                    string.
76410b1787SMichael Walsh    key_val_delim                   The delimiter to be used to separate keys from values in the resulting
77410b1787SMichael Walsh                                    string.
78ced4eb0aSMichael Walsh
79ced4eb0aSMichael Walsh    Example use:
80ced4eb0aSMichael Walsh
81ced4eb0aSMichael Walsh    gp.print_var(var_dict)
82ced4eb0aSMichael Walsh    str1 = join_dict(var_dict)
83c2762f62SMichael Walsh    gp.print_var(str1)
84ced4eb0aSMichael Walsh
85ced4eb0aSMichael Walsh    Program output.
86ced4eb0aSMichael Walsh    var_dict:
87ced4eb0aSMichael Walsh      var_dict[first_name]:                           Steve
88ced4eb0aSMichael Walsh      var_dict[last_name]:                            Smith
89410b1787SMichael Walsh    str1:                                             first_name.Steve:last_name.Smith
90ced4eb0aSMichael Walsh    """
91ced4eb0aSMichael Walsh
92*20f38712SPatrick Williams    format_str = "%s" + key_val_delim + "%s"
93*20f38712SPatrick Williams    return record_delim.join(
94*20f38712SPatrick Williams        [format_str % (key, value) for (key, value) in dict.items()]
95*20f38712SPatrick Williams    )
96ced4eb0aSMichael Walsh
97ced4eb0aSMichael Walsh
98*20f38712SPatrick Williamsdef split_to_dict(
99*20f38712SPatrick Williams    string,
100ced4eb0aSMichael Walsh    record_delim=default_record_delim,
101*20f38712SPatrick Williams    key_val_delim=default_key_val_delim,
102*20f38712SPatrick Williams):
103ced4eb0aSMichael Walsh    r"""
104ced4eb0aSMichael Walsh    Split a string into a dictionary and return it.
105ced4eb0aSMichael Walsh
106ced4eb0aSMichael Walsh    This function is the complement to join_dict.
107ced4eb0aSMichael Walsh
108ced4eb0aSMichael Walsh    Description of argument(s):
109410b1787SMichael Walsh    string                          The string to be split into a dictionary.  The string must have the
110410b1787SMichael Walsh                                    proper delimiters in it.  A string created by join_dict would qualify.
111410b1787SMichael Walsh    record_delim                    The delimiter to be used to separate dictionary pairs in the input string.
112410b1787SMichael Walsh    key_val_delim                   The delimiter to be used to separate keys/values in the input string.
113ced4eb0aSMichael Walsh
114ced4eb0aSMichael Walsh    Example use:
115ced4eb0aSMichael Walsh
116ced4eb0aSMichael Walsh    gp.print_var(str1)
117ced4eb0aSMichael Walsh    new_dict = split_to_dict(str1)
118ced4eb0aSMichael Walsh    gp.print_var(new_dict)
119ced4eb0aSMichael Walsh
120ced4eb0aSMichael Walsh
121ced4eb0aSMichael Walsh    Program output.
122410b1787SMichael Walsh    str1:                                             first_name.Steve:last_name.Smith
123ced4eb0aSMichael Walsh    new_dict:
124ced4eb0aSMichael Walsh      new_dict[first_name]:                           Steve
125ced4eb0aSMichael Walsh      new_dict[last_name]:                            Smith
126ced4eb0aSMichael Walsh    """
127ced4eb0aSMichael Walsh
128ced4eb0aSMichael Walsh    try:
129ced4eb0aSMichael Walsh        result_dict = collections.OrderedDict()
130ced4eb0aSMichael Walsh    except AttributeError:
131ced4eb0aSMichael Walsh        result_dict = DotDict()
132ced4eb0aSMichael Walsh
133ced4eb0aSMichael Walsh    raw_keys_values = string.split(record_delim)
134ced4eb0aSMichael Walsh    for key_value in raw_keys_values:
135ced4eb0aSMichael Walsh        key_value_list = key_value.split(key_val_delim)
136ced4eb0aSMichael Walsh        try:
137ced4eb0aSMichael Walsh            result_dict[key_value_list[0]] = key_value_list[1]
138ced4eb0aSMichael Walsh        except IndexError:
139ced4eb0aSMichael Walsh            result_dict[key_value_list[0]] = ""
140ced4eb0aSMichael Walsh
141ced4eb0aSMichael Walsh    return result_dict
142ced4eb0aSMichael Walsh
143ced4eb0aSMichael Walsh
144*20f38712SPatrick Williamsdef create_file_path(file_name_dict, dir_path="/tmp/", file_suffix=""):
145ced4eb0aSMichael Walsh    r"""
146ced4eb0aSMichael Walsh    Create a file path using the given parameters and return it.
147ced4eb0aSMichael Walsh
148ced4eb0aSMichael Walsh    Description of argument(s):
149410b1787SMichael Walsh    file_name_dict                  A dictionary with keys/values which are to appear as part of the file
150410b1787SMichael Walsh                                    name.
151410b1787SMichael Walsh    dir_path                        The dir_path that is to appear as part of the file name.
152410b1787SMichael Walsh    file_suffix                     A suffix to be included as part of the file name.
153ced4eb0aSMichael Walsh    """
154ced4eb0aSMichael Walsh
155ced4eb0aSMichael Walsh    dir_path = gm.add_trailing_slash(dir_path)
156ced4eb0aSMichael Walsh    return dir_path + join_dict(file_name_dict) + file_suffix
157ced4eb0aSMichael Walsh
158ced4eb0aSMichael Walsh
159ced4eb0aSMichael Walshdef parse_file_path(file_path):
160ced4eb0aSMichael Walsh    r"""
161410b1787SMichael Walsh    Parse a file path created by create_file_path and return the result as a dictionary.
162ced4eb0aSMichael Walsh
163ced4eb0aSMichael Walsh    This function is the complement to create_file_path.
164ced4eb0aSMichael Walsh
165ced4eb0aSMichael Walsh    Description of argument(s):
166ced4eb0aSMichael Walsh    file_path                       The file_path.
167ced4eb0aSMichael Walsh
168ced4eb0aSMichael Walsh    Example use:
169c2762f62SMichael Walsh    gp.print_var(boot_results_file_path)
170ced4eb0aSMichael Walsh    file_path_data = parse_file_path(boot_results_file_path)
171c2762f62SMichael Walsh    gp.print_var(file_path_data)
172ced4eb0aSMichael Walsh
173ced4eb0aSMichael Walsh    Program output.
174ced4eb0aSMichael Walsh
175ced4eb0aSMichael Walsh    boot_results_file_path:
176410b1787SMichael Walsh    /tmp/pgm_name.obmc_boot_test:openbmc_nickname.beye6:master_pid.2039:boot_results
177ced4eb0aSMichael Walsh    file_path_data:
178ced4eb0aSMichael Walsh      file_path_data[dir_path]:                       /tmp/
179ced4eb0aSMichael Walsh      file_path_data[pgm_name]:                       obmc_boot_test
180ced4eb0aSMichael Walsh      file_path_data[openbmc_nickname]:               beye6
181ced4eb0aSMichael Walsh      file_path_data[master_pid]:                     2039
182ced4eb0aSMichael Walsh      file_path_data[boot_results]:
183ced4eb0aSMichael Walsh    """
184ced4eb0aSMichael Walsh
185ced4eb0aSMichael Walsh    try:
186ced4eb0aSMichael Walsh        result_dict = collections.OrderedDict()
187ced4eb0aSMichael Walsh    except AttributeError:
188ced4eb0aSMichael Walsh        result_dict = DotDict()
189ced4eb0aSMichael Walsh
190ced4eb0aSMichael Walsh    dir_path = os.path.dirname(file_path) + os.sep
191ced4eb0aSMichael Walsh    file_path = os.path.basename(file_path)
192ced4eb0aSMichael Walsh
193*20f38712SPatrick Williams    result_dict["dir_path"] = dir_path
194ced4eb0aSMichael Walsh
195ced4eb0aSMichael Walsh    result_dict.update(split_to_dict(file_path))
196ced4eb0aSMichael Walsh
197ced4eb0aSMichael Walsh    return result_dict
19805c68d98SMichael Walsh
19905c68d98SMichael Walsh
200*20f38712SPatrick Williamsdef parse_key_value(string, delim=":", strip=" ", to_lower=1, underscores=1):
20105c68d98SMichael Walsh    r"""
20205c68d98SMichael Walsh    Parse a key/value string and return as a key/value tuple.
20305c68d98SMichael Walsh
204410b1787SMichael Walsh    This function is useful for parsing a line of program output or data that is in the following form:
20505c68d98SMichael Walsh    <key or variable name><delimiter><value>
20605c68d98SMichael Walsh
20705c68d98SMichael Walsh    An example of a key/value string would be as follows:
20805c68d98SMichael Walsh
20905c68d98SMichael Walsh    Current Limit State: No Active Power Limit
21005c68d98SMichael Walsh
211410b1787SMichael Walsh    In the example shown, the delimiter is ":".  The resulting key would be as follows:
21205c68d98SMichael Walsh    Current Limit State
21305c68d98SMichael Walsh
214410b1787SMichael Walsh    Note: If one were to take the default values of to_lower=1 and underscores=1, the resulting key would be
215410b1787SMichael Walsh    as follows:
21605c68d98SMichael Walsh    current_limit_state
21705c68d98SMichael Walsh
218410b1787SMichael Walsh    The to_lower and underscores arguments are provided for those who wish to have their key names have the
219410b1787SMichael Walsh    look and feel of python variable names.
22005c68d98SMichael Walsh
22105c68d98SMichael Walsh    The resulting value for the example above would be as follows:
22205c68d98SMichael Walsh    No Active Power Limit
22305c68d98SMichael Walsh
22405c68d98SMichael Walsh    Another example:
22505c68d98SMichael Walsh    name=Mike
22605c68d98SMichael Walsh
227410b1787SMichael Walsh    In this case, the delim would be "=", the key is "name" and the value is "Mike".
22805c68d98SMichael Walsh
22905c68d98SMichael Walsh    Description of argument(s):
23005c68d98SMichael Walsh    string                          The string to be parsed.
231410b1787SMichael Walsh    delim                           The delimiter which separates the key from the value.
232410b1787SMichael Walsh    strip                           The characters (if any) to strip from the beginning and end of both the
233410b1787SMichael Walsh                                    key and the value.
23405c68d98SMichael Walsh    to_lower                        Change the key name to lower case.
235410b1787SMichael Walsh    underscores                     Change any blanks found in the key name to underscores.
23605c68d98SMichael Walsh    """
23705c68d98SMichael Walsh
23805c68d98SMichael Walsh    pair = string.split(delim)
23905c68d98SMichael Walsh
24005c68d98SMichael Walsh    key = pair[0].strip(strip)
24105c68d98SMichael Walsh    if len(pair) == 0:
24205c68d98SMichael Walsh        value = ""
24305c68d98SMichael Walsh    else:
2449509a0ffSMICHAEL J. WALSH        value = delim.join(pair[1:]).strip(strip)
24505c68d98SMichael Walsh
24605c68d98SMichael Walsh    if to_lower:
24705c68d98SMichael Walsh        key = key.lower()
24805c68d98SMichael Walsh    if underscores:
24905c68d98SMichael Walsh        key = re.sub(r" ", "_", key)
25005c68d98SMichael Walsh
25105c68d98SMichael Walsh    return key, value
25205c68d98SMichael Walsh
25305c68d98SMichael Walsh
254*20f38712SPatrick Williamsdef key_value_list_to_dict(key_value_list, process_indent=0, **args):
25505c68d98SMichael Walsh    r"""
256410b1787SMichael Walsh    Convert a list containing key/value strings or tuples to a dictionary and return it.
25705c68d98SMichael Walsh
25805c68d98SMichael Walsh    See docstring of parse_key_value function for details on key/value strings.
25905c68d98SMichael Walsh
26005c68d98SMichael Walsh    Example usage:
26105c68d98SMichael Walsh
26219df7aaaSMichael Walsh    For the following value of key_value_list:
26305c68d98SMichael Walsh
26419df7aaaSMichael Walsh    key_value_list:
26519df7aaaSMichael Walsh      [0]:          Current Limit State: No Active Power Limit
26619df7aaaSMichael Walsh      [1]:          Exception actions:   Hard Power Off & Log Event to SEL
26719df7aaaSMichael Walsh      [2]:          Power Limit:         0 Watts
26819df7aaaSMichael Walsh      [3]:          Correction time:     0 milliseconds
26919df7aaaSMichael Walsh      [4]:          Sampling period:     0 seconds
27005c68d98SMichael Walsh
27105c68d98SMichael Walsh    And the following call in python:
27205c68d98SMichael Walsh
27319df7aaaSMichael Walsh    power_limit = key_value_outbuf_to_dict(key_value_list)
27405c68d98SMichael Walsh
27505c68d98SMichael Walsh    The resulting power_limit directory would look like this:
27605c68d98SMichael Walsh
27705c68d98SMichael Walsh    power_limit:
27805c68d98SMichael Walsh      [current_limit_state]:        No Active Power Limit
27905c68d98SMichael Walsh      [exception_actions]:          Hard Power Off & Log Event to SEL
28005c68d98SMichael Walsh      [power_limit]:                0 Watts
28105c68d98SMichael Walsh      [correction_time]:            0 milliseconds
28205c68d98SMichael Walsh      [sampling_period]:            0 seconds
28305c68d98SMichael Walsh
2841db8687dSMichael Walsh    For the following list:
2851db8687dSMichael Walsh
2861db8687dSMichael Walsh    headers:
2871db8687dSMichael Walsh      headers[0]:
2881db8687dSMichael Walsh        headers[0][0]:           content-length
2891db8687dSMichael Walsh        headers[0][1]:           559
2901db8687dSMichael Walsh      headers[1]:
2911db8687dSMichael Walsh        headers[1][0]:           x-xss-protection
2921db8687dSMichael Walsh        headers[1][1]:           1; mode=block
2931db8687dSMichael Walsh
2941db8687dSMichael Walsh    And the following call in python:
2951db8687dSMichael Walsh
2961db8687dSMichael Walsh    headers_dict = key_value_list_to_dict(headers)
2971db8687dSMichael Walsh
2981db8687dSMichael Walsh    The resulting headers_dict would look like this:
2991db8687dSMichael Walsh
3001db8687dSMichael Walsh    headers_dict:
3011db8687dSMichael Walsh      [content-length]:          559
3021db8687dSMichael Walsh      [x-xss-protection]:        1; mode=block
3031db8687dSMichael Walsh
304410b1787SMichael Walsh    Another example containing a sub-list (see process_indent description below):
305cad0713eSMichael Walsh
306cad0713eSMichael Walsh    Provides Device SDRs      : yes
307cad0713eSMichael Walsh    Additional Device Support :
308cad0713eSMichael Walsh        Sensor Device
309cad0713eSMichael Walsh        SEL Device
310cad0713eSMichael Walsh        FRU Inventory Device
311cad0713eSMichael Walsh        Chassis Device
312cad0713eSMichael Walsh
313410b1787SMichael Walsh    Note that the 2 qualifications for containing a sub-list are met: 1) 'Additional Device Support' has no
314410b1787SMichael Walsh    value and 2) The entries below it are indented.  In this case those entries contain no delimiters (":")
315410b1787SMichael Walsh    so they will be processed as a list rather than as a dictionary.  The result would be as follows:
316cad0713eSMichael Walsh
317cad0713eSMichael Walsh    mc_info:
318cad0713eSMichael Walsh      mc_info[provides_device_sdrs]:            yes
319cad0713eSMichael Walsh      mc_info[additional_device_support]:
320cad0713eSMichael Walsh        mc_info[additional_device_support][0]:  Sensor Device
321cad0713eSMichael Walsh        mc_info[additional_device_support][1]:  SEL Device
322cad0713eSMichael Walsh        mc_info[additional_device_support][2]:  FRU Inventory Device
323cad0713eSMichael Walsh        mc_info[additional_device_support][3]:  Chassis Device
324cad0713eSMichael Walsh
32505c68d98SMichael Walsh    Description of argument(s):
32619df7aaaSMichael Walsh    key_value_list                  A list of key/value strings.  (See docstring of parse_key_value function
327410b1787SMichael Walsh                                    for details).
328410b1787SMichael Walsh    process_indent                  This indicates that indented sub-dictionaries and sub-lists are to be
329410b1787SMichael Walsh                                    processed as such.  An entry may have a sub-dict or sub-list if 1) It has
330410b1787SMichael Walsh                                    no value other than blank 2) There are entries below it that are
331410b1787SMichael Walsh                                    indented.  Note that process_indent is not allowed for a list of tuples
332410b1787SMichael Walsh                                    (vs. a list of key/value strings).
333410b1787SMichael Walsh    **args                          Arguments to be interpreted by parse_key_value.  (See docstring of
33405c68d98SMichael Walsh                                    parse_key_value function for details).
33505c68d98SMichael Walsh    """
33605c68d98SMichael Walsh
33705c68d98SMichael Walsh    try:
33805c68d98SMichael Walsh        result_dict = collections.OrderedDict()
33905c68d98SMichael Walsh    except AttributeError:
34005c68d98SMichael Walsh        result_dict = DotDict()
34105c68d98SMichael Walsh
342cad0713eSMichael Walsh    if not process_indent:
34319df7aaaSMichael Walsh        for entry in key_value_list:
3441db8687dSMichael Walsh            if type(entry) is tuple:
3451db8687dSMichael Walsh                key, value = entry
3461db8687dSMichael Walsh            else:
347c1dfc781SMichael Walsh                key, value = parse_key_value(entry, **args)
34805c68d98SMichael Walsh            result_dict[key] = value
349cad0713eSMichael Walsh        return result_dict
350cad0713eSMichael Walsh
351cad0713eSMichael Walsh    # Process list while paying heed to indentation.
352cad0713eSMichael Walsh    delim = args.get("delim", ":")
353cad0713eSMichael Walsh    # Initialize "parent_" indentation level variables.
35419df7aaaSMichael Walsh    parent_indent = len(key_value_list[0]) - len(key_value_list[0].lstrip())
355cad0713eSMichael Walsh    sub_list = []
35619df7aaaSMichael Walsh    for entry in key_value_list:
357cad0713eSMichael Walsh        key, value = parse_key_value(entry, **args)
358cad0713eSMichael Walsh
359cad0713eSMichael Walsh        indent = len(entry) - len(entry.lstrip())
360cad0713eSMichael Walsh
361cad0713eSMichael Walsh        if indent > parent_indent and parent_value == "":
362410b1787SMichael Walsh            # This line is indented compared to the parent entry and the parent entry has no value.
363cad0713eSMichael Walsh            # Append the entry to sub_list for later processing.
364cad0713eSMichael Walsh            sub_list.append(str(entry))
365cad0713eSMichael Walsh            continue
366cad0713eSMichael Walsh
367410b1787SMichael Walsh        # Process any outstanding sub_list and add it to result_dict[parent_key].
368cad0713eSMichael Walsh        if len(sub_list) > 0:
369cad0713eSMichael Walsh            if any(delim in word for word in sub_list):
370410b1787SMichael Walsh                # If delim is found anywhere in the sub_list, we'll process as a sub-dictionary.
371*20f38712SPatrick Williams                result_dict[parent_key] = key_value_list_to_dict(
372*20f38712SPatrick Williams                    sub_list, **args
373*20f38712SPatrick Williams                )
374cad0713eSMichael Walsh            else:
37519df7aaaSMichael Walsh                result_dict[parent_key] = list(map(str.strip, sub_list))
376cad0713eSMichael Walsh            del sub_list[:]
377cad0713eSMichael Walsh
378cad0713eSMichael Walsh        result_dict[key] = value
379cad0713eSMichael Walsh
380cad0713eSMichael Walsh        parent_key = key
381cad0713eSMichael Walsh        parent_value = value
382cad0713eSMichael Walsh        parent_indent = indent
383cad0713eSMichael Walsh
384cad0713eSMichael Walsh    # Any outstanding sub_list to be processed?
385cad0713eSMichael Walsh    if len(sub_list) > 0:
386cad0713eSMichael Walsh        if any(delim in word for word in sub_list):
387410b1787SMichael Walsh            # If delim is found anywhere in the sub_list, we'll process as a sub-dictionary.
388cad0713eSMichael Walsh            result_dict[parent_key] = key_value_list_to_dict(sub_list, **args)
389cad0713eSMichael Walsh        else:
39019df7aaaSMichael Walsh            result_dict[parent_key] = list(map(str.strip, sub_list))
39105c68d98SMichael Walsh
39205c68d98SMichael Walsh    return result_dict
39305c68d98SMichael Walsh
39405c68d98SMichael Walsh
395*20f38712SPatrick Williamsdef key_value_outbuf_to_dict(out_buf, **args):
39605c68d98SMichael Walsh    r"""
397410b1787SMichael Walsh    Convert a buffer with a key/value string on each line to a dictionary and return it.
39805c68d98SMichael Walsh
39905c68d98SMichael Walsh    Each line in the out_buf should end with a \n.
40005c68d98SMichael Walsh
40105c68d98SMichael Walsh    See docstring of parse_key_value function for details on key/value strings.
40205c68d98SMichael Walsh
40305c68d98SMichael Walsh    Example usage:
40405c68d98SMichael Walsh
40505c68d98SMichael Walsh    For the following value of out_buf:
40605c68d98SMichael Walsh
40705c68d98SMichael Walsh    Current Limit State: No Active Power Limit
40805c68d98SMichael Walsh    Exception actions:   Hard Power Off & Log Event to SEL
40905c68d98SMichael Walsh    Power Limit:         0 Watts
41005c68d98SMichael Walsh    Correction time:     0 milliseconds
41105c68d98SMichael Walsh    Sampling period:     0 seconds
41205c68d98SMichael Walsh
41305c68d98SMichael Walsh    And the following call in python:
41405c68d98SMichael Walsh
41505c68d98SMichael Walsh    power_limit = key_value_outbuf_to_dict(out_buf)
41605c68d98SMichael Walsh
41705c68d98SMichael Walsh    The resulting power_limit directory would look like this:
41805c68d98SMichael Walsh
41905c68d98SMichael Walsh    power_limit:
42005c68d98SMichael Walsh      [current_limit_state]:        No Active Power Limit
42105c68d98SMichael Walsh      [exception_actions]:          Hard Power Off & Log Event to SEL
42205c68d98SMichael Walsh      [power_limit]:                0 Watts
42305c68d98SMichael Walsh      [correction_time]:            0 milliseconds
42405c68d98SMichael Walsh      [sampling_period]:            0 seconds
42505c68d98SMichael Walsh
42605c68d98SMichael Walsh    Description of argument(s):
427410b1787SMichael Walsh    out_buf                         A buffer with a key/value string on each line. (See docstring of
428410b1787SMichael Walsh                                    parse_key_value function for details).
429410b1787SMichael Walsh    **args                          Arguments to be interpreted by parse_key_value.  (See docstring of
43005c68d98SMichael Walsh                                    parse_key_value function for details).
43105c68d98SMichael Walsh    """
43205c68d98SMichael Walsh
43305c68d98SMichael Walsh    # Create key_var_list and remove null entries.
43405c68d98SMichael Walsh    key_var_list = list(filter(None, out_buf.split("\n")))
435c1dfc781SMichael Walsh    return key_value_list_to_dict(key_var_list, **args)
436db560d46SMichael Walsh
437db560d46SMichael Walsh
438*20f38712SPatrick Williamsdef key_value_outbuf_to_dicts(out_buf, **args):
43979af4395SMichael Walsh    r"""
44079af4395SMichael Walsh    Convert a buffer containing multiple sections with key/value strings on each line to a list of
44179af4395SMichael Walsh    dictionaries and return it.
44279af4395SMichael Walsh
44379af4395SMichael Walsh    Sections in the output are delimited by blank lines.
44479af4395SMichael Walsh
44579af4395SMichael Walsh    Example usage:
44679af4395SMichael Walsh
44779af4395SMichael Walsh    For the following value of out_buf:
44879af4395SMichael Walsh
44979af4395SMichael Walsh    Maximum User IDs     : 15
45079af4395SMichael Walsh    Enabled User IDs     : 1
45179af4395SMichael Walsh
45279af4395SMichael Walsh    User ID              : 1
45379af4395SMichael Walsh    User Name            : root
45479af4395SMichael Walsh    Fixed Name           : No
45579af4395SMichael Walsh    Access Available     : callback
45679af4395SMichael Walsh    Link Authentication  : enabled
45779af4395SMichael Walsh    IPMI Messaging       : enabled
45879af4395SMichael Walsh    Privilege Level      : ADMINISTRATOR
45979af4395SMichael Walsh    Enable Status        : enabled
46079af4395SMichael Walsh
46179af4395SMichael Walsh    User ID              : 2
46279af4395SMichael Walsh    User Name            :
46379af4395SMichael Walsh    Fixed Name           : No
46479af4395SMichael Walsh    Access Available     : call-in / callback
46579af4395SMichael Walsh    Link Authentication  : disabled
46679af4395SMichael Walsh    IPMI Messaging       : disabled
46779af4395SMichael Walsh    Privilege Level      : NO ACCESS
46879af4395SMichael Walsh    Enable Status        : disabled
46979af4395SMichael Walsh
47079af4395SMichael Walsh    And the following call in python:
47179af4395SMichael Walsh
47279af4395SMichael Walsh    user_info = key_value_outbuf_to_dicts(out_buf)
47379af4395SMichael Walsh
47479af4395SMichael Walsh    The resulting user_info list would look like this:
47579af4395SMichael Walsh
47679af4395SMichael Walsh    user_info:
47779af4395SMichael Walsh      [0]:
47879af4395SMichael Walsh        [maximum_user_ids]:      15
47979af4395SMichael Walsh        [enabled_user_ids]:      1
48079af4395SMichael Walsh      [1]:
48179af4395SMichael Walsh        [user_id]:               1
48279af4395SMichael Walsh        [user_name]:             root
48379af4395SMichael Walsh        [fixed_name]:            No
48479af4395SMichael Walsh        [access_available]:      callback
48579af4395SMichael Walsh        [link_authentication]:   enabled
48679af4395SMichael Walsh        [ipmi_messaging]:        enabled
48779af4395SMichael Walsh        [privilege_level]:       ADMINISTRATOR
48879af4395SMichael Walsh        [enable_status]:         enabled
48979af4395SMichael Walsh      [2]:
49079af4395SMichael Walsh        [user_id]:               2
49179af4395SMichael Walsh        [user_name]:
49279af4395SMichael Walsh        [fixed_name]:            No
49379af4395SMichael Walsh        [access_available]:      call-in / callback
49479af4395SMichael Walsh        [link_authentication]:   disabled
49579af4395SMichael Walsh        [ipmi_messaging]:        disabled
49679af4395SMichael Walsh        [privilege_level]:       NO ACCESS
49779af4395SMichael Walsh        [enable_status]:         disabled
49879af4395SMichael Walsh
49979af4395SMichael Walsh    Description of argument(s):
50079af4395SMichael Walsh    out_buf                         A buffer with multiple secionts of key/value strings on each line.
50179af4395SMichael Walsh                                    Sections are delimited by one or more blank lines (i.e. line feeds). (See
50279af4395SMichael Walsh                                    docstring of parse_key_value function for details).
50379af4395SMichael Walsh    **args                          Arguments to be interpreted by parse_key_value.  (See docstring of
50479af4395SMichael Walsh                                    parse_key_value function for details).
50579af4395SMichael Walsh    """
506*20f38712SPatrick Williams    return [
507*20f38712SPatrick Williams        key_value_outbuf_to_dict(x, **args)
508*20f38712SPatrick Williams        for x in re.split("\n[\n]+", out_buf)
509*20f38712SPatrick Williams    ]
51079af4395SMichael Walsh
51179af4395SMichael Walsh
512dc97882eSMichael Walshdef create_field_desc_regex(line):
513dc97882eSMichael Walsh    r"""
514410b1787SMichael Walsh    Create a field descriptor regular expression based on the input line and return it.
515dc97882eSMichael Walsh
516410b1787SMichael Walsh    This function is designed for use by the list_to_report function (defined below).
517dc97882eSMichael Walsh
518dc97882eSMichael Walsh    Example:
519dc97882eSMichael Walsh
520dc97882eSMichael Walsh    Given the following input line:
521dc97882eSMichael Walsh
522dc97882eSMichael Walsh    --------   ------------ ------------------ ------------------------
523dc97882eSMichael Walsh
524dc97882eSMichael Walsh    This function will return this regular expression:
525dc97882eSMichael Walsh
526dc97882eSMichael Walsh    (.{8})   (.{12}) (.{18}) (.{24})
527dc97882eSMichael Walsh
528410b1787SMichael Walsh    This means that other report lines interpreted using the regular expression are expected to have:
529dc97882eSMichael Walsh    - An 8 character field
530dc97882eSMichael Walsh    - 3 spaces
531dc97882eSMichael Walsh    - A 12 character field
532dc97882eSMichael Walsh    - One space
533dc97882eSMichael Walsh    - An 18 character field
534dc97882eSMichael Walsh    - One space
535dc97882eSMichael Walsh    - A 24 character field
536dc97882eSMichael Walsh
537dc97882eSMichael Walsh    Description of argument(s):
538410b1787SMichael Walsh    line                            A line consisting of dashes to represent fields and spaces to delimit
539410b1787SMichael Walsh                                    fields.
540dc97882eSMichael Walsh    """
541dc97882eSMichael Walsh
542dc97882eSMichael Walsh    # Split the line into a descriptors list.  Example:
543dc97882eSMichael Walsh    # descriptors:
544dc97882eSMichael Walsh    #  descriptors[0]:            --------
545dc97882eSMichael Walsh    #  descriptors[1]:
546dc97882eSMichael Walsh    #  descriptors[2]:
547dc97882eSMichael Walsh    #  descriptors[3]:            ------------
548dc97882eSMichael Walsh    #  descriptors[4]:            ------------------
549dc97882eSMichael Walsh    #  descriptors[5]:            ------------------------
550dc97882eSMichael Walsh    descriptors = line.split(" ")
551dc97882eSMichael Walsh
552dc97882eSMichael Walsh    # Create regexes list.  Example:
553dc97882eSMichael Walsh    # regexes:
554dc97882eSMichael Walsh    #  regexes[0]:                (.{8})
555dc97882eSMichael Walsh    #  regexes[1]:
556dc97882eSMichael Walsh    #  regexes[2]:
557dc97882eSMichael Walsh    #  regexes[3]:                (.{12})
558dc97882eSMichael Walsh    #  regexes[4]:                (.{18})
559dc97882eSMichael Walsh    #  regexes[5]:                (.{24})
560dc97882eSMichael Walsh    regexes = []
561dc97882eSMichael Walsh    for descriptor in descriptors:
562dc97882eSMichael Walsh        if descriptor == "":
563dc97882eSMichael Walsh            regexes.append("")
564dc97882eSMichael Walsh        else:
565dc97882eSMichael Walsh            regexes.append("(.{" + str(len(descriptor)) + "})")
566dc97882eSMichael Walsh
567dc97882eSMichael Walsh    # Join the regexes list into a regex string.
568*20f38712SPatrick Williams    field_desc_regex = " ".join(regexes)
569dc97882eSMichael Walsh
570dc97882eSMichael Walsh    return field_desc_regex
571dc97882eSMichael Walsh
572dc97882eSMichael Walsh
573*20f38712SPatrick Williamsdef list_to_report(report_list, to_lower=1, field_delim=None):
574db560d46SMichael Walsh    r"""
575410b1787SMichael Walsh    Convert a list containing report text lines to a report "object" and return it.
576db560d46SMichael Walsh
577410b1787SMichael Walsh    The first entry in report_list must be a header line consisting of column names delimited by white space.
578410b1787SMichael Walsh    No column name may contain white space.  The remaining report_list entries should contain tabular data
579410b1787SMichael Walsh    which corresponds to the column names.
580db560d46SMichael Walsh
581410b1787SMichael Walsh    A report object is a list where each entry is a dictionary whose keys are the field names from the first
582410b1787SMichael Walsh    entry in report_list.
583db560d46SMichael Walsh
584db560d46SMichael Walsh    Example:
585db560d46SMichael Walsh    Given the following report_list as input:
586db560d46SMichael Walsh
587db560d46SMichael Walsh    rl:
588db560d46SMichael Walsh      rl[0]: Filesystem           1K-blocks      Used Available Use% Mounted on
589db560d46SMichael Walsh      rl[1]: dev                     247120         0    247120   0% /dev
590db560d46SMichael Walsh      rl[2]: tmpfs                   248408     79792    168616  32% /run
591db560d46SMichael Walsh
592db560d46SMichael Walsh    This function will return a list of dictionaries as shown below:
593db560d46SMichael Walsh
594db560d46SMichael Walsh    df_report:
595db560d46SMichael Walsh      df_report[0]:
596db560d46SMichael Walsh        [filesystem]:                  dev
597db560d46SMichael Walsh        [1k-blocks]:                   247120
598db560d46SMichael Walsh        [used]:                        0
599db560d46SMichael Walsh        [available]:                   247120
600db560d46SMichael Walsh        [use%]:                        0%
601db560d46SMichael Walsh        [mounted]:                     /dev
602db560d46SMichael Walsh      df_report[1]:
603db560d46SMichael Walsh        [filesystem]:                  dev
604db560d46SMichael Walsh        [1k-blocks]:                   247120
605db560d46SMichael Walsh        [used]:                        0
606db560d46SMichael Walsh        [available]:                   247120
607db560d46SMichael Walsh        [use%]:                        0%
608db560d46SMichael Walsh        [mounted]:                     /dev
609db560d46SMichael Walsh
610410b1787SMichael Walsh    Notice that because "Mounted on" contains a space, "on" would be considered the 7th field.  In this case,
611410b1787SMichael Walsh    there is never any data in field 7 so things work out nicely.  A caller could do some pre-processing if
612db560d46SMichael Walsh    desired (e.g. change "Mounted on" to "Mounted_on").
613db560d46SMichael Walsh
614dc97882eSMichael Walsh    Example 2:
615dc97882eSMichael Walsh
616410b1787SMichael Walsh    If the 2nd line of report data is a series of dashes and spaces as in the following example, that line
617410b1787SMichael Walsh    will serve to delineate columns.
618dc97882eSMichael Walsh
619dc97882eSMichael Walsh    The 2nd line of data is like this:
620410b1787SMichael Walsh    ID                              status       size               tool,clientid,userid
621dc97882eSMichael Walsh    -------- ------------ ------------------ ------------------------
622dc97882eSMichael Walsh    20000001 in progress  0x7D0              ,,
623dc97882eSMichael Walsh
624db560d46SMichael Walsh    Description of argument(s):
625410b1787SMichael Walsh    report_list                     A list where each entry is one line of output from a report.  The first
626410b1787SMichael Walsh                                    entry must be a header line which contains column names.  Column names
627410b1787SMichael Walsh                                    may not contain spaces.
628410b1787SMichael Walsh    to_lower                        Change the resulting key names to lower case.
629410b1787SMichael Walsh    field_delim                     Indicates that there are field delimiters in report_list entries (which
630410b1787SMichael Walsh                                    should be removed).
631db560d46SMichael Walsh    """
632db560d46SMichael Walsh
633dc97882eSMichael Walsh    if len(report_list) <= 1:
634410b1787SMichael Walsh        # If we don't have at least a descriptor line and one line of data, return an empty array.
635dc97882eSMichael Walsh        return []
636dc97882eSMichael Walsh
63764043d54SMichael Walsh    if field_delim is not None:
63864043d54SMichael Walsh        report_list = [re.sub("\\|", "", line) for line in report_list]
63964043d54SMichael Walsh
640db560d46SMichael Walsh    header_line = report_list[0]
641db560d46SMichael Walsh    if to_lower:
642db560d46SMichael Walsh        header_line = header_line.lower()
643dc97882eSMichael Walsh
644dc97882eSMichael Walsh    field_desc_regex = ""
645dc97882eSMichael Walsh    if re.match(r"^-[ -]*$", report_list[1]):
646dc97882eSMichael Walsh        # We have a field descriptor line (as shown in example 2 above).
647dc97882eSMichael Walsh        field_desc_regex = create_field_desc_regex(report_list[1])
648dc97882eSMichael Walsh        field_desc_len = len(report_list[1])
649dc97882eSMichael Walsh        pad_format_string = "%-" + str(field_desc_len) + "s"
650dc97882eSMichael Walsh        # The field descriptor line has served its purpose.  Deleting it.
651dc97882eSMichael Walsh        del report_list[1]
652dc97882eSMichael Walsh
653dc97882eSMichael Walsh    # Process the header line by creating a list of column names.
654dc97882eSMichael Walsh    if field_desc_regex == "":
655db560d46SMichael Walsh        columns = header_line.split()
656dc97882eSMichael Walsh    else:
657410b1787SMichael Walsh        # Pad the line with spaces on the right to facilitate processing with field_desc_regex.
658dc97882eSMichael Walsh        header_line = pad_format_string % header_line
659*20f38712SPatrick Williams        columns = list(
660*20f38712SPatrick Williams            map(str.strip, re.findall(field_desc_regex, header_line)[0])
661*20f38712SPatrick Williams        )
662db560d46SMichael Walsh
663db560d46SMichael Walsh    report_obj = []
664db560d46SMichael Walsh    for report_line in report_list[1:]:
665dc97882eSMichael Walsh        if field_desc_regex == "":
666dc97882eSMichael Walsh            line = report_line.split()
667dc97882eSMichael Walsh        else:
668410b1787SMichael Walsh            # Pad the line with spaces on the right to facilitate processing with field_desc_regex.
669dc97882eSMichael Walsh            report_line = pad_format_string % report_line
670*20f38712SPatrick Williams            line = list(
671*20f38712SPatrick Williams                map(str.strip, re.findall(field_desc_regex, report_line)[0])
672*20f38712SPatrick Williams            )
673db560d46SMichael Walsh        try:
674db560d46SMichael Walsh            line_dict = collections.OrderedDict(zip(columns, line))
675db560d46SMichael Walsh        except AttributeError:
676db560d46SMichael Walsh            line_dict = DotDict(zip(columns, line))
677db560d46SMichael Walsh        report_obj.append(line_dict)
678db560d46SMichael Walsh
679db560d46SMichael Walsh    return report_obj
680db560d46SMichael Walsh
681db560d46SMichael Walsh
682*20f38712SPatrick Williamsdef outbuf_to_report(out_buf, **args):
683db560d46SMichael Walsh    r"""
684410b1787SMichael Walsh    Convert a text buffer containing report lines to a report "object" and return it.
685db560d46SMichael Walsh
686db560d46SMichael Walsh    Refer to list_to_report (above) for more details.
687db560d46SMichael Walsh
688db560d46SMichael Walsh    Example:
689db560d46SMichael Walsh
690db560d46SMichael Walsh    Given the following out_buf:
691db560d46SMichael Walsh
692410b1787SMichael Walsh    Filesystem                      1K-blocks      Used Available Use% Mounted on
693db560d46SMichael Walsh    dev                             247120         0    247120   0% /dev
694db560d46SMichael Walsh    tmpfs                           248408     79792    168616  32% /run
695db560d46SMichael Walsh
696db560d46SMichael Walsh    This function will return a list of dictionaries as shown below:
697db560d46SMichael Walsh
698db560d46SMichael Walsh    df_report:
699db560d46SMichael Walsh      df_report[0]:
700db560d46SMichael Walsh        [filesystem]:                  dev
701db560d46SMichael Walsh        [1k-blocks]:                   247120
702db560d46SMichael Walsh        [used]:                        0
703db560d46SMichael Walsh        [available]:                   247120
704db560d46SMichael Walsh        [use%]:                        0%
705db560d46SMichael Walsh        [mounted]:                     /dev
706db560d46SMichael Walsh      df_report[1]:
707db560d46SMichael Walsh        [filesystem]:                  dev
708db560d46SMichael Walsh        [1k-blocks]:                   247120
709db560d46SMichael Walsh        [used]:                        0
710db560d46SMichael Walsh        [available]:                   247120
711db560d46SMichael Walsh        [use%]:                        0%
712db560d46SMichael Walsh        [mounted]:                     /dev
713db560d46SMichael Walsh
714db560d46SMichael Walsh    Other possible uses:
715db560d46SMichael Walsh    - Process the output of a ps command.
716410b1787SMichael Walsh    - Process the output of an ls command (the caller would need to supply column names)
717db560d46SMichael Walsh
718db560d46SMichael Walsh    Description of argument(s):
719410b1787SMichael Walsh    out_buf                         A text report.  The first line must be a header line which contains
720410b1787SMichael Walsh                                    column names.  Column names may not contain spaces.
721410b1787SMichael Walsh    **args                          Arguments to be interpreted by list_to_report.  (See docstring of
722db560d46SMichael Walsh                                    list_to_report function for details).
723db560d46SMichael Walsh    """
724db560d46SMichael Walsh
725255181c1SMichael Walsh    report_list = list(filter(None, out_buf.split("\n")))
726db560d46SMichael Walsh    return list_to_report(report_list, **args)
7277822b9e9SMichael Walsh
7287822b9e9SMichael Walsh
72946ef0a24SMichael Walshdef nested_get(key_name, structure):
7307822b9e9SMichael Walsh    r"""
731410b1787SMichael Walsh    Return a list of all values from the nested structure that have the given key name.
7327822b9e9SMichael Walsh
7337822b9e9SMichael Walsh    Example:
7347822b9e9SMichael Walsh
73546ef0a24SMichael Walsh    Given a dictionary structure named "personnel" with the following contents:
7367822b9e9SMichael Walsh
7377822b9e9SMichael Walsh    personnel:
7387822b9e9SMichael Walsh      [manager]:
7397822b9e9SMichael Walsh        [last_name]:             Doe
7407822b9e9SMichael Walsh        [first_name]:            John
7417822b9e9SMichael Walsh      [accountant]:
7427822b9e9SMichael Walsh        [last_name]:             Smith
7437822b9e9SMichael Walsh        [first_name]:            Will
7447822b9e9SMichael Walsh
7457822b9e9SMichael Walsh    The following code...
7467822b9e9SMichael Walsh
7477822b9e9SMichael Walsh    last_names = nested_get('last_name', personnel)
7487822b9e9SMichael Walsh    print_var(last_names)
7497822b9e9SMichael Walsh
75046ef0a24SMichael Walsh    Would result in the following data returned:
7517822b9e9SMichael Walsh
7527822b9e9SMichael Walsh    last_names:
7537822b9e9SMichael Walsh      last_names[0]:             Doe
7547822b9e9SMichael Walsh      last_names[1]:             Smith
7557822b9e9SMichael Walsh
7567822b9e9SMichael Walsh    Description of argument(s):
75746ef0a24SMichael Walsh    key_name                        The key name (e.g. 'last_name').
758410b1787SMichael Walsh    structure                       Any nested combination of lists or dictionaries (e.g. a dictionary, a
759410b1787SMichael Walsh                                    dictionary of dictionaries, a list of dictionaries, etc.).  This function
760410b1787SMichael Walsh                                    will locate the given key at any level within the structure and include
761410b1787SMichael Walsh                                    its value in the returned list.
7627822b9e9SMichael Walsh    """
7637822b9e9SMichael Walsh
7647822b9e9SMichael Walsh    result = []
765d882cdc5SMichael Walsh    if type(structure) is list:
766d882cdc5SMichael Walsh        for entry in structure:
76746ef0a24SMichael Walsh            result += nested_get(key_name, entry)
768d882cdc5SMichael Walsh        return result
76946ef0a24SMichael Walsh    elif gp.is_dict(structure):
77046ef0a24SMichael Walsh        for key, value in structure.items():
77146ef0a24SMichael Walsh            result += nested_get(key_name, value)
77246ef0a24SMichael Walsh            if key == key_name:
77346ef0a24SMichael Walsh                result.append(value)
7747822b9e9SMichael Walsh
7757822b9e9SMichael Walsh    return result
776074b7654SMichael Walsh
777074b7654SMichael Walsh
77846ef0a24SMichael Walshdef match_struct(structure, match_dict, regex=False):
77946ef0a24SMichael Walsh    r"""
780410b1787SMichael Walsh    Return True or False to indicate whether the structure matches the match dictionary.
78146ef0a24SMichael Walsh
78246ef0a24SMichael Walsh    Example:
78346ef0a24SMichael Walsh
78446ef0a24SMichael Walsh    Given a dictionary structure named "personnel" with the following contents:
78546ef0a24SMichael Walsh
78646ef0a24SMichael Walsh    personnel:
78746ef0a24SMichael Walsh      [manager]:
78846ef0a24SMichael Walsh        [last_name]:             Doe
78946ef0a24SMichael Walsh        [first_name]:            John
79046ef0a24SMichael Walsh      [accountant]:
79146ef0a24SMichael Walsh        [last_name]:             Smith
79246ef0a24SMichael Walsh        [first_name]:            Will
79346ef0a24SMichael Walsh
79446ef0a24SMichael Walsh    The following call would return True.
79546ef0a24SMichael Walsh
79646ef0a24SMichael Walsh    match_struct(personnel, {'last_name': '^Doe$'}, regex=True)
79746ef0a24SMichael Walsh
79846ef0a24SMichael Walsh    Whereas the following call would return False.
79946ef0a24SMichael Walsh
80046ef0a24SMichael Walsh    match_struct(personnel, {'last_name': 'Johnson'}, regex=True)
80146ef0a24SMichael Walsh
80246ef0a24SMichael Walsh    Description of argument(s):
803410b1787SMichael Walsh    structure                       Any nested combination of lists or dictionaries.  See the prolog of
80446ef0a24SMichael Walsh                                    get_nested() for details.
805410b1787SMichael Walsh    match_dict                      Each key/value pair in match_dict must exist somewhere in the structure
806410b1787SMichael Walsh                                    for the structure to be considered a match.  A match value of None is
807410b1787SMichael Walsh                                    considered a special case where the structure would be considered a match
808410b1787SMichael Walsh                                    only if the key in question is found nowhere in the structure.
809410b1787SMichael Walsh    regex                           Indicates whether the values in the match_dict should be interpreted as
81046ef0a24SMichael Walsh                                    regular expressions.
81146ef0a24SMichael Walsh    """
81246ef0a24SMichael Walsh
813410b1787SMichael Walsh    # The structure must match for each match_dict entry to be considered a match.  Therefore, any failure
814410b1787SMichael Walsh    # to match is grounds for returning False.
81546ef0a24SMichael Walsh    for match_key, match_value in match_dict.items():
81646ef0a24SMichael Walsh        struct_key_values = nested_get(match_key, structure)
81746ef0a24SMichael Walsh        if match_value is None:
81846ef0a24SMichael Walsh            # Handle this as special case.
81946ef0a24SMichael Walsh            if len(struct_key_values) != 0:
82046ef0a24SMichael Walsh                return False
82146ef0a24SMichael Walsh        else:
82246ef0a24SMichael Walsh            if len(struct_key_values) == 0:
82346ef0a24SMichael Walsh                return False
82446ef0a24SMichael Walsh            if regex:
825*20f38712SPatrick Williams                matches = [
826*20f38712SPatrick Williams                    x
827*20f38712SPatrick Williams                    for x in struct_key_values
828*20f38712SPatrick Williams                    if re.search(match_value, str(x))
829*20f38712SPatrick Williams                ]
83046ef0a24SMichael Walsh                if not matches:
83146ef0a24SMichael Walsh                    return False
83246ef0a24SMichael Walsh            elif match_value not in struct_key_values:
83346ef0a24SMichael Walsh                return False
83446ef0a24SMichael Walsh
83546ef0a24SMichael Walsh    return True
83646ef0a24SMichael Walsh
83746ef0a24SMichael Walsh
838399df5a2SMichael Walshdef filter_struct(structure, filter_dict, regex=False, invert=False):
839074b7654SMichael Walsh    r"""
840410b1787SMichael Walsh    Filter the structure by removing any entries that do NOT contain the keys/values specified in filter_dict
841410b1787SMichael Walsh    and return the result.
842074b7654SMichael Walsh
843410b1787SMichael Walsh    The selection process is directed only at the first-level entries of the structure.
84446ef0a24SMichael Walsh
845074b7654SMichael Walsh    Example:
846074b7654SMichael Walsh
847074b7654SMichael Walsh    Given a dictionary named "properties" that has the following structure:
848074b7654SMichael Walsh
849074b7654SMichael Walsh    properties:
850074b7654SMichael Walsh      [/redfish/v1/Systems/system/Processors]:
851074b7654SMichael Walsh        [Members]:
852074b7654SMichael Walsh          [0]:
853410b1787SMichael Walsh            [@odata.id]:                              /redfish/v1/Systems/system/Processors/cpu0
854074b7654SMichael Walsh          [1]:
855410b1787SMichael Walsh            [@odata.id]:                              /redfish/v1/Systems/system/Processors/cpu1
856074b7654SMichael Walsh      [/redfish/v1/Systems/system/Processors/cpu0]:
857074b7654SMichael Walsh        [Status]:
858074b7654SMichael Walsh          [State]:                                    Enabled
859074b7654SMichael Walsh          [Health]:                                   OK
860074b7654SMichael Walsh      [/redfish/v1/Systems/system/Processors/cpu1]:
861074b7654SMichael Walsh        [Status]:
862074b7654SMichael Walsh          [State]:                                    Enabled
863074b7654SMichael Walsh          [Health]:                                   Bad
864074b7654SMichael Walsh
865074b7654SMichael Walsh    The following call:
866074b7654SMichael Walsh
867074b7654SMichael Walsh    properties = filter_struct(properties, "[('Health', 'OK')]")
868074b7654SMichael Walsh
869074b7654SMichael Walsh    Would return a new properties dictionary that looks like this:
870074b7654SMichael Walsh
871074b7654SMichael Walsh    properties:
872074b7654SMichael Walsh      [/redfish/v1/Systems/system/Processors/cpu0]:
873074b7654SMichael Walsh        [Status]:
874074b7654SMichael Walsh          [State]:                                    Enabled
875074b7654SMichael Walsh          [Health]:                                   OK
876074b7654SMichael Walsh
877410b1787SMichael Walsh    Note that the first item in the original properties directory had no key anywhere in the structure named
878410b1787SMichael Walsh    "Health".  Therefore, that item failed to make the cut.  The next item did have a key named "Health"
879410b1787SMichael Walsh    whose value was "OK" so it was included in the new structure.  The third item had a key named "Health"
880410b1787SMichael Walsh    but its value was not "OK" so it also failed to make the cut.
881074b7654SMichael Walsh
882074b7654SMichael Walsh    Description of argument(s):
883410b1787SMichael Walsh    structure                       Any nested combination of lists or dictionaries.  See the prolog of
88446ef0a24SMichael Walsh                                    get_nested() for details.
885410b1787SMichael Walsh    filter_dict                     For each key/value pair in filter_dict, each entry in structure must
886410b1787SMichael Walsh                                    contain the same key/value pair at some level.  A filter_dict value of
887410b1787SMichael Walsh                                    None is treated as a special case.  Taking the example shown above,
888410b1787SMichael Walsh                                    [('State', None)] would mean that the result should only contain records
88946ef0a24SMichael Walsh                                    that have no State key at all.
890410b1787SMichael Walsh    regex                           Indicates whether the values in the filter_dict should be interpreted as
89146ef0a24SMichael Walsh                                    regular expressions.
892410b1787SMichael Walsh    invert                          Invert the results.  Instead of including only matching entries in the
893410b1787SMichael Walsh                                    results, include only NON-matching entries in the results.
894074b7654SMichael Walsh    """
895074b7654SMichael Walsh
896410b1787SMichael Walsh    # Convert filter_dict from a string containing a python object definition to an actual python object (if
897410b1787SMichael Walsh    # warranted).
898074b7654SMichael Walsh    filter_dict = fa.source_to_object(filter_dict)
899074b7654SMichael Walsh
900410b1787SMichael Walsh    # Determine whether structure is a list or a dictionary and process accordingly.  The result returned
901410b1787SMichael Walsh    # will be of the same type as the structure.
902074b7654SMichael Walsh    if type(structure) is list:
903074b7654SMichael Walsh        result = []
90446ef0a24SMichael Walsh        for element in structure:
905399df5a2SMichael Walsh            if match_struct(element, filter_dict, regex) != invert:
90646ef0a24SMichael Walsh                result.append(element)
907074b7654SMichael Walsh    else:
908074b7654SMichael Walsh        try:
909074b7654SMichael Walsh            result = collections.OrderedDict()
910074b7654SMichael Walsh        except AttributeError:
911074b7654SMichael Walsh            result = DotDict()
912074b7654SMichael Walsh        for struct_key, struct_value in structure.items():
913399df5a2SMichael Walsh            if match_struct(struct_value, filter_dict, regex) != invert:
914074b7654SMichael Walsh                result[struct_key] = struct_value
915074b7654SMichael Walsh
916074b7654SMichael Walsh    return result
917f3a8ae10SMichael Walsh
918f3a8ae10SMichael Walsh
919f3a8ae10SMichael Walshdef split_dict_on_key(split_key, dictionary):
920f3a8ae10SMichael Walsh    r"""
921f3a8ae10SMichael Walsh    Split a dictionary into two dictionaries based on the first occurrence of the split key and return the
922f3a8ae10SMichael Walsh    resulting sub-dictionaries.
923f3a8ae10SMichael Walsh
924f3a8ae10SMichael Walsh    Example:
925f3a8ae10SMichael Walsh    dictionary = {'one': 1, 'two': 2, 'three':3, 'four':4}
926f3a8ae10SMichael Walsh    dict1, dict2 = split_dict_on_key('three', dictionary)
927f3a8ae10SMichael Walsh    pvars(dictionary, dict1, dict2)
928f3a8ae10SMichael Walsh
929f3a8ae10SMichael Walsh    Output:
930f3a8ae10SMichael Walsh    dictionary:
931f3a8ae10SMichael Walsh      [one]:                                          1
932f3a8ae10SMichael Walsh      [two]:                                          2
933f3a8ae10SMichael Walsh      [three]:                                        3
934f3a8ae10SMichael Walsh      [four]:                                         4
935f3a8ae10SMichael Walsh    dict1:
936f3a8ae10SMichael Walsh      [one]:                                          1
937f3a8ae10SMichael Walsh      [two]:                                          2
938f3a8ae10SMichael Walsh    dict2:
939f3a8ae10SMichael Walsh      [three]:                                        3
940f3a8ae10SMichael Walsh      [four]:                                         4
941f3a8ae10SMichael Walsh
942f3a8ae10SMichael Walsh    Description of argument(s):
943f3a8ae10SMichael Walsh    split_key                       The key value to be used to determine where the dictionary should be
944f3a8ae10SMichael Walsh                                    split.
945f3a8ae10SMichael Walsh    dictionary                      The dictionary to be split.
946f3a8ae10SMichael Walsh    """
947f3a8ae10SMichael Walsh    dict1 = {}
948f3a8ae10SMichael Walsh    dict2 = {}
949f3a8ae10SMichael Walsh    found_split_key = False
950f3a8ae10SMichael Walsh    for key in list(dictionary.keys()):
951f3a8ae10SMichael Walsh        if key == split_key:
952f3a8ae10SMichael Walsh            found_split_key = True
953f3a8ae10SMichael Walsh        if found_split_key:
954f3a8ae10SMichael Walsh            dict2[key] = dictionary[key]
955f3a8ae10SMichael Walsh        else:
956f3a8ae10SMichael Walsh            dict1[key] = dictionary[key]
957f3a8ae10SMichael Walsh    return dict1, dict2
958