[Scipy-svn] r2677 - trunk/Lib/sandbox/timeseries

scipy-svn@scip... scipy-svn@scip...
Mon Feb 5 09:51:13 CST 2007


Author: mattknox_ca
Date: 2007-02-05 09:51:10 -0600 (Mon, 05 Feb 2007)
New Revision: 2677

Modified:
   trunk/Lib/sandbox/timeseries/reportlib.py
Log:
major overhaul

Modified: trunk/Lib/sandbox/timeseries/reportlib.py
===================================================================
--- trunk/Lib/sandbox/timeseries/reportlib.py	2007-02-03 04:19:06 UTC (rev 2676)
+++ trunk/Lib/sandbox/timeseries/reportlib.py	2007-02-05 15:51:10 UTC (rev 2677)
@@ -4,18 +4,27 @@
 :author: Pierre GF Gerard-Marchant & Matt Knox
 :contact: pierregm_at_uga_dot_edu - mattknow_ca_at_hotmail_dot_com
 :version: $Id: tdates.py 2641 2007-01-30 18:40:17Z mattknox_ca $
+
+Ideas borrowed from:
+
+- George Sakkis
+    http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/267662
+
+- Mike Brown
+    http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/148061
 """
 __author__ = "Pierre GF Gerard-Marchant & Matt Knox ($Author: mattknox_ca $)"
 __version__ = '1.0'
 __revision__ = "$Revision: 2641 $"
 __date__     = '$Date: 2007-01-30 13:40:17 -0500 (Tue, 30 Jan 2007) $'
 
-import cStringIO,operator, types
+import sys
+import cStringIO, operator, types, copy
 import tseries as ts
 import tdates as td
 
 __all__ = [
-    'report', 'wrap_onspace', 'wrap_onspace_strict',
+    'Report', 'wrap_onspace', 'wrap_onspace_strict',
     'wrap_always']
 
 class fmtfunc_wrapper:
@@ -26,7 +35,10 @@
     - `mask_rep` : string to use for masked values
     """
     def __init__ (self, fmtfunc, mask_rep):
-        self.f = fmtfunc
+        if fmtfunc is None:
+            self.f = str
+        else:
+            self.f = fmtfunc
         self.mr = mask_rep
 
     def __call__ (self, item):
@@ -38,59 +50,109 @@
             return self.f(item)
 
 
-def report(*tseries, **kwargs):
-    """generate a table report of *tseries with dates in the left column.
+_default_options = {
+    'dates':None,
+    'header_row':None,
+    'header_char':'-',
+    'row_char':None,
+    'footer_label':None,
+    'footer_char':'-',
+    'footer_func':None,
+    'delim':' | ',
+    'justify':None,
+    'prefix':'',
+    'postfix':'',
+    'mask_rep':'--',
+    'datefmt':None,
+    'fmtfunc':str,
+    'wrapfunc':lambda x:x,
+    'col_width':None,
+    'nls':'\n',
+    'output':sys.stdout
+}
 
-:Parameters:
+class Report(object):
+    """Create a tabular TimeSeries report with dates in the left column.
+All instance variables are optional and simply serve as the defaults when calling
+the report. Parameters for calling the report are the exact same as for
+initialization. When calling the report, new options specified will not be saved
+to the instance.
+
+:IVariables:
         - `*tseries` : time series objects. Must all be at the same frequency, but
           do not need to be aligned.
+          
         - `dates` (DateArray, *[None]*) : dates at which values of all the series
           will be output. If not specified, data will be output from the minimum
           start_date to the maximum end_date of all the time series objects
-        - `header_row` (list, *[None]*) : optional list of column headers. Length
-          must be equal to len(tseries) (no date column header specified) or
-          len(tseries)+1 (first header is assumed to be date column header)
+          
+        - `header_row` (list, *[None]*) : List of column headers. Specifying
+          the header for the date column is optional.
+          
         - `header_char` (string, *['-']*): Character to be used for the row separator
           line between the header and first row of data. None for no separator. This
           is ignored if `header_row` is None.
+          
         - `row_char` (string, *[None]*): Character to be used for the row separator
           line between each row of data. None for no separator
+          
         - `footer_func` (List of functions or single function, *[None]*) : A function or
           list of functions for summarizing each data column in the report. For example,
           ma.sum to get the sum of the column. If a list of functions is provided
-          there must be exactly one function for each column.
+          there must be exactly one function for each column. Do not specify a function
+          for the Date column.
+          
         - `footer_char` (string, *['-']*): Character to be used for the row separator
           line between the last row of data and the footer. None for no separator. This
           is ignored if `footer_func` is None.
-        - `footer_label` (string, *[None]*) : label for the footer row. This is
-          ignored if footer_func is None.
+          
+        - `footer_label` (string, *[None]*) : label for the footer row. This goes at the
+          end of the date column. This is ignored if footer_func is None.
+          
         - `justify` (List of strings or single string, *[None]*) : Determines how are
           data justified in their column. If not specified, the date column and string
           columns are left justified, and everything else is right justified. If a
           string is specified, it must be one of 'left', 'right', or 'center' and all
           columns will be justified the same way. If a list is specified, each column
           will be justified according to the specification for that column in the list
-          (specifying the justification for the date column is optional).
+          Specifying the justification for the date column is optional.
+          
         - `prefix` (string, *['']*) : A string prepended to each printed row.
+        
         - `postfix` (string, *['']*) : A string appended to each printed row.
+        
         - `mask_rep` (string, *['--']*): String used to represent masked values in
           output
+          
         - `datefmt` (string, *[None]*) : Formatting string used for displaying the
           dates in the date column. If None, str() is simply called on the dates
+          
         - `fmtfunc` (List of functions or single function, *[None]*) : A function or
           list of functions for formatting each data column in the report. If not
           specified, str() is simply called on each item. If a list of functions is
-          provided, there must be exactly one function for each column
-        - `wrapfunc` (function, *[lambda x:x]*): A function f(text) for wrapping text;
-          each element in the table is first wrapped by this function. Useful functions
-          for this are wrap_onspace, wrap_onspace_strict, and wrap_always (which are
-          part of this module). Eg wrapfunc=lambda x: wrap_onspace(x, 10)
+          provided, there must be exactly one function for each column. Do not specify
+          a function for the Date column, that is handled by the datefmt argument
           
+        - `wrapfunc` (List of functions or single function, *[lambda x:x]*): A function
+          f(text) for wrapping text; each element in the column is first wrapped by this
+          function. Instances of  wrap_onspace, wrap_onspace_strict, and wrap_always
+          (which are part of this module) work well for this. Eg. wrapfunc=wrap_onspace(10)
+          If a list is specified, each column will be wrapped according to the
+          specification for that column in the list. Specifying a function for the Date
+          column is optional
+          
+        - `col_width` (list of integers or single integer, *[None]*): use this to specify
+          a width for all columns (single integer), or each column individually (list
+          of integers). The column will be at least as wide as col_width, but may be
+          larger if cell contents exceed col_width. If specifying a list, you may
+          optionally specify the width for the Date column as the first entry
+          
 :Examples:
 
     import numpy as np
     import timeseries as ts
-    from timeseries import report, wrap_onspace
+    import maskedarray as ma
+    from timeseries import Report, wrap_onspace
 
     series1 = ts.time_series(np.random.uniform(-100,100,15), start_date=ts.thisday('b')-15)
     series2 = ts.time_series(np.random.uniform(-100,100,13), start_date=ts.thisday('b')-10)
@@ -98,213 +160,345 @@
     
     darray = ts.date_array(start_date=ts.thisday('b')-8, end_date=ts.thisday('b')-3)
     
-    # print all values of series1 and series2 and show 2 decimal places.
-    # show masked values as "N/A"
-    print report(series1, series2, fmtfunc=lambda x:'%.2f' % x, mask_rep='N/A')
-
-    # same thing, but format one column one with 2 decimal places, and column two with 4
-    print report(series1, series2, fmtfunc=[(lambda x:'%.2f' % x), (lambda x:'%.4f' % x)], mask_rep='N/A')
-
-    # print an html table of the data over a specified range
-    print "<table>" + \
-          report(series1, series2, series3, dates=darray,
-                   delim="</td><td>", prefix="<tr><td>", postfix="</td></tr>") + \
-          "</table>"
+    txt_o = open('myfile.txt', 'w')
+    html_o = open('myfile.html', 'w')
     
-    # print a table with columns 10 characters wide when possible, but don't break up a word
-    print r.report(series1, series3, series2, wrapfunc=lambda x: wrap_onspace(x, 10))"""
+    # report containing only numerical series, showing 2 decimal places
+    num_report = Report(series1, series2, fmtfunc=lambda x:'%.2f' % x)
     
-    dates = kwargs.pop('dates', None)
-    header_row = kwargs.pop('header_row', None)
-    header_char = kwargs.pop('header_char', '-')
-    row_char = kwargs.pop('row_char', None)
-    footer_label = kwargs.pop('footer_label', None)
-    footer_char = kwargs.pop('footer_char', '-')
-    footer_func = kwargs.pop('footer_func', None)
-    delim = kwargs.pop('delim', ' | ')
-    justify = kwargs.pop('justify', None)
-    prefix = kwargs.pop('prefix', '')
-    postfix = kwargs.pop('postfix', '')
-    mask_rep = kwargs.pop('mask_rep', '--')
-    datefmt = kwargs.pop('datefmt', None)
-    fmtfunc = kwargs.pop('fmtfunc', str)
-    
-    
-    if type(fmtfunc) != types.ListType:
-        fmtfunc = [fmtfunc_wrapper(fmtfunc, mask_rep)]*len(tseries)
-    else:
-        fmtfunc = [fmtfunc_wrapper(f, mask_rep) for f in fmtfunc]
-    
-    wrapfunc = kwargs.pop('wrapfunc', lambda x:x)
+    # report containing some string and numerical data
+    mixed_report = Report(series1, series2, series3)
 
-    if len(kwargs) > 0:
-        raise KeyError("Unrecognized keyword(s): %s" % (", ".join(kwargs.keys())))
+    # output a csv report suitable for excel to sys.stdout, show masked values as "N/A"
+    num_report(delim=', ', mask_rep='N/A')
 
-    if header_row is not None:
-        has_header=True
-        if len(header_row) == len(tseries)+1:
-            # label for date column included
-            rows = [header_row]
-        elif len(header_row) == len(tseries):
-            # label for date column not included
-            rows = [['']+header_row]
-    else:
-        has_header=False
-        rows=[]
+    # format one column one with 2 decimal places, and column two with 4.
+    # Add a sum footer. Write the output to txt_o
+    num_report(fmtfunc=[(lambda x:'%.2f' % x), (lambda x:'%.4f' % x)],
+                 footer_func=ma.sum, footer_label='sum', output=txt_o)
+
+    # create an html table of the data over a specified range.
+    # Wrap text in cells to width 10. Output to html_o
+    html_o.write("<table>")
+    mixed_report(series1, series2, series3, dates=darray,
+               delim="</td><td>", prefix="<tr><td>", postfix="</td></tr>",
+               wrapfunc=wrap_onspace(10, nls='<BR>'), output=html_o)
+    html_o.write("</table>")"""
+
+    def __init__(self, *tseries, **kwargs):
         
-    if justify is not None:
-        if type(justify) == types.StringType:
-            # justify all columns the the same way
-            justify = [justify for x in range(len(tseries)+1)]
-        else: #assume it is a list or tuple, etc
-            if len(justify) == len(tseries):
-                # justification for date column not included, so set that
-                # to left by default
-                justify = ['left'] + justify
-    else:
-        # default column justification
-        justify = ['left']
-        for ser in tseries:
-            if str(ser.dtype)[:2] == '|S': justify.append('left')
-            else: justify.append('right')
+        self.options = {}
+        self.tseries = None
+        if len(tseries) > 0:
+            self.tseries = tseries
+        self.options = self.__make_dict(**kwargs)
         
+    def __make_dict(self, **kwargs):
+    
+        option_dict = copy.copy(self.options)
         
-    if datefmt is None:
-        def datefmt_func(date): return str(date)
-    else:
-        def datefmt_func(date): return date.strfmt(datefmt)
+        option_list = list(_default_options)
 
-    if dates is None:
-        tseries = ts.align_series(*tseries)
-        dates = td.date_array(start_date=tseries[0].start_date,
-                              end_date=tseries[0].end_date)
-    else:
-        tseries = ts.align_series(start_date=dates[0], end_date=dates[-1], *tseries)
+        for x in [kw for kw in option_list if kw in kwargs]:
+            option_dict[x] = kwargs.pop(x)
+            
+        if len(kwargs) > 0:
+            raise KeyError("Unrecognized keyword(s): %s" % (", ".join(kwargs.keys())))
+            
+        return option_dict
+        
+    def set_series(self, *tseries):
+        """set new time series for the report
+
+:Paramaters:
+    - `*tseries` : the TimeSeries objects to be used in the report"""
+        self.tseries = tseries
+
+    def set_options(self, **kwargs):
+        """set new options or modify options in the report
+
+:Paramaters:
+    - `**kwargs` : the options to be used in the report. See the __doc__
+      string for the Report class for valid options"""
+        self.options = self.__make_dict(**kwargs)
+
     
-    for d in dates:
-        rows.append([datefmt_func(d)]+[fmtfunc[i](ser[d]) for i, ser in enumerate(tseries)])
+    def __call__(self, *tseries, **kwargs):
+        """generate a report
+
+:Paramaters:
+    - `*tseries` : the TimeSeries objects to be used in the report. If
+      omitted, the previously set TimeSeries objects will be used
+    - `**kwargs` : the options to be used in the report. See the __doc__
+      string for the Report class for valid options. If omitted, the
+      previously set options will be used"""
+
+        option_dict = self.__make_dict(**kwargs)
+        if len(tseries) == 0:
+            tseries = self.tseries
         
-    if footer_func is not None:
-        has_footer=True
-        if type(footer_func) != types.ListType:
-            footer_func = [footer_func]*len(tseries)
+        def option(kw):
+            return option_dict.get(kw, _default_options[kw])
+            
+        dates = option('dates')
+        header_row = option('header_row')
+        header_char = option('header_char')
+        row_char = option('row_char')
+        footer_label = option('footer_label')
+        footer_char = option('footer_char')
+        footer_func = option('footer_func')
+        delim = option('delim')
+        justify = option('justify')
+        prefix = option('prefix')
+        postfix = option('postfix')
+        mask_rep = option('mask_rep')
+        datefmt = option('datefmt')
+        fmtfunc = option('fmtfunc')
+        wrapfunc = option('wrapfunc')
+        col_width = option('col_width')
+        nls=option('nls')
+        output=option('output')
+        
+        if header_row is not None:
+            has_header=True
+            if len(header_row) == len(tseries)+1:
+                # label for date column included
+                rows = [header_row]
+            elif len(header_row) == len(tseries):
+                # label for date column not included
+                rows = [['']+header_row]
+            else:
+                raise ValueError("mismatch with number of headers and series")
+        else:
+            has_header=False
+            rows=[]
 
-        if footer_label is None: footer_label = ['']
-        else: footer_label = [footer_label]
-        rows.append(footer_label + [fmtfunc[i](footer_func[i](ser)) for i, ser in enumerate(tseries)])
-    else:
-        has_footer=False
+        if justify is not None:
+            _justify = kwargs.pop('justify')
+            if isinstance(justify, str):
+                # justify all columns the the same way
+                justify = [justify for x in range(len(tseries)+1)]
+            elif isinstance(justify, list): #assume it is a list or tuple, etc
+                if len(justify) == len(tseries):
+                    # justification for date column not included, so set that
+                    # to left by default
+                    justify = ['left'] + justify
+            else:
+                raise ValueError("invalid `justify` specification")
+        else:
+            # default column justification
+            justify = ['left']
+            for ser in tseries:
+                if str(ser.dtype)[:2] == '|S': justify.append('left')
+                else: justify.append('right')
 
-    return indent(rows,
-                  has_header=has_header, header_char=header_char,
-                  has_footer=has_footer, footer_char=footer_char,
-                  separate_rows=separate_rows, row_char=row_char,
-                  delim=delim, justify=justify,
-                  prefix=prefix, postfix=postfix, wrapfunc=wrapfunc)
-   
+        if datefmt is None:
+            def datefmt_func(date): return str(date)
+        else:
+            def datefmt_func(date): return date.strfmt(datefmt)
 
+        if dates is None:
+            tseries = ts.align_series(*tseries)
+            dates = td.date_array(start_date=tseries[0].start_date,
+                                  end_date=tseries[0].end_date)
+        else:
+            tseries = ts.align_series(start_date=dates[0], end_date=dates[-1], *tseries)
 
+        if isinstance(fmtfunc, list):
+            fmtfunc = [fmtfunc_wrapper(f, mask_rep) for f in fmtfunc]
+        else:
+            fmtfunc = [fmtfunc_wrapper(fmtfunc, mask_rep)]*len(tseries)
+            
+        if isinstance(wrapfunc, list):
+            if len(wrapfunc) == len(tseries):
+                wrapfunc = [lambda x: x] + wrapfunc
+        else:
+            wrapfunc = [wrapfunc for x in range(len(tseries)+1)]
+            
+        if isinstance(col_width, list):
+            if len(col_width) == len(tseries):
+                col_width = [-1] + col_width
+        else:
+            col_width = [col_width for x in range(len(tseries)+1)]
 
-# written by George Sakkis
-# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/267662
-def indent(rows,
-           has_header=False, header_char='-',
-           has_footer=False, footer_char='-',
-           separate_rows=False, row_char='_',
-           delim=' | ', justify=None, 
-           prefix='', postfix='', wrapfunc=lambda x:x):
-    """Indents a table by column.
-       - rows: A sequence of sequences of items, one sequence per row.
-       - has_header: True if the first row consists of the columns' names.
-       - header_char: Character to be used for the row separator line
-         (if has_header==True or separate_rows==True).
-       - delim: The column delimiter.
-       - justify: Determines how are data justified in their column. 
-         Valid values are 'left','right' and 'center'.
-       - separate_rows: True if rows are to be separated by a line
-         of 'header_char's.
-       - prefix: A string prepended to each printed row.
-       - postfix: A string appended to each printed row.
-       - wrapfunc: A function f(text) for wrapping text; each element in
-         the table is first wrapped by this function."""
+        ############################################################
+        # temporary hack to handle singletons for different types of
+        # behaviour until we finalize how they will be handled
+        ############################################################
+        if ts.time_series([1,2], start_date=ts.thisday('b'))[0].ndim == 0:
+            def getval(series, date): return series[date]
+        else:
+            def getval(series, date):
+                temp = series[date]
+                if temp is ts.tsmasked:
+                    return temp
+                else:
+                    return temp.series[0]
+        ############################################################
 
+        for d in dates:
+            rows.append([datefmt_func(d)]+[fmtfunc[i](getval(ser, d)) for i, ser in enumerate(tseries)])
+
+        if footer_func is not None:
+            has_footer=True
+            if not isinstance(footer_func, list):
+                footer_func = [footer_func]*len(tseries)
+
+            if footer_label is None: footer_label = ['']
+            else: footer_label = [footer_label]
+
+            footer_data = []
+            for i, ser in enumerate(tseries):
+                if footer_func[i] is None:
+                    footer_data.append('')
+                else:
+                    footer_data.append(fmtfunc[i](footer_func[i](ser)))
+
+            rows.append(footer_label + footer_data)
+        else:
+            has_footer=False
+            
+            
+        def rowWrapper(row):
+            newRows = [wrapfunc[i](item).split('\n') for i, item in enumerate(row)]
+            return [[(substr or '') for substr in item] for item in map(None,*newRows)]
+        # break each logical row into one or more physical ones
+        logicalRows = [rowWrapper(row) for row in rows]
+        numLogicalRows = len(logicalRows)
+        # columns of physical rows
+        columns = map(None,*reduce(operator.add,logicalRows))
+        numCols = len(columns)
+        colNums = list(range(numCols))
+
+        # get the maximum of each column by the string length of its items
+        maxWidths = [max(col_width[i], *[len(str(item)) for item in column])
+                        for i, column in enumerate(columns)]
+
+        def getSeparator(char, separate):
+            if char is not None and separate:
+                return char * (len(prefix) + len(postfix) + sum(maxWidths) + \
+                                             len(delim)*(len(maxWidths)-1))
+            else:
+                return None
+
+        header_separator = getSeparator(header_char, has_header)
+        footer_separator = getSeparator(footer_char, has_footer)
+        row_separator = getSeparator(row_char, True)
+
+        # select the appropriate justify method
+        justify_funcs = {'center':str.center, 'right':str.rjust, 'left':str.ljust}
+
+        if has_header and has_footer:
+            data_start = 1
+            data_end = numLogicalRows-3
+        elif has_header:
+            data_start = 1
+            data_end = numLogicalRows-2
+        elif has_footer:
+            data_start = 0
+            data_end = numLogicalRows-3
+        else:
+            data_start = 0
+            data_end = numLogicalRows-2
+
+        for rowNum, physicalRows in enumerate(logicalRows):
+            for row in physicalRows:
+                output.write(prefix \
+                           + delim.join([justify_funcs[justify[colNum].lower()](str(item),width) for (colNum,item,width) in zip(colNums,row,maxWidths)]) \
+                           + postfix + nls)
+
+            if row_separator and (data_start <= rowNum <= data_end):
+                output.write(row_separator + nls)
+            elif header_separator and rowNum < data_start:
+                output.write(header_separator + nls)
+            elif footer_separator and rowNum == data_end + 1:
+                output.write(footer_separator + nls)
+
+
+class wrap_onspace(object):
+    """A callable word-wrap class that preserves existing line breaks
+and most spaces in the text.
     
-    def rowWrapper(row):
-        newRows = [wrapfunc(item).split('\n') for item in row]
-        return [[substr or '' for substr in item] for item in map(None,*newRows)]
-    # break each logical row into one or more physical ones
-    logicalRows = [rowWrapper(row) for row in rows]
-    numLogicalRows = len(logicalRows)
-    # columns of physical rows
-    columns = map(None,*reduce(operator.add,logicalRows))
-    numCols = len(columns)
-    colNums = list(range(numCols))
+:IVariables:
+    - `width` (int): width to wrap at. Won't split up words wider than `width`
+    - `nls` (str, *['\n']*): New line separator. Assumes existing line
+      breaks use this new line separator as well.
+      
+:Parameters (for __call__ method):
+    - `text` (str): text to wrap"""
+
+    def __init__(self, width, nls='\n'):
+        self.width = width
+        self.nls = nls
+        
+    def __call__(self, text):
     
-    if justify is None:
-        justify = ['left' for x in range(numCols)]
+        width = self.width
+        nls = self.nls
     
-    # get the maximum of each column by the string length of its items
-    maxWidths = [max([len(str(item)) for item in column]) for column in columns]
-    
-    def getSeparator(char, separate):
-        if char is not None and separate:
-            return char * (len(prefix) + len(postfix) + sum(maxWidths) + \
-                                         len(delim)*(len(maxWidths)-1))
-        else:
-            return None
-    
-    header_separator = getSeparator(header_char, has_header)
-    footer_separator = getSeparator(footer_char, has_footer)
-    row_separator = getSeparator(row_char, separate_rows)
-    
-    # select the appropriate justify method
-    justify_funcs = {'center':str.center, 'right':str.rjust, 'left':str.ljust}
-   
-    output=cStringIO.StringIO()
+        if nls[-1] == '\n': line_end = nls
+        else:               line_end = nls + '\n'
 
-    for rowNum, physicalRows in enumerate(logicalRows):
-        for row in physicalRows:
-            print >> output, \
-                prefix \
-                + delim.join([justify_funcs[justify[colNum].lower()](str(item),width) for (colNum,item,width) in zip(colNums,row,maxWidths)]) \
-                + postfix
+        def break_or_space(line, word, width):
+            temp_idx = (len(line[line.rfind(nls)+1:]) + len(word.split(nls,1)[0]) >= width)
+            if temp_idx:
+                return line_end
+            else:
+                return ' '
 
-        if row_separator and (0 < rowNum < numLogicalRows-2):
-            print >> output, row_separator
-        elif header_separator and rowNum == 0:
-            print >> output, header_separator            
-        elif footer_separator and rowNum == numLogicalRows-2:
-            print >> output, footer_separator
-            
-    return output.getvalue()
+        return reduce(lambda line, word, width=width: '%s%s%s' %
+                      (line,
+                       break_or_space(line, word, width),
+                       word),
+                      text.split(' ')
+                     )    
 
-# written by Mike Brown
-# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/148061
-def wrap_onspace(text, width):
-    """
-    A word-wrap function that preserves existing line breaks
-    and most spaces in the text. Expects that existing line
-    breaks are posix newlines (\n).
-    """
-    return reduce(lambda line, word, width=width: '%s%s%s' %
-                  (line,
-                   ' \n'[(len(line[line.rfind('\n')+1:])
-                         + len(word.split('\n',1)[0]
-                              ) >= width)],
-                   word),
-                  text.split(' ')
-                 )
 
 import re
-def wrap_onspace_strict(text, width):
-    """Similar to wrap_onspace, but enforces the width constraint:
-       words longer than width are split."""
-    wordRegex = re.compile(r'\S{'+str(width)+r',}')
-    return wrap_onspace(wordRegex.sub(lambda m: wrap_always(m.group(),width),text),width)
+class wrap_onspace_strict(object):
+    """A callable word-wrap class similar to wrap_onspace, but
+enforces the width constraint: words longer than width are split.
+    
+:IVariables:
+    - `width` (int): width to wrap at. Will split up words wider than `width`
+    - `nls` (str, *['\n']*): New line separator. Assumes existing line
+      breaks use this new line separator as well.
+      
+:Parameters (for __call__ method):
+    - `text` (str): text to wrap"""
 
+    def __init__(self, width, nls='\n'):
+        self.width = width
+        self.nls = nls
+        
+    def __call__(self, text):
+    
+        width = self.width
+        nls = self.nls
+
+        wordRegex = re.compile(r'\S{'+str(width)+r',}')
+        return wrap_onspace(wordRegex.sub(lambda m: wrap_always(m.group(),width, nls=nls),text),width, nls=nls)
+
+
 import math
-def wrap_always(text, width):
-    """A simple word-wrap function that wraps text on exactly width characters.
-       It doesn't split the text in words."""
-    return '\n'.join([ text[width*i:width*(i+1)] \
-                       for i in xrange(int(math.ceil(1.*len(text)/width))) ])
+class wrap_always(object):
+    """A callable word-wrap class that wraps text on exactly width
+characters. It doesn't split the text into words.
+    
+:IVariables:
+    - `width` (int): width to wrap at.
+    - `nls` (str, *['\n']*): New line separator.
+      
+:Parameters (for __call__ method):
+    - `text` (str): text to wrap"""
+
+    def __init__(self, width, nls='\n'):
+        self.width = width
+        self.nls = nls
+        
+    def __call__(self, text):
+    
+        width = self.width
+        nls = self.nls
+        return nls.join([ text[width*i:width*(i+1)] \
+                           for i in xrange(int(math.ceil(1.*len(text)/width))) ])



More information about the Scipy-svn mailing list