#!/usr/bin/python

# tz_module_reloader.py
#       --copyright--                   Copyright 2011 (C) Tranzoa, Co. All rights reserved.    Warranty: You're free and on your own here. This code is not necessarily up-to-date or of public quality.
#       --url--                         http://www.tranzoa.net/tzpython/
#       --email--                       pycode is the name to send to. tranzoa.com is the place to send to.
#       --bodstamps--
#       November 6, 2011        bar
#       November 20, 2011       bar     ding with globals in here and in caller
#       November 30, 2011       bar     optionally update all loaded modules with the new module
#       March 28, 2012          bar     'all' means all, including modules not in our list
#       May 27, 2012            bar     doxygen namespace
#       --eodstamps--
##      \file
#       \namespace              tzpython.tz_module_reloader
#
#
#       Reload a .py module if it's changed.
#
#       Does not reload modules imported from the given .py file.
#
#

import  inspect
import  os
import  sys
import  threading
import  time
import  traceback
from    types       import  ModuleType

import  tzlib



LOG_DELAY   = 10.0

NO_SUCH_SIG = 0x1ffffFFFF


class   a_module_reloader(object) :

    def log(me, s)      :
        pass

    def __init__(me, file_name, on_log = None) :
        me.file_name    = file_name             or ""       # the module's full file name
        me.on_log       = on_log                or me.log   # callback to show the error in the module's ways, should there be errors

        me.lock         = threading.RLock()
        me.sig          = NO_SUCH_SIG                       # most recent module file "signature"


    def reload(me, blocking = True, reload_callers_module = False, force = False) :
        """
            Reload our module if it's changed. Return the module reference if it's reloaded.

            If 'reload_callers_module' is not None or False, reload put the modules in globals() if it's already there.
            If 'reload_callers_module' is 'all' then put the module in all modules in globals() if it's part of said modules.
        """

        retval      = None

        me.lock.acquire()

        sig         = me.sig - 1                            # causes the module to be unloaded if it's not there now
        if  not force :
            try         :
                sig     = tzlib.file_signature(me.file_name)
            except ( IOError, OSError ) :
                pass
            pass

        if  me.sig != sig :
            me.sig  = sig

            mn      = os.path.splitext(os.path.basename(me.file_name))[0]
            if  mn in sys.modules :
                del(sys.modules[mn])
            ing     = False
            if  mn in globals() :
                del(  globals()[mn])
                ing = True

            shown   = 0
            while True :
                try :
                    retval      = __import__(mn)
                    break
                except StandardError :              # ImportError, SyntaxError, NameError, TypeError, IndexError, ValueError, ) :
                    if  tzlib.elapsed_time() - shown >= LOG_DELAY :
                        shown   = tzlib.elapsed_time()
                        try     :
                            e   = sys.exc_info()
                            lns = traceback.format_exception_only(e[0], e[1])
                            es  = ""
                            if  len(lns) > 1 :
                                es  = " : " + lns[-1].strip()
                            me.on_log("a_module_reloader: %s : %s%s" % ( me.file_name, lns[0].strip(), es ) )
                        except IOError :
                            pass
                        pass
                    if  not blocking :
                        retval  = None
                        break
                    time.sleep(0.2)
                pass
            if  retval and ing  :
                globals()[mn]   = retval                # this tells us the module if we imported it

            if  reload_callers_module   :
                mod = inspect.getmodule(inspect.stack()[1][0])
                if  isinstance(getattr(mod, mn, None), ModuleType) :
                    setattr(mod, mn, retval)            # change the caller's module to the new one
                if  reload_callers_module is "all" :
                    for nm, mod in globals().items() :
                        if  isinstance(mod, ModuleType) and isinstance(getattr(mod, mn, None), ModuleType) :
                            setattr(mod, mn, retval)    # change all known modules to the new one
                        pass
                    for nm, mod in sys.modules.items() :
                        if  isinstance(mod, ModuleType) and isinstance(getattr(mod, mn, None), ModuleType) :
                            setattr(mod, mn, retval)    # change all known modules to the new one
                        pass
                    pass
                pass
            pass

        me.lock.release()

        return(retval)


    #   a_module_reloader





if  __name__ == '__main__' :
    try :
        import  tzlibx
    except ImportError :
        tzlibx  = None

    def log(s) :
        print s

    me  = a_module_reloader((sys.argv[1] if len(sys.argv) > 1 else "tzlibx.py"), on_log = log)

    while True :
        print "RELOAD:", me.reload(reload_callers_module = "all")   # force = True,
        print tzlib.elapsed_time(), tzlibx.xx.xstrrev("abc") if tzlibx else ""
        time.sleep(0.5)
    pass


#
#
# eof
