# $Id: NetStringIO.py,v 1.2 2001/04/30 10:42:00 drt Exp $

# Geistiges Eigentum ist Aberglaube.

__version__ = '0.1 beta'

# $Log: NetStringIO.py,v $
# Revision 1.2  2001/04/30 10:42:00  drt
# Polished the Code
#

# * Im not sure at the moment what NetStringIO has to implement to
#   qualify as an file-like object.
#
# * Should I define an own error object for this module instead of using
#   IOerror?
#
# * Should I implement readline(), readlines() and writelinesU()? 
#   They do not realy make sense withe netstrings, do they?

import os, sys, string

class NetStringIO:
    __doc__ = """Warping arround a file object changing all I/O to netstrings.

    I use this class to warp it arround filehandles obtained by a call
    to socket.makefile(). With this I can build protocols using
    netstrings with ease. For more information in netstrings see
    http://cr.yp.to/proto/netstrings.txt
    
    I like sending an '\n' after every netstring. This makes
    inspection of data on the wire much more readable. You can change
    this behaviour by calling the constructor with an optional empty
    string argument. You even can specify another delimiter than '\n'
    """

    def __init__(self, file, delim = '\n'):
        __doc__ = """Create a Netstring object warping a file-like object.
        
        n = NetStringIO.NetStringIO(file [, delimiter])

        The returned object should work like reading or writing
        directly from file while the difference that all reading or
        writing is preformed via netstrings. If you leave out
        'delimiter' every netstring will be followed by '\n' while
        writing and is expected to be followed by '\n' while
        reading. By passing a second parameter to the constructor you
        can change '\n' to something else. By passing '' as second
        parameter you can get the behaviour which is used in bernstein
        protocols: no delimiter between netstrings.
        """

        self.file = file
        self.delim = delim

        
    def close(self):
        __doc__ = "Closes the underlying file object."

        self.file.close()


    def isatty(self):
        __doc__ = """Checks if the underlying file Object is a tty.

        I guess using ttys with netstrings is of limited use but who
        knows.
        """
        
        return self.file.isatty()


    def read(self):
        """Read from the underlying file object.

        If the data does not contain a valid netstring or is not
        followed by the delimiter we raise IOerror.

        This is more or less an 1:1 port of the code in
        http://cr.yp.to/proto/netstrings.txt - any more pythonish ways
        to do this?
        """
        
        # read length of netstring
        l = ''
        c = self.file.read(1)
        while c:
            if c == ':':
                # we are ready reading the length
                strlen = int(l)
                break
            if c not in string.digits:
                raise IOError, "not a valid netstring: %s is not digit" % (c)
            l += c
            c = self.file.read(1)

        if not c:
            return None
        
        s = self.file.read(strlen)
        c = self.file.read(1 + len(self.delim))
        if c != "," + self.delim:
            raise IOError, "not a valid netstring: %s not proprtply terminated" % (s, c)

        return s

    
    def write(self, s):
        __doc__ = """Writes a Netstring to the underlying fileobject."""

        self.file.write(str(len(s)) + ':' + s + "," + self.delim)
        self.file.flush()


def test():
    __doc__ = """A little test suite."""

    import StringIO, sys

    testtext = 'Netstrings rule'
    inf = StringIO.StringIO()
    outf = StringIO.StringIO()

    print "Writing a Netstring ... " ,
    f = NetStringIO(outf)
    f.write(testtext)
    print outf.getvalue() ,

    inf = StringIO.StringIO(outf.getvalue())
    f.close()

    print "Reading this Netstring ... " ,
    
    fz = NetStringIO(inf)
    ret = fz.read()
    assert ret == testtext, "String is different after reading"
    print ret
    fz.close()

if __name__ == '__main__':
	test()
