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
:
184 os
.path
.walk(directory
, self
.visit
, recurse
)
186 def visit(self
, recurse
, directory
, names
):
187 settings
= self
.get_settings('', directory
)
188 if settings
.prune
and (os
.path
.abspath(directory
) in settings
.prune
):
189 print >>sys
.stderr
, '/// ...Skipping directory (pruned):', directory
193 if not self
.initial_settings
.silent
:
194 print >>sys
.stderr
, '/// Processing directory:', directory
196 # settings.ignore grows many duplicate entries as we recurse
197 # if we add patterns in config files or on the command line.
198 for pattern
in utils
.uniq(settings
.ignore
):
199 for i
in range(len(names
) - 1, -1, -1):
200 if fnmatch(names
[i
], pattern
):
205 if name
.endswith('.txt'):
206 prune
= self
.process_txt(directory
, name
)
212 def process_txt(self
, directory
, name
):
213 if name
.startswith('pep-'):
217 settings
= self
.get_settings(publisher
, directory
)
218 pub_struct
= self
.publishers
[publisher
]
219 if settings
.prune
and (directory
in settings
.prune
):
221 settings
._source
= os
.path
.normpath(os
.path
.join(directory
, name
))
222 settings
._destination
= settings
._source
[:-4]+'.html'
223 if not self
.initial_settings
.silent
:
224 print >>sys
.stderr
, ' ::: Processing:', name
227 core
.publish_file(source_path
=settings
._source
,
228 destination_path
=settings
._destination
,
229 reader_name
=pub_struct
.reader_name
,
230 parser_name
='restructuredtext',
231 writer_name
=pub_struct
.writer_name
,
233 except ApplicationError
, error
:
234 print >>sys
.stderr
, (' Error (%s): %s'
235 % (error
.__class
__.__name
__, error
))
238 if __name__
== "__main__":