[PATCH 3 of 3 RFC] setup: install & build documentation using docutils
Dan Villiom Podlaski Christiansen
danchr at gmail.com
Tue Nov 17 15:36:44 CST 2009
# HG changeset patch
# User Dan Villiom Podlaski Christiansen <danchr at gmail.com>
# Date 1257772950 -3600
# Node ID a1ec03ca952b86cc057587cc36ccb5ccfd289c59
# Parent 9cf85ff4e6016a227dba8f94708f8e89a9814440
setup: install & build documentation using docutils.
diff --git a/doc/__init__.py b/doc/__init__.py
new file mode 100644
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -29,6 +29,8 @@ except:
import os, subprocess, time
import shutil
import tempfile
+import re
+
from distutils.core import setup, Extension
from distutils.dist import Distribution
from distutils.command.install_data import install_data
@@ -50,6 +52,9 @@ extra = {}
scripts = ['hg']
if os.name == 'nt':
scripts.append('contrib/win32/hg.bat')
+else:
+ # required for generated sources
+ os.environ['LANG'] = os.environ['LC_ALL'] = 'C'
# simplified version of distutils.ccompiler.CCompiler.has_function
# that actually removes its temporary files.
@@ -212,6 +217,10 @@ class build_mo(build):
self.distribution.package_data_files = []
datafiles = self.distribution.package_data_files
+ if not hasattr(self.distribution, 'package_data_files'):
+ self.distribution.package_data_files = []
+ datafiles = self.distribution.package_data_files
+
join = os.path.join
for po in os.listdir(podir):
if not po.endswith('.po'):
@@ -229,6 +238,148 @@ class build_mo(build):
build.sub_commands.append(('build_mo', None))
+class build_doc(build):
+
+ description = "build documentation from ReST sources"
+
+ user_options = [('build-dir=', 'd', 'directory to build to'),
+ ('force', 'f',
+ 'forcibly build everything (ignore file timestamps')]
+ boolean_options = ['force']
+
+ def initialize_options (self):
+ self.build_dir = None
+ self.force = None
+
+ def finalize_options (self):
+ # bit of a hack for turning "lib.<whatever>" into "data.<whatever>"
+ undefined = self.build_dir is None
+ self.set_undefined_options('build',
+ ('build_lib', 'build_dir'),
+ ('force', 'force'))
+ if undefined:
+ self.build_dir = self.build_dir.replace('lib', 'data')
+
+ def gendoc(self, output):
+ from doc import gendoc
+ from cStringIO import StringIO
+
+ s = StringIO()
+ gendoc.show_doc(s)
+ s = s.getvalue()
+
+ # only write to the file if the output has changed
+ if not (os.path.exists(output) and file(output).read() == s):
+ file(output, 'w').write(s)
+
+ def run(self):
+ '''
+ This method will build the Mercurial documentation from its ReST
+ sources, as well as extracting docstrings using the `gendoc' method.
+
+ For each source file, we use docutils to read it and obtain its
+ dependancies. Then, both manual pages and HTML documentation is
+ generated, but only when needed. This is approach is reasonably fast
+ and flexible, but at the cost of some implementation complexity.
+ '''
+ from os.path import join
+
+ try:
+ # import early, so we can fail early
+ from docutils import frontend, io, readers, writers
+ except ImportError:
+ self.warn('skipping documentation; docutils not found.')
+ return
+
+ try:
+ from docutils.writers import manpage
+ except ImportError:
+ # load & use the bundled rst2man
+ from doc import rst2man as manpage
+
+ if not os.path.isdir('doc'):
+ self.warn("could not find doc directory")
+ return
+
+ # avoid cluttering the source directory by copying all documentation
+ # files to the build directory
+ self.mkpath(self.build_dir)
+ self.copy_tree('doc', self.build_dir)
+
+ # update extracted documentation
+ self.execute(self.gendoc,
+ [join(self.build_dir, 'hg.1.gendoc.txt')],
+ ('extracting %s from source files'
+ % join(self.build_dir, 'hg.1.gendoc.txt')))
+
+ datafiles = self.distribution.data_files
+ pattern = re.compile(r'^((.*)\.([0-9]))\.txt$')
+
+ stylesheet = 'style.css'
+ datafiles.append((join('doc', self.distribution.get_name()),
+ [join(self.build_dir, stylesheet)]))
+
+ for srcfile in os.listdir(self.build_dir):
+ match = pattern.match(srcfile)
+ if not match:
+ continue
+
+ srcfile = join(self.build_dir, srcfile)
+ htmlfile = join(self.build_dir, match.group(1) + '.html')
+ manfile = join(self.build_dir, match.group(1))
+
+ # add resulting files to distrubution
+ datafiles.append((join('doc', self.distribution.get_name()),
+ [htmlfile]))
+ datafiles.append((join('man', 'man' + manfile.rsplit('.', 1)[1]),
+ [manfile]))
+
+ # reader & writer classes
+ # NOTE: writers.get_writer_class('manpage') will be used eventually
+ reader = readers.get_reader_class('standalone')(parser_name='rst')
+ htmlwriter = writers.get_writer_class('html')()
+ manwriter = manpage.Writer()
+ components = (reader, reader.parser, htmlwriter, manwriter,)
+
+ # get & set docutils settings
+ optparser = frontend.OptionParser(components)
+ settings = optparser.parse_args(['--halt', 'warning',
+ '--link-stylesheet',
+ '--stylesheet-path', 'style.css'
+ '--option-limit' '0'])
+
+ # read & parse ReST source
+ reader.read(io.FileInput(source_path=srcfile, encoding='ascii'),
+ None, settings)
+
+ document = reader.document
+ document.transformer.populate_from_components(components)
+ document.transformer.apply_transforms()
+
+ # extract dependancies from docutils
+ dependancies = [srcfile] + settings.record_dependencies.list
+
+ # generate the documentation files; using distutils to avoid
+ # unnecessary processing
+ self.make_file(infiles=[srcfile] + dependancies, outfile=htmlfile,
+ func=htmlwriter.write,
+ args=(reader.document,
+ io.FileOutput(destination_path=htmlfile,
+ encoding='utf-8')),
+ exec_msg='generating %s' % htmlfile)
+
+ settings = optparser.parse_args(['--strip-elements-with-class',
+ 'htmlonly'], settings)
+
+ self.make_file(infiles=[srcfile] + dependancies, outfile=manfile,
+ func=manwriter.write,
+ args=(reader.document,
+ io.FileOutput(destination_path=manfile,
+ encoding='ascii')),
+ exec_msg='generating %s' % manfile)
+
+build.sub_commands.append(('build_doc', None))
+
Distribution.pure = 0
Distribution.global_options.append(('pure', None, "use pure (slow) Python "
"code instead of C extensions"))
@@ -257,6 +408,7 @@ class hg_build_py(build_py):
cmdclass = {'install_data': install_package_data,
'build_mo': build_mo,
+ 'build_doc': build_doc,
'build_py': hg_build_py}
ext_modules=[
More information about the Mercurial-devel
mailing list