| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249 |
- #!/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()
|