#!/usr/bin/env python
# system
# ### Twisted Preamble
# # This makes sure that users don't have to set up their environment
# # specially in order to run these programs from bin/.
import new
import os
import re
import shutil
import sys

import gobject
import kiwi
import kiwi.utils
from kiwi.__version__ import version

from twisted.python import reflect

class _GObject(object):
    "I am not a real type, just a helper type for epydoc"
    __module__ = 'kiwi'
kiwi._GObject = _GObject

def fakeFunction(func):
    code = func.func_code
    newcode = new.code(code.co_argcount, code.co_nlocals, code.co_stacksize,
                       code.co_flags, 'd\x01\x00S', code.co_consts,
                       code.co_names, code.co_varnames, code.co_filename,
                       code.co_name, 1, code.co_lnotab)
    return new.function(newcode, func.func_globals, func.__name__,
                        func.func_defaults, func.func_closure)

class FakeModule(object):

    def __init__(self, name, level, *ignore):
        self.__level = level
        self.__name__ = name
 
    def __repr__(self):
        return '<Fake %s>' % self.__name__
    __str__ = __repr__

    def __nonzero__(self):
        return 1

    def __call__(self, *args, **kw):
        pass #print 'Called:', args

    def __getattr__(self, attr):
        if self.__level == 0:
            raise AttributeError
        if self.__name__ == 'gobject':
            if attr == 'GObject':
                return object
            #elif attr == 'list_properties':
            #    return lambda x: []
            elif attr == 'pygtk_version':
                return gobject.pygtk_version
            
        if (self.__name__, attr) in skipped_types:
            return _GObject
            
        return FakeModule(self.__name__+'.'+attr, self.__level-1)

    def __cmp__(self, other):
        if not hasattr(other, '___name__'):
            return -1
        return cmp(self.__name__, other.__name__)

def fakeOut(modname):
    modpath = modname.split('.')
    prevmod = None
    for m in range(len(modpath)):
        mp = '.'.join(modpath[:m+1])
        nm = FakeModule(mp, 4)
        if prevmod:
            setattr(prevmod, modpath[m], nm)
        sys.modules[mp] = nm
        prevmod = nm
        
kiwi.utils.gproperty = fakeFunction(kiwi.utils.gproperty)
kiwi.utils.type_register = fakeFunction(kiwi.utils.type_register)
gobject.type_is_a = lambda x: None
gobject.list_properties = lambda x: []

skipped_types = {}
for name in ('gobject', 'pango', 'atk', 'gtk', 'gtk.gdk', 'gtk.glade'):
    mod = reflect.namedAny(name)
    for attr in dir(mod):
        value = getattr(mod, attr)
        if not issubclass(type(value), gobject.GObjectMeta):
            continue
        if issubclass(value, gobject.GObject):
            skipped_types[(name , attr)] = None
fakeOut("gobject")
fakeOut("pango")
fakeOut("atk")
fakeOut("gtk")
fakeOut("gtk.gdk")
fakeOut("gtk.glade")

# epydoc
import epydoc
assert epydoc.__version__[0] == '2', "You need epydoc 2.x!"
from epydoc.cli import cli
from epydoc.html import HTMLFormatter
from epydoc import uid
import epydoc.html

# HACK: Force everything to be public.

def is_private(self):
    if self._name.split('.')[-1].startswith('_'):
        return True
    return False

def is_public(self):
    return not self.is_private() #True

uid.UID.is_private = is_private
uid.UID.is_public = is_public

#from twisted.cred.portal import IRealm
#print uid.ObjectUID(IRealm.requestAvatar).is_routine()
#sys.exit()

# HACK: Don't append -module and -class to the filenames, and generate
# redirecty-files for all methods.

class TwistedHTMLFormatter(HTMLFormatter):
    def _uid_to_filename(self, uid):
        # Enforce same restrictions as HTMLFormatter._uid_to_filename for sanity
        # checking
        assert uid.is_module() or uid.is_class(), 'Bad UID type: %r' % (uid,)
        return uid.name() + '.html'
        
    def _uid_to_uri(self, uid):
        if uid.is_module() or uid.is_class():
            return uid.name() + '.html'

        parent = uid.parent()
        if parent is None:
            return uid.name() + '.html'

        return parent.name() + '.html#' + uid.shortname()

    def _method_to_html(self, uid):
        """
        Dodgy redirect hack.
        """
        str = ('<html><head>\n'
               '<meta http-equiv=refresh content="0; url=%s#%s">\n'
               '</head></html>\n')
        str = str % (self._uid_to_uri(uid.parent()), uid.shortname())
        return str

    def write(self, directory=None, progress_callback=None):
        HTMLFormatter.write(self, directory, progress_callback)
        
        # Write method redirectors
        self._write_method_redirects(directory)

    def _write_method_redirects(self, directory):
        import os.path
        seen = {}
        for uid, doc in self._docmap.data.iteritems():
            if uid.is_method() or uid.is_function():
                if uid.name() in seen: continue
                seen[uid.name()] = 1
                filename = os.path.join(directory, uid.name() + '.html')
                #assert not os.path.exists(filename), filename
                s = self._method_to_html(uid)
                open(filename, 'w').write(s)

    # when doing non-full builds we need to fake epydoc in to thinking that
    # all twisted modules are being documented so that the html links
    # to the modules that aren't being documented get generated correctly
    def _documented(self, uid):
        value = HTMLFormatter._documented(self, uid)
        if not document_all:
            if not value:
                try:
                    if uid._name.startswith('kiwi.'): #ha ha sucker
                        return True
                except:
                    pass
        return value

epydoc.html.HTMLFormatter = TwistedHTMLFormatter

# HACK: Another "only doc what we tell you". We don't want epydoc to
# automatically recurse into subdirectories: "twisted"'s presence was
# causing "twisted/test" to be docced, even thought we explicitly
# didn't put any twisted/test in our modnames.

from epydoc import imports
orig_find_modules = imports.find_modules

def find_modules(dirname):
    if not os.path.isdir(dirname): return []
    found_init = 0
    modules = {}
    dirs = []

    # Search for directories & modules, and check for __init__.py.
    # Don't include duplicates (like foo.py and foo.pyc), and give
    # precedance to the .py files.
    for file in os.listdir(dirname):
        filepath = os.path.join(dirname, file)
        if os.path.isdir(filepath): dirs.append(filepath)
        elif not re.match(r'\w+.py.?', file):
            continue # Ignore things like ".#foo.py" or "a-b.py"
        elif file[-3:] == '.py':
            modules[file] = os.path.join(dirname, file)
            if file == '__init__.py': found_init = 1
        elif file[-4:-1] == '.py':
            modules.setdefault(file[:-1], file)
            if file[:-1] == '__init__.py': found_init = 1
    modules = modules.values()

    # If there was no __init__.py, then this isn't a package
    # directory; return nothing.
    if not found_init: return []

    # Recurse to the child directories.
    # **twisted** here's the change: commented next line out
    #for d in dirs: modules += find_modules(d)
    return modules

imports.find_modules = find_modules

# Now, set up the list of modules for epydoc to document
modnames = []
def addMod(arg, path, files):
    if 'topfiles' in files:
        files.remove('topfiles')
    for fn in files:
        file = os.path.join(path, fn).replace('%s__init__'%os.sep, '')
        if file[-3:] == '.py':
            modName = file[:-3].replace(os.sep,'.')
            try:
                #print 'pre-loading', modName
                reflect.namedModule(modName)
            except ImportError:
                print 'import error:', modName
            except Exception, e:
                print 'other error:', modName
                raise
            else:
                modnames.append(modName)

document_all = True # are we doing a full build?
names = ['kiwi/'] #default, may be overriden below
tmp_dir = '_epyrun_tmp' #only used for partial builds

#get list of modules/pkgs on cmd-line
try:
    i = sys.argv.index("--modules")
except:
    pass
else:
    names = sys.argv[i+1:]
    document_all = False
    sys.argv[i:] = []
    #sanity check on names
    for i in range(len(names)):
        try:
            j = names[i].rindex('kiwi/') 
        except:
            raise SystemExit, 'You can only specify twisted modules or packages'
        else:
            #strip off any leading directories before the 'twisted/'
            #dir. this makes it easy to specify full paths, such as
            #from TwistedEmacs
            names[i] = names[i][j:]

    old_out_dir = "html"
    #if -o was specified, we need to change it to point to a tmp dir
    #otherwise add our own -o option
    try:
        i = sys.argv.index('-o')
        old_out_dir = sys.argv[i+1]
        try:
            os.mkdir(tmp_dir)
        except OSError:
            pass
        sys.argv[i+1] = tmp_dir
    except ValueError:
        sys.argv[1:1] = ['-o', tmp_dir]

osrv = sys.argv
sys.argv=["IGNORE"]

for name in names:
    if name.endswith(".py"):
        # turn it in to a python module name
        name = name[:-3].replace(os.sep, ".")
        try:
            reflect.namedModule(name)
        except ImportError:
            print 'import error:', name
        except Exception, e:
            print 'other error:', name
            raise e
        else:
            modnames.append(name)
    else: #assume it's a dir
        os.path.walk(name, addMod, None)

sys.argv.extend(modnames)

sys.argv[1:1] = ['-n', 'Kiwi %s' % '.'.join(map(str, version)),
                 '-u', 'http://www.async.com.br/projects/kiwi/',
                 '--no-private']

# Make it easy to profile epyrun
cli()

# used when doing partial builds to move the new files
# out of the tmp dir and in to the real output dir.
# only does "twisted." files since the others (index.html and such)
# won't be right when not doing full builds.
def moveFilesOut(arg, dirname, fnames):
    for fn in fnames:
        if fn.startswith('kiwi.'):
            shutil.move('%s%s%s' % (tmp_dir, os.sep, fn), old_out_dir)

if not document_all:
    print "Updating files in %s" % old_out_dir
    #move the right html files in to place
    os.path.walk(tmp_dir, moveFilesOut, None)
    #clean up
    shutil.rmtree(tmp_dir)
