| # |
| # Copyright 2016 the original author or authors. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| # |
| import sys |
| |
| from google.protobuf.internal.containers import RepeatedCompositeFieldContainer |
| from google.protobuf.message import Message |
| from termcolor import colored |
| |
| _printfn = lambda l: sys.stdout.write(l + '\n') |
| |
| |
| class TablePrinter(object): |
| """Simple tabular data printer utility. For usage, see bottom of file""" |
| |
| def __init__(self): |
| self.max_field_lengths = {} |
| self.field_names = {} |
| self.cell_values = {} |
| |
| def add_cell(self, row_number, field_key, field_name, value): |
| if not isinstance(value, str): |
| value = str(value) |
| self._add_field_type(field_key, field_name) |
| row = self.cell_values.setdefault(row_number, {}) |
| row[field_key] = value |
| self._update_max_length(field_key, value) |
| |
| def number_of_rows(self): |
| return len(self.cell_values) |
| |
| def print_table(self, header=None, printfn=_printfn, dividers=10): |
| |
| if header is not None: |
| printfn(header) |
| |
| field_keys = sorted(self.field_names.keys()) |
| |
| if not field_keys: |
| printfn('table empty') |
| return |
| |
| def p_sep(): |
| printfn('+' + '+'.join( |
| [(self.max_field_lengths[k] + 2) * '-' |
| for k in field_keys]) + '+') |
| |
| p_sep() |
| |
| printfn('| ' + ' | '.join( |
| '%%%ds' % self.max_field_lengths[k] % self.field_names[k] |
| for k in field_keys) + ' |') |
| p_sep() |
| |
| for i in range(len(self.cell_values)): |
| row = self.cell_values[i] |
| printfn(colored('| ' + ' | '.join( |
| '%%%ds' % self.max_field_lengths[k] % row.get(k, '') |
| for k in field_keys |
| ) + ' |')) |
| if not ((i + 1) % dividers): |
| p_sep() |
| |
| if (i + 1) % dividers: |
| p_sep() |
| |
| def _update_max_length(self, field_key, string): |
| length = len(string) |
| if length > self.max_field_lengths.get(field_key, 0): |
| self.max_field_lengths[field_key] = length |
| |
| def _add_field_type(self, field_key, field_name): |
| if field_key not in self.field_names: |
| self.field_names[field_key] = field_name |
| self._update_max_length(field_key, field_name) |
| else: |
| assert self.field_names[field_key] == field_name |
| |
| |
| def print_pb_list_as_table(header, items, fields_to_omit=None, |
| printfn=_printfn, dividers=10, show_nulls=False): |
| from cli.utils import pb2dict |
| |
| t = TablePrinter() |
| for row, obj in enumerate(items): |
| assert isinstance(obj, Message) |
| |
| def set_row(_row, field, field_name, value, t, prefix, |
| fields_to_omit, number): |
| fname = prefix + field.name |
| if fname in fields_to_omit: |
| return |
| if isinstance(value, Message): |
| add(_row, value, fname + '.', |
| 100 * (number + field.number)) |
| else: |
| t.add_cell(_row, number + field.number, fname, |
| field_name) |
| |
| def add(_row, pb, prefix='', number=0): |
| d = pb2dict(pb) |
| if show_nulls: |
| fields = pb.DESCRIPTOR.fields |
| for field in fields: |
| set_row(_row, |
| field, |
| d.get(field.name), |
| getattr(pb, field.name), |
| t, |
| prefix, |
| fields_to_omit, |
| number) |
| else: |
| fields = pb.ListFields() |
| for (field, value) in fields: |
| set_row(_row, |
| field, |
| d.get(field.name), |
| value, |
| t, |
| prefix, |
| fields_to_omit, |
| number) |
| add(row, obj) |
| |
| t.print_table(header, printfn, dividers) |
| |
| |
| def print_pb_as_table(header, pb, fields_to_omit={}, printfn=_printfn, |
| show_nulls=False): |
| from cli.utils import pb2dict |
| |
| def set_cell(pb, field, value, t, prefix, fields_to_omit): |
| fname = prefix + field.name |
| if fname in fields_to_omit: |
| return |
| if isinstance(value, Message): |
| pr(value, fname + '.') |
| elif isinstance(value, RepeatedCompositeFieldContainer): |
| row = t.number_of_rows() |
| t.add_cell(row, 0, 'field', fname) |
| t.add_cell(row, 1, 'value', '{} item(s)'.format((field.name))) |
| else: |
| row = t.number_of_rows() |
| t.add_cell(row, 0, 'field', fname) |
| t.add_cell(row, 1, 'value', field.name) |
| |
| t = TablePrinter() |
| |
| def pr(_pb, prefix=''): |
| d = pb2dict(_pb) |
| if show_nulls: |
| fields = _pb.DESCRIPTOR.fields |
| for field in sorted(fields, key=lambda f: f.number): |
| set_cell(_pb, |
| field, |
| getattr(_pb, field.name), |
| t, |
| prefix, |
| fields_to_omit) |
| else: |
| fields = _pb.ListFields() |
| for (field, value) in sorted(fields, key=lambda (f, v): f.number): |
| set_cell(_pb, |
| field, |
| value, |
| t, |
| prefix, |
| fields_to_omit) |
| |
| pr(pb) |
| |
| t.print_table(header, printfn) |
| |
| |
| if __name__ == '__main__': |
| import random |
| |
| t = TablePrinter() |
| for row in range(10): |
| t.add_cell(row, 0, 'id', row + 100) |
| t.add_cell(row, 1, 'name', 'Joe Somebody') |
| t.add_cell(row, 2, 'ows', '${}'.format(random.randint(10, 100000))) |
| t.print_table() |