# See the autograph/LICENSE file for license information.
"""
Support code for rendering function calls to strings to be sent to Autograph.
This module provides a function mkstreamtype() that you can call to create a new
class that will render calls from its instances to a byte stream instead of
calling anything by itself.

For example, if you want to render calls to instances of a class 'Line' to
stderr, you would to this::

    Line = mkstreamtype('Line', sys.stderr)  # bind new class

    ln = Line('line', (0.1, 1, 0.2), hintx="time", hinty="pnl")
    ln.add(12.2, 13.1)
    ln.remove('something')

which will result in the following output to stderr::

    line = Line((0.10000000000000001, 1, 0.20000000000000001), hintx='time', hinty='pnl')
    line.add(12.199999999999999, 13.1)
    line.remove('something')

(Note that the representation of floats prevents us from precisely rendering out the original code.)

This can be useful for implementing a quick unidirectional protocol where the
server evals the lines in an environment, instead of using something like
XML-RPC.
"""


__all__ = ('mkstreamtype',)


def mkstreamtype(typename, stream):
    "Return a new type with the given typename that will render itself to stream."

    class CallType(CallRenderer):
        def __init__(self, varname, *args, **kwds):
            CallRenderer.__init__(self, stream, varname, typename, *args, **kwds)

    return CallType


def args2str(args, kwds):
    """
    Convert arguments and keywords into a string repr of themselves as it would
    appear if called.
    """
    pargs = ([repr(x) for x in args] +
             ['%s=%s' % (k, repr(v)) for k, v in kwds.iteritems()])
    return ', '.join(pargs)


class CallRenderer(object):
    """
    A simple class that renders all attributes to itself as a string printed out
    to a stream, as if it was a function call.
    """
    def __init__(self, stream, varname, clsname, *args, **kwds):
        self._stream = stream
        self._varname = varname

        # Render the constructor.
        s = '%s = %s(%s)\n' % (varname, clsname, args2str(args, kwds))
        stream.write(s)
        stream.flush()

    def __getattribute__(self, name):
        if name.startswith('_'): 
            return object.__getattribute__(self, name)
        else:
            return GabbyMethodWrapper(self, name)

class GabbyMethodWrapper(object):
    """
    Wrapper object for a method to be called.
    """
    def __init__(self, obj, methname):
        self.obj = obj
        self.methname = methname

    def __call__(self, *args, **kwds):
        # Render the method call.
        obj = self.obj
        s = '%s.%s(%s)\n' % (obj._varname, self.methname, args2str(args, kwds))
        obj._stream.write(s)
        obj._stream.flush()



def test():
    import optparse
    parser = optparse.OptionParser(__doc__.strip())
    opts, args = parser.parse_args()

    import sys
    Line = mkstreamtype('Line', sys.stdout)

    line = Line('line', (0.1, 1, 0.2), hintx="time", hinty="pnl")
    line.add(12.2, 13.1)
    line.callRaymond()


if __name__ == '__main__':
    test()




