| #!/usr/bin/env python |
| |
| r""" |
| Define the tally_sheet class. |
| """ |
| |
| import sys |
| import collections |
| import copy |
| import re |
| |
| try: |
| from robot.utils import DotDict |
| except ImportError: |
| pass |
| |
| import gen_print as gp |
| |
| |
| class tally_sheet: |
| |
| r""" |
| This class is the implementation of a tally sheet. The sheet can be viewed as rows and columns. Each |
| row has a unique key field. |
| |
| This class provides methods to tally the results (totals, etc.). |
| |
| Example code: |
| |
| # Create an ordered dict to represent your field names/initial values. |
| try: |
| boot_results_fields = collections.OrderedDict([('total', 0), ('pass', 0), ('fail', 0)]) |
| except AttributeError: |
| boot_results_fields = DotDict([('total', 0), ('pass', 0), ('fail', 0)]) |
| # Create the tally sheet. |
| boot_test_results = tally_sheet('boot type', boot_results_fields, 'boot_test_results') |
| # Set your sum fields (fields which are to be totalled). |
| boot_test_results.set_sum_fields(['total', 'pass', 'fail']) |
| # Set calc fields (within a row, a certain field can be derived from other fields in the row. |
| boot_test_results.set_calc_fields(['total=pass+fail']) |
| |
| # Create some records. |
| boot_test_results.add_row('BMC Power On') |
| boot_test_results.add_row('BMC Power Off') |
| |
| # Increment field values. |
| boot_test_results.inc_row_field('BMC Power On', 'pass') |
| boot_test_results.inc_row_field('BMC Power Off', 'pass') |
| boot_test_results.inc_row_field('BMC Power On', 'fail') |
| # Have the results tallied... |
| boot_test_results.calc() |
| # And printed... |
| boot_test_results.print_report() |
| |
| Example result: |
| |
| Boot Type Total Pass Fail |
| ----------------------------------- ----- ---- ---- |
| BMC Power On 2 1 1 |
| BMC Power Off 1 1 0 |
| =================================================== |
| Totals 3 2 1 |
| |
| """ |
| |
| def __init__(self, |
| row_key_field_name='Description', |
| init_fields_dict=dict(), |
| obj_name='tally_sheet'): |
| r""" |
| Create a tally sheet object. |
| |
| Description of arguments: |
| row_key_field_name The name of the row key field (e.g. boot_type, team_name, etc.) |
| init_fields_dict A dictionary which contains field names/initial values. |
| obj_name The name of the tally sheet. |
| """ |
| |
| self.__obj_name = obj_name |
| # The row key field uniquely identifies the row. |
| self.__row_key_field_name = row_key_field_name |
| # Create a "table" which is an ordered dictionary. |
| # If we're running python 2.7 or later, collections has an OrderedDict we can use. Otherwise, we'll |
| # try to use the DotDict (a robot library). If neither of those are available, we fail. |
| try: |
| self.__table = collections.OrderedDict() |
| except AttributeError: |
| self.__table = DotDict() |
| # Save the initial fields dictionary. |
| self.__init_fields_dict = init_fields_dict |
| self.__totals_line = init_fields_dict |
| self.__sum_fields = [] |
| self.__calc_fields = [] |
| |
| def init(self, |
| row_key_field_name, |
| init_fields_dict, |
| obj_name='tally_sheet'): |
| self.__init__(row_key_field_name, |
| init_fields_dict, |
| obj_name='tally_sheet') |
| |
| def set_sum_fields(self, sum_fields): |
| r""" |
| Set the sum fields, i.e. create a list of field names which are to be summed and included on the |
| totals line of reports. |
| |
| Description of arguments: |
| sum_fields A list of field names. |
| """ |
| |
| self.__sum_fields = sum_fields |
| |
| def set_calc_fields(self, calc_fields): |
| r""" |
| Set the calc fields, i.e. create a list of field names within a given row which are to be calculated |
| for the user. |
| |
| Description of arguments: |
| calc_fields A string expression such as 'total=pass+fail' which shows which field on |
| a given row is derived from other fields in the same row. |
| """ |
| |
| self.__calc_fields = calc_fields |
| |
| def add_row(self, row_key, init_fields_dict=None): |
| r""" |
| Add a row to the tally sheet. |
| |
| Description of arguments: |
| row_key A unique key value. |
| init_fields_dict A dictionary of field names/initial values. The number of fields in this |
| dictionary must be the same as what was specified when the tally sheet |
| was created. If no value is passed, the value used to create the tally |
| sheet will be used. |
| """ |
| |
| if row_key in self.__table: |
| # If we allow this, the row values get re-initialized. |
| message = "An entry for \"" + row_key + "\" already exists in" |
| message += " tally sheet." |
| raise ValueError(message) |
| if init_fields_dict is None: |
| init_fields_dict = self.__init_fields_dict |
| try: |
| self.__table[row_key] = collections.OrderedDict(init_fields_dict) |
| except AttributeError: |
| self.__table[row_key] = DotDict(init_fields_dict) |
| |
| def update_row_field(self, row_key, field_key, value): |
| r""" |
| Update a field in a row with the specified value. |
| |
| Description of arguments: |
| row_key A unique key value that identifies the row to be updated. |
| field_key The key that identifies which field in the row that is to be updated. |
| value The value to set into the specified row/field. |
| """ |
| |
| self.__table[row_key][field_key] = value |
| |
| def inc_row_field(self, row_key, field_key): |
| r""" |
| Increment the value of the specified field in the specified row. The value of the field must be |
| numeric. |
| |
| Description of arguments: |
| row_key A unique key value that identifies the row to be updated. |
| field_key The key that identifies which field in the row that is to be updated. |
| """ |
| |
| self.__table[row_key][field_key] += 1 |
| |
| def dec_row_field(self, row_key, field_key): |
| r""" |
| Decrement the value of the specified field in the specified row. The value of the field must be |
| numeric. |
| |
| Description of arguments: |
| row_key A unique key value that identifies the row to be updated. |
| field_key The key that identifies which field in the row that is to be updated. |
| """ |
| |
| self.__table[row_key][field_key] -= 1 |
| |
| def calc(self): |
| r""" |
| Calculate totals and row calc fields. Also, return totals_line dictionary. |
| """ |
| |
| self.__totals_line = copy.deepcopy(self.__init_fields_dict) |
| # Walk through the rows of the table. |
| for row_key, value in self.__table.items(): |
| # Walk through the calc fields and process them. |
| for calc_field in self.__calc_fields: |
| tokens = [i for i in re.split(r'(\d+|\W+)', calc_field) if i] |
| cmd_buf = "" |
| for token in tokens: |
| if token in ("=", "+", "-", "*", "/"): |
| cmd_buf += token + " " |
| else: |
| # Note: Using "mangled" name for the sake of the exec |
| # statement (below). |
| cmd_buf += "self._" + self.__class__.__name__ +\ |
| "__table['" + row_key + "']['" +\ |
| token + "'] " |
| exec(cmd_buf) |
| |
| for field_key, sub_value in value.items(): |
| if field_key in self.__sum_fields: |
| self.__totals_line[field_key] += sub_value |
| |
| return self.__totals_line |
| |
| def sprint_obj(self): |
| r""" |
| sprint the fields of this object. This would normally be for debug purposes. |
| """ |
| |
| buffer = "" |
| |
| buffer += "class name: " + self.__class__.__name__ + "\n" |
| buffer += gp.sprint_var(self.__obj_name) |
| buffer += gp.sprint_var(self.__row_key_field_name) |
| buffer += gp.sprint_var(self.__table) |
| buffer += gp.sprint_var(self.__init_fields_dict) |
| buffer += gp.sprint_var(self.__sum_fields) |
| buffer += gp.sprint_var(self.__totals_line) |
| buffer += gp.sprint_var(self.__calc_fields) |
| buffer += gp.sprint_var(self.__table) |
| |
| return buffer |
| |
| def print_obj(self): |
| r""" |
| print the fields of this object to stdout. This would normally be for debug purposes. |
| """ |
| |
| sys.stdout.write(self.sprint_obj()) |
| |
| def sprint_report(self): |
| r""" |
| sprint the tally sheet in a formatted way. |
| """ |
| |
| buffer = "" |
| # Build format strings. |
| col_names = [self.__row_key_field_name.title()] |
| report_width = 40 |
| key_width = 40 |
| format_string = '{0:<' + str(key_width) + '}' |
| dash_format_string = '{0:-<' + str(key_width) + '}' |
| field_num = 0 |
| |
| try: |
| first_rec = next(iter(self.__table.items())) |
| for row_key, value in first_rec[1].items(): |
| field_num += 1 |
| if isinstance(value, int): |
| align = ':>' |
| else: |
| align = ':<' |
| format_string += ' {' + str(field_num) + align +\ |
| str(len(row_key)) + '}' |
| dash_format_string += ' {' + str(field_num) + ':->' +\ |
| str(len(row_key)) + '}' |
| report_width += 1 + len(row_key) |
| col_names.append(row_key.title()) |
| except StopIteration: |
| pass |
| num_fields = field_num + 1 |
| totals_line_fmt = '{0:=<' + str(report_width) + '}' |
| |
| buffer += format_string.format(*col_names) + "\n" |
| buffer += dash_format_string.format(*([''] * num_fields)) + "\n" |
| for row_key, value in self.__table.items(): |
| buffer += format_string.format(row_key, *value.values()) + "\n" |
| |
| buffer += totals_line_fmt.format('') + "\n" |
| buffer += format_string.format('Totals', |
| *self.__totals_line.values()) + "\n" |
| |
| return buffer |
| |
| def print_report(self): |
| r""" |
| print the tally sheet in a formatted way. |
| """ |
| |
| sys.stdout.write(self.sprint_report()) |