0001"""
0002Generate Python documentation and other stuff.
0003
0004
0005"""
0006
0007import sys
0008import os
0009import os.path as path
0010import inspect
0011import kid
0012
0013import pudge
0014import pudge.browser as browser
0015import pudge.colorizer as colorizer
0016import pudge.rst as rst
0017
0018from pkg_resources import resource_filename
0019
0020class Generator(object):
0021    """
0022    Takes configuration options and performs file generation.
0023    """
0024
0025    file_extension = '.html'
0026    title = 'Project Name'
0027    dest = '.'
0028    force = 1
0029    modules = []
0030    document_files = []
0031    xhtml = 0
0032    trac_url = None
0033    trac_links = [('wiki', 'wiki', 'Collaborative documentation'),
0034                  ('repository', 'browser', 'Browse subversion repository'),
0035                  ('roadmap', 'roadmap', 'View the project roadmap'),
0036                  ('feedback!', 'newticket', 'Report an issue / request an enhancement')]
0037    mailing_list_url = None
0038    organization = None
0039    organization_name = None
0040    blog_url = None
0041    syndication_url = None
0042    template_dir = None
0043    theme = None
0044    stages = ['copy', 'docs', 'index', 'reference', 'source']
0045
0046    def __init__(self, modules=None, dest='.', force=1):
0047        self.modules = modules or []
0048        self.dest = dest
0049        self.force = force
0050
0051    def _prepare(self):
0052        from os.path import join
0053        template_base = resource_filename(__name__, 'template')
0054        templates = []
0055        templates.insert(0, join(template_base, 'base'))
0056        if self.theme:
0057            templates.insert(0, join(template_base, self.theme))
0058        if self.template_dir:
0059            templates.insert(0, self.template_dir)
0060        for t in reversed(templates):
0061            kid.path.insert(t)
0062        self.templates = templates
0063        self._template_cache = {}
0064
0065    def generate(self):
0066        self._prepare()
0067        if 'copy' in self.stages:
0068            self.copy_files()
0069        if 'docs' in self.stages:
0070            self.load_documents()
0071            self.generate_documents()
0072        if 'index' in self.stages:
0073            self.generate_index()
0074        if 'reference' in self.stages:
0075            self.generate_modules()
0076        if 'source' in self.stages:
0077            self.generate_source()
0078
0079    def get_template(self, name):
0080        Template = self._template_cache.get(name)
0081        if Template is None:
0082            pudge.log.debug('loading template: %s', name)
0083            Template = kid.load_template(name).Template
0084            self._template_cache[name] = Template
0085        import xml.parsers.expat
0086        try:
0087            return Template(generator=self,
0088                            browser=self.browser)
0089        except xml.parsers.expat.ExpatError, e:
0090            raise '%s: File "%s", line %d\n\t%r'                   % (e, e.filename, e.lineno, e.source)
0092
0093    def expand_template(self, name, destfile, **kw):
0094        t = self.get_template(name)
0095        for k, v in kw.items():
0096            setattr(t, k, v)
0097        basedir = self._ensure_dir(path.dirname(destfile))
0098        filename = path.join(basedir, destfile + self.file_extension)
0099        pudge.log.info("generating: %s using template %s", filename, name)
0100        t.write(filename,
0101                output=(self.xhtml and 'xhtml-strict' or 'html-strict'),
0102                encoding='utf-8')
0103
0104    def load_documents(self):
0105        docs = []
0106        self.index_document = { 'title' : self.title, 'fragment' : '',
0107                                'basename': '' }
0108        document_files = [(path.splitext(path.basename(f))[0], f)
0109                           for f in self.document_files]
0110        if self.license:
0111            license_file = 'licenses/%s.rst' % (self.license, )
0112            document_files.append(('doc-license',
0113                                   resource_filename(__name__,
0114                                                     license_file)))
0115        for name, file in document_files:
0116            pudge.log.debug("loading rst document: %s" % (file,))
0117            parts = rst.parts(file)
0118            parts['basename'] = name
0119            if name == 'index':
0120                self.index_document = parts
0121            else:
0122                docs.append(parts)
0123        self.documents = docs
0124
0125    def get_document(self, name):
0126        for doc in self.documents:
0127            if doc['basename'] == name:
0128                return doc
0129
0130    def generate_documents(self):
0131        for doc in self.documents:
0132            basename = doc['basename']
0133            self.expand_template('document.html',
0134                                 basename,
0135                                 parts=doc)
0136
0137    def generate_index(self):
0138        self.expand_template('master-index.html', 'index')
0139        self.expand_template('package-index.html', 'module-index')
0140
0141    def generate_modules(self):
0142        for m in self.browser.modules():
0143            self.generate_module(m)
0144
0145    def generate_module(self, m):
0146        self.check_module(m)
0147        filename = 'module-' + m.qualified_name()
0148        self.expand_template('module.html', filename,
0149                             subject=m)
0150        filename += '-index'
0151        self.expand_template('module-index.html', filename,
0152                             subject=m)
0153        self.generate_classes(m)
0154        for submodule in m.modules():
0155            self.generate_module(submodule)
0156
0157    def generate_classes(self, m):
0158        for c in m.classes():
0159            filename = 'class-' + c.qualified_name()
0160            self.expand_template('class.html', filename,
0161                                 subject=c)
0162
0163    def check_module(self, m):
0164        #for msg in m.check_all_attribute():
0165        #    self.log(2, 'warn: %s' % msg)
0166        pass
0167
0168    def generate_source(self):
0169        for m in self.browser.modules(recursive=1):
0170            module = m.obj
0171            src_file = inspect.getsourcefile(module)
0172            if src_file is None:
0173                pudge.log.debug('skipping: %s (no source file)'
0174                                % m.qualified_name())
0175                continue
0176            src_mtime = path.getmtime(src_file)
0177            dest_file = m.relative_file('.py%s' % self.file_extension)
0178            dest_file = path.join(self.dest, dest_file)
0179            if not self.force and path.exists(dest_file):
0180                if path.getmtime(dest_file) >= src_mtime:
0181                    pudge.log.debug('skipping: %s (fresh)', m.qualified_name())
0182                    continue
0183            pudge.log.info("colorizing: %s", dest_file)
0184            if not path.exists(path.dirname(dest_file)):
0185                os.makedirs(path.dirname(dest_file))
0186            fout = open(dest_file, 'w')
0187            colorizer.Parser(src_file, fout).format()
0188            fout.close()
0189            os.utime(dest_file, (-1, src_mtime))
0190
0191    def copy_files(self):
0192        from shutil import copy
0193        from os import listdir
0194        from os.path import join, splitext, getmtime, exists
0195        extensions = ['.css', '.png', '.txt']
0196        templates = self.templates[:]
0197        templates.reverse()
0198        files = {}
0199        self._ensure_dir()
0200        for basedir in templates:
0201            for f in listdir(basedir):
0202                root, ext = splitext(f)
0203                if ext in extensions and not root.startswith('.'):
0204                    files[f] = join(basedir, f)
0205        dest, force = self.dest, self.force
0206        for basename, file in files.items():
0207            destfile = join(dest, basename)
0208            if not force and exists(destfile):
0209                if getmtime(destfile) >= getmtime(file):
0210                    pudge.log.debug('skipping: %s (fresh)', file)
0211                    continue
0212            pudge.log.info('copying: %s -> %s', file, destfile)
0213            copy(file, self.dest)
0214
0215    def _ensure_dir(self, *dirs):
0216        p = path.join(self.dest, *dirs)
0217        if not path.exists(p):
0218            os.makedirs(p)
0219        return p
0220
0221    def browser(self):
0222        if not hasattr(self, '_browser'):
0223            self._browser = browser.Browser(self.modules)
0224        return self._browser
0225    browser = property(browser)
0226
0227    __call__ = generate
0228
0229class Page(object):
0230
0231    def link_to(self, obj):
0232        if not obj:
0233            return
0234        if obj.ismodule():
0235            path = 'module-%s.html' % (obj.qualified_name(), )
0236        elif obj.isclass():
0237            path = 'class-%s.html' % (obj.qualified_name(), )
0238        elif obj.isroutine() or obj.isdata():
0239            if obj.parent.ismodule():
0240                path = 'module-%s.html#%s'
0241            elif obj.parent.isclass():
0242                path = 'class-%s.html#%s'
0243            path = path % (obj.parent.qualified_name(),
0244                           obj.name)
0245        return path
0246
0247    def link_to_source(self, obj):
0248        (line, last_line) = obj.source_lines()
0249        if line:
0250            hash = '#%d' % line
0251            params = '?f=%d&l=%d' % (line, last_line)
0252        else:
0253            line = '???'
0254            hash = params = ''
0255        return '%s.html%s%s' % (obj.relative_file(),
0256                                params, hash)
0257
0258__all__ = ['Generator']
0259
0260# module attributes    
0261__author__ = "Ryan Tomayko <rtomayko@gmail.com>"
0262__date__ = "$Date: 2005-07-10 22:30:09 -0400 (Sun, 10 Jul 2005) $"
0263__revision__ = "$Revision: 63 $"
0264__url__ = "$URL: svn://lesscode.org/pudge/trunk/pudge/generator.py $"
0265__copyright__ = "Copyright 2005, Ryan Tomayko"
0266__license__ = "MIT <http://www.opensource.org/licenses/mit-license.php>"