3
# $Id: buildhtml.py 5741 2008-12-01 07:21:03Z grubert $
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.parsers import rst
33
from docutils.readers import standalone, pep
34
from docutils.writers import html4css1, pep_html
37
usage = '%prog [options] [<directory> ...]'
38
description = ('Generates .html from all the reStructuredText .txt files '
39
'(including PEPs) in each <directory> '
40
'(default is the current directory).')
43
class SettingsSpec(docutils.SettingsSpec):
46
Runtime settings & command-line options for the front end.
49
# Can't be included in OptionParser below because we don't want to
50
# override the base class.
54
(('Recursively scan subdirectories for files to process. This is '
57
{'action': 'store_true', 'default': 1,
58
'validator': frontend.validate_boolean}),
59
('Do not scan subdirectories for files to process.',
60
['--local'], {'dest': 'recurse', 'action': 'store_false'}),
61
('Do not process files in <directory>. This option may be used '
62
'more than once to specify multiple directories.',
64
{'metavar': '<directory>', 'action': 'append',
65
'validator': frontend.validate_colon_separated_string_list}),
66
('Recursively ignore files or directories matching any of the given '
67
'wildcard (shell globbing) patterns (separated by colons). '
68
'Default: ".svn:CVS"',
70
{'metavar': '<patterns>', 'action': 'append',
71
'default': ['.svn', 'CVS'],
72
'validator': frontend.validate_colon_separated_string_list}),
73
('Work silently (no progress messages). Independent of "--quiet".',
75
{'action': 'store_true', 'validator': frontend.validate_boolean}),))
77
relative_path_settings = ('prune',)
78
config_section = 'buildhtml application'
79
config_section_dependencies = ('applications',)
82
class OptionParser(frontend.OptionParser):
85
Command-line option processing for the ``buildhtml.py`` front end.
88
def check_values(self, values, args):
89
frontend.OptionParser.check_values(self, values, args)
93
def check_args(self, args):
94
source = destination = None
96
self.values._directories = args
98
self.values._directories = [os.getcwd()]
99
return source, destination
104
"""Stores data attributes for dotted-attribute access."""
106
def __init__(self, **keywordargs):
107
self.__dict__.update(keywordargs)
114
'': Struct(components=(pep.Reader, rst.Parser, pep_html.Writer,
116
'.txt': Struct(components=(rst.Parser, standalone.Reader,
117
html4css1.Writer, SettingsSpec),
118
reader_name='standalone',
120
'PEPs': Struct(components=(rst.Parser, pep.Reader,
121
pep_html.Writer, SettingsSpec),
123
writer_name='pep_html')}
124
"""Publisher-specific settings. Key '' is for the front-end script
125
itself. ``self.publishers[''].components`` must contain a superset of
126
all components used by individual publishers."""
128
self.setup_publishers()
130
def setup_publishers(self):
132
Manage configurations for individual publishers.
134
Each publisher (combination of parser, reader, and writer) may have
135
its own configuration defaults, which must be kept separate from those
136
of the other publishers. Setting defaults are combined with the
137
config file settings and command-line options by
138
`self.get_settings()`.
140
for name, publisher in self.publishers.items():
141
option_parser = OptionParser(
142
components=publisher.components, read_config_files=1,
143
usage=usage, description=description)
144
publisher.option_parser = option_parser
145
publisher.setting_defaults = option_parser.get_default_values()
146
frontend.make_paths_absolute(publisher.setting_defaults.__dict__,
147
option_parser.relative_path_settings)
148
publisher.config_settings = (
149
option_parser.get_standard_config_settings())
150
self.settings_spec = self.publishers[''].option_parser.parse_args(
151
values=frontend.Values()) # no defaults; just the cmdline opts
152
self.initial_settings = self.get_settings('')
154
def get_settings(self, publisher_name, directory=None):
156
Return a settings object, from multiple sources.
158
Copy the setting defaults, overlay the startup config file settings,
159
then the local config file settings, then the command-line options.
160
Assumes the current directory has been set.
162
publisher = self.publishers[publisher_name]
163
settings = frontend.Values(publisher.setting_defaults.__dict__)
164
settings.update(publisher.config_settings, publisher.option_parser)
166
local_config = publisher.option_parser.get_config_file_settings(
167
os.path.join(directory, 'docutils.conf'))
168
frontend.make_paths_absolute(
169
local_config, publisher.option_parser.relative_path_settings,
171
settings.update(local_config, publisher.option_parser)
172
settings.update(self.settings_spec.__dict__, publisher.option_parser)
175
def run(self, directory=None, recurse=1):
176
recurse = recurse and self.initial_settings.recurse
178
self.directories = [directory]
179
elif self.settings_spec._directories:
180
self.directories = self.settings_spec._directories
182
self.directories = [os.getcwd()]
183
for directory in self.directories:
185
for root, dirs, files in os.walk(directory):
186
self.visit(recurse, root, dirs+files)
187
except (AttributeError): # python2.2 does not have os.walk
189
os.path.walk(directory, self.visit, recurse)
191
def visit(self, recurse, directory, names):
192
settings = self.get_settings('', directory)
193
if settings.prune and (os.path.abspath(directory) in settings.prune):
194
print >>sys.stderr, '/// ...Skipping directory (pruned):', directory
198
if not self.initial_settings.silent:
199
print >>sys.stderr, '/// Processing directory:', directory
201
# settings.ignore grows many duplicate entries as we recurse
202
# if we add patterns in config files or on the command line.
203
for pattern in utils.uniq(settings.ignore):
204
for i in range(len(names) - 1, -1, -1):
205
if fnmatch(names[i], pattern):
210
if name.endswith('.txt'):
211
prune = self.process_txt(directory, name)
217
def process_txt(self, directory, name):
218
if name.startswith('pep-'):
222
settings = self.get_settings(publisher, directory)
223
pub_struct = self.publishers[publisher]
224
if settings.prune and (directory in settings.prune):
226
settings._source = os.path.normpath(os.path.join(directory, name))
227
settings._destination = settings._source[:-4]+'.html'
228
if not self.initial_settings.silent:
229
print >>sys.stderr, ' ::: Processing:', name
232
core.publish_file(source_path=settings._source,
233
destination_path=settings._destination,
234
reader_name=pub_struct.reader_name,
235
parser_name='restructuredtext',
236
writer_name=pub_struct.writer_name,
238
except ApplicationError, error:
239
print >>sys.stderr, (' Error (%s): %s'
240
% (error.__class__.__name__, error))
243
if __name__ == "__main__":