blob: 7b3614f637f7d523b950ca58df6a562e3263c2b7 [file] [log] [blame]
#!/usr/bin/python2.6
# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Support generic spreadsheet-like table information."""
class Table(object):
"""Class to represent column headers and rows of data."""
__slots__ = ['_columns', # List of column headers in order
'_column_set', # Set of column headers (for faster lookup)
'_rows', # List of row dicts
]
def __init__(self, columns):
self._columns = columns
self._column_set = set(columns)
self._rows = []
def __str__(self):
"""Return a table-like string representation of this table."""
cols = ['%10s' % col for col in self._columns]
text = 'Columns: %s\n' % ', '.join(cols)
ix = 0
for row in self._rows:
vals = ['%10s' % row[col] for col in self._columns]
text += 'Row %3d: %s\n' % (ix, ', '.join(vals))
ix += 1
return text
def __len__(self):
"""Length of table equals the number of rows."""
return self.GetNumRows()
def __getitem__(self, index):
"""Access one or more rows by index or slice."""
return self.GetRowByIndex(index)
def __setitem__(self, key, item):
"""Set one or more rows by index or slice."""
raise NotImplementedError("Implementation not done, yet.")
def __delitem__(self, index):
"""Delete one or more rows by index or slice."""
self.RemoveRowByIndex(index)
def __iter__(self):
"""Declare that this class supports iteration (over rows)."""
return self._rows.__iter__()
def Clear(self):
"""Remove all row data."""
self._rows = []
def GetNumRows(self):
"""Return the number of rows in the table."""
return len(self._rows)
def GetNumColumns(self):
"""Return the number of columns in the table."""
return len(self._columns)
def GetRowByIndex(self, index):
"""Access one or more rows by index or slice.
If more than one row is returned they will be contained in a list."""
return self._rows[index]
def GetRowsByValue(self, values):
"""Return list of rows that match key/value pairs in |values|."""
def grep(row):
for key in values:
if values[key] != row.get(key, None):
return False
return True
return [r for r in self._rows if grep(r)]
def _PrepareValuesForAdd(self, values):
"""Prepare a |values| dict to be added as a row.
Verify that only supported column values are included.
Add empty string values for columns not seen in the row.
"""
for col in values:
if not col in self._column_set:
raise LookupError("Tried adding data to unknown column '%s'" % col)
for col in self._columns:
if not col in values:
values[col] = ""
def AppendRow(self, values):
"""Add a single row of data to the table, according to |values| dict."""
self._PrepareValuesForAdd(values)
self._rows.append(values)
def SetRowByIndex(self, index, values):
"""Replace the row at |index| with values from |values| dict."""
self._PrepareValuesForAdd(values)
self._rows[index] = values;
def SetRowByValue(self, id_column, values):
"""Set the |values| of the row identified by the value in |id_column|.
The column specified by |id_column| should be a unique identifier column,
where the value in that column is different for every row. If it is not
unique, the behavior of this method is undefined.
"""
self._PrepareValuesForAdd(values)
raise NotImplementedError("Implementation not done, yet.")
def RemoveRowByIndex(self, index):
"""Remove the row at |index|."""
del self._rows[index]
def RemoveRowsByValue(self, column, value):
"""Remove any rows with |value| in the |column| column."""
raise NotImplementedError("Implementation not done, yet.")
def Sort(self, key, reverse=False):
"""Sort the rows using the given |key| function."""
self._rows.sort(key=key, reverse=reverse)
def WriteCSV(self, filehandle, hiddencols=None):
"""Write this table out as comma-separated values to |filehandle|.
To skip certain columns during the write, use the |hiddencols| set.
"""
def colfilter(col):
return not hiddencols or col not in hiddencols
cols = [col for col in self._columns if colfilter(col)]
filehandle.write(','.join(cols) + '\n')
for row in self._rows:
vals = [row.get(col, "") for col in self._columns if colfilter(col)]
filehandle.write(','.join(vals) + '\n')
# Support having this module test itself if run as __main__, by leveraging
# the table_unittest module.
if __name__ == "__main__":
import table_unittest
table_unittest.unittest.main(table_unittest)