Viewing file: BuildDocs.py (24.26 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
######################################################################## # $Header: /var/local/cvsroot/4Suite/Ft/Lib/DistExt/BuildDocs.py,v 1.33 2005/04/07 20:05:51 jkloth Exp $ """ Main distutils extensions for generating documentation
Copyright 2005 Fourthought, Inc. (USA). Detailed license and copyright information: http://4suite.org/COPYRIGHT Project home, documentation, distributions: http://4suite.org/ """
import sys, os, imp, cStringIO, tempfile from distutils.core import Command from distutils.dep_util import newer_group from distutils.file_util import copy_file from distutils.errors import DistutilsFileError from distutils.util import convert_path
import Structures
class BuildDocs(Command):
command_name = 'build_docs'
description = "build documentation files"
user_options = [ ('build-dir=', 'd', "directory to \"build\" (generate) to"), ('force', 'f', "forcibly build everything (ignore file timestamps)"), ]
boolean_options = ['force']
def initialize_options(self): self.build_dir = None self.build_lib = None self.debug = None self.force = None # Not user selectable, needed for bdist_* commands self.xml_dir = None self.html_dir = None self.text_dir = None self.outfiles = [] return
def finalize_options(self): self.set_undefined_options('build', ('build_docs', 'build_dir'), ('build_lib', 'build_lib'), ('debug','debug'), ('force', 'force'))
self.xml_dir = os.path.join(self.build_dir, 'xml') self.html_dir = os.path.join(self.build_dir, 'html') self.text_dir = os.path.join(self.build_dir, 'text') self.docs = self.distribution.doc_files or [] return
def get_outputs(self): if not self.distribution.have_run.get(self.command_name): self.dry_run = 1 self.run() del self.dry_run return self.outfiles
def get_source_files(self): # Stylesheets are assumed to included elsewhere docs = filter(lambda x: isinstance(x, Structures.Document), self.docs) filenames = [] for document in docs: filenames.append(document.source) return filenames
def _get_modules(self, has_any, build_cmd, cmd_option):
if not has_any: return []
build_cmd = self.get_finalized_command(build_cmd) build_files = build_cmd.get_outputs() build_dir = getattr(build_cmd, cmd_option)
prefix_len = len(build_dir) + len(os.sep) return [ fname[prefix_len:] for fname in build_files ]
def run(self): # Most all of the work is done in an external process to allow # importing of all modules
if os.name == 'nt' and self.debug: # On Windows, the logic for documenting ext modules needs # to learn about '_d' suffix on debug builds return
# Generated XML content (API and XSLT extensions) modules = [] if self.distribution.has_pure_modules(): self.run_command('build_py') build_py = self.get_finalized_command('build_py') modules.extend(build_py.get_outputs())
if self.distribution.has_ext_modules(): self.run_command('build_ext') build_ext = self.get_finalized_command('build_ext') modules.extend(build_ext.get_outputs())
if self.distribution.has_l10n(): self.run_command('build_l10n')
# The commandline applications scripts = self.distribution.scripts or [] scripts = [ x for x in scripts if isinstance(x, Structures.Script) ]
# Handwritten XML docs = filter(lambda x: isinstance(x, Structures.Document), self.docs)
# Create a script to do the documenting in a sub-process script_name = tempfile.mktemp('.py') outputs_name = tempfile.mktemp('.out') script = open(script_name, 'w')
script.write('import sys\n') script.write('sys.path.insert(0, %r)\n' % self.build_lib)
script.write('from distutils.dist import Distribution\n') script.write('from Ft.Lib.DistExt.Structures import *\n') script.write('from %s import ExternalBuildDocs\n' % (__name__))
script.write('modules = [%s]\n' % ',\n'.join(map(repr, modules))) script.write('scripts = [%s]\n' % ',\n'.join(map(repr, scripts))) script.write('docs = [%s]\n' % ',\n'.join(map(repr, docs)))
script.write('attrs = %r\n' % vars(self.distribution.metadata)) script.write('dist = Distribution(attrs)\n') script.write('cmd = ExternalBuildDocs(dist, modules, scripts, docs, ' '%r, %r, %r, %r, %r, %r, %r, %r)\n' % (self.build_lib, self.build_dir, self.xml_dir, self.html_dir, self.text_dir, self.force, self.verbose, self.dry_run)) script.write('cmd.run()\n')
script.write('file = open(%r, "w")\n' % outputs_name) script.write('file.write(repr(cmd.get_outputs()))\n') script.write('file.close()\n') script.close()
try: os.spawnv(os.P_WAIT, sys.executable, [sys.executable, script_name])
# Get the list of generated files try: outputs = open(outputs_name).read() except: self.warn('failure during documenting') else: self.outfiles.extend(eval(outputs)) finally: #try: os.remove(script_name) #except: pass #try: os.remove(outputs_name) #except: pass pass
return
# -- externally executed -----------------------------------------------------
import pydoc, inspect, imp
class ExternalBuildDocs(Command):
command_name = 'build_docs'
def __init__(self, dist, modules, scripts, docs, build_lib, build_dir, xml_dir, html_dir, text_dir, force, verbose, dry_run): self.distribution = dist self.modules = modules self.scripts = scripts self.docs = docs self.build_lib = build_lib self.build_dir = build_dir self.xml_dir = xml_dir self.html_dir = html_dir self.text_dir = text_dir self.force = force self.verbose = verbose try: from distutils import log except ImportError: pass else: log.set_verbosity(self.verbose) self.dry_run = dry_run self.outfiles = [] return
def get_outputs(self): return self.outfiles
def run(self): index = { 'modules' : [], 'extensions' : [], 'commandline' : [], }
# Handwritten XML content (packages add to this list) for doc in self.docs: files = {} source = convert_path(doc.source) outdir = doc.outdir and convert_path(doc.outdir)
files['source'] = source
files['xml'] = os.path.join(self.xml_dir, outdir, os.path.basename(source))
files['name'] = doc.title or os.path.basename(source) if doc.htmlOutfile is not None: files['html'] = os.path.join(self.html_dir, outdir,convert_path(doc.htmlOutfile)) if doc.textOutfile is not None: files['text'] = os.path.join(self.text_dir, outdir,convert_path(doc.textOutfile))
if not index.has_key(doc.category): index[doc.category] = [] index[doc.category].append(files)
self.outfiles.append(files['xml']) self.mkpath(os.path.dirname(files['xml'])) self.copy_file(files['source'], files['xml'])
# Generated XML content (API and XSLT extensions) if self.modules: common = os.path.commonprefix(self.modules) if common == self.build_lib: # Multiple top-level packages and/or modules installed installed = os.listdir(self.build_lib) else: # A single top-level package/module top = common[len(self.build_lib)+len(os.sep):] installed = os.path.split(top)[:1]
top_level = [] for file in installed: path = os.path.join(self.build_lib, file) if os.path.isdir(path): # A package for ext in ['.py', '.pyc', '.pyo']: module = os.path.join(path, '__init__' + ext) if os.path.isfile(module): if module in self.modules: top_level.append(module) break elif path in self.modules: # A module top_level.append(path) self.doc_modules(index, top_level)
# Generated XML content for command-line applications if self.scripts: self.doc_scripts(index)
if self.docs: try: from Ft.Xml.Xslt import Processor Processor.Processor() except: self.warn('skipping rendering (unable to get a processor)') else: for doc in self.docs: self.render_doc(doc)
self.build_xml_index(index) self.build_html_index(index) return
def locate(self, path, name=None): try: module = __import__(path, {}, {}, ['*']) except ImportError, e: self.announce('not documenting %s (%s)' % (name or path, e), 2) module = None
return module
def document(self, category, name, sources, object, formatter): # Add the module for the formatter to the sources list sources.append(inspect.getmodule(formatter.__class__).__file__)
xmlfile = os.path.join(self.xml_dir, category, name + '.xml') htmlfile = os.path.join(self.html_dir, category, name + '.html') textfile = os.path.join(self.text_dir, category, name + '.txt')
self.outfiles.append(xmlfile) self.mkpath(os.path.dirname(xmlfile)) if self.force or newer_group(sources, xmlfile): self.announce("documenting %s -> %s" % (name, xmlfile), 2) if not self.dry_run: xml = formatter.document(object, encoding='iso-8859-1') open(xmlfile, 'wb').write(xml) else: self.announce('not documenting %s (up-to-date)' % name, 1)
# Add the XSLT generated versions to the document list # Even if the XML hasn't changed, the stylesheets might have. self.docs.append(Structures.Document(xmlfile, category, ['Ft/Data/%s_html.xslt' % category], name + '.html', #['Ft/Data/%s_text.xslt' % category], #name + '.txt', category = category ))
return xmlfile, htmlfile, textfile
def add_index_info(self, index, category, name, (xml, html, text)): info = {'name' : name, 'xml' : xml, 'html' : html, 'text' : text, } index[category].append(info) return
def doc_modules(self, index, top_level): sources = {} prefix_len = len(self.build_lib) + len(os.sep) for file in self.modules: module = os.path.splitext(file[prefix_len:])[0] if os.path.basename(module) == '__init__': module = os.path.dirname(module) module = module.replace(os.sep, '.') sources[module] = file
from Formatters import ApiFormatter apidoc = ApiFormatter.ApiFormatter(self, sources)
category = 'modules'
modules = sources.keys() modules.sort()
extensions = []
thirdparty = '!'
for name in modules: # Only document our code (no third-party inclusions) if name.endswith('.ThirdParty'): thirdparty = name + '.' elif not name.startswith(thirdparty): module = self.locate(name)
if module is None: continue
source = sources[name]
if hasattr(module, 'ExtFunctions') or hasattr(module, 'ExtElements'): # An XPath/XSLT extension module (documented later) extensions.append((name, source, module))
files = self.document(category, name, [source], module, apidoc)
if source in top_level: # Add this to the list of files for the index page self.add_index_info(index, category, name, files)
if extensions: self.doc_extensions(extensions, index)
return
def doc_extensions(self, extensions, index): """ Create XML documentation for XPath/XSLT extensions """ from Formatters import ExtensionFormatter extdoc = ExtensionFormatter.ExtensionFormatter(self)
category = 'extensions'
# Remove modules whose package also defines extensions pkgs = filter(lambda (n, s, m): hasattr(m, '__path__'), extensions) ignored = [] for name, source, module in pkgs: package_dir = os.path.dirname(source) package_file = os.path.basename(source) for file in os.listdir(package_dir): if file != package_file: ignored.append(os.path.join(package_dir, file))
for name, source, module in extensions: if source not in ignored: files = self.document(category, name, [source], module, extdoc) self.add_index_info(index, category, name, files) return
def doc_scripts(self, index): from Formatters import CommandLineFormatter cmdlinedoc = CommandLineFormatter.CommandLineFormatter(self)
category = 'commandline'
for script in self.scripts: name = script.name module = self.locate(getattr(script, 'module', None), name) if module is None: continue
app = getattr(module, script.identifier)()
sources = [module.__file__] for cmd_name, cmd in app.get_help_doc_info(): classmod = __import__(cmd.__class__.__module__, {}, {}, ['*']) sources.append(classmod.__file__)
files = self.document(category, name, sources, app, cmdlinedoc)
# Add this to the list of files for the index page self.add_index_info(index, category, name, files)
return
def render_doc(self, doc): source = convert_path(doc.source) outdir = doc.outdir and convert_path(doc.outdir)
if doc.htmlOutfile is not None: outfile = os.path.join(self.html_dir, outdir,convert_path(doc.htmlOutfile)) self.render_one(outfile, os.path.join(self.xml_dir,outdir,os.path.basename(source)), doc.htmlStylesheets, doc.params) self.outfiles.append(outfile) if doc.textOutfile: outfile = os.path.join(self.text_dir, outdir,convert_path(doc.textOutfile)) self.render_one(outfile, os.path.join(self.xml_dir,outdir,os.path.basename(source)), doc.textStylesheets, doc.params) self.outfiles.append(outfile)
def render_one(self,outfile,source,stylesheets,params): from Ft.Xml.Xslt import Processor from Ft.Xml import InputSource from Ft.Lib import Uri
self.mkpath(os.path.dirname(outfile))
if not self.force: sources = [source] for file in stylesheets: file = convert_path(file) if os.path.exists(file): sources.append(file) if not newer_group(sources, outfile): self.announce('not rendering %s (up-to-date)' % outfile, 1) return
self.announce("rendering %s -> %s" % (source, outfile), 2) if not self.dry_run: # Add the stylesheets processor = Processor.Processor() ignorePis = 1 for file in stylesheets: file = convert_path(file) if os.path.exists(file): #url = 'file:' + urllib.pathname2url(file) url = Uri.OsPathToUri(file, attemptAbsolute=True) try: iSrc = InputSource.DefaultFactory.fromUri(url) processor.appendStylesheet(iSrc) except Exception, exc: raise DistutilsFileError, \ "could not use stylesheet '%s': %s" % (file, exc) else: ignorePis = 0
# Process the source stream = open(outfile, 'w') #url = 'file:' + urllib.pathname2url(source) url = Uri.OsPathToUri(source, attemptAbsolute=True)
params = {'version' : self.distribution.get_version()} params.update(params) try: iSrc = InputSource.DefaultFactory.fromUri(url) processor.run(iSrc, ignorePis, params, outputStream=stream) except Exception, exc: raise DistutilsFileError, \ "could not render '%s': %s" % (outfile, exc) stream.write('\n') stream.close()
def build_html_index(self, index): htmlfile = os.path.join(self.html_dir, 'index.html') self.outfiles.append(htmlfile) self.announce("creating HTML index -> %s" % (htmlfile), 2)
prefix_len = len(self.build_dir) for section, infos in index.items(): for info in infos: for key, value in info.items(): if value.startswith(self.build_dir): info[key] = '..' + value[prefix_len:] if key in ['html','xml','text'] and os.sep != '/': info[key] = info[key].replace(os.sep, '/')
if not self.dry_run: f = open(htmlfile, 'wb') title = '%s Documentation Index' % self.distribution.metadata.get_fullname() f.write('<html>\n') f.write('<head><title>%s</title></head>\n' % title) f.write('<body>\n<h1>%s</h1>\n' % title) f.write('<div>%s</div>\n' % PREAMBLE)
def section(title, list_, write=f.write): if not list_: return list_.sort(lambda a, b: cmp(a['name'], b['name'])) write('<h2>%s</h2>\n' % title) write('<table width="100%">\n') rows = (len(list_)+3)/4 for row in range(rows): write(' <tr>\n') for i in range(row, rows*4, rows): write(' <td width="25%" valign="top">') if i < len(list_): write('<a href="%(html)s">%(name)s</a>' % list_[i]) else: write(' ') write('</td>\n') write(' </tr>\n') write('</table>\n') return
section('General', filter(lambda x: x.has_key('html'), index['general'])) section('Modules', filter(lambda x: x.has_key('html'), index['modules'])) section('XPath/XSLT Extensions', filter(lambda x: x.has_key('html'), index['extensions'])) section('Command-line Applications', filter(lambda x: x.has_key('html'), index['commandline'])) f.write('</body>\n</html>\n') f.close() return
def build_xml_index(self, index): from Ft.Lib import Uri xmlfile = os.path.join(self.xml_dir, 'index.doc') self.outfiles.append(xmlfile) self.announce("creating XML index -> %s" % (xmlfile), 2)
prefix_len = len(self.xml_dir) for section, infos in index.items(): for info in infos: for key, value in info.items(): if key in ['xml'] and value.startswith(self.xml_dir): info[key] = value[prefix_len+1:]
if not self.dry_run: f = open(xmlfile, 'wb') title = '%s Document Index' % self.distribution.metadata.get_fullname() f.write("""<?xml version="1.0"?> <article xmlns="http://docbook.org/docbook/xml/4.0/namespace" xmlns:dc= "http://purl.org/dc/elements/1.1/" xmlns:common="http://namespaces.fourthought.com/doc-common" xmlns:f = "http://xmlns.4suite.org/ext" version="1.0" > <articleinfo> <title>%s</title> <author>Generated by build_docs</author> </articleinfo>""" % title)
def section(title, list, write=f.write): if not list: return list.sort(lambda a, b: cmp(a['name'], b['name'])) write('<section>\n <title>%s</title>\n' % title) write(' <glosslist>\n') for item in list: name = item['name'] xmlospath = item['xml'] #xmluripath = urllib.pathname2url(xmlospath) xmluri = Uri.OsPathToUri(xmlospath, attemptAbsolute=False) xmluripath = Uri.SplitUriRef(xmluri)[2] write(' <glossentry><glossterm>%s</glossterm><glossdef>%s</glossdef></glossentry>\n' % (name, xmluripath)) write(" </glosslist>\n") write('</section>\n') return
section('General', filter(lambda x: x.has_key('html'), index['general'])) section('Modules', filter(lambda x: x.has_key('html'), index['modules'])) section('XPath/XSLT Extensions', filter(lambda x: x.has_key('html'), index['extensions'])) section('Command-line Applications', filter(lambda x: x.has_key('html'), index['commandline'])) f.write('</article>\n') f.close() return
PREAMBLE = """ <p>Copyright 2001-2005 Fourthought, Inc., USA. Please read the <a href='COPYRIGHT.html'>COPYRIGHT</a> file for terms of license.</p> <p>For release notes and news, see the <a href='http://4suite.org/'>4Suite home page</a> (http://4suite.org/).</p> <p>For complete installation instructions, see the following documents listed in the "General" section below: Installing 4Suite on UNIX/Windows, and 4Suite CVS access.</p> <p>If you have questions not answered by the documents, first stop is the <a href='http://4suite.org/docs/FAQ.xml'>FAQ</a>. Next try the Akara pages on <a href='http://uche.ogbuji.net/tech/akara/pyxml/'>Python/XML</a> and <a href='http://uche.ogbuji.net/tech/akara/4suite/'>4Suite</a>. Then check the <a href='http://lists.fourthought.com/pipermail/4suite/'>mailing archives</a> of the 4Suite list or post there. Or drop by on IRC (#4suite on <a href="http://freenode.net/irc_servers.shtml">irc.freenode.net</a>).</p> <p>People interested in participating in or closely following the development process of 4Suite can join the <a href='http://lists.fourthought.com/mailman/listinfo/4suite-dev'>mailing list for core 4Suite developers</a>, which is open (as are its <a href='http://lists.fourthought.com/pipermail/4suite-dev/'>archives</a>).</p> <p>These links and others are also provided on <a href='http://uche.ogbuji.net/tech/4Suite/'>Uche Ogbuji's 4Suite page</a>.</a></p> <hr> """
|