epyrun   [plain text]


#!/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 sys, os, string, shutil
if string.find(os.path.abspath(sys.argv[0]), os.sep+'Twisted') != -1:
    sys.path.insert(0, os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]
), os.pardir, os.pardir)))
sys.path.insert(0, os.curdir)
### end of preamble

import os, sys
tmp_dir = '_epyrun_tmp' #only used for partial builds

# HACK: Don't import stuff that we don't like
# this is why we have to convert 
def myimport(*args):
    try: 
        return im(*args)
    except:
        mod = FakeModule(args[0], 4)
        i = args[0].rfind('.')
        if i != -1:
            setattr(sys.modules[args[0][:i]], args[0][i+1:], mod)
        return mod

class FakeModule:

    def __init__(self, name, level):
        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
        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

fakeOut("gnome.ui")
fakeOut("pygtk")
fakeOut("gtk")
fakeOut("wxPython.wx")

# HACK: make Twisted Interfaces not use __metaclass__ =
# MetaInterface. Epydoc barfs on any classes whose type isn't
# TypeType.

from twisted.python import components

class Interface:
    pass

Interface.__module__ = 'twisted.python.components'
Interface.__doc__ = components.Interface.__doc__
components.Interface = Interface

# initial twisted
from twisted.python import reflect
from twisted.internet import reactor
# 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):
    return False
def is_public(self):
    return True

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

# 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('twisted.'): #ha ha sucker
                        return True
                except:
                    pass
        return value

epydoc.html.HTMLFormatter = TwistedHTMLFormatter

# HACK: Only document stuff that we _tell_ you to document, you stupid
# &#@&!#@

from epydoc import objdoc

orig_add = objdoc.DocMap._add

def _add(self, objID):
    m = objID.module()
    if m and m.name() not in modnames: # see below for where modnames is defined
        return
    return orig_add(self, objID)

objdoc.DocMap._add = _add

# 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

import re

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):
    for fn in files:
        file = os.path.join(path, fn).replace('%s__init__'%os.sep, '')
        if file[-3:] == '.py' and not file.count('%stest%s' % (os.sep,os.sep)):
            modName = file[:-3].replace(os.sep,'.')
            try:
                #print 'pre-loading', modName
                reflect.namedModule(modName)
            except ImportError:
                print 'import error:', modName
            except:
                print 'other error:', modName
            else:
                modnames.append(modName)

document_all = True # are we doing a full build?
names = ['twisted/'] #default, may be overriden below

#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('twisted/') 
        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"]
im = __import__
__builtins__.__import__ = myimport

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:
            print 'other error:', name
        else:
            modnames.append(name)
    else: #assume it's a dir
        os.path.walk(name, addMod, None)

__builtins__.__import__ = im
sys.argv = osrv




if 'twisted.test' in modnames:
    modnames.remove('twisted.test')
##if 'twisted' in modnames:
##    modnames.remove('twisted')

sys.argv.extend(modnames)

from twisted import copyright

sys.argv[1:1] = ['-n', 'Twisted %s' % copyright.version, '-u', 'http://twistedmatrix.com/', '--no-private']

# Make it easy to profile epyrun
if 0:
    import profile
    profile.run('cli()', 'epyrun.prof')
else:
    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('twisted.'):
            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)

print 'Done!'