viff.dk

view buildhtml.py @ 176:9e1de2a70022

Update copyright to 2010.
author Martin Geisler <mg@lazybytes.net>
date Mon Feb 01 10:04:59 2010 +0100 (2010-02-01)
parents a0207c736c67
children
line source
1 #!/usr/bin/env python
3 # $Id: buildhtml.py 5019 2007-03-12 21:48:30Z wiemann $
4 # Author: David Goodger <goodger@python.org>
5 # Copyright: This module has been placed in the public domain.
7 """
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.
12 """
13 # Once PySource is here, build .html from .py as well.
15 __docformat__ = 'reStructuredText'
18 try:
19 import locale
20 locale.setlocale(locale.LC_ALL, '')
21 except:
22 pass
24 import sys
25 import os
26 import os.path
27 import copy
28 from fnmatch import fnmatch
29 import docutils
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
36 # This enables the highlighted sourcecode directive:
37 import pygments_rst
39 usage = '%prog [options] [<directory> ...]'
40 description = ('Generates .html from all the reStructuredText .txt files '
41 '(including PEPs) in each <directory> '
42 '(default is the current directory).')
45 class SettingsSpec(docutils.SettingsSpec):
47 """
48 Runtime settings & command-line options for the front end.
49 """
51 # Can't be included in OptionParser below because we don't want to
52 # override the base class.
53 settings_spec = (
54 'Build-HTML Options',
55 None,
56 (('Recursively scan subdirectories for files to process. This is '
57 'the default.',
58 ['--recurse'],
59 {'action': 'store_true', 'default': 1,
60 'validator': frontend.validate_boolean}),
61 ('Do not scan subdirectories for files to process.',
62 ['--local'], {'dest': 'recurse', 'action': 'store_false'}),
63 ('Do not process files in <directory>. This option may be used '
64 'more than once to specify multiple directories.',
65 ['--prune'],
66 {'metavar': '<directory>', 'action': 'append',
67 'validator': frontend.validate_colon_separated_string_list}),
68 ('Recursively ignore files or directories matching any of the given '
69 'wildcard (shell globbing) patterns (separated by colons). '
70 'Default: ".svn:CVS"',
71 ['--ignore'],
72 {'metavar': '<patterns>', 'action': 'append',
73 'default': ['.svn', 'CVS'],
74 'validator': frontend.validate_colon_separated_string_list}),
75 ('Work silently (no progress messages). Independent of "--quiet".',
76 ['--silent'],
77 {'action': 'store_true', 'validator': frontend.validate_boolean}),))
79 relative_path_settings = ('prune',)
80 config_section = 'buildhtml application'
81 config_section_dependencies = ('applications',)
84 class OptionParser(frontend.OptionParser):
86 """
87 Command-line option processing for the ``buildhtml.py`` front end.
88 """
90 def check_values(self, values, args):
91 frontend.OptionParser.check_values(self, values, args)
92 values._source = None
93 return values
95 def check_args(self, args):
96 source = destination = None
97 if args:
98 self.values._directories = args
99 else:
100 self.values._directories = [os.getcwd()]
101 return source, destination
104 class Struct:
106 """Stores data attributes for dotted-attribute access."""
108 def __init__(self, **keywordargs):
109 self.__dict__.update(keywordargs)
112 class Builder:
114 def __init__(self):
115 self.publishers = {
116 '': Struct(components=(pep.Reader, rst.Parser, pep_html.Writer,
117 SettingsSpec)),
118 '.txt': Struct(components=(rst.Parser, standalone.Reader,
119 html4css1.Writer, SettingsSpec),
120 reader_name='standalone',
121 writer_name='html'),
122 'PEPs': Struct(components=(rst.Parser, pep.Reader,
123 pep_html.Writer, SettingsSpec),
124 reader_name='pep',
125 writer_name='pep_html')}
126 """Publisher-specific settings. Key '' is for the front-end script
127 itself. ``self.publishers[''].components`` must contain a superset of
128 all components used by individual publishers."""
130 self.setup_publishers()
132 def setup_publishers(self):
133 """
134 Manage configurations for individual publishers.
136 Each publisher (combination of parser, reader, and writer) may have
137 its own configuration defaults, which must be kept separate from those
138 of the other publishers. Setting defaults are combined with the
139 config file settings and command-line options by
140 `self.get_settings()`.
141 """
142 for name, publisher in self.publishers.items():
143 option_parser = OptionParser(
144 components=publisher.components, read_config_files=1,
145 usage=usage, description=description)
146 publisher.option_parser = option_parser
147 publisher.setting_defaults = option_parser.get_default_values()
148 frontend.make_paths_absolute(publisher.setting_defaults.__dict__,
149 option_parser.relative_path_settings)
150 publisher.config_settings = (
151 option_parser.get_standard_config_settings())
152 self.settings_spec = self.publishers[''].option_parser.parse_args(
153 values=frontend.Values()) # no defaults; just the cmdline opts
154 self.initial_settings = self.get_settings('')
156 def get_settings(self, publisher_name, directory=None):
157 """
158 Return a settings object, from multiple sources.
160 Copy the setting defaults, overlay the startup config file settings,
161 then the local config file settings, then the command-line options.
162 Assumes the current directory has been set.
163 """
164 publisher = self.publishers[publisher_name]
165 settings = frontend.Values(publisher.setting_defaults.__dict__)
166 settings.update(publisher.config_settings, publisher.option_parser)
167 if directory:
168 local_config = publisher.option_parser.get_config_file_settings(
169 os.path.join(directory, 'docutils.conf'))
170 frontend.make_paths_absolute(
171 local_config, publisher.option_parser.relative_path_settings,
172 directory)
173 settings.update(local_config, publisher.option_parser)
174 settings.update(self.settings_spec.__dict__, publisher.option_parser)
175 return settings
177 def run(self, directory=None, recurse=1):
178 recurse = recurse and self.initial_settings.recurse
179 if directory:
180 self.directories = [directory]
181 elif self.settings_spec._directories:
182 self.directories = self.settings_spec._directories
183 else:
184 self.directories = [os.getcwd()]
185 for directory in self.directories:
186 os.path.walk(directory, self.visit, recurse)
188 def visit(self, recurse, directory, names):
189 settings = self.get_settings('', directory)
190 if settings.prune and (os.path.abspath(directory) in settings.prune):
191 print >>sys.stderr, '/// ...Skipping directory (pruned):', directory
192 sys.stderr.flush()
193 names[:] = []
194 return
195 if not self.initial_settings.silent:
196 print >>sys.stderr, '/// Processing directory:', directory
197 sys.stderr.flush()
198 # settings.ignore grows many duplicate entries as we recurse
199 # if we add patterns in config files or on the command line.
200 for pattern in utils.uniq(settings.ignore):
201 for i in range(len(names) - 1, -1, -1):
202 if fnmatch(names[i], pattern):
203 # Modify in place!
204 del names[i]
205 prune = 0
206 for name in names:
207 if name.endswith('.txt'):
208 prune = self.process_txt(directory, name)
209 if prune:
210 break
211 if not recurse:
212 del names[:]
214 def process_txt(self, directory, name):
215 if name.startswith('pep-'):
216 publisher = 'PEPs'
217 else:
218 publisher = '.txt'
219 settings = self.get_settings(publisher, directory)
220 pub_struct = self.publishers[publisher]
221 if settings.prune and (directory in settings.prune):
222 return 1
223 settings._source = os.path.normpath(os.path.join(directory, name))
224 settings._destination = settings._source[:-4]+'.html'
225 if not self.initial_settings.silent:
226 print >>sys.stderr, ' ::: Processing:', name
227 sys.stderr.flush()
228 try:
229 core.publish_file(source_path=settings._source,
230 destination_path=settings._destination,
231 reader_name=pub_struct.reader_name,
232 parser_name='restructuredtext',
233 writer_name=pub_struct.writer_name,
234 settings=settings)
235 except ApplicationError, error:
236 print >>sys.stderr, (' Error (%s): %s'
237 % (error.__class__.__name__, error))
240 if __name__ == "__main__":
241 Builder().run()