Add: some doc to PreLoadUnicodePage.
[docutils.git] / tools / buildhtml.py
blobe9ee0d16d566961890bfde55d2f7a2457beffab5
1 #!/usr/bin/env python
3 # Author: David Goodger
4 # Contact: goodger@users.sourceforge.net
5 # Revision: $Revision$
6 # Date: $Date$
7 # Copyright: This module has been placed in the public domain.
9 """
10 Generates .html from all the .txt files in a directory.
12 Ordinary .txt files are understood to be standalone reStructuredText.
13 Files named ``pep-*.txt`` are interpreted as reStructuredText PEPs.
14 """
15 # Once PySource is here, build .html from .py as well.
17 __docformat__ = 'reStructuredText'
20 try:
21 import locale
22 locale.setlocale(locale.LC_ALL, '')
23 except:
24 pass
26 import sys
27 import os
28 import os.path
29 import copy
30 import docutils
31 from docutils import ApplicationError
32 from docutils import core, frontend
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):
46 """
47 Runtime settings & command-line options for the front end.
48 """
50 # Can't be included in OptionParser below because we don't want to
51 # override the base class.
52 settings_spec = (
53 'Build-HTML Options',
54 None,
55 (('Recursively scan subdirectories for files to process. This is '
56 'the default.',
57 ['--recurse'],
58 {'action': 'store_true', 'default': 1,
59 'validator': frontend.validate_boolean}),
60 ('Do not scan subdirectories for files to process.',
61 ['--local'], {'dest': 'recurse', 'action': 'store_false'}),
62 ('Do not process files in <directory>. This option may be used '
63 'more than once to specify multiple directories.',
64 ['--prune'],
65 {'metavar': '<directory>', 'action': 'append',
66 'validator': frontend.validate_colon_separated_string_list}),
67 ('Work silently (no progress messages). Independent of "--quiet".',
68 ['--silent'],
69 {'action': 'store_true', 'validator': frontend.validate_boolean}),))
71 relative_path_settings = ('prune',)
72 config_section = 'buildhtml application'
73 config_section_dependencies = ('applications',)
76 class OptionParser(frontend.OptionParser):
78 """
79 Command-line option processing for the ``buildhtml.py`` front end.
80 """
82 def check_values(self, values, args):
83 frontend.OptionParser.check_values(self, values, args)
84 values._source = None
85 return values
87 def check_args(self, args):
88 source = destination = None
89 if args:
90 self.values._directories = args
91 else:
92 self.values._directories = [os.getcwd()]
93 return source, destination
96 class Struct:
98 """Stores data attributes for dotted-attribute access."""
100 def __init__(self, **keywordargs):
101 self.__dict__.update(keywordargs)
104 class Builder:
106 def __init__(self):
107 self.publishers = {
108 '': Struct(components=(pep.Reader, rst.Parser, pep_html.Writer,
109 SettingsSpec)),
110 '.txt': Struct(components=(rst.Parser, standalone.Reader,
111 html4css1.Writer, SettingsSpec),
112 reader_name='standalone',
113 writer_name='html'),
114 'PEPs': Struct(components=(rst.Parser, pep.Reader,
115 pep_html.Writer, SettingsSpec),
116 reader_name='pep',
117 writer_name='pep_html')}
118 """Publisher-specific settings. Key '' is for the front-end script
119 itself. ``self.publishers[''].components`` must contain a superset of
120 all components used by individual publishers."""
122 self.setup_publishers()
124 def setup_publishers(self):
126 Manage configurations for individual publishers.
128 Each publisher (combination of parser, reader, and writer) may have
129 its own configuration defaults, which must be kept separate from those
130 of the other publishers. Setting defaults are combined with the
131 config file settings and command-line options by
132 `self.get_settings()`.
134 for name, publisher in self.publishers.items():
135 option_parser = OptionParser(
136 components=publisher.components, read_config_files=1,
137 usage=usage, description=description)
138 publisher.option_parser = option_parser
139 publisher.setting_defaults = option_parser.get_default_values()
140 frontend.make_paths_absolute(publisher.setting_defaults.__dict__,
141 option_parser.relative_path_settings)
142 publisher.config_settings = (
143 option_parser.get_standard_config_settings())
144 self.settings_spec = self.publishers[''].option_parser.parse_args(
145 values=frontend.Values()) # no defaults; just the cmdline opts
146 self.initial_settings = self.get_settings('')
148 def get_settings(self, publisher_name, directory=None):
150 Return a settings object, from multiple sources.
152 Copy the setting defaults, overlay the startup config file settings,
153 then the local config file settings, then the command-line options.
154 Assumes the current directory has been set.
156 publisher = self.publishers[publisher_name]
157 settings = frontend.Values(publisher.setting_defaults.__dict__)
158 settings.update(publisher.config_settings, publisher.option_parser)
159 if directory:
160 local_config = publisher.option_parser.get_config_file_settings(
161 os.path.join(directory, 'docutils.conf'))
162 frontend.make_paths_absolute(
163 local_config, publisher.option_parser.relative_path_settings,
164 directory)
165 settings.update(local_config, publisher.option_parser)
166 settings.update(self.settings_spec.__dict__, publisher.option_parser)
167 return settings
169 def run(self, directory=None, recurse=1):
170 recurse = recurse and self.initial_settings.recurse
171 if directory:
172 self.directories = [directory]
173 elif self.settings_spec._directories:
174 self.directories = self.settings_spec._directories
175 else:
176 self.directories = [os.getcwd()]
177 for directory in self.directories:
178 os.path.walk(directory, self.visit, recurse)
180 def visit(self, recurse, directory, names):
181 settings = self.get_settings('', directory)
182 if settings.prune and (os.path.abspath(directory) in settings.prune):
183 print >>sys.stderr, '/// ...Skipping directory (pruned):', directory
184 sys.stderr.flush()
185 names[:] = []
186 return
187 if not self.initial_settings.silent:
188 print >>sys.stderr, '/// Processing directory:', directory
189 sys.stderr.flush()
190 prune = 0
191 for name in names:
192 if name.endswith('.txt'):
193 prune = self.process_txt(directory, name)
194 if prune:
195 break
196 if not recurse:
197 del names[:]
199 def process_txt(self, directory, name):
200 if name.startswith('pep-'):
201 publisher = 'PEPs'
202 else:
203 publisher = '.txt'
204 settings = self.get_settings(publisher, directory)
205 pub_struct = self.publishers[publisher]
206 if settings.prune and (directory in settings.prune):
207 return 1
208 settings._source = os.path.normpath(os.path.join(directory, name))
209 settings._destination = settings._source[:-4]+'.html'
210 if not self.initial_settings.silent:
211 print >>sys.stderr, ' ::: Processing:', name
212 sys.stderr.flush()
213 try:
214 core.publish_file(source_path=settings._source,
215 destination_path=settings._destination,
216 reader_name=pub_struct.reader_name,
217 parser_name='restructuredtext',
218 writer_name=pub_struct.writer_name,
219 settings=settings)
220 except ApplicationError, error:
221 print >>sys.stderr, (' Error (%s): %s'
222 % (error.__class__.__name__, error))
225 if __name__ == "__main__":
226 Builder().run()