You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
250 lines
9.7 KiB
250 lines
9.7 KiB
7 years ago
|
#!/usr/bin/env python
|
||
|
|
||
|
# $Id: buildhtml.py 7579 2012-12-31 10:40:14Z grubert $
|
||
|
# Author: David Goodger <goodger@python.org>
|
||
|
# Copyright: This module has been placed in the public domain.
|
||
|
|
||
|
"""
|
||
|
Generates .html from all the .txt files in a directory.
|
||
|
|
||
|
Ordinary .txt files are understood to be standalone reStructuredText.
|
||
|
Files named ``pep-*.txt`` are interpreted as reStructuredText PEPs.
|
||
|
"""
|
||
|
# Once PySource is here, build .html from .py as well.
|
||
|
|
||
|
__docformat__ = 'reStructuredText'
|
||
|
|
||
|
|
||
|
try:
|
||
|
import locale
|
||
|
locale.setlocale(locale.LC_ALL, '')
|
||
|
except:
|
||
|
pass
|
||
|
|
||
|
import sys
|
||
|
import os
|
||
|
import os.path
|
||
|
import copy
|
||
|
from fnmatch import fnmatch
|
||
|
import docutils
|
||
|
from docutils import ApplicationError
|
||
|
from docutils import core, frontend, utils
|
||
|
from docutils.utils.error_reporting import ErrorOutput, ErrorString
|
||
|
from docutils.parsers import rst
|
||
|
from docutils.readers import standalone, pep
|
||
|
from docutils.writers import html4css1, pep_html
|
||
|
|
||
|
|
||
|
usage = '%prog [options] [<directory> ...]'
|
||
|
description = ('Generates .html from all the reStructuredText .txt files '
|
||
|
'(including PEPs) in each <directory> '
|
||
|
'(default is the current directory).')
|
||
|
|
||
|
|
||
|
class SettingsSpec(docutils.SettingsSpec):
|
||
|
|
||
|
"""
|
||
|
Runtime settings & command-line options for the front end.
|
||
|
"""
|
||
|
|
||
|
prune_default = ['.hg', '.bzr', '.git', '.svn', 'CVS']
|
||
|
|
||
|
# Can't be included in OptionParser below because we don't want to
|
||
|
# override the base class.
|
||
|
settings_spec = (
|
||
|
'Build-HTML Options',
|
||
|
None,
|
||
|
(('Recursively scan subdirectories for files to process. This is '
|
||
|
'the default.',
|
||
|
['--recurse'],
|
||
|
{'action': 'store_true', 'default': 1,
|
||
|
'validator': frontend.validate_boolean}),
|
||
|
('Do not scan subdirectories for files to process.',
|
||
|
['--local'], {'dest': 'recurse', 'action': 'store_false'}),
|
||
|
('Do not process files in <directory> (shell globbing patterns, '
|
||
|
'separated by colons). This option may be used '
|
||
|
'more than once to specify multiple directories. Default: "%s".'
|
||
|
% ':'.join(prune_default),
|
||
|
['--prune'],
|
||
|
{'metavar': '<directory>', 'action': 'append',
|
||
|
'validator': frontend.validate_colon_separated_string_list,
|
||
|
'default': prune_default,}),
|
||
|
('Recursively ignore files matching any of the given '
|
||
|
'wildcard (shell globbing) patterns (separated by colons).',
|
||
|
['--ignore'],
|
||
|
{'metavar': '<patterns>', 'action': 'append',
|
||
|
'default': [],
|
||
|
'validator': frontend.validate_colon_separated_string_list}),
|
||
|
('Work silently (no progress messages). Independent of "--quiet".',
|
||
|
['--silent'],
|
||
|
{'action': 'store_true', 'validator': frontend.validate_boolean}),
|
||
|
('Do not process files, show files that would be processed.',
|
||
|
['--dry-run'],
|
||
|
{'action': 'store_true', 'validator': frontend.validate_boolean}),))
|
||
|
|
||
|
relative_path_settings = ('prune',)
|
||
|
config_section = 'buildhtml application'
|
||
|
config_section_dependencies = ('applications',)
|
||
|
|
||
|
|
||
|
class OptionParser(frontend.OptionParser):
|
||
|
|
||
|
"""
|
||
|
Command-line option processing for the ``buildhtml.py`` front end.
|
||
|
"""
|
||
|
|
||
|
def check_values(self, values, args):
|
||
|
frontend.OptionParser.check_values(self, values, args)
|
||
|
values._source = None
|
||
|
return values
|
||
|
|
||
|
def check_args(self, args):
|
||
|
source = destination = None
|
||
|
if args:
|
||
|
self.values._directories = args
|
||
|
else:
|
||
|
self.values._directories = [os.getcwd()]
|
||
|
return source, destination
|
||
|
|
||
|
|
||
|
class Struct:
|
||
|
|
||
|
"""Stores data attributes for dotted-attribute access."""
|
||
|
|
||
|
def __init__(self, **keywordargs):
|
||
|
self.__dict__.update(keywordargs)
|
||
|
|
||
|
|
||
|
class Builder:
|
||
|
|
||
|
def __init__(self):
|
||
|
self.publishers = {
|
||
|
'': Struct(components=(pep.Reader, rst.Parser, pep_html.Writer,
|
||
|
SettingsSpec)),
|
||
|
'.txt': Struct(components=(rst.Parser, standalone.Reader,
|
||
|
html4css1.Writer, SettingsSpec),
|
||
|
reader_name='standalone',
|
||
|
writer_name='html'),
|
||
|
'PEPs': Struct(components=(rst.Parser, pep.Reader,
|
||
|
pep_html.Writer, SettingsSpec),
|
||
|
reader_name='pep',
|
||
|
writer_name='pep_html')}
|
||
|
"""Publisher-specific settings. Key '' is for the front-end script
|
||
|
itself. ``self.publishers[''].components`` must contain a superset of
|
||
|
all components used by individual publishers."""
|
||
|
|
||
|
self.setup_publishers()
|
||
|
|
||
|
def setup_publishers(self):
|
||
|
"""
|
||
|
Manage configurations for individual publishers.
|
||
|
|
||
|
Each publisher (combination of parser, reader, and writer) may have
|
||
|
its own configuration defaults, which must be kept separate from those
|
||
|
of the other publishers. Setting defaults are combined with the
|
||
|
config file settings and command-line options by
|
||
|
`self.get_settings()`.
|
||
|
"""
|
||
|
for name, publisher in self.publishers.items():
|
||
|
option_parser = OptionParser(
|
||
|
components=publisher.components, read_config_files=1,
|
||
|
usage=usage, description=description)
|
||
|
publisher.option_parser = option_parser
|
||
|
publisher.setting_defaults = option_parser.get_default_values()
|
||
|
frontend.make_paths_absolute(publisher.setting_defaults.__dict__,
|
||
|
option_parser.relative_path_settings)
|
||
|
publisher.config_settings = (
|
||
|
option_parser.get_standard_config_settings())
|
||
|
self.settings_spec = self.publishers[''].option_parser.parse_args(
|
||
|
values=frontend.Values()) # no defaults; just the cmdline opts
|
||
|
self.initial_settings = self.get_settings('')
|
||
|
|
||
|
def get_settings(self, publisher_name, directory=None):
|
||
|
"""
|
||
|
Return a settings object, from multiple sources.
|
||
|
|
||
|
Copy the setting defaults, overlay the startup config file settings,
|
||
|
then the local config file settings, then the command-line options.
|
||
|
Assumes the current directory has been set.
|
||
|
"""
|
||
|
publisher = self.publishers[publisher_name]
|
||
|
settings = frontend.Values(publisher.setting_defaults.__dict__)
|
||
|
settings.update(publisher.config_settings, publisher.option_parser)
|
||
|
if directory:
|
||
|
local_config = publisher.option_parser.get_config_file_settings(
|
||
|
os.path.join(directory, 'docutils.conf'))
|
||
|
frontend.make_paths_absolute(
|
||
|
local_config, publisher.option_parser.relative_path_settings,
|
||
|
directory)
|
||
|
settings.update(local_config, publisher.option_parser)
|
||
|
settings.update(self.settings_spec.__dict__, publisher.option_parser)
|
||
|
return settings
|
||
|
|
||
|
def run(self, directory=None, recurse=1):
|
||
|
recurse = recurse and self.initial_settings.recurse
|
||
|
if directory:
|
||
|
self.directories = [directory]
|
||
|
elif self.settings_spec._directories:
|
||
|
self.directories = self.settings_spec._directories
|
||
|
else:
|
||
|
self.directories = [os.getcwd()]
|
||
|
for directory in self.directories:
|
||
|
for root, dirs, files in os.walk(directory):
|
||
|
# os.walk by default this recurses down the tree,
|
||
|
# influence by modifying dirs.
|
||
|
if not recurse:
|
||
|
del dirs[:]
|
||
|
self.visit(root, files, dirs)
|
||
|
|
||
|
def visit(self, directory, names, subdirectories):
|
||
|
settings = self.get_settings('', directory)
|
||
|
errout = ErrorOutput(encoding=settings.error_encoding)
|
||
|
if settings.prune and (os.path.abspath(directory) in settings.prune):
|
||
|
errout.write('/// ...Skipping directory (pruned): %s\n' %
|
||
|
directory)
|
||
|
sys.stderr.flush()
|
||
|
del subdirectories[:]
|
||
|
return
|
||
|
if not self.initial_settings.silent:
|
||
|
errout.write('/// Processing directory: %s\n' % directory)
|
||
|
sys.stderr.flush()
|
||
|
# settings.ignore grows many duplicate entries as we recurse
|
||
|
# if we add patterns in config files or on the command line.
|
||
|
for pattern in utils.uniq(settings.ignore):
|
||
|
for i in range(len(names) - 1, -1, -1):
|
||
|
if fnmatch(names[i], pattern):
|
||
|
# Modify in place!
|
||
|
del names[i]
|
||
|
for name in names:
|
||
|
if name.endswith('.txt'):
|
||
|
self.process_txt(directory, name)
|
||
|
|
||
|
def process_txt(self, directory, name):
|
||
|
if name.startswith('pep-'):
|
||
|
publisher = 'PEPs'
|
||
|
else:
|
||
|
publisher = '.txt'
|
||
|
settings = self.get_settings(publisher, directory)
|
||
|
errout = ErrorOutput(encoding=settings.error_encoding)
|
||
|
pub_struct = self.publishers[publisher]
|
||
|
settings._source = os.path.normpath(os.path.join(directory, name))
|
||
|
settings._destination = settings._source[:-4]+'.html'
|
||
|
if not self.initial_settings.silent:
|
||
|
errout.write(' ::: Processing: %s\n' % name)
|
||
|
sys.stderr.flush()
|
||
|
try:
|
||
|
if not settings.dry_run:
|
||
|
core.publish_file(source_path=settings._source,
|
||
|
destination_path=settings._destination,
|
||
|
reader_name=pub_struct.reader_name,
|
||
|
parser_name='restructuredtext',
|
||
|
writer_name=pub_struct.writer_name,
|
||
|
settings=settings)
|
||
|
except ApplicationError:
|
||
|
error = sys.exc_info()[1] # get exception in Python <2.6 and 3.x
|
||
|
errout.write(' %s\n' % ErrorString(error))
|
||
|
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
Builder().run()
|