new version of redirect ( tofile, tostring, tolines, ... )

Steven D. Majewski (sdm7g@elvis.med.virginia.edu)
Mon, 21 Mar 1994 00:24:27 -0500

Cleaned up version of module redirect -
functions to redirect output to a file or string.

I have added Perl like open syntax to tofile, so it is easy to do:
tofile( '|more ', py_func, arg0, arg1 ... )
to page the functions output.

There is now a test function at the end that demonstrates
this, and also uses tolines() to print the last 10 lines
of output from a function. If the module is run as a
script, the test function will be called.

I have removed fileno as a method. It used to return None. I think
it makes more sense to let an error be raised here rather than
let someone try to pass None to posix.read or posix.write.

tofile( file, func, *args ) now prints all output to file,
including the final value of function, AND returns the
value of func as the value of tofile. tolines( func, *args )
and tostring( func, *args ) will return the output lines
as a list of lines, or a single string of lines. The functions
value will always be in the last line of the string, or the
last string of lines. If the value is not None, it will also
be printed out on the original sys.stdout as a side effect.

Does the above make sense ?
I struggled a bit to try to figure out sensible behaviour.
It may be a bit redundant, but the value can always be found.
( Previously, it was in the last line if it was not None,
otherwise, something else was in the last line. That seemed
a bit too indeterminate! ;-)
[ I'll listen to other suggestions. ]

StringFile().seek() _seems_ to work correctly.

- Steve Majewski (804-982-0831) <sdm7g@Virginia.EDU>
- UVA Department of Molecular Physiology and Biological Physics

#!/usr/local/bin/python
#
# <module 'redirect'>
#
# - Steven D. Majewski <sdm7g@Virginia.EDU>
#
# Functions:
# tofile( file, func, *args ) ==> func(*args)
# tostring( func, *args ) ==> string
# tolines( func, *args ) ==> [ line0, line1, ... lineN ]
#
# Functions apply a function to args, either redirecting the output
# or returning it as a string or a readlines() like list of lines.
#
# tofile will print (to the file) and return the value returned by
# apply( func, *args ). The value is also in the last string in
# tolines ( or the last line in tostring ). tolines and tostring,
# will print the value on the original sys.stdout as well (unless
# it's == None ).
#
#
# Class StringFile()
# Methods:
# read(),write(),readline(),readlines(),writelines(),
# seek(),tell(),flush(),close(),isatty() [NO fileno()]
#
# Creates a file-like interface to a character array.
# Write's append to the array; Read's return the characters in the array.
#
#
# 'tofile()' temporarily reassigns sys.stdout while doing func.
# 'tostring()' and 'tolines()' both call 'tofile()' with an instance
# of StringFile().
#
#
# tofile( '|lpr', func, output )
#
import sys
import os

def tofile( file, func, *args ):
# apply func( args ), temporarily redirecting stdout to file.
# file can be a file or any writable object, or a filename string.
# a "|cmd" string will pipe output to cmd.
# Returns value of apply( func, *args )
ret = None
if not hasattr( file, 'write' ) :
if file[0] == '|' : file = os.popen( file[1:], 'w' )
else: file = open( file, 'w' )
sys.stdout, file = file, sys.stdout
try:
ret = apply( func, args )
finally:
print ret
sys.stdout, file = file, sys.stdout
return ret

def tostring( func, *args ):
# apply func( *args ) with stdout redirected to return string.
string = StringFile()
apply( tofile, ( string, func ) + args )
return string.read()

def tolines( func, *args ):
# apply func( *args ), returning a list of redirected stdout lines.
string = StringFile()
apply( tofile, ( string, func ) + args )
return string.readlines()

from array import array

# A class that mimics a r/w file.
# strings written to the file are stored in a character array.
# a read reads back what has been written.
# Note that the buffer pointer for read is independent of write,
# which ALWAYS appends to the end of buffer.
# Not exactly the same as file semantics, but it happens to be
# what we want!
# Some methods are no-ops, or otherwise not bery useful, but
# are included anyway: close(), fileno(), flush(),

class StringFile:
def __init__( self ):
self._buf = array( 'c' )
self._bp = 0
def close(self):
return self
# On second thought, I think it better to leave this out
# to cause an exception, rather than letting someone try
# posix.write( None, string )
# def fileno(self):
# return None
def flush(self):
pass
def isatty(self):
return 0
def read(self, *howmuch ):
buf = self._buf.tostring()[self._bp:]
if howmuch:
howmuch = howmuch[0]
else:
howmuch = len( buf )
ret = buf[:howmuch]
self._bp = self._bp + len(ret)
return ret
def readline(self):
line = ''
for c in self._buf.tostring()[self._bp:] :
line = line + c
self._bp = self._bp + 1
if c == '\n' : return line
def readlines(self):
lines = []
while 'True' :
lines.append( self.readline() )
if not lines[-1] : return lines[:-1]
def seek(self, where, how ):
if how == 0 :
self._bp = where
elif how == 1 :
self._bp = self._bp + where
elif how == 2 :
self._bp = len(self._buf.tostring()) + where
def tell(self):
return self._bp
def write(self, what ):
self._buf.fromstring( what )
def writelines( self, lines ):
for eachl in lines:
self._buf.fromstring( eachl )

if __name__ == '__main__':
def testf( n ):
for i in range( n ):
print '._.'*10 + '[', '%03d' % i, ']' + 10*'._.'
if hasattr( os, 'popen' ):
tofile( '|more', testf, 300 )
print '\n# Last 10 lines "printed" by testf(): '
print '# (the Python equivalent of \'tail\'.)'
for line in tolines( testf, 300 )[-10:] :
print line[:-1]