Autocomplete working in query_editor plugin
[myslice.git] / manifold / util / autolog.py
diff --git a/manifold/util/autolog.py b/manifold/util/autolog.py
new file mode 100644 (file)
index 0000000..70c5c05
--- /dev/null
@@ -0,0 +1,422 @@
+# Written by Brendan O'Connor, brenocon@gmail.com, www.anyall.org\r
+#  * Originally written Aug. 2005\r
+#  * Posted to gist.github.com/16173 on Oct. 2008\r
+\r
+#   Copyright (c) 2003-2006 Open Source Applications Foundation\r
+#\r
+#   Licensed under the Apache License, Version 2.0 (the "License");\r
+#   you may not use this file except in compliance with the License.\r
+#   You may obtain a copy of the License at\r
+#\r
+#       http://www.apache.org/licenses/LICENSE-2.0\r
+#\r
+#   Unless required by applicable law or agreed to in writing, software\r
+#   distributed under the License is distributed on an "AS IS" BASIS,\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+#   See the License for the specific language governing permissions and\r
+#   limitations under the License.\r
+\r
+import re, sys, types\r
+\r
+"""\r
+Have all your function & method calls automatically logged, in indented outline\r
+form - unlike the stack snapshots in an interactive debugger, it tracks call\r
+structure & stack depths across time!\r
+\r
+It hooks into all function calls that you specify, and logs each time they're\r
+called.  I find it especially useful when I don't know what's getting called\r
+when, or need to continuously test for state changes.  (by hacking this file)\r
+\r
+Originally inspired from the python cookbook: \r
+http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/198078\r
+\r
+Currently you can\r
+ - tag functions or individual methods to be autologged\r
+ - tag an entire class's methods to be autologged\r
+ - tag an entire module's classes and functions to be autologged\r
+\r
+TODO:\r
+ - allow tagging of ALL modules in the program on startup?\r
+\r
+CAVEATS:\r
+ - certain classes barf when you logclass() them -- most notably,\r
+   SWIG-generated wrappers, and perhaps others.\r
+\r
+USAGE: see examples on the bottom of this file.\r
+\r
+\r
+Viewing tips\r
+============\r
+\r
+If your terminal can't keep up, try xterm or putty, they seem to be highest\r
+performance.  xterm is available for all platforms through X11...\r
+\r
+Also try:    (RunChandler > log &); tail -f log\r
+\r
+Also, you can  "less -R log"  afterward and get the colors correct.\r
+\r
+If you have long lines, less -RS kills wrapping, enhancing readability. Also\r
+can chop at formatAllArgs().\r
+\r
+If you want long lines to be chopped realtime, try piping through less::\r
+\r
+   RunChandler | less -RS\r
+\r
+but then you have to hit 'space' lots to prevent chandler from freezing.\r
+less's 'F' command is supposed to do this correctly but doesn't work for me.\r
+"""\r
+\r
+\r
+#@@@ should use the standard python logging system?\r
+log = sys.stdout\r
+\r
+# Globally incremented across function calls, so tracks stack depth\r
+indent = 0\r
+indStr = '  '\r
+\r
+\r
+# ANSI escape codes for terminals.\r
+#  X11 xterm: always works, all platforms\r
+#  cygwin dosbox: run through |cat and then colors work\r
+#  linux: works on console & gnome-terminal\r
+#  mac: untested\r
+\r
+\r
+BLACK     =        "\033[0;30m"\r
+BLUE      =        "\033[0;34m"\r
+GREEN     =        "\033[0;32m"\r
+CYAN      =       "\033[0;36m"\r
+RED       =        "\033[0;31m"\r
+PURPLE    =        "\033[0;35m"\r
+BROWN     =        "\033[0;33m"\r
+GRAY      =        "\033[0;37m"\r
+BOLDGRAY  =       "\033[1;30m"\r
+BOLDBLUE     =   "\033[1;34m"\r
+BOLDGREEN    =   "\033[1;32m"\r
+BOLDCYAN     =   "\033[1;36m"\r
+BOLDRED      =   "\033[1;31m"\r
+BOLDPURPLE   =   "\033[1;35m"\r
+BOLDYELLOW   =         "\033[1;33m"\r
+WHITE     =        "\033[1;37m"\r
+\r
+NORMAL = "\033[0m"\r
+\r
+\r
+def indentlog(message):\r
+    global log, indStr, indent\r
+    print >>log, "%s%s" %(indStr*indent, message)\r
+    log.flush()\r
+\r
+def shortstr(obj):\r
+    """\r
+    Where to put gritty heuristics to make an object appear in most useful\r
+    form. defaults to __str__.\r
+    """\r
+    if "wx." in str(obj.__class__)  or  obj.__class__.__name__.startswith("wx"):\r
+        shortclassname = obj.__class__.__name__\r
+        ##shortclassname = str(obj.__class__).split('.')[-1]\r
+        if hasattr(obj, "blockItem") and hasattr(obj.blockItem, "blockName"):\r
+            moreInfo = "block:'%s'" %obj.blockItem.blockName\r
+        else:\r
+            moreInfo = "at %d" %id(obj)\r
+        return "<%s %s>" % (shortclassname, moreInfo)\r
+    else:\r
+        return str(obj)\r
+\r
+def formatAllArgs(args, kwds):\r
+    """\r
+    makes a nice string representation of all the arguments\r
+    """\r
+    allargs = []\r
+    for item in args:\r
+        allargs.append('%s' % shortstr(item))\r
+    for key,item in kwds.items():\r
+        allargs.append('%s=%s' % (key,shortstr(item)))\r
+    formattedArgs = ', '.join(allargs)\r
+    if len(formattedArgs) > 150:\r
+        return formattedArgs[:146] + " ..."\r
+    return formattedArgs\r
+\r
+\r
+def logmodules(listOfModules):\r
+    for m in listOfModules: \r
+        bindmodule(m)\r
+\r
+def logmodule(module, logMatch=".*", logNotMatch="nomatchasfdasdf"):\r
+    """ \r
+    WARNING: this seems to break if you import SWIG wrapper classes\r
+    directly into the module namespace ... logclass() creates weirdness when\r
+    used on them, for some reason.\r
+    \r
+    @param module: could be either an actual module object, or the string\r
+                   you can import (which seems to be the same thing as its\r
+                   __name__).  So you can say logmodule(__name__) at the end\r
+                   of a module definition, to log all of it.\r
+    """\r
+    \r
+    allow = lambda s: re.match(logMatch, s) and not re.match(logNotMatch, s)\r
+    \r
+    if isinstance(module, str):\r
+        d = {}\r
+        exec "import %s" % module  in d\r
+        import sys\r
+        module = sys.modules[module]\r
+        \r
+    names = module.__dict__.keys()\r
+    for name in names:\r
+        if not allow(name): continue\r
+        \r
+        value = getattr(module, name)\r
+        if isinstance(value, type):\r
+            setattr(module, name, logclass(value))\r
+            print>>log,"autolog.logmodule(): bound %s" %name\r
+        elif isinstance(value, types.FunctionType):\r
+            setattr(module, name, logfunction(value))\r
+            print>>log,"autolog.logmodule(): bound %s" %name\r
+\r
+def logfunction(theFunction, displayName=None):\r
+    """a decorator."""\r
+    if not displayName: displayName = theFunction.__name__\r
+\r
+    def _wrapper(*args, **kwds):\r
+        global indent\r
+        argstr = formatAllArgs(args, kwds)\r
+        \r
+        # Log the entry into the function\r
+        indentlog("%s%s%s  (%s) " % (BOLDRED,displayName,NORMAL, argstr))\r
+        log.flush()\r
+        \r
+        indent += 1\r
+        returnval = theFunction(*args,**kwds)\r
+        indent -= 1\r
+        \r
+        # Log return\r
+        ##indentlog("return: %s"% shortstr(returnval)\r
+        return returnval\r
+    return _wrapper\r
+\r
+def logmethod(theMethod, displayName=None):\r
+    """use this for class or instance methods, it formats with the object out front."""\r
+    if not displayName: displayName = theMethod.__name__\r
+    def _methodWrapper(self, *args, **kwds):\r
+        "Use this one for instance or class methods"\r
+        global indent\r
+\r
+        argstr = formatAllArgs(args, kwds)        \r
+        selfstr = shortstr(self)\r
+            \r
+        #print >> log,"%s%s.  %s  (%s) " % (indStr*indent,selfstr,methodname,argstr)\r
+        indentlog("%s.%s%s%s  (%s) " % (selfstr,  BOLDRED,theMethod.__name__,NORMAL, argstr))\r
+        log.flush()\r
+        \r
+        indent += 1\r
+        \r
+        if theMethod.__name__ == 'OnSize':\r
+            indentlog("position, size = %s%s %s%s" %(BOLDBLUE, self.GetPosition(), self.GetSize(), NORMAL))\r
+\r
+        returnval = theMethod(self, *args,**kwds)\r
+\r
+        indent -= 1\r
+        \r
+        return returnval\r
+    return _methodWrapper\r
+\r
+\r
+def logclass(cls, methodsAsFunctions=False, \r
+             logMatch=".*", logNotMatch="asdfnomatch"):\r
+    """ \r
+    A class "decorator". But python doesn't support decorator syntax for\r
+    classes, so do it manually::\r
+    \r
+        class C(object):\r
+           ...\r
+        C = logclass(C)\r
+    \r
+    @param methodsAsFunctions: set to True if you always want methodname first\r
+    in the display.  Probably breaks if you're using class/staticmethods?\r
+    """\r
+\r
+    allow = lambda s: re.match(logMatch, s) and not re.match(logNotMatch, s) and \\r
+          s not in ('__str__','__repr__')\r
+    \r
+    namesToCheck = cls.__dict__.keys()\r
+                    \r
+    for name in namesToCheck:\r
+        if not allow(name): continue\r
+        # unbound methods show up as mere functions in the values of\r
+        # cls.__dict__,so we have to go through getattr\r
+        value = getattr(cls, name)\r
+        \r
+        if methodsAsFunctions and callable(value):\r
+            setattr(cls, name, logfunction(value))            \r
+        elif isinstance(value, types.MethodType):\r
+            #a normal instance method\r
+            if value.im_self == None:                 \r
+                setattr(cls, name, logmethod(value))\r
+            \r
+            #class & static method are more complex.\r
+            #a class method\r
+            elif value.im_self == cls:                \r
+                w = logmethod(value.im_func, \r
+                              displayName="%s.%s" %(cls.__name__, value.__name__))\r
+                setattr(cls, name, classmethod(w))\r
+            else: assert False\r
+\r
+        #a static method\r
+        elif isinstance(value, types.FunctionType):\r
+            w = logfunction(value, \r
+                            displayName="%s.%s" %(cls.__name__, value.__name__))\r
+            setattr(cls, name, staticmethod(w))\r
+    return cls\r
+\r
+class LogMetaClass(type):\r
+    """\r
+    Alternative to logclass(), you set this as a class's __metaclass__.\r
+    \r
+    It will not work if the metaclass has already been overridden (e.g.\r
+    schema.Item or zope.interface (used in Twisted) \r
+    \r
+    Also, it should fail for class/staticmethods, that hasnt been added here\r
+    yet. \r
+    """\r
+    \r
+    def __new__(cls,classname,bases,classdict):\r
+        logmatch = re.compile(classdict.get('logMatch','.*'))\r
+        lognotmatch = re.compile(classdict.get('logNotMatch', 'nevermatchthisstringasdfasdf'))\r
+        \r
+        for attr,item in classdict.items():\r
+            if callable(item) and logmatch.match(attr) and not lognotmatch.match(attr):\r
+                classdict['_H_%s'%attr] = item    # rebind the method\r
+                classdict[attr] = logmethod(item) # replace method by wrapper\r
+\r
+        return type.__new__(cls,classname,bases,classdict)   \r
+\r
+\r
+            \r
+# ---------------------------- Tests and examples ----------------------------\r
+\r
+if __name__=='__main__':\r
+    print; print "------------------- single function logging ---------------"\r
+    @logfunction\r
+    def test():\r
+        return 42\r
+    \r
+    test()\r
+\r
+    print; print "------------------- single method logging -----------------"\r
+    class Test1(object):\r
+        def __init__(self):\r
+            self.a = 10\r
+        \r
+        @logmethod\r
+        def add(self,a,b): return a+b\r
+        \r
+        @logmethod\r
+        def fac(self,val):\r
+            if val == 1:\r
+                return 1\r
+            else:\r
+                return val * self.fac(val-1)\r
+        \r
+        @logfunction\r
+        def fac2(self, val):\r
+            if val == 1:\r
+                return 1\r
+            else:\r
+                return val * self.fac2(val-1)            \r
+    \r
+    t = Test1()\r
+    t.add(5,6)\r
+    t.fac(4)\r
+    print "--- tagged as @logfunction, doesn't understand 'self' is special:"\r
+    t.fac2(4)\r
+        \r
+\r
+    print; print """-------------------- class "decorator" usage ------------------"""\r
+    class Test2(object):\r
+        #will be ignored\r
+        def __init__(self):\r
+            self.a = 10\r
+        def ignoreThis(self): pass\r
+        \r
+\r
+        def add(self,a,b):return a+b\r
+        def fac(self,val):\r
+            if val == 1:\r
+                return 1\r
+            else:\r
+                return val * self.fac(val-1)\r
+    \r
+    Test2 = logclass(Test2, logMatch='fac|add')\r
+\r
+    t2 = Test2()\r
+    t2.add(5,6)\r
+    t2.fac(4)\r
+    t2.ignoreThis()\r
+    \r
+    \r
+    print; print "-------------------- metaclass usage ------------------"\r
+    class Test3(object):\r
+        __metaclass__ = LogMetaClass\r
+        logNotMatch = 'ignoreThis'\r
+        \r
+        def __init__(self): pass\r
+    \r
+        def fac(self,val):\r
+            if val == 1:\r
+                return 1\r
+            else:\r
+                return val * self.fac(val-1)\r
+        def ignoreThis(self): pass\r
+    t3 = Test3()\r
+    t3.fac(4)\r
+    t3.ignoreThis()\r
+\r
+    print; print "-------------- testing static & classmethods --------------"\r
+    class Test4(object):\r
+        @classmethod\r
+        def cm(cls, a, b): \r
+            print cls\r
+            return a+b\r
+\r
+        def im(self, a, b): \r
+            print self\r
+            return a+b\r
+        \r
+        @staticmethod\r
+        def sm(a,b): return a+b\r
+\r
+    Test4 = logclass(Test4)\r
+\r
+    Test4.cm(4,3)\r
+    Test4.sm(4,3)\r
+\r
+    t4 = Test4()\r
+    t4.im(4,3)\r
+    t4.sm(4,3)\r
+    t4.cm(4,3)\r
+    \r
+    #print; print "-------------- static & classmethods: where to put decorators? --------------"\r
+    #class Test5(object):\r
+        #@classmethod\r
+        #@logmethod\r
+        #def cm(cls, a, b): \r
+            #print cls\r
+            #return a+b\r
+        #@logmethod\r
+        #def im(self, a, b): \r
+            #print self\r
+            #return a+b\r
+        \r
+        #@staticmethod\r
+        #@logfunction\r
+        #def sm(a,b): return a+b\r
+\r
+\r
+    #Test5.cm(4,3)\r
+    #Test5.sm(4,3)\r
+\r
+    #t5 = Test5()\r
+    #t5.im(4,3)\r
+    #t5.sm(4,3)\r
+    #t5.cm(4,3)    \r