1
|
1 |
#!/usr/bin/env python
|
|
2 |
|
|
3 |
# Author: David Goodger
|
|
4 |
# Contact: goodger@users.sourceforge.net
|
|
5 |
# Revision: $Revision: 1.1 $
|
|
6 |
# Date: $Date: 2009/02/05 23:03:30 $
|
|
7 |
# Copyright: This module has been placed in the public domain.
|
|
8 |
|
|
9 |
# Addapted by William Caban to look for .rst by default instead of .txt
|
|
10 |
# added docutils availability
|
|
11 |
|
|
12 |
"""
|
|
13 |
Generates .html from all the .rst files in a directory.
|
|
14 |
|
|
15 |
Ordinary .rst files are understood to be standalone reStructuredText.
|
|
16 |
Files named ``pep-*.rst`` are interpreted as reStructuredText PEPs.
|
|
17 |
"""
|
|
18 |
# Once PySource is here, build .html from .py as well.
|
|
19 |
|
|
20 |
__docformat__ = 'reStructuredText'
|
|
21 |
|
|
22 |
|
|
23 |
try:
|
|
24 |
import locale
|
|
25 |
locale.setlocale(locale.LC_ALL, '')
|
|
26 |
except:
|
|
27 |
pass
|
|
28 |
|
|
29 |
import sys
|
|
30 |
import os
|
|
31 |
import os.path
|
|
32 |
import copy
|
|
33 |
try:
|
|
34 |
import docutils
|
|
35 |
from docutils import ApplicationError
|
|
36 |
from docutils import core, frontend
|
|
37 |
from docutils.parsers import rst
|
|
38 |
from docutils.readers import standalone, pep
|
|
39 |
from docutils.writers import html4css1, pep_html
|
|
40 |
except:
|
|
41 |
print "################################################################"
|
|
42 |
print "# You need 'docutils' installed to execute this program. #"
|
|
43 |
print "# 'docutils' is available from http://docutils.sourceforge.net #"
|
|
44 |
print "################################################################"
|
|
45 |
sys.exit(1)
|
|
46 |
|
|
47 |
|
|
48 |
usage = '%prog [options] [<directory> ...]'
|
|
49 |
description = ('Generates .html from all the reStructuredText .rst files '
|
|
50 |
'(including PEPs) in each <directory> '
|
|
51 |
'(default is the current directory).')
|
|
52 |
|
|
53 |
|
|
54 |
class SettingsSpec(docutils.SettingsSpec):
|
|
55 |
|
|
56 |
"""
|
|
57 |
Runtime settings & command-line options for the front end.
|
|
58 |
"""
|
|
59 |
|
|
60 |
# Can't be included in OptionParser below because we don't want to
|
|
61 |
# override the base class.
|
|
62 |
settings_spec = (
|
|
63 |
'Build-HTML Options',
|
|
64 |
None,
|
|
65 |
(('Recursively scan subdirectories for files to process. This is '
|
|
66 |
'the default.',
|
|
67 |
['--recurse'],
|
|
68 |
{'action': 'store_true', 'default': 1,
|
|
69 |
'validator': frontend.validate_boolean}),
|
|
70 |
('Generate output files in the specified directory. The default is '
|
|
71 |
'to put them in the same directory of the input files.',
|
|
72 |
['--outpath'], {'metavar': '<directory>', 'type': 'string'}),
|
|
73 |
('Do not scan subdirectories for files to process.',
|
|
74 |
['--local'], {'dest': 'recurse', 'action': 'store_false'}),
|
|
75 |
('Do not process files in <directory>. This option may be used '
|
|
76 |
'more than once to specify multiple directories.',
|
|
77 |
['--prune'],
|
|
78 |
{'metavar': '<directory>', 'action': 'append',
|
|
79 |
'validator': frontend.validate_colon_separated_string_list}),
|
|
80 |
('Work silently (no progress messages). Independent of "--quiet".',
|
|
81 |
['--silent'],
|
|
82 |
{'action': 'store_true', 'validator': frontend.validate_boolean}),))
|
|
83 |
|
|
84 |
relative_path_settings = ('prune',)
|
|
85 |
config_section = 'buildhtml application'
|
|
86 |
config_section_dependencies = ('applications',)
|
|
87 |
|
|
88 |
|
|
89 |
class OptionParser(frontend.OptionParser):
|
|
90 |
|
|
91 |
"""
|
|
92 |
Command-line option processing for the ``buildhtml.py`` front end.
|
|
93 |
"""
|
|
94 |
|
|
95 |
def check_values(self, values, args):
|
|
96 |
frontend.OptionParser.check_values(self, values, args)
|
|
97 |
values._source = None
|
|
98 |
return values
|
|
99 |
|
|
100 |
def check_args(self, args):
|
|
101 |
source = destination = None
|
|
102 |
if args:
|
|
103 |
self.values._directories = args
|
|
104 |
else:
|
|
105 |
self.values._directories = [os.getcwd()]
|
|
106 |
return source, destination
|
|
107 |
|
|
108 |
|
|
109 |
class Struct:
|
|
110 |
|
|
111 |
"""Stores data attributes for dotted-attribute access."""
|
|
112 |
|
|
113 |
def __init__(self, **keywordargs):
|
|
114 |
self.__dict__.update(keywordargs)
|
|
115 |
|
|
116 |
|
|
117 |
class Builder:
|
|
118 |
|
|
119 |
def __init__(self):
|
|
120 |
self.publishers = {
|
|
121 |
'': Struct(components=(pep.Reader, rst.Parser, pep_html.Writer,
|
|
122 |
SettingsSpec)),
|
|
123 |
'.rst': Struct(components=(rst.Parser, standalone.Reader,
|
|
124 |
html4css1.Writer, SettingsSpec),
|
|
125 |
reader_name='standalone',
|
|
126 |
writer_name='html'),
|
|
127 |
'PEPs': Struct(components=(rst.Parser, pep.Reader,
|
|
128 |
pep_html.Writer, SettingsSpec),
|
|
129 |
reader_name='pep',
|
|
130 |
writer_name='pep_html')}
|
|
131 |
"""Publisher-specific settings. Key '' is for the front-end script
|
|
132 |
itself. ``self.publishers[''].components`` must contain a superset of
|
|
133 |
all components used by individual publishers."""
|
|
134 |
|
|
135 |
self.setup_publishers()
|
|
136 |
|
|
137 |
def setup_publishers(self):
|
|
138 |
"""
|
|
139 |
Manage configurations for individual publishers.
|
|
140 |
|
|
141 |
Each publisher (combination of parser, reader, and writer) may have
|
|
142 |
its own configuration defaults, which must be kept separate from those
|
|
143 |
of the other publishers. Setting defaults are combined with the
|
|
144 |
config file settings and command-line options by
|
|
145 |
`self.get_settings()`.
|
|
146 |
"""
|
|
147 |
for name, publisher in self.publishers.items():
|
|
148 |
option_parser = OptionParser(
|
|
149 |
components=publisher.components, read_config_files=1,
|
|
150 |
usage=usage, description=description)
|
|
151 |
publisher.option_parser = option_parser
|
|
152 |
publisher.setting_defaults = option_parser.get_default_values()
|
|
153 |
frontend.make_paths_absolute(publisher.setting_defaults.__dict__,
|
|
154 |
option_parser.relative_path_settings)
|
|
155 |
publisher.config_settings = (
|
|
156 |
option_parser.get_standard_config_settings())
|
|
157 |
self.settings_spec = self.publishers[''].option_parser.parse_args(
|
|
158 |
values=frontend.Values()) # no defaults; just the cmdline opts
|
|
159 |
self.initial_settings = self.get_settings('')
|
|
160 |
|
|
161 |
def get_settings(self, publisher_name, directory=None):
|
|
162 |
"""
|
|
163 |
Return a settings object, from multiple sources.
|
|
164 |
|
|
165 |
Copy the setting defaults, overlay the startup config file settings,
|
|
166 |
then the local config file settings, then the command-line options.
|
|
167 |
Assumes the current directory has been set.
|
|
168 |
"""
|
|
169 |
publisher = self.publishers[publisher_name]
|
|
170 |
settings = frontend.Values(publisher.setting_defaults.__dict__)
|
|
171 |
settings.update(publisher.config_settings, publisher.option_parser)
|
|
172 |
if directory:
|
|
173 |
local_config = publisher.option_parser.get_config_file_settings(
|
|
174 |
os.path.join(directory, 'docutils.conf'))
|
|
175 |
frontend.make_paths_absolute(
|
|
176 |
local_config, publisher.option_parser.relative_path_settings,
|
|
177 |
directory)
|
|
178 |
settings.update(local_config, publisher.option_parser)
|
|
179 |
settings.update(self.settings_spec.__dict__, publisher.option_parser)
|
|
180 |
return settings
|
|
181 |
|
|
182 |
def run(self, directory=None, recurse=1):
|
|
183 |
recurse = recurse and self.initial_settings.recurse
|
|
184 |
if directory:
|
|
185 |
self.directories = [directory]
|
|
186 |
elif self.settings_spec._directories:
|
|
187 |
self.directories = self.settings_spec._directories
|
|
188 |
else:
|
|
189 |
self.directories = [os.getcwd()]
|
|
190 |
for directory in self.directories:
|
|
191 |
os.path.walk(directory, self.visit, recurse)
|
|
192 |
|
|
193 |
def visit(self, recurse, directory, names):
|
|
194 |
settings = self.get_settings('', directory)
|
|
195 |
if settings.prune and (os.path.abspath(directory) in settings.prune):
|
|
196 |
print >>sys.stderr, '/// ...Skipping directory (pruned):', directory
|
|
197 |
sys.stderr.flush()
|
|
198 |
names[:] = []
|
|
199 |
return
|
|
200 |
if not self.initial_settings.silent:
|
|
201 |
print >>sys.stderr, '/// Processing directory:', directory
|
|
202 |
sys.stderr.flush()
|
|
203 |
prune = 0
|
|
204 |
for name in names:
|
|
205 |
if name.endswith('.rst'):
|
|
206 |
prune = self.process_rst(directory, name)
|
|
207 |
if prune:
|
|
208 |
break
|
|
209 |
if not recurse:
|
|
210 |
del names[:]
|
|
211 |
|
|
212 |
def process_rst(self, directory, name):
|
|
213 |
if name.startswith('pep-'):
|
|
214 |
publisher = 'PEPs'
|
|
215 |
else:
|
|
216 |
publisher = '.rst'
|
|
217 |
settings = self.get_settings(publisher, directory)
|
|
218 |
pub_struct = self.publishers[publisher]
|
|
219 |
if settings.prune and (directory in settings.prune):
|
|
220 |
return 1
|
|
221 |
settings._source = os.path.normpath(os.path.join(directory, name))
|
|
222 |
settings._destination = settings._source[:-4]+'.html'
|
|
223 |
if settings.outpath:
|
|
224 |
# FIXME: we should probably try and recreate the exising structure here,
|
|
225 |
# but that's more work than we need right now.
|
|
226 |
settings._destination = os.path.join(settings.outpath, os.path.basename(settings._destination))
|
|
227 |
if not self.initial_settings.silent:
|
|
228 |
print >>sys.stderr, ' ::: Processing:', name
|
|
229 |
sys.stderr.flush()
|
|
230 |
try:
|
|
231 |
core.publish_file(source_path=settings._source,
|
|
232 |
destination_path=settings._destination,
|
|
233 |
reader_name=pub_struct.reader_name,
|
|
234 |
parser_name='restructuredtext',
|
|
235 |
writer_name=pub_struct.writer_name,
|
|
236 |
settings=settings)
|
|
237 |
except ApplicationError, error:
|
|
238 |
print >>sys.stderr, (' Error (%s): %s'
|
|
239 |
% (error.__class__.__name__, error))
|
|
240 |
|
|
241 |
|
|
242 |
if __name__ == "__main__":
|
|
243 |
Builder().run()
|