3
# $Id: buildhtml.py 7464 2012-06-25 13:16:03Z milde $
4
# Author: David Goodger <goodger@python.org>
5
# Copyright: This module has been placed in the public domain.
8
Generates .html from all the .txt files in a directory.
10
Ordinary .txt files are understood to be standalone reStructuredText.
11
Files named ``pep-*.txt`` are interpreted as reStructuredText PEPs.
13
# Once PySource is here, build .html from .py as well.
15
__docformat__ = 'reStructuredText'
20
locale.setlocale(locale.LC_ALL, '')
28
from fnmatch import fnmatch
30
from docutils import ApplicationError
31
from docutils import core, frontend, utils
32
from docutils.utils.error_reporting import ErrorOutput, ErrorString
33
from docutils.parsers import rst
34
from docutils.readers import standalone, pep
35
from docutils.writers import html4css1, pep_html
38
usage = '%prog [options] [<directory> ...]'
39
description = ('Generates .html from all the reStructuredText .txt files '
40
'(including PEPs) in each <directory> '
41
'(default is the current directory).')
44
class SettingsSpec(docutils.SettingsSpec):
47
Runtime settings & command-line options for the front end.
50
prune_default = ['.hg', '.bzr', '.git', '.svn', 'CVS']
52
# Can't be included in OptionParser below because we don't want to
53
# override the base class.
57
(('Recursively scan subdirectories for files to process. This is '
60
{'action': 'store_true', 'default': 1,
61
'validator': frontend.validate_boolean}),
62
('Do not scan subdirectories for files to process.',
63
['--local'], {'dest': 'recurse', 'action': 'store_false'}),
64
('Do not process files in <directory> (shell globbing patterns, '
65
'separated by colons). This option may be used '
66
'more than once to specify multiple directories. Default: "%s".'
67
% ':'.join(prune_default),
69
{'metavar': '<directory>', 'action': 'append',
70
'validator': frontend.validate_colon_separated_string_list,
71
'default': prune_default,}),
72
('Recursively ignore files matching any of the given '
73
'wildcard (shell globbing) patterns (separated by colons).',
75
{'metavar': '<patterns>', 'action': 'append',
77
'validator': frontend.validate_colon_separated_string_list}),
78
('Work silently (no progress messages). Independent of "--quiet".',
80
{'action': 'store_true', 'validator': frontend.validate_boolean}),
81
('Do not process files, show files that would be processed.',
83
{'action': 'store_true', 'validator': frontend.validate_boolean}),))
85
relative_path_settings = ('prune',)
86
config_section = 'buildhtml application'
87
config_section_dependencies = ('applications',)
90
class OptionParser(frontend.OptionParser):
93
Command-line option processing for the ``buildhtml.py`` front end.
96
def check_values(self, values, args):
97
frontend.OptionParser.check_values(self, values, args)
101
def check_args(self, args):
102
source = destination = None
104
self.values._directories = args
106
self.values._directories = [os.getcwd()]
107
return source, destination
112
"""Stores data attributes for dotted-attribute access."""
114
def __init__(self, **keywordargs):
115
self.__dict__.update(keywordargs)
122
'': Struct(components=(pep.Reader, rst.Parser, pep_html.Writer,
124
'.txt': Struct(components=(rst.Parser, standalone.Reader,
125
html4css1.Writer, SettingsSpec),
126
reader_name='standalone',
128
'PEPs': Struct(components=(rst.Parser, pep.Reader,
129
pep_html.Writer, SettingsSpec),
131
writer_name='pep_html')}
132
"""Publisher-specific settings. Key '' is for the front-end script
133
itself. ``self.publishers[''].components`` must contain a superset of
134
all components used by individual publishers."""
136
self.setup_publishers()
138
def setup_publishers(self):
140
Manage configurations for individual publishers.
142
Each publisher (combination of parser, reader, and writer) may have
143
its own configuration defaults, which must be kept separate from those
144
of the other publishers. Setting defaults are combined with the
145
config file settings and command-line options by
146
`self.get_settings()`.
148
for name, publisher in self.publishers.items():
149
option_parser = OptionParser(
150
components=publisher.components, read_config_files=1,
151
usage=usage, description=description)
152
publisher.option_parser = option_parser
153
publisher.setting_defaults = option_parser.get_default_values()
154
frontend.make_paths_absolute(publisher.setting_defaults.__dict__,
155
option_parser.relative_path_settings)
156
publisher.config_settings = (
157
option_parser.get_standard_config_settings())
158
self.settings_spec = self.publishers[''].option_parser.parse_args(
159
values=frontend.Values()) # no defaults; just the cmdline opts
160
self.initial_settings = self.get_settings('')
162
def get_settings(self, publisher_name, directory=None):
164
Return a settings object, from multiple sources.
166
Copy the setting defaults, overlay the startup config file settings,
167
then the local config file settings, then the command-line options.
168
Assumes the current directory has been set.
170
publisher = self.publishers[publisher_name]
171
settings = frontend.Values(publisher.setting_defaults.__dict__)
172
settings.update(publisher.config_settings, publisher.option_parser)
174
local_config = publisher.option_parser.get_config_file_settings(
175
os.path.join(directory, 'docutils.conf'))
176
frontend.make_paths_absolute(
177
local_config, publisher.option_parser.relative_path_settings,
179
settings.update(local_config, publisher.option_parser)
180
settings.update(self.settings_spec.__dict__, publisher.option_parser)
183
def run(self, directory=None, recurse=1):
184
recurse = recurse and self.initial_settings.recurse
186
self.directories = [directory]
187
elif self.settings_spec._directories:
188
self.directories = self.settings_spec._directories
190
self.directories = [os.getcwd()]
191
for directory in self.directories:
192
for root, dirs, files in os.walk(directory):
193
# os.walk by default this recurses down the tree,
194
# influence by modifying dirs.
197
self.visit(root, files, dirs)
199
def visit(self, directory, names, subdirectories):
200
settings = self.get_settings('', directory)
201
errout = ErrorOutput(encoding=settings.error_encoding)
202
if settings.prune and (os.path.abspath(directory) in settings.prune):
203
errout.write('/// ...Skipping directory (pruned): %s\n' %
206
del subdirectories[:]
208
if not self.initial_settings.silent:
209
errout.write('/// Processing directory: %s' % directory)
211
# settings.ignore grows many duplicate entries as we recurse
212
# if we add patterns in config files or on the command line.
213
for pattern in utils.uniq(settings.ignore):
214
for i in range(len(names) - 1, -1, -1):
215
if fnmatch(names[i], pattern):
219
if name.endswith('.txt'):
220
self.process_txt(directory, name)
222
def process_txt(self, directory, name):
223
if name.startswith('pep-'):
227
settings = self.get_settings(publisher, directory)
228
errout = ErrorOutput(encoding=settings.error_encoding)
229
pub_struct = self.publishers[publisher]
230
settings._source = os.path.normpath(os.path.join(directory, name))
231
settings._destination = settings._source[:-4]+'.html'
232
if not self.initial_settings.silent:
233
errout.write(' ::: Processing: %s\n' % name)
236
if not settings.dry_run:
237
core.publish_file(source_path=settings._source,
238
destination_path=settings._destination,
239
reader_name=pub_struct.reader_name,
240
parser_name='restructuredtext',
241
writer_name=pub_struct.writer_name,
243
except ApplicationError:
244
error = sys.exc_info()[1] # get exception in Python <2.6 and 3.x
245
errout.write(' %s\n' % ErrorString(error))
248
if __name__ == "__main__":