"""

autoreload.py - automatically reload changed source
code into a running program

You might want to add the following to your ~/.ipython/ipythonrc

    import_mod sys
    execute sys.path.append('path/where/this/file/resides')
    import_mod autoreload
    execute autoreload.run()

or adding the following

    import sys
    sys.path.append('path/where/this/file/resides')
    import autoreload
    autoreload.run()

to some startup file.
    

Created: Thomas Heller, 2000-04-17
Modified: Pauli Virtanen, 2006
"""

# $Id: autoreload.py 3117 2006-09-27 20:28:46Z pauli $
#
# $Log: autoreload.py,v $
# Revision 1.9  2001/11/15 18:41:18  thomas
# Cleaned up and made working again before posting to c.l.p.
# Added code to update bound (or unbound) methods as suggested
# by Just van Rossum. Thanks!
#
# ...
#
# Revision 1.1  2001/10/04 16:54:04  thomas
# Discovered this old module on my machine, it didn't work too well,
# but seems worth to continue with it...
# Checked in as a first step.


__version__ = "$Revision: 1.9 $".split()[1]

# ToDo:
#
#  Cannot reload __main__ - explain why this cannot work,
#  and explain a workaround.
#
#  Optimize - the number of watches objects (in old_objects)
#  grows without limits. Think if this is really necessary...


import time, os, threading, sys, types, imp, inspect, traceback

def _get_compiled_ext():
    for ext, mode, typ in imp.get_suffixes():
        if typ == imp.PY_COMPILED:
            return ext

# the official way to get the extension of compiled files (.pyc or .pyo)
PY_COMPILED_EXT = _get_compiled_ext()

class ModuleWatcher:
    running = 0
    def __init__(self):
        # If we don't do this, there may be tracebacks
        # when shutting down python.
        import atexit
        atexit.register(self.stop)

    def run(self):
        if self.running:
            print "# autoreload already running"
            return
        print "# starting autoreload"
        self.running = 1
        self.thread = threading.Thread(target=self._check_modules)
        self.thread.setDaemon(1)
        self.thread.start()

    def stop(self):
        if not self.running:
            print "# autoreload not running"
            return
        self.running = 0
        self.thread.join()
        #print "# autoreload stopped"

    def _check_modules(self):
        skipped = {}
        while self.running:
            time.sleep(0.01)
            for m in sys.modules.values():
                if not hasattr(m, '__file__'):
                    continue
                if m.__name__ == '__main__':

                    # we cannot reload(__main__) First I thought we
                    # could use mod = imp.load_module() and then
                    # reload(mod) to simulate reload(main), but this
                    # would execute the code in __main__ a second
                    # time.

                    continue
                file = m.__file__
                dirname = os.path.dirname(file)
                path, ext = os.path.splitext(file)

                if ext.lower() == '.py':
                    ext = PY_COMPILED_EXT
                    file = os.path.join(dirname, path + PY_COMPILED_EXT)

                if ext != PY_COMPILED_EXT:
                    continue

                try:
		    pymtime = os.stat(file[:-1])[8]
                    if pymtime <= os.stat(file)[8]:
                        continue
		    if skipped.get(file[:-1], None) == pymtime:
		        continue
                except OSError:
                    continue

                try:
                    superreload(m)
		    if file[:-1] in skipped:
		        del skipped[file[:-1]]
                except:
		    skipped[file[:-1]] = pymtime
                    import traceback
                    traceback.print_exc(0)

def update_function(old, new, attrnames):
    for name in attrnames:
        setattr(old, name, getattr(new, name))

def superreload(module,
                reload=reload,
                _old_objects = {}):
    """superreload (module) -> module

    Enhanced version of the builtin reload function.
    superreload replaces the class dictionary of every top-level
    class in the module with the new one automatically,
    as well as every function's code object.
    """
##    start = time.clock()
    # retrieve the attributes from the module before the reload,
    # and remember them in _old_objects.
    for name, object in module.__dict__.items():
        key = (module.__name__, name)
        _old_objects.setdefault(key, []).append(object)
        # print the refcount of old objects:
##        if type(object) in (types.FunctionType, types.ClassType):
##            print name, map(sys.getrefcount, _old_objects[key])

##    print "# reloading module %r" % module

    module = reload(module)
    # XXX We have a problem here if importing the module fails!

    # iterate over all objects and update them
    count = 0
    # XXX Can we optimize here?
    # It may be that no references to the objects are present
    # except those from our _old_objects dictionary.
    # We should remove those. I have to learn about weak-refs!
    for name, new_obj in module.__dict__.items():
        key = (module.__name__, name)
        if _old_objects.has_key(key):
            for old_obj in _old_objects[key]:
                if type(new_obj) == types.ClassType:
                    old_obj.__dict__.update(new_obj.__dict__)
                    count += 1
                elif type(new_obj) == types.FunctionType:
                    update_function(old_obj,
                           new_obj,
                           "func_code func_defaults func_doc".split())
                    count += 1
                elif type(new_obj) == types.MethodType:
                    update_function(old_obj.im_func,
                           new_obj.im_func,
                           "func_code func_defaults func_doc".split())
                    count += 1
##    stop = time.clock()
##    print "# updated %d objects from %s" % (count, module)
##    print "# This took %.3f seconds" % (stop - start)

    return module

_watcher = ModuleWatcher()

run = _watcher.run
stop = _watcher.stop

__all__ = ['run', 'stop', 'superreload']
