|  | #!/usr/bin/env python | 
|  |  | 
|  | r""" | 
|  | Define variable manipulation functions. | 
|  | """ | 
|  |  | 
|  | import os | 
|  | import re | 
|  |  | 
|  | try: | 
|  | from robot.utils import DotDict | 
|  | except ImportError: | 
|  | pass | 
|  |  | 
|  | import collections | 
|  |  | 
|  | import gen_print as gp | 
|  | import gen_misc as gm | 
|  |  | 
|  |  | 
|  | def create_var_dict(*args): | 
|  | r""" | 
|  | Create a dictionary whose keys/values are the arg names/arg values passed | 
|  | to it and return it to the caller. | 
|  |  | 
|  | Note: The resulting dictionary will be ordered. | 
|  |  | 
|  | Description of argument(s): | 
|  | *args  An unlimited number of arguments to be processed. | 
|  |  | 
|  | Example use: | 
|  |  | 
|  | first_name = 'Steve' | 
|  | last_name = 'Smith' | 
|  | var_dict = create_var_dict(first_name, last_name) | 
|  |  | 
|  | gp.print_var(var_dict) | 
|  |  | 
|  | The print-out of the resulting var dictionary is: | 
|  | var_dict: | 
|  | var_dict[first_name]:                           Steve | 
|  | var_dict[last_name]:                            Smith | 
|  | """ | 
|  |  | 
|  | try: | 
|  | result_dict = collections.OrderedDict() | 
|  | except AttributeError: | 
|  | result_dict = DotDict() | 
|  |  | 
|  | arg_num = 1 | 
|  | for arg in args: | 
|  | arg_name = gp.get_arg_name(None, arg_num, stack_frame_ix=2) | 
|  | result_dict[arg_name] = arg | 
|  | arg_num += 1 | 
|  |  | 
|  | return result_dict | 
|  |  | 
|  |  | 
|  | default_record_delim = ':' | 
|  | default_key_val_delim = '.' | 
|  |  | 
|  |  | 
|  | def join_dict(dict, | 
|  | record_delim=default_record_delim, | 
|  | key_val_delim=default_key_val_delim): | 
|  | r""" | 
|  | Join a dictionary's keys and values into a string and return the string. | 
|  |  | 
|  | Description of argument(s): | 
|  | dict                            The dictionary whose keys and values are | 
|  | to be joined. | 
|  | record_delim                    The delimiter to be used to separate | 
|  | dictionary pairs in the resulting string. | 
|  | key_val_delim                   The delimiter to be used to separate keys | 
|  | from values in the resulting string. | 
|  |  | 
|  | Example use: | 
|  |  | 
|  | gp.print_var(var_dict) | 
|  | str1 = join_dict(var_dict) | 
|  | gp.pvar(str1) | 
|  |  | 
|  | Program output. | 
|  | var_dict: | 
|  | var_dict[first_name]:                           Steve | 
|  | var_dict[last_name]:                            Smith | 
|  | str1: | 
|  | first_name.Steve:last_name.Smith | 
|  | """ | 
|  |  | 
|  | format_str = '%s' + key_val_delim + '%s' | 
|  | return record_delim.join([format_str % (key, value) for (key, value) in | 
|  | dict.items()]) | 
|  |  | 
|  |  | 
|  | def split_to_dict(string, | 
|  | record_delim=default_record_delim, | 
|  | key_val_delim=default_key_val_delim): | 
|  | r""" | 
|  | Split a string into a dictionary and return it. | 
|  |  | 
|  | This function is the complement to join_dict. | 
|  |  | 
|  | Description of argument(s): | 
|  | string                          The string to be split into a dictionary. | 
|  | The string must have the proper delimiters | 
|  | in it.  A string created by join_dict | 
|  | would qualify. | 
|  | record_delim                    The delimiter to be used to separate | 
|  | dictionary pairs in the input string. | 
|  | key_val_delim                   The delimiter to be used to separate | 
|  | keys/values in the input string. | 
|  |  | 
|  | Example use: | 
|  |  | 
|  | gp.print_var(str1) | 
|  | new_dict = split_to_dict(str1) | 
|  | gp.print_var(new_dict) | 
|  |  | 
|  |  | 
|  | Program output. | 
|  | str1: | 
|  | first_name.Steve:last_name.Smith | 
|  | new_dict: | 
|  | new_dict[first_name]:                           Steve | 
|  | new_dict[last_name]:                            Smith | 
|  | """ | 
|  |  | 
|  | try: | 
|  | result_dict = collections.OrderedDict() | 
|  | except AttributeError: | 
|  | result_dict = DotDict() | 
|  |  | 
|  | raw_keys_values = string.split(record_delim) | 
|  | for key_value in raw_keys_values: | 
|  | key_value_list = key_value.split(key_val_delim) | 
|  | try: | 
|  | result_dict[key_value_list[0]] = key_value_list[1] | 
|  | except IndexError: | 
|  | result_dict[key_value_list[0]] = "" | 
|  |  | 
|  | return result_dict | 
|  |  | 
|  |  | 
|  | def create_file_path(file_name_dict, | 
|  | dir_path="/tmp/", | 
|  | file_suffix=""): | 
|  | r""" | 
|  | Create a file path using the given parameters and return it. | 
|  |  | 
|  | Description of argument(s): | 
|  | file_name_dict                  A dictionary with keys/values which are to | 
|  | appear as part of the file name. | 
|  | dir_path                        The dir_path that is to appear as part of | 
|  | the file name. | 
|  | file_suffix                     A suffix to be included as part of the | 
|  | file name. | 
|  | """ | 
|  |  | 
|  | dir_path = gm.add_trailing_slash(dir_path) | 
|  | return dir_path + join_dict(file_name_dict) + file_suffix | 
|  |  | 
|  |  | 
|  | def parse_file_path(file_path): | 
|  | r""" | 
|  | Parse a file path created by create_file_path and return the result as a | 
|  | dictionary. | 
|  |  | 
|  | This function is the complement to create_file_path. | 
|  |  | 
|  | Description of argument(s): | 
|  | file_path                       The file_path. | 
|  |  | 
|  | Example use: | 
|  | gp.pvar(boot_results_file_path) | 
|  | file_path_data = parse_file_path(boot_results_file_path) | 
|  | gp.pvar(file_path_data) | 
|  |  | 
|  | Program output. | 
|  |  | 
|  | boot_results_file_path: | 
|  | /tmp/pgm_name.obmc_boot_test:openbmc_nickname.beye6:master_pid.2039:boot_re | 
|  | sults | 
|  | file_path_data: | 
|  | file_path_data[dir_path]:                       /tmp/ | 
|  | file_path_data[pgm_name]:                       obmc_boot_test | 
|  | file_path_data[openbmc_nickname]:               beye6 | 
|  | file_path_data[master_pid]:                     2039 | 
|  | file_path_data[boot_results]: | 
|  | """ | 
|  |  | 
|  | try: | 
|  | result_dict = collections.OrderedDict() | 
|  | except AttributeError: | 
|  | result_dict = DotDict() | 
|  |  | 
|  | dir_path = os.path.dirname(file_path) + os.sep | 
|  | file_path = os.path.basename(file_path) | 
|  |  | 
|  | result_dict['dir_path'] = dir_path | 
|  |  | 
|  | result_dict.update(split_to_dict(file_path)) | 
|  |  | 
|  | return result_dict | 
|  |  | 
|  |  | 
|  | def parse_key_value(string, | 
|  | delim=":", | 
|  | strip=" ", | 
|  | to_lower=1, | 
|  | underscores=1): | 
|  | r""" | 
|  | Parse a key/value string and return as a key/value tuple. | 
|  |  | 
|  | This function is useful for parsing a line of program output or data that | 
|  | is in the following form: | 
|  | <key or variable name><delimiter><value> | 
|  |  | 
|  | An example of a key/value string would be as follows: | 
|  |  | 
|  | Current Limit State: No Active Power Limit | 
|  |  | 
|  | In the example shown, the delimiter is ":".  The resulting key would be as | 
|  | follows: | 
|  | Current Limit State | 
|  |  | 
|  | Note: If one were to take the default values of to_lower=1 and | 
|  | underscores=1, the resulting key would be as follows: | 
|  | current_limit_state | 
|  |  | 
|  | The to_lower and underscores arguments are provided for those who wish to | 
|  | have their key names have the look and feel of python variable names. | 
|  |  | 
|  | The resulting value for the example above would be as follows: | 
|  | No Active Power Limit | 
|  |  | 
|  | Another example: | 
|  | name=Mike | 
|  |  | 
|  | In this case, the delim would be "=", the key is "name" and the value is | 
|  | "Mike". | 
|  |  | 
|  | Description of argument(s): | 
|  | string                          The string to be parsed. | 
|  | delim                           The delimiter which separates the key from | 
|  | the value. | 
|  | strip                           The characters (if any) to strip from the | 
|  | beginning and end of both the key and the | 
|  | value. | 
|  | to_lower                        Change the key name to lower case. | 
|  | underscores                     Change any blanks found in the key name to | 
|  | underscores. | 
|  | """ | 
|  |  | 
|  | pair = string.split(delim) | 
|  |  | 
|  | key = pair[0].strip(strip) | 
|  | if len(pair) == 0: | 
|  | value = "" | 
|  | else: | 
|  | value = delim.join(pair[1:]).strip(strip) | 
|  |  | 
|  | if to_lower: | 
|  | key = key.lower() | 
|  | if underscores: | 
|  | key = re.sub(r" ", "_", key) | 
|  |  | 
|  | return key, value | 
|  |  | 
|  |  | 
|  | def key_value_list_to_dict(list, | 
|  | process_indent=0, | 
|  | **args): | 
|  | r""" | 
|  | Convert a list containing key/value strings to a dictionary and return it. | 
|  |  | 
|  | See docstring of parse_key_value function for details on key/value strings. | 
|  |  | 
|  | Example usage: | 
|  |  | 
|  | For the following value of list: | 
|  |  | 
|  | list: | 
|  | list[0]:          Current Limit State: No Active Power Limit | 
|  | list[1]:          Exception actions:   Hard Power Off & Log Event to SEL | 
|  | list[2]:          Power Limit:         0 Watts | 
|  | list[3]:          Correction time:     0 milliseconds | 
|  | list[4]:          Sampling period:     0 seconds | 
|  |  | 
|  | And the following call in python: | 
|  |  | 
|  | power_limit = key_value_outbuf_to_dict(list) | 
|  |  | 
|  | The resulting power_limit directory would look like this: | 
|  |  | 
|  | power_limit: | 
|  | [current_limit_state]:        No Active Power Limit | 
|  | [exception_actions]:          Hard Power Off & Log Event to SEL | 
|  | [power_limit]:                0 Watts | 
|  | [correction_time]:            0 milliseconds | 
|  | [sampling_period]:            0 seconds | 
|  |  | 
|  | Another example containing a sub-list (see process_indent description | 
|  | below): | 
|  |  | 
|  | Provides Device SDRs      : yes | 
|  | Additional Device Support : | 
|  | Sensor Device | 
|  | SEL Device | 
|  | FRU Inventory Device | 
|  | Chassis Device | 
|  |  | 
|  | Note that the 2 qualifications for containing a sub-list are met: 1) | 
|  | 'Additional Device Support' has no value and 2) The entries below it are | 
|  | indented.  In this case those entries contain no delimiters (":") so they | 
|  | will be processed as a list rather than as a dictionary.  The result would | 
|  | be as follows: | 
|  |  | 
|  | mc_info: | 
|  | mc_info[provides_device_sdrs]:            yes | 
|  | mc_info[additional_device_support]: | 
|  | mc_info[additional_device_support][0]:  Sensor Device | 
|  | mc_info[additional_device_support][1]:  SEL Device | 
|  | mc_info[additional_device_support][2]:  FRU Inventory Device | 
|  | mc_info[additional_device_support][3]:  Chassis Device | 
|  |  | 
|  | Description of argument(s): | 
|  | list                            A list of key/value strings.  (See | 
|  | docstring of parse_key_value function for | 
|  | details). | 
|  | process_indent                  This indicates that indented | 
|  | sub-dictionaries and sub-lists are to be | 
|  | processed as such.  An entry may have a | 
|  | sub-dict or sub-list if 1) It has no value | 
|  | other than blank 2) There are entries | 
|  | below it that are indented. | 
|  | **args                          Arguments to be interpreted by | 
|  | parse_key_value.  (See docstring of | 
|  | parse_key_value function for details). | 
|  | """ | 
|  |  | 
|  | try: | 
|  | result_dict = collections.OrderedDict() | 
|  | except AttributeError: | 
|  | result_dict = DotDict() | 
|  |  | 
|  | if not process_indent: | 
|  | for entry in list: | 
|  | key, value = parse_key_value(entry, **args) | 
|  | result_dict[key] = value | 
|  | return result_dict | 
|  |  | 
|  | # Process list while paying heed to indentation. | 
|  | delim = args.get("delim", ":") | 
|  | # Initialize "parent_" indentation level variables. | 
|  | parent_indent = len(list[0]) - len(list[0].lstrip()) | 
|  | sub_list = [] | 
|  | for entry in list: | 
|  | key, value = parse_key_value(entry, **args) | 
|  |  | 
|  | indent = len(entry) - len(entry.lstrip()) | 
|  |  | 
|  | if indent > parent_indent and parent_value == "": | 
|  | # This line is indented compared to the parent entry and the | 
|  | # parent entry has no value. | 
|  | # Append the entry to sub_list for later processing. | 
|  | sub_list.append(str(entry)) | 
|  | continue | 
|  |  | 
|  | # Process any outstanding sub_list and add it to | 
|  | # result_dict[parent_key]. | 
|  | if len(sub_list) > 0: | 
|  | if any(delim in word for word in sub_list): | 
|  | # If delim is found anywhere in the sub_list, we'll process | 
|  | # as a sub-dictionary. | 
|  | result_dict[parent_key] = key_value_list_to_dict(sub_list, | 
|  | **args) | 
|  | else: | 
|  | result_dict[parent_key] = map(str.strip, sub_list) | 
|  | del sub_list[:] | 
|  |  | 
|  | result_dict[key] = value | 
|  |  | 
|  | parent_key = key | 
|  | parent_value = value | 
|  | parent_indent = indent | 
|  |  | 
|  | # Any outstanding sub_list to be processed? | 
|  | if len(sub_list) > 0: | 
|  | if any(delim in word for word in sub_list): | 
|  | # If delim is found anywhere in the sub_list, we'll process as a | 
|  | # sub-dictionary. | 
|  | result_dict[parent_key] = key_value_list_to_dict(sub_list, **args) | 
|  | else: | 
|  | result_dict[parent_key] = map(str.strip, sub_list) | 
|  |  | 
|  | return result_dict | 
|  |  | 
|  |  | 
|  | def key_value_outbuf_to_dict(out_buf, | 
|  | **args): | 
|  | r""" | 
|  | Convert a buffer with a key/value string on each line to a dictionary and | 
|  | return it. | 
|  |  | 
|  | Each line in the out_buf should end with a \n. | 
|  |  | 
|  | See docstring of parse_key_value function for details on key/value strings. | 
|  |  | 
|  | Example usage: | 
|  |  | 
|  | For the following value of out_buf: | 
|  |  | 
|  | Current Limit State: No Active Power Limit | 
|  | Exception actions:   Hard Power Off & Log Event to SEL | 
|  | Power Limit:         0 Watts | 
|  | Correction time:     0 milliseconds | 
|  | Sampling period:     0 seconds | 
|  |  | 
|  | And the following call in python: | 
|  |  | 
|  | power_limit = key_value_outbuf_to_dict(out_buf) | 
|  |  | 
|  | The resulting power_limit directory would look like this: | 
|  |  | 
|  | power_limit: | 
|  | [current_limit_state]:        No Active Power Limit | 
|  | [exception_actions]:          Hard Power Off & Log Event to SEL | 
|  | [power_limit]:                0 Watts | 
|  | [correction_time]:            0 milliseconds | 
|  | [sampling_period]:            0 seconds | 
|  |  | 
|  | Description of argument(s): | 
|  | out_buf                         A buffer with a key/value string on each | 
|  | line. (See docstring of parse_key_value | 
|  | function for details). | 
|  | **args                          Arguments to be interpreted by | 
|  | parse_key_value.  (See docstring of | 
|  | parse_key_value function for details). | 
|  | """ | 
|  |  | 
|  | # Create key_var_list and remove null entries. | 
|  | key_var_list = list(filter(None, out_buf.split("\n"))) | 
|  | return key_value_list_to_dict(key_var_list, **args) | 
|  |  | 
|  |  | 
|  | def create_field_desc_regex(line): | 
|  |  | 
|  | r""" | 
|  | Create a field descriptor regular expression based on the input line and | 
|  | return it. | 
|  |  | 
|  | This function is designed for use by the list_to_report function (defined | 
|  | below). | 
|  |  | 
|  | Example: | 
|  |  | 
|  | Given the following input line: | 
|  |  | 
|  | --------   ------------ ------------------ ------------------------ | 
|  |  | 
|  | This function will return this regular expression: | 
|  |  | 
|  | (.{8})   (.{12}) (.{18}) (.{24}) | 
|  |  | 
|  | This means that other report lines interpreted using the regular | 
|  | expression are expected to have: | 
|  | - An 8 character field | 
|  | - 3 spaces | 
|  | - A 12 character field | 
|  | - One space | 
|  | - An 18 character field | 
|  | - One space | 
|  | - A 24 character field | 
|  |  | 
|  | Description of argument(s): | 
|  | line                            A line consisting of dashes to represent | 
|  | fields and spaces to delimit fields. | 
|  | """ | 
|  |  | 
|  | # Split the line into a descriptors list.  Example: | 
|  | # descriptors: | 
|  | #  descriptors[0]:            -------- | 
|  | #  descriptors[1]: | 
|  | #  descriptors[2]: | 
|  | #  descriptors[3]:            ------------ | 
|  | #  descriptors[4]:            ------------------ | 
|  | #  descriptors[5]:            ------------------------ | 
|  | descriptors = line.split(" ") | 
|  |  | 
|  | # Create regexes list.  Example: | 
|  | # regexes: | 
|  | #  regexes[0]:                (.{8}) | 
|  | #  regexes[1]: | 
|  | #  regexes[2]: | 
|  | #  regexes[3]:                (.{12}) | 
|  | #  regexes[4]:                (.{18}) | 
|  | #  regexes[5]:                (.{24}) | 
|  | regexes = [] | 
|  | for descriptor in descriptors: | 
|  | if descriptor == "": | 
|  | regexes.append("") | 
|  | else: | 
|  | regexes.append("(.{" + str(len(descriptor)) + "})") | 
|  |  | 
|  | # Join the regexes list into a regex string. | 
|  | field_desc_regex = ' '.join(regexes) | 
|  |  | 
|  | return field_desc_regex | 
|  |  | 
|  |  | 
|  | def list_to_report(report_list, | 
|  | to_lower=1, | 
|  | field_delim=None): | 
|  | r""" | 
|  | Convert a list containing report text lines to a report "object" and | 
|  | return it. | 
|  |  | 
|  | The first entry in report_list must be a header line consisting of column | 
|  | names delimited by white space.  No column name may contain white space. | 
|  | The remaining report_list entries should contain tabular data which | 
|  | corresponds to the column names. | 
|  |  | 
|  | A report object is a list where each entry is a dictionary whose keys are | 
|  | the field names from the first entry in report_list. | 
|  |  | 
|  | Example: | 
|  | Given the following report_list as input: | 
|  |  | 
|  | rl: | 
|  | rl[0]: Filesystem           1K-blocks      Used Available Use% Mounted on | 
|  | rl[1]: dev                     247120         0    247120   0% /dev | 
|  | rl[2]: tmpfs                   248408     79792    168616  32% /run | 
|  |  | 
|  | This function will return a list of dictionaries as shown below: | 
|  |  | 
|  | df_report: | 
|  | df_report[0]: | 
|  | [filesystem]:                  dev | 
|  | [1k-blocks]:                   247120 | 
|  | [used]:                        0 | 
|  | [available]:                   247120 | 
|  | [use%]:                        0% | 
|  | [mounted]:                     /dev | 
|  | df_report[1]: | 
|  | [filesystem]:                  dev | 
|  | [1k-blocks]:                   247120 | 
|  | [used]:                        0 | 
|  | [available]:                   247120 | 
|  | [use%]:                        0% | 
|  | [mounted]:                     /dev | 
|  |  | 
|  | Notice that because "Mounted on" contains a space, "on" would be | 
|  | considered the 7th field.  In this case, there is never any data in field | 
|  | 7 so things work out nicely.  A caller could do some pre-processing if | 
|  | desired (e.g. change "Mounted on" to "Mounted_on"). | 
|  |  | 
|  | Example 2: | 
|  |  | 
|  | If the 2nd line of report data is a series of dashes and spaces as in the | 
|  | following example, that line will serve to delineate columns. | 
|  |  | 
|  | The 2nd line of data is like this: | 
|  | ID                              status       size | 
|  | tool,clientid,userid | 
|  | -------- ------------ ------------------ ------------------------ | 
|  | 20000001 in progress  0x7D0              ,, | 
|  |  | 
|  | Description of argument(s): | 
|  | report_list                     A list where each entry is one line of | 
|  | output from a report.  The first entry | 
|  | must be a header line which contains | 
|  | column names.  Column names may not | 
|  | contain spaces. | 
|  | to_lower                        Change the resulting key names to lower | 
|  | case. | 
|  | field_delim                     Indicates that there are field delimiters | 
|  | in report_list entries (which should be | 
|  | removed). | 
|  | """ | 
|  |  | 
|  | if len(report_list) <= 1: | 
|  | # If we don't have at least a descriptor line and one line of data, | 
|  | # return an empty array. | 
|  | return [] | 
|  |  | 
|  | if field_delim is not None: | 
|  | report_list = [re.sub("\\|", "", line) for line in report_list] | 
|  |  | 
|  | header_line = report_list[0] | 
|  | if to_lower: | 
|  | header_line = header_line.lower() | 
|  |  | 
|  | field_desc_regex = "" | 
|  | if re.match(r"^-[ -]*$", report_list[1]): | 
|  | # We have a field descriptor line (as shown in example 2 above). | 
|  | field_desc_regex = create_field_desc_regex(report_list[1]) | 
|  | field_desc_len = len(report_list[1]) | 
|  | pad_format_string = "%-" + str(field_desc_len) + "s" | 
|  | # The field descriptor line has served its purpose.  Deleting it. | 
|  | del report_list[1] | 
|  |  | 
|  | # Process the header line by creating a list of column names. | 
|  | if field_desc_regex == "": | 
|  | columns = header_line.split() | 
|  | else: | 
|  | # Pad the line with spaces on the right to facilitate processing with | 
|  | # field_desc_regex. | 
|  | header_line = pad_format_string % header_line | 
|  | columns = map(str.strip, re.findall(field_desc_regex, header_line)[0]) | 
|  |  | 
|  | report_obj = [] | 
|  | for report_line in report_list[1:]: | 
|  | if field_desc_regex == "": | 
|  | line = report_line.split() | 
|  | else: | 
|  | # Pad the line with spaces on the right to facilitate processing | 
|  | # with field_desc_regex. | 
|  | report_line = pad_format_string % report_line | 
|  | line = map(str.strip, re.findall(field_desc_regex, report_line)[0]) | 
|  | try: | 
|  | line_dict = collections.OrderedDict(zip(columns, line)) | 
|  | except AttributeError: | 
|  | line_dict = DotDict(zip(columns, line)) | 
|  | report_obj.append(line_dict) | 
|  |  | 
|  | return report_obj | 
|  |  | 
|  |  | 
|  | def outbuf_to_report(out_buf, | 
|  | **args): | 
|  | r""" | 
|  | Convert a text buffer containing report lines to a report "object" and | 
|  | return it. | 
|  |  | 
|  | Refer to list_to_report (above) for more details. | 
|  |  | 
|  | Example: | 
|  |  | 
|  | Given the following out_buf: | 
|  |  | 
|  | Filesystem                      1K-blocks      Used Available Use% Mounted | 
|  | on | 
|  | dev                             247120         0    247120   0% /dev | 
|  | tmpfs                           248408     79792    168616  32% /run | 
|  |  | 
|  | This function will return a list of dictionaries as shown below: | 
|  |  | 
|  | df_report: | 
|  | df_report[0]: | 
|  | [filesystem]:                  dev | 
|  | [1k-blocks]:                   247120 | 
|  | [used]:                        0 | 
|  | [available]:                   247120 | 
|  | [use%]:                        0% | 
|  | [mounted]:                     /dev | 
|  | df_report[1]: | 
|  | [filesystem]:                  dev | 
|  | [1k-blocks]:                   247120 | 
|  | [used]:                        0 | 
|  | [available]:                   247120 | 
|  | [use%]:                        0% | 
|  | [mounted]:                     /dev | 
|  |  | 
|  | Other possible uses: | 
|  | - Process the output of a ps command. | 
|  | - Process the output of an ls command (the caller would need to supply | 
|  | column names) | 
|  |  | 
|  | Description of argument(s): | 
|  | out_buf                         A text report.  The first line must be a | 
|  | header line which contains column names. | 
|  | Column names may not contain spaces. | 
|  | **args                          Arguments to be interpreted by | 
|  | list_to_report.  (See docstring of | 
|  | list_to_report function for details). | 
|  | """ | 
|  |  | 
|  | report_list = list(filter(None, out_buf.split("\n"))) | 
|  | return list_to_report(report_list, **args) | 
|  |  | 
|  |  | 
|  | def nested_get(key, dictionary): | 
|  | r""" | 
|  | Return a list of all values from the nested dictionary with the given key. | 
|  |  | 
|  | Example: | 
|  |  | 
|  | Given a dictionary named personnel with the following contents: | 
|  |  | 
|  | personnel: | 
|  | [manager]: | 
|  | [last_name]:             Doe | 
|  | [first_name]:            John | 
|  | [accountant]: | 
|  | [last_name]:             Smith | 
|  | [first_name]:            Will | 
|  |  | 
|  | The following code... | 
|  |  | 
|  | last_names = nested_get('last_name', personnel) | 
|  | print_var(last_names) | 
|  |  | 
|  | Would result in the following data: | 
|  |  | 
|  | last_names: | 
|  | last_names[0]:             Doe | 
|  | last_names[1]:             Smith | 
|  |  | 
|  | Description of argument(s): | 
|  | key                             The key value. | 
|  | dictionary                      The nested dictionary. | 
|  | """ | 
|  |  | 
|  | result = [] | 
|  | for k, v in dictionary.items(): | 
|  | if isinstance(v, dict): | 
|  | result += nested_get(key, v) | 
|  | if k == key: | 
|  | result.append(v) | 
|  |  | 
|  | return result |