summaryrefslogtreecommitdiffstats
path: root/cobbler/Cheetah/Tools/MondoReport.py
diff options
context:
space:
mode:
Diffstat (limited to 'cobbler/Cheetah/Tools/MondoReport.py')
-rw-r--r--cobbler/Cheetah/Tools/MondoReport.py464
1 files changed, 464 insertions, 0 deletions
diff --git a/cobbler/Cheetah/Tools/MondoReport.py b/cobbler/Cheetah/Tools/MondoReport.py
new file mode 100644
index 0000000..f73e7fc
--- /dev/null
+++ b/cobbler/Cheetah/Tools/MondoReport.py
@@ -0,0 +1,464 @@
+#!/usr/bin/env python
+"""
+@@TR: This code is pretty much unsupported.
+
+MondoReport.py -- Batching module for Python and Cheetah.
+
+Version 2001-Nov-18. Doesn't do much practical yet, but the companion
+testMondoReport.py passes all its tests.
+-Mike Orr (Iron)
+
+TODO: BatchRecord.prev/next/prev_batches/next_batches/query, prev.query,
+next.query.
+
+How about Report: .page(), .all(), .summary()? Or PageBreaker.
+"""
+import operator, types
+try:
+ from Cheetah.NameMapper import valueForKey as lookup_func
+except ImportError:
+ def lookup_func(obj, name):
+ if hasattr(obj, name):
+ return getattr(obj, name)
+ else:
+ return obj[name] # Raises KeyError.
+
+########## CONSTANTS ##############################
+
+True, False = (1==1), (1==0)
+numericTypes = types.IntType, types.LongType, types.FloatType
+
+########## PUBLIC GENERIC FUNCTIONS ##############################
+
+class NegativeError(ValueError):
+ pass
+
+def isNumeric(v):
+ return type(v) in numericTypes
+
+def isNonNegative(v):
+ ret = isNumeric(v)
+ if ret and v < 0:
+ raise NegativeError(v)
+
+def isNotNone(v):
+ return v is not None
+
+def Roman(n):
+ n = int(n) # Raises TypeError.
+ if n < 1:
+ raise ValueError("roman numeral for zero or negative undefined: " + n)
+ roman = ''
+ while n >= 1000:
+ n = n - 1000
+ roman = roman + 'M'
+ while n >= 500:
+ n = n - 500
+ roman = roman + 'D'
+ while n >= 100:
+ n = n - 100
+ roman = roman + 'C'
+ while n >= 50:
+ n = n - 50
+ roman = roman + 'L'
+ while n >= 10:
+ n = n - 10
+ roman = roman + 'X'
+ while n >= 5:
+ n = n - 5
+ roman = roman + 'V'
+ while n < 5 and n >= 1:
+ n = n - 1
+ roman = roman + 'I'
+ roman = roman.replace('DCCCC', 'CM')
+ roman = roman.replace('CCCC', 'CD')
+ roman = roman.replace('LXXXX', 'XC')
+ roman = roman.replace('XXXX', 'XL')
+ roman = roman.replace('VIIII', 'IX')
+ roman = roman.replace('IIII', 'IV')
+ return roman
+
+
+def sum(lis):
+ return reduce(operator.add, lis, 0)
+
+def mean(lis):
+ """Always returns a floating-point number.
+ """
+ lis_len = len(lis)
+ if lis_len == 0:
+ return 0.00 # Avoid ZeroDivisionError (not raised for floats anyway)
+ total = float( sum(lis) )
+ return total / lis_len
+
+def median(lis):
+ lis = lis[:]
+ lis.sort()
+ return lis[int(len(lis)/2)]
+
+
+def variance(lis):
+ raise NotImplementedError()
+
+def variance_n(lis):
+ raise NotImplementedError()
+
+def standardDeviation(lis):
+ raise NotImplementedError()
+
+def standardDeviation_n(lis):
+ raise NotImplementedError()
+
+
+
+class IndexFormats:
+ """Eight ways to display a subscript index.
+ ("Fifty ways to leave your lover....")
+ """
+ def __init__(self, index, item=None):
+ self._index = index
+ self._number = index + 1
+ self._item = item
+
+ def index(self):
+ return self._index
+
+ __call__ = index
+
+ def number(self):
+ return self._number
+
+ def even(self):
+ return self._number % 2 == 0
+
+ def odd(self):
+ return not self.even()
+
+ def even_i(self):
+ return self._index % 2 == 0
+
+ def odd_i(self):
+ return not self.even_i()
+
+ def letter(self):
+ return self.Letter().lower()
+
+ def Letter(self):
+ n = ord('A') + self._index
+ return chr(n)
+
+ def roman(self):
+ return self.Roman().lower()
+
+ def Roman(self):
+ return Roman(self._number)
+
+ def item(self):
+ return self._item
+
+
+
+########## PRIVATE CLASSES ##############################
+
+class ValuesGetterMixin:
+ def __init__(self, origList):
+ self._origList = origList
+
+ def _getValues(self, field=None, criteria=None):
+ if field:
+ ret = [lookup_func(elm, field) for elm in self._origList]
+ else:
+ ret = self._origList
+ if criteria:
+ ret = filter(criteria, ret)
+ return ret
+
+
+class RecordStats(IndexFormats, ValuesGetterMixin):
+ """The statistics that depend on the current record.
+ """
+ def __init__(self, origList, index):
+ record = origList[index] # Raises IndexError.
+ IndexFormats.__init__(self, index, record)
+ ValuesGetterMixin.__init__(self, origList)
+
+ def length(self):
+ return len(self._origList)
+
+ def first(self):
+ return self._index == 0
+
+ def last(self):
+ return self._index >= len(self._origList) - 1
+
+ def _firstOrLastValue(self, field, currentIndex, otherIndex):
+ currentValue = self._origList[currentIndex] # Raises IndexError.
+ try:
+ otherValue = self._origList[otherIndex]
+ except IndexError:
+ return True
+ if field:
+ currentValue = lookup_func(currentValue, field)
+ otherValue = lookup_func(otherValue, field)
+ return currentValue != otherValue
+
+ def firstValue(self, field=None):
+ return self._firstOrLastValue(field, self._index, self._index - 1)
+
+ def lastValue(self, field=None):
+ return self._firstOrLastValue(field, self._index, self._index + 1)
+
+ # firstPage and lastPage not implemented. Needed?
+
+ def percentOfTotal(self, field=None, suffix='%', default='N/A', decimals=2):
+ rec = self._origList[self._index]
+ if field:
+ val = lookup_func(rec, field)
+ else:
+ val = rec
+ try:
+ lis = self._getValues(field, isNumeric)
+ except NegativeError:
+ return default
+ total = sum(lis)
+ if total == 0.00: # Avoid ZeroDivisionError.
+ return default
+ val = float(val)
+ try:
+ percent = (val / total) * 100
+ except ZeroDivisionError:
+ return default
+ if decimals == 0:
+ percent = int(percent)
+ else:
+ percent = round(percent, decimals)
+ if suffix:
+ return str(percent) + suffix # String.
+ else:
+ return percent # Numeric.
+
+ def __call__(self): # Overrides IndexFormats.__call__
+ """This instance is not callable, so we override the super method.
+ """
+ raise NotImplementedError()
+
+ def prev(self):
+ if self._index == 0:
+ return None
+ else:
+ length = self.length()
+ start = self._index - length
+ return PrevNextPage(self._origList, length, start)
+
+ def next(self):
+ if self._index + self.length() == self.length():
+ return None
+ else:
+ length = self.length()
+ start = self._index + length
+ return PrevNextPage(self._origList, length, start)
+
+ def prevPages(self):
+ raise NotImplementedError()
+
+ def nextPages(self):
+ raise NotImplementedError()
+
+ prev_batches = prevPages
+ next_batches = nextPages
+
+ def summary(self):
+ raise NotImplementedError()
+
+
+
+ def _prevNextHelper(self, start,end,size,orphan,sequence):
+ """Copied from Zope's DT_InSV.py's "opt" function.
+ """
+ if size < 1:
+ if start > 0 and end > 0 and end >= start:
+ size=end+1-start
+ else: size=7
+
+ if start > 0:
+
+ try: sequence[start-1]
+ except: start=len(sequence)
+ # if start > l: start=l
+
+ if end > 0:
+ if end < start: end=start
+ else:
+ end=start+size-1
+ try: sequence[end+orphan-1]
+ except: end=len(sequence)
+ # if l - end < orphan: end=l
+ elif end > 0:
+ try: sequence[end-1]
+ except: end=len(sequence)
+ # if end > l: end=l
+ start=end+1-size
+ if start - 1 < orphan: start=1
+ else:
+ start=1
+ end=start+size-1
+ try: sequence[end+orphan-1]
+ except: end=len(sequence)
+ # if l - end < orphan: end=l
+ return start,end,size
+
+
+
+class Summary(ValuesGetterMixin):
+ """The summary statistics, that don't depend on the current record.
+ """
+ def __init__(self, origList):
+ ValuesGetterMixin.__init__(self, origList)
+
+ def sum(self, field=None):
+ lis = self._getValues(field, isNumeric)
+ return sum(lis)
+
+ total = sum
+
+ def count(self, field=None):
+ lis = self._getValues(field, isNotNone)
+ return len(lis)
+
+ def min(self, field=None):
+ lis = self._getValues(field, isNotNone)
+ return min(lis) # Python builtin function min.
+
+ def max(self, field=None):
+ lis = self._getValues(field, isNotNone)
+ return max(lis) # Python builtin function max.
+
+ def mean(self, field=None):
+ """Always returns a floating point number.
+ """
+ lis = self._getValues(field, isNumeric)
+ return mean(lis)
+
+ average = mean
+
+ def median(self, field=None):
+ lis = self._getValues(field, isNumeric)
+ return median(lis)
+
+ def variance(self, field=None):
+ raiseNotImplementedError()
+
+ def variance_n(self, field=None):
+ raiseNotImplementedError()
+
+ def standardDeviation(self, field=None):
+ raiseNotImplementedError()
+
+ def standardDeviation_n(self, field=None):
+ raiseNotImplementedError()
+
+
+class PrevNextPage:
+ def __init__(self, origList, size, start):
+ end = start + size
+ self.start = IndexFormats(start, origList[start])
+ self.end = IndexFormats(end, origList[end])
+ self.length = size
+
+
+########## MAIN PUBLIC CLASS ##############################
+class MondoReport:
+ _RecordStatsClass = RecordStats
+ _SummaryClass = Summary
+
+ def __init__(self, origlist):
+ self._origList = origlist
+
+ def page(self, size, start, overlap=0, orphan=0):
+ """Returns list of ($r, $a, $b)
+ """
+ if overlap != 0:
+ raise NotImplementedError("non-zero overlap")
+ if orphan != 0:
+ raise NotImplementedError("non-zero orphan")
+ origList = self._origList
+ origList_len = len(origList)
+ start = max(0, start)
+ end = min( start + size, len(self._origList) )
+ mySlice = origList[start:end]
+ ret = []
+ for rel in range(size):
+ abs_ = start + rel
+ r = mySlice[rel]
+ a = self._RecordStatsClass(origList, abs_)
+ b = self._RecordStatsClass(mySlice, rel)
+ tup = r, a, b
+ ret.append(tup)
+ return ret
+
+
+ batch = page
+
+ def all(self):
+ origList_len = len(self._origList)
+ return self.page(origList_len, 0, 0, 0)
+
+
+ def summary(self):
+ return self._SummaryClass(self._origList)
+
+"""
+**********************************
+ Return a pageful of records from a sequence, with statistics.
+
+ in : origlist, list or tuple. The entire set of records. This is
+ usually a list of objects or a list of dictionaries.
+ page, int >= 0. Which page to display.
+ size, int >= 1. How many records per page.
+ widow, int >=0. Not implemented.
+ orphan, int >=0. Not implemented.
+ base, int >=0. Number of first page (usually 0 or 1).
+
+ out: list of (o, b) pairs. The records for the current page. 'o' is
+ the original element from 'origlist' unchanged. 'b' is a Batch
+ object containing meta-info about 'o'.
+ exc: IndexError if 'page' or 'size' is < 1. If 'origlist' is empty or
+ 'page' is too high, it returns an empty list rather than raising
+ an error.
+
+ origlist_len = len(origlist)
+ start = (page + base) * size
+ end = min(start + size, origlist_len)
+ ret = []
+ # widow, orphan calculation: adjust 'start' and 'end' up and down,
+ # Set 'widow', 'orphan', 'first_nonwidow', 'first_nonorphan' attributes.
+ for i in range(start, end):
+ o = origlist[i]
+ b = Batch(origlist, size, i)
+ tup = o, b
+ ret.append(tup)
+ return ret
+
+ def prev(self):
+ # return a PrevNextPage or None
+
+ def next(self):
+ # return a PrevNextPage or None
+
+ def prev_batches(self):
+ # return a list of SimpleBatch for the previous batches
+
+ def next_batches(self):
+ # return a list of SimpleBatch for the next batches
+
+########## PUBLIC MIXIN CLASS FOR CHEETAH TEMPLATES ##############
+class MondoReportMixin:
+ def batch(self, origList, size=None, start=0, overlap=0, orphan=0):
+ bat = MondoReport(origList)
+ return bat.batch(size, start, overlap, orphan)
+ def batchstats(self, origList):
+ bat = MondoReport(origList)
+ return bat.stats()
+"""
+
+# vim: shiftwidth=4 tabstop=4 expandtab textwidth=79