myfile.py: a Class wrapper for file objects & prompting readlines()

Steven D. Majewski (sdm7g@elvis.med.virginia.edu)
Tue, 1 Mar 1994 18:45:24 -0500

Leaving the land of Lambda for a moment:
Here's my version of a file class with a prompting readline
( prompts only if file.isatty() ).

It requires module() and resolve() from previously posted names.py,
But they are only used in trying to find a prompt string.
You can comment that out and add a literal default.

To try it:

>>> from myfile import *
>>> f = SPFile( sys.stdin )
>>> f.readlines()

Note: a while ago, I recall some discussion of how to structure
__init__()'s in a class hierarchy. I guess I wasn't paying
attention at the time. :-)

class NumPromptFile( SimplePromptFile, File )
was originally
class NumPromptFile( SimplePromptFile )
but
apply ( self.__class__.__bases__[-1].__init__, (self,)+args )
caused an infinite recursion.

This may be obvious to some of you, but I misunderstood what would be
in self.__class__.__bases__ : I thought that it would have ALL of the
ancestors, but it only contains the ancestors mentioned in the class
inheritance list. To allow some scheme of chaining __init__'s, Either
the base superclass must be put in explicitly ( the current kludge )
or we need a function to return the entire inheritance tree
(partially flattened ? )


As you can guess, my inheritance tree's don't usually get very deep.
This was the first case where I was trying to plan for an arbitrarily
deep hierarchy ... and I screwed it up!

Well, we learn from our mistakes!

You might also note that there is a problem with leading spaces
from the "print ps2," lines. I added an explicit space to
"print ' '+ps1" - but maybe I'll have to go back and reread that
thread about print-comma and soft-spaces, again.

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

# ----------------------------------------
# myfile.py
# ----------------------------------------

from string import split
import sys, __builtin__
_sysfile = sys.stdin
_filetype = type(sys.stdin)

# This class is just a wrapper class around builtin file objects
# and is the base class to all modified file classes.
#

class File:
# define all of the usual file methods
for _M in _sysfile.__methods__ :
_E = 'def '+_M+'(self,*args): return apply( self._basefile.'+_M+', args )'
# def `M`(self,args): return apply(self._basefile.`M`,args)
print _E # DEMO: this line just to show you what's going on -
exec( _E )

def __init__( self, *args ):
if args:
if type(args[0]) == _filetype :
# args can be an already created file
self._basefile = args[0]
else:
# or the args to open( filename-string, mode-string )
self._basefile = apply( open, args )
else: self._basefile = None
self._classname = split(repr(self.__class__))[1] # save for repr()

def open( self, *args ):
if self._basefile :
self.close()
self.basefile = None
apply( self.__init__, args )
return self

def __repr__(self):
# <_CLASSNAME instance [of: <_BASEFILE>] at HEX-ID>
if self._basefile :
return '<'+self._classname+ ' instance of: '+repr(self._basefile)+ ' at '+ hex(id(self))[2:] + '>'
else: return '<'+self._classname+' at '+ hex(id(self))[2:] + '>'

# If file.isatty(), then prompt first on readline() & readlines()
# Otherwise, much the same as Class File.
#
# Will try to find ps1/ps2 defined in first of:
# self (instance/class variable )
# This module
# module: __main__
# module sys
#

__builtin__.ps1 = 'Enter a line of text : '
__builtin__.ps2 = 'More/End with ^D ... : '

from names import resolve,module

_ps_path = ( module(__name__), module('__main__'), module('__builtin__'), sys )

# Ok - maybe the above is OVERKILL, but I wanted to try it out! ;-)

class SimplePromptFile( File ):
def __init__( self, *args ):
# the next line caused some troubles in the next class!!! (*note A)
apply ( self.__class__.__bases__[-1].__init__, (self,)+args )
if hasattr( self, '_basefile' ) and self._basefile :
self._isatty = self._basefile.isatty()
else: self._isatty = None
self.ps1 = apply( resolve, ( 'ps1', self ) + _ps_path )
self.ps2 = apply( resolve, ( 'ps2', self ) + _ps_path )

# and override these methods
def readline(self):
if self._isatty:
print self.ps1,
return self._basefile.readline()
def readlines(self):
if not self._isatty: return self._basefile.readlines()
print ' '+self.ps1,
ret = [ self._basefile.readline() ]
while ret[-1] :
print self.ps2,
ret.append( self._basefile.readline() )
return ret[:-1]

# and add one method to set/change the prompts.
def setprompt( self, ps1, *ps2 ):
self.ps1 = ps1
if ps2 : self.ps2 = ps2[0]
if self._basefile and hasattr( self, '_isatty' ) and not self._isatty:
print "It doesn\'t matter,", repr(self._basefile), "is not a tty."
return self # So you can compose PromptFile().setprompt().readlines()

SPFile = SimplePromptFile

# (*note A)
class NumPromptFile( SimplePromptFile, File ):
def ps1(self):
self._N = 1
return ' ['+('%03d' % self._N )+']__: '
def ps2(self):
self._N = self._N + 1
return '['+('%03d' % self._N )+']__: '
# and override these methods
def readline(self):
if self._isatty:
print self.ps1(),
return self._basefile.readline()
def readlines(self):
if not self._isatty: return self._basefile.readlines()
print self.ps1(),
ret = [ self._basefile.readline() ]
while ret[-1] :
print self.ps2(),
ret.append( self._basefile.readline() )
return ret[:-1]

NPFile = NumPromptFile