1#!/usr/bin/env python3
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 func_args as fa
18import gen_misc as gm
19import gen_print as gp
20
21
22def create_var_dict(*args):
23    r"""
24    Create a dictionary whose keys/values are the arg names/arg values passed to it and return it to the
25    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(
65    dict,
66    record_delim=default_record_delim,
67    key_val_delim=default_key_val_delim,
68):
69    r"""
70    Join a dictionary's keys and values into a string and return the string.
71
72    Description of argument(s):
73    dict                            The dictionary whose keys and values are to be joined.
74    record_delim                    The delimiter to be used to separate dictionary pairs in the resulting
75                                    string.
76    key_val_delim                   The delimiter to be used to separate keys from values in the resulting
77                                    string.
78
79    Example use:
80
81    gp.print_var(var_dict)
82    str1 = join_dict(var_dict)
83    gp.print_var(str1)
84
85    Program output.
86    var_dict:
87      var_dict[first_name]:                           Steve
88      var_dict[last_name]:                            Smith
89    str1:                                             first_name.Steve:last_name.Smith
90    """
91
92    format_str = "%s" + key_val_delim + "%s"
93    return record_delim.join(
94        [format_str % (key, value) for (key, value) in dict.items()]
95    )
96
97
98def split_to_dict(
99    string,
100    record_delim=default_record_delim,
101    key_val_delim=default_key_val_delim,
102):
103    r"""
104    Split a string into a dictionary and return it.
105
106    This function is the complement to join_dict.
107
108    Description of argument(s):
109    string                          The string to be split into a dictionary.  The string must have the
110                                    proper delimiters in it.  A string created by join_dict would qualify.
111    record_delim                    The delimiter to be used to separate dictionary pairs in the input string.
112    key_val_delim                   The delimiter to be used to separate keys/values in the input string.
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:                                             first_name.Steve:last_name.Smith
123    new_dict:
124      new_dict[first_name]:                           Steve
125      new_dict[last_name]:                            Smith
126    """
127
128    try:
129        result_dict = collections.OrderedDict()
130    except AttributeError:
131        result_dict = DotDict()
132
133    raw_keys_values = string.split(record_delim)
134    for key_value in raw_keys_values:
135        key_value_list = key_value.split(key_val_delim)
136        try:
137            result_dict[key_value_list[0]] = key_value_list[1]
138        except IndexError:
139            result_dict[key_value_list[0]] = ""
140
141    return result_dict
142
143
144def create_file_path(file_name_dict, dir_path="/tmp/", file_suffix=""):
145    r"""
146    Create a file path using the given parameters and return it.
147
148    Description of argument(s):
149    file_name_dict                  A dictionary with keys/values which are to appear as part of the file
150                                    name.
151    dir_path                        The dir_path that is to appear as part of the file name.
152    file_suffix                     A suffix to be included as part of the file name.
153    """
154
155    dir_path = gm.add_trailing_slash(dir_path)
156    return dir_path + join_dict(file_name_dict) + file_suffix
157
158
159def parse_file_path(file_path):
160    r"""
161    Parse a file path created by create_file_path and return the result as a dictionary.
162
163    This function is the complement to create_file_path.
164
165    Description of argument(s):
166    file_path                       The file_path.
167
168    Example use:
169    gp.print_var(boot_results_file_path)
170    file_path_data = parse_file_path(boot_results_file_path)
171    gp.print_var(file_path_data)
172
173    Program output.
174
175    boot_results_file_path:
176    /tmp/pgm_name.obmc_boot_test:openbmc_nickname.beye6:master_pid.2039:boot_results
177    file_path_data:
178      file_path_data[dir_path]:                       /tmp/
179      file_path_data[pgm_name]:                       obmc_boot_test
180      file_path_data[openbmc_nickname]:               beye6
181      file_path_data[master_pid]:                     2039
182      file_path_data[boot_results]:
183    """
184
185    try:
186        result_dict = collections.OrderedDict()
187    except AttributeError:
188        result_dict = DotDict()
189
190    dir_path = os.path.dirname(file_path) + os.sep
191    file_path = os.path.basename(file_path)
192
193    result_dict["dir_path"] = dir_path
194
195    result_dict.update(split_to_dict(file_path))
196
197    return result_dict
198
199
200def parse_key_value(string, delim=":", strip=" ", to_lower=1, underscores=1):
201    r"""
202    Parse a key/value string and return as a key/value tuple.
203
204    This function is useful for parsing a line of program output or data that is in the following form:
205    <key or variable name><delimiter><value>
206
207    An example of a key/value string would be as follows:
208
209    Current Limit State: No Active Power Limit
210
211    In the example shown, the delimiter is ":".  The resulting key would be as follows:
212    Current Limit State
213
214    Note: If one were to take the default values of to_lower=1 and underscores=1, the resulting key would be
215    as follows:
216    current_limit_state
217
218    The to_lower and underscores arguments are provided for those who wish to have their key names have the
219    look and feel of python variable names.
220
221    The resulting value for the example above would be as follows:
222    No Active Power Limit
223
224    Another example:
225    name=Mike
226
227    In this case, the delim would be "=", the key is "name" and the value is "Mike".
228
229    Description of argument(s):
230    string                          The string to be parsed.
231    delim                           The delimiter which separates the key from the value.
232    strip                           The characters (if any) to strip from the beginning and end of both the
233                                    key and the value.
234    to_lower                        Change the key name to lower case.
235    underscores                     Change any blanks found in the key name to underscores.
236    """
237
238    pair = string.split(delim)
239
240    key = pair[0].strip(strip)
241    if len(pair) == 0:
242        value = ""
243    else:
244        value = delim.join(pair[1:]).strip(strip)
245
246    if to_lower:
247        key = key.lower()
248    if underscores:
249        key = re.sub(r" ", "_", key)
250
251    return key, value
252
253
254def key_value_list_to_dict(key_value_list, process_indent=0, **args):
255    r"""
256    Convert a list containing key/value strings or tuples to a dictionary and return it.
257
258    See docstring of parse_key_value function for details on key/value strings.
259
260    Example usage:
261
262    For the following value of key_value_list:
263
264    key_value_list:
265      [0]:          Current Limit State: No Active Power Limit
266      [1]:          Exception actions:   Hard Power Off & Log Event to SEL
267      [2]:          Power Limit:         0 Watts
268      [3]:          Correction time:     0 milliseconds
269      [4]:          Sampling period:     0 seconds
270
271    And the following call in python:
272
273    power_limit = key_value_outbuf_to_dict(key_value_list)
274
275    The resulting power_limit directory would look like this:
276
277    power_limit:
278      [current_limit_state]:        No Active Power Limit
279      [exception_actions]:          Hard Power Off & Log Event to SEL
280      [power_limit]:                0 Watts
281      [correction_time]:            0 milliseconds
282      [sampling_period]:            0 seconds
283
284    For the following list:
285
286    headers:
287      headers[0]:
288        headers[0][0]:           content-length
289        headers[0][1]:           559
290      headers[1]:
291        headers[1][0]:           x-xss-protection
292        headers[1][1]:           1; mode=block
293
294    And the following call in python:
295
296    headers_dict = key_value_list_to_dict(headers)
297
298    The resulting headers_dict would look like this:
299
300    headers_dict:
301      [content-length]:          559
302      [x-xss-protection]:        1; mode=block
303
304    Another example containing a sub-list (see process_indent description 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) 'Additional Device Support' has no
314    value and 2) The entries below it are indented.  In this case those entries contain no delimiters (":")
315    so they will be processed as a list rather than as a dictionary.  The result would be as follows:
316
317    mc_info:
318      mc_info[provides_device_sdrs]:            yes
319      mc_info[additional_device_support]:
320        mc_info[additional_device_support][0]:  Sensor Device
321        mc_info[additional_device_support][1]:  SEL Device
322        mc_info[additional_device_support][2]:  FRU Inventory Device
323        mc_info[additional_device_support][3]:  Chassis Device
324
325    Description of argument(s):
326    key_value_list                  A list of key/value strings.  (See docstring of parse_key_value function
327                                    for details).
328    process_indent                  This indicates that indented sub-dictionaries and sub-lists are to be
329                                    processed as such.  An entry may have a sub-dict or sub-list if 1) It has
330                                    no value other than blank 2) There are entries below it that are
331                                    indented.  Note that process_indent is not allowed for a list of tuples
332                                    (vs. a list of key/value strings).
333    **args                          Arguments to be interpreted by parse_key_value.  (See docstring of
334                                    parse_key_value function for details).
335    """
336
337    try:
338        result_dict = collections.OrderedDict()
339    except AttributeError:
340        result_dict = DotDict()
341
342    if not process_indent:
343        for entry in key_value_list:
344            if type(entry) is tuple:
345                key, value = entry
346            else:
347                key, value = parse_key_value(entry, **args)
348            result_dict[key] = value
349        return result_dict
350
351    # Process list while paying heed to indentation.
352    delim = args.get("delim", ":")
353    # Initialize "parent_" indentation level variables.
354    parent_indent = len(key_value_list[0]) - len(key_value_list[0].lstrip())
355    sub_list = []
356    for entry in key_value_list:
357        key, value = parse_key_value(entry, **args)
358
359        indent = len(entry) - len(entry.lstrip())
360
361        if indent > parent_indent and parent_value == "":
362            # This line is indented compared to the parent entry and the parent entry has no value.
363            # Append the entry to sub_list for later processing.
364            sub_list.append(str(entry))
365            continue
366
367        # Process any outstanding sub_list and add it to result_dict[parent_key].
368        if len(sub_list) > 0:
369            if any(delim in word for word in sub_list):
370                # If delim is found anywhere in the sub_list, we'll process as a sub-dictionary.
371                result_dict[parent_key] = key_value_list_to_dict(
372                    sub_list, **args
373                )
374            else:
375                result_dict[parent_key] = list(map(str.strip, sub_list))
376            del sub_list[:]
377
378        result_dict[key] = value
379
380        parent_key = key
381        parent_value = value
382        parent_indent = indent
383
384    # Any outstanding sub_list to be processed?
385    if len(sub_list) > 0:
386        if any(delim in word for word in sub_list):
387            # If delim is found anywhere in the sub_list, we'll process as a sub-dictionary.
388            result_dict[parent_key] = key_value_list_to_dict(sub_list, **args)
389        else:
390            result_dict[parent_key] = list(map(str.strip, sub_list))
391
392    return result_dict
393
394
395def key_value_outbuf_to_dict(out_buf, **args):
396    r"""
397    Convert a buffer with a key/value string on each line to a dictionary and return it.
398
399    Each line in the out_buf should end with a \n.
400
401    See docstring of parse_key_value function for details on key/value strings.
402
403    Example usage:
404
405    For the following value of out_buf:
406
407    Current Limit State: No Active Power Limit
408    Exception actions:   Hard Power Off & Log Event to SEL
409    Power Limit:         0 Watts
410    Correction time:     0 milliseconds
411    Sampling period:     0 seconds
412
413    And the following call in python:
414
415    power_limit = key_value_outbuf_to_dict(out_buf)
416
417    The resulting power_limit directory would look like this:
418
419    power_limit:
420      [current_limit_state]:        No Active Power Limit
421      [exception_actions]:          Hard Power Off & Log Event to SEL
422      [power_limit]:                0 Watts
423      [correction_time]:            0 milliseconds
424      [sampling_period]:            0 seconds
425
426    Description of argument(s):
427    out_buf                         A buffer with a key/value string on each line. (See docstring of
428                                    parse_key_value function for details).
429    **args                          Arguments to be interpreted by parse_key_value.  (See docstring of
430                                    parse_key_value function for details).
431    """
432
433    # Create key_var_list and remove null entries.
434    key_var_list = list(filter(None, out_buf.split("\n")))
435    return key_value_list_to_dict(key_var_list, **args)
436
437
438def key_value_outbuf_to_dicts(out_buf, **args):
439    r"""
440    Convert a buffer containing multiple sections with key/value strings on each line to a list of
441    dictionaries and return it.
442
443    Sections in the output are delimited by blank lines.
444
445    Example usage:
446
447    For the following value of out_buf:
448
449    Maximum User IDs     : 15
450    Enabled User IDs     : 1
451
452    User ID              : 1
453    User Name            : root
454    Fixed Name           : No
455    Access Available     : callback
456    Link Authentication  : enabled
457    IPMI Messaging       : enabled
458    Privilege Level      : ADMINISTRATOR
459    Enable Status        : enabled
460
461    User ID              : 2
462    User Name            :
463    Fixed Name           : No
464    Access Available     : call-in / callback
465    Link Authentication  : disabled
466    IPMI Messaging       : disabled
467    Privilege Level      : NO ACCESS
468    Enable Status        : disabled
469
470    And the following call in python:
471
472    user_info = key_value_outbuf_to_dicts(out_buf)
473
474    The resulting user_info list would look like this:
475
476    user_info:
477      [0]:
478        [maximum_user_ids]:      15
479        [enabled_user_ids]:      1
480      [1]:
481        [user_id]:               1
482        [user_name]:             root
483        [fixed_name]:            No
484        [access_available]:      callback
485        [link_authentication]:   enabled
486        [ipmi_messaging]:        enabled
487        [privilege_level]:       ADMINISTRATOR
488        [enable_status]:         enabled
489      [2]:
490        [user_id]:               2
491        [user_name]:
492        [fixed_name]:            No
493        [access_available]:      call-in / callback
494        [link_authentication]:   disabled
495        [ipmi_messaging]:        disabled
496        [privilege_level]:       NO ACCESS
497        [enable_status]:         disabled
498
499    Description of argument(s):
500    out_buf                         A buffer with multiple secionts of key/value strings on each line.
501                                    Sections are delimited by one or more blank lines (i.e. line feeds). (See
502                                    docstring of parse_key_value function for details).
503    **args                          Arguments to be interpreted by parse_key_value.  (See docstring of
504                                    parse_key_value function for details).
505    """
506    return [
507        key_value_outbuf_to_dict(x, **args)
508        for x in re.split("\n[\n]+", out_buf)
509    ]
510
511
512def create_field_desc_regex(line):
513    r"""
514    Create a field descriptor regular expression based on the input line and return it.
515
516    This function is designed for use by the list_to_report function (defined below).
517
518    Example:
519
520    Given the following input line:
521
522    --------   ------------ ------------------ ------------------------
523
524    This function will return this regular expression:
525
526    (.{8})   (.{12}) (.{18}) (.{24})
527
528    This means that other report lines interpreted using the regular expression are expected to have:
529    - An 8 character field
530    - 3 spaces
531    - A 12 character field
532    - One space
533    - An 18 character field
534    - One space
535    - A 24 character field
536
537    Description of argument(s):
538    line                            A line consisting of dashes to represent fields and spaces to delimit
539                                    fields.
540    """
541
542    # Split the line into a descriptors list.  Example:
543    # descriptors:
544    #  descriptors[0]:            --------
545    #  descriptors[1]:
546    #  descriptors[2]:
547    #  descriptors[3]:            ------------
548    #  descriptors[4]:            ------------------
549    #  descriptors[5]:            ------------------------
550    descriptors = line.split(" ")
551
552    # Create regexes list.  Example:
553    # regexes:
554    #  regexes[0]:                (.{8})
555    #  regexes[1]:
556    #  regexes[2]:
557    #  regexes[3]:                (.{12})
558    #  regexes[4]:                (.{18})
559    #  regexes[5]:                (.{24})
560    regexes = []
561    for descriptor in descriptors:
562        if descriptor == "":
563            regexes.append("")
564        else:
565            regexes.append("(.{" + str(len(descriptor)) + "})")
566
567    # Join the regexes list into a regex string.
568    field_desc_regex = " ".join(regexes)
569
570    return field_desc_regex
571
572
573def list_to_report(report_list, to_lower=1, field_delim=None):
574    r"""
575    Convert a list containing report text lines to a report "object" and return it.
576
577    The first entry in report_list must be a header line consisting of column names delimited by white space.
578    No column name may contain white space.  The remaining report_list entries should contain tabular data
579    which corresponds to the column names.
580
581    A report object is a list where each entry is a dictionary whose keys are the field names from the first
582    entry in report_list.
583
584    Example:
585    Given the following report_list as input:
586
587    rl:
588      rl[0]: Filesystem           1K-blocks      Used Available Use% Mounted on
589      rl[1]: dev                     247120         0    247120   0% /dev
590      rl[2]: tmpfs                   248408     79792    168616  32% /run
591
592    This function will return a list of dictionaries as shown below:
593
594    df_report:
595      df_report[0]:
596        [filesystem]:                  dev
597        [1k-blocks]:                   247120
598        [used]:                        0
599        [available]:                   247120
600        [use%]:                        0%
601        [mounted]:                     /dev
602      df_report[1]:
603        [filesystem]:                  dev
604        [1k-blocks]:                   247120
605        [used]:                        0
606        [available]:                   247120
607        [use%]:                        0%
608        [mounted]:                     /dev
609
610    Notice that because "Mounted on" contains a space, "on" would be considered the 7th field.  In this case,
611    there is never any data in field 7 so things work out nicely.  A caller could do some pre-processing if
612    desired (e.g. change "Mounted on" to "Mounted_on").
613
614    Example 2:
615
616    If the 2nd line of report data is a series of dashes and spaces as in the following example, that line
617    will serve to delineate columns.
618
619    The 2nd line of data is like this:
620    ID                              status       size               tool,clientid,userid
621    -------- ------------ ------------------ ------------------------
622    20000001 in progress  0x7D0              ,,
623
624    Description of argument(s):
625    report_list                     A list where each entry is one line of output from a report.  The first
626                                    entry must be a header line which contains column names.  Column names
627                                    may not contain spaces.
628    to_lower                        Change the resulting key names to lower case.
629    field_delim                     Indicates that there are field delimiters in report_list entries (which
630                                    should be removed).
631    """
632
633    if len(report_list) <= 1:
634        # If we don't have at least a descriptor line and one line of data, return an empty array.
635        return []
636
637    if field_delim is not None:
638        report_list = [re.sub("\\|", "", line) for line in report_list]
639
640    header_line = report_list[0]
641    if to_lower:
642        header_line = header_line.lower()
643
644    field_desc_regex = ""
645    if re.match(r"^-[ -]*$", report_list[1]):
646        # We have a field descriptor line (as shown in example 2 above).
647        field_desc_regex = create_field_desc_regex(report_list[1])
648        field_desc_len = len(report_list[1])
649        pad_format_string = "%-" + str(field_desc_len) + "s"
650        # The field descriptor line has served its purpose.  Deleting it.
651        del report_list[1]
652
653    # Process the header line by creating a list of column names.
654    if field_desc_regex == "":
655        columns = header_line.split()
656    else:
657        # Pad the line with spaces on the right to facilitate processing with field_desc_regex.
658        header_line = pad_format_string % header_line
659        columns = list(
660            map(str.strip, re.findall(field_desc_regex, header_line)[0])
661        )
662
663    report_obj = []
664    for report_line in report_list[1:]:
665        if field_desc_regex == "":
666            line = report_line.split()
667        else:
668            # Pad the line with spaces on the right to facilitate processing with field_desc_regex.
669            report_line = pad_format_string % report_line
670            line = list(
671                map(str.strip, re.findall(field_desc_regex, report_line)[0])
672            )
673        try:
674            line_dict = collections.OrderedDict(zip(columns, line))
675        except AttributeError:
676            line_dict = DotDict(zip(columns, line))
677        report_obj.append(line_dict)
678
679    return report_obj
680
681
682def outbuf_to_report(out_buf, **args):
683    r"""
684    Convert a text buffer containing report lines to a report "object" and return it.
685
686    Refer to list_to_report (above) for more details.
687
688    Example:
689
690    Given the following out_buf:
691
692    Filesystem                      1K-blocks      Used Available Use% Mounted on
693    dev                             247120         0    247120   0% /dev
694    tmpfs                           248408     79792    168616  32% /run
695
696    This function will return a list of dictionaries as shown below:
697
698    df_report:
699      df_report[0]:
700        [filesystem]:                  dev
701        [1k-blocks]:                   247120
702        [used]:                        0
703        [available]:                   247120
704        [use%]:                        0%
705        [mounted]:                     /dev
706      df_report[1]:
707        [filesystem]:                  dev
708        [1k-blocks]:                   247120
709        [used]:                        0
710        [available]:                   247120
711        [use%]:                        0%
712        [mounted]:                     /dev
713
714    Other possible uses:
715    - Process the output of a ps command.
716    - Process the output of an ls command (the caller would need to supply column names)
717
718    Description of argument(s):
719    out_buf                         A text report.  The first line must be a header line which contains
720                                    column names.  Column names may not contain spaces.
721    **args                          Arguments to be interpreted by list_to_report.  (See docstring of
722                                    list_to_report function for details).
723    """
724
725    report_list = list(filter(None, out_buf.split("\n")))
726    return list_to_report(report_list, **args)
727
728
729def nested_get(key_name, structure):
730    r"""
731    Return a list of all values from the nested structure that have the given key name.
732
733    Example:
734
735    Given a dictionary structure named "personnel" with the following contents:
736
737    personnel:
738      [manager]:
739        [last_name]:             Doe
740        [first_name]:            John
741      [accountant]:
742        [last_name]:             Smith
743        [first_name]:            Will
744
745    The following code...
746
747    last_names = nested_get('last_name', personnel)
748    print_var(last_names)
749
750    Would result in the following data returned:
751
752    last_names:
753      last_names[0]:             Doe
754      last_names[1]:             Smith
755
756    Description of argument(s):
757    key_name                        The key name (e.g. 'last_name').
758    structure                       Any nested combination of lists or dictionaries (e.g. a dictionary, a
759                                    dictionary of dictionaries, a list of dictionaries, etc.).  This function
760                                    will locate the given key at any level within the structure and include
761                                    its value in the returned list.
762    """
763
764    result = []
765    if type(structure) is list:
766        for entry in structure:
767            result += nested_get(key_name, entry)
768        return result
769    elif gp.is_dict(structure):
770        for key, value in structure.items():
771            result += nested_get(key_name, value)
772            if key == key_name:
773                result.append(value)
774
775    return result
776
777
778def match_struct(structure, match_dict, regex=False):
779    r"""
780    Return True or False to indicate whether the structure matches the match dictionary.
781
782    Example:
783
784    Given a dictionary structure named "personnel" with the following contents:
785
786    personnel:
787      [manager]:
788        [last_name]:             Doe
789        [first_name]:            John
790      [accountant]:
791        [last_name]:             Smith
792        [first_name]:            Will
793
794    The following call would return True.
795
796    match_struct(personnel, {'last_name': '^Doe$'}, regex=True)
797
798    Whereas the following call would return False.
799
800    match_struct(personnel, {'last_name': 'Johnson'}, regex=True)
801
802    Description of argument(s):
803    structure                       Any nested combination of lists or dictionaries.  See the prolog of
804                                    get_nested() for details.
805    match_dict                      Each key/value pair in match_dict must exist somewhere in the structure
806                                    for the structure to be considered a match.  A match value of None is
807                                    considered a special case where the structure would be considered a match
808                                    only if the key in question is found nowhere in the structure.
809    regex                           Indicates whether the values in the match_dict should be interpreted as
810                                    regular expressions.
811    """
812
813    # The structure must match for each match_dict entry to be considered a match.  Therefore, any failure
814    # to match is grounds for returning False.
815    for match_key, match_value in match_dict.items():
816        struct_key_values = nested_get(match_key, structure)
817        if match_value is None:
818            # Handle this as special case.
819            if len(struct_key_values) != 0:
820                return False
821        else:
822            if len(struct_key_values) == 0:
823                return False
824            if regex:
825                matches = [
826                    x
827                    for x in struct_key_values
828                    if re.search(match_value, str(x))
829                ]
830                if not matches:
831                    return False
832            elif match_value not in struct_key_values:
833                return False
834
835    return True
836
837
838def filter_struct(structure, filter_dict, regex=False, invert=False):
839    r"""
840    Filter the structure by removing any entries that do NOT contain the keys/values specified in filter_dict
841    and return the result.
842
843    The selection process is directed only at the first-level entries of the structure.
844
845    Example:
846
847    Given a dictionary named "properties" that has the following structure:
848
849    properties:
850      [/redfish/v1/Systems/system/Processors]:
851        [Members]:
852          [0]:
853            [@odata.id]:                              /redfish/v1/Systems/system/Processors/cpu0
854          [1]:
855            [@odata.id]:                              /redfish/v1/Systems/system/Processors/cpu1
856      [/redfish/v1/Systems/system/Processors/cpu0]:
857        [Status]:
858          [State]:                                    Enabled
859          [Health]:                                   OK
860      [/redfish/v1/Systems/system/Processors/cpu1]:
861        [Status]:
862          [State]:                                    Enabled
863          [Health]:                                   Bad
864
865    The following call:
866
867    properties = filter_struct(properties, "[('Health', 'OK')]")
868
869    Would return a new properties dictionary that looks like this:
870
871    properties:
872      [/redfish/v1/Systems/system/Processors/cpu0]:
873        [Status]:
874          [State]:                                    Enabled
875          [Health]:                                   OK
876
877    Note that the first item in the original properties directory had no key anywhere in the structure named
878    "Health".  Therefore, that item failed to make the cut.  The next item did have a key named "Health"
879    whose value was "OK" so it was included in the new structure.  The third item had a key named "Health"
880    but its value was not "OK" so it also failed to make the cut.
881
882    Description of argument(s):
883    structure                       Any nested combination of lists or dictionaries.  See the prolog of
884                                    get_nested() for details.
885    filter_dict                     For each key/value pair in filter_dict, each entry in structure must
886                                    contain the same key/value pair at some level.  A filter_dict value of
887                                    None is treated as a special case.  Taking the example shown above,
888                                    [('State', None)] would mean that the result should only contain records
889                                    that have no State key at all.
890    regex                           Indicates whether the values in the filter_dict should be interpreted as
891                                    regular expressions.
892    invert                          Invert the results.  Instead of including only matching entries in the
893                                    results, include only NON-matching entries in the results.
894    """
895
896    # Convert filter_dict from a string containing a python object definition to an actual python object (if
897    # warranted).
898    filter_dict = fa.source_to_object(filter_dict)
899
900    # Determine whether structure is a list or a dictionary and process accordingly.  The result returned
901    # will be of the same type as the structure.
902    if type(structure) is list:
903        result = []
904        for element in structure:
905            if match_struct(element, filter_dict, regex) != invert:
906                result.append(element)
907    else:
908        try:
909            result = collections.OrderedDict()
910        except AttributeError:
911            result = DotDict()
912        for struct_key, struct_value in structure.items():
913            if match_struct(struct_value, filter_dict, regex) != invert:
914                result[struct_key] = struct_value
915
916    return result
917
918
919def split_dict_on_key(split_key, dictionary):
920    r"""
921    Split a dictionary into two dictionaries based on the first occurrence of the split key and return the
922    resulting sub-dictionaries.
923
924    Example:
925    dictionary = {'one': 1, 'two': 2, 'three':3, 'four':4}
926    dict1, dict2 = split_dict_on_key('three', dictionary)
927    pvars(dictionary, dict1, dict2)
928
929    Output:
930    dictionary:
931      [one]:                                          1
932      [two]:                                          2
933      [three]:                                        3
934      [four]:                                         4
935    dict1:
936      [one]:                                          1
937      [two]:                                          2
938    dict2:
939      [three]:                                        3
940      [four]:                                         4
941
942    Description of argument(s):
943    split_key                       The key value to be used to determine where the dictionary should be
944                                    split.
945    dictionary                      The dictionary to be split.
946    """
947    dict1 = {}
948    dict2 = {}
949    found_split_key = False
950    for key in list(dictionary.keys()):
951        if key == split_key:
952            found_split_key = True
953        if found_split_key:
954            dict2[key] = dictionary[key]
955        else:
956            dict1[key] = dictionary[key]
957    return dict1, dict2
958