added Python version info
[docutils.git] / docutils / docutils / frontend.py
blob5c1da2f85094207f6f83d8f4163a7032e40f4358
1 # $Id$
2 # Author: David Goodger <goodger@python.org>
3 # Copyright: This module has been placed in the public domain.
5 """
6 Command-line and common processing for Docutils front-end tools.
8 Exports the following classes:
10 * `OptionParser`: Standard Docutils command-line processing.
11 * `Option`: Customized version of `optparse.Option`; validation support.
12 * `Values`: Runtime settings; objects are simple structs
13 (``object.attribute``). Supports cumulative list settings (attributes).
14 * `ConfigParser`: Standard Docutils config file processing.
16 Also exports the following functions:
18 * Option callbacks: `store_multiple`, `read_config_file`.
19 * Setting validators: `validate_encoding`,
20 `validate_encoding_error_handler`,
21 `validate_encoding_and_error_handler`, `validate_boolean`,
22 `validate_threshold`, `validate_colon_separated_string_list`,
23 `validate_dependency_file`.
24 * `make_paths_absolute`.
25 """
27 __docformat__ = 'reStructuredText'
29 import os
30 import os.path
31 import sys
32 import types
33 import warnings
34 import ConfigParser as CP
35 import codecs
36 import docutils
37 import docutils.utils
38 import docutils.nodes
39 try:
40 import optparse
41 from optparse import SUPPRESS_HELP
42 except ImportError:
43 import optik as optparse
44 from optik import SUPPRESS_HELP
47 def store_multiple(option, opt, value, parser, *args, **kwargs):
48 """
49 Store multiple values in `parser.values`. (Option callback.)
51 Store `None` for each attribute named in `args`, and store the value for
52 each key (attribute name) in `kwargs`.
53 """
54 for attribute in args:
55 setattr(parser.values, attribute, None)
56 for key, value in kwargs.items():
57 setattr(parser.values, key, value)
59 def read_config_file(option, opt, value, parser):
60 """
61 Read a configuration file during option processing. (Option callback.)
62 """
63 try:
64 new_settings = parser.get_config_file_settings(value)
65 except ValueError, error:
66 parser.error(error)
67 parser.values.update(new_settings, parser)
69 def validate_encoding(setting, value, option_parser,
70 config_parser=None, config_section=None):
71 try:
72 codecs.lookup(value)
73 except LookupError:
74 raise (LookupError('setting "%s": unknown encoding: "%s"'
75 % (setting, value)),
76 None, sys.exc_info()[2])
77 return value
79 def validate_encoding_error_handler(setting, value, option_parser,
80 config_parser=None, config_section=None):
81 try:
82 codecs.lookup_error(value)
83 except AttributeError: # prior to Python 2.3
84 if value not in ('strict', 'ignore', 'replace', 'xmlcharrefreplace'):
85 raise (LookupError(
86 'unknown encoding error handler: "%s" (choices: '
87 '"strict", "ignore", "replace", or "xmlcharrefreplace")' % value),
88 None, sys.exc_info()[2])
89 except LookupError:
90 raise (LookupError(
91 'unknown encoding error handler: "%s" (choices: '
92 '"strict", "ignore", "replace", "backslashreplace", '
93 '"xmlcharrefreplace", and possibly others; see documentation for '
94 'the Python ``codecs`` module)' % value),
95 None, sys.exc_info()[2])
96 return value
98 def validate_encoding_and_error_handler(
99 setting, value, option_parser, config_parser=None, config_section=None):
101 Side-effect: if an error handler is included in the value, it is inserted
102 into the appropriate place as if it was a separate setting/option.
104 if ':' in value:
105 encoding, handler = value.split(':')
106 validate_encoding_error_handler(
107 setting + '_error_handler', handler, option_parser,
108 config_parser, config_section)
109 if config_parser:
110 config_parser.set(config_section, setting + '_error_handler',
111 handler)
112 else:
113 setattr(option_parser.values, setting + '_error_handler', handler)
114 else:
115 encoding = value
116 validate_encoding(setting, encoding, option_parser,
117 config_parser, config_section)
118 return encoding
120 def validate_boolean(setting, value, option_parser,
121 config_parser=None, config_section=None):
122 if isinstance(value, types.UnicodeType):
123 try:
124 return option_parser.booleans[value.strip().lower()]
125 except KeyError:
126 raise (LookupError('unknown boolean value: "%s"' % value),
127 None, sys.exc_info()[2])
128 return value
130 def validate_nonnegative_int(setting, value, option_parser,
131 config_parser=None, config_section=None):
132 value = int(value)
133 if value < 0:
134 raise ValueError('negative value; must be positive or zero')
135 return value
137 def validate_threshold(setting, value, option_parser,
138 config_parser=None, config_section=None):
139 try:
140 return int(value)
141 except ValueError:
142 try:
143 return option_parser.thresholds[value.lower()]
144 except (KeyError, AttributeError):
145 raise (LookupError('unknown threshold: %r.' % value),
146 None, sys.exc_info[2])
148 def validate_colon_separated_string_list(
149 setting, value, option_parser, config_parser=None, config_section=None):
150 if isinstance(value, types.UnicodeType):
151 value = value.split(':')
152 else:
153 last = value.pop()
154 value.extend(last.split(':'))
155 return value
157 def validate_url_trailing_slash(
158 setting, value, option_parser, config_parser=None, config_section=None):
159 if not value:
160 return './'
161 elif value.endswith('/'):
162 return value
163 else:
164 return value + '/'
166 def validate_dependency_file(setting, value, option_parser,
167 config_parser=None, config_section=None):
168 try:
169 return docutils.utils.DependencyList(value)
170 except IOError:
171 return docutils.utils.DependencyList(None)
173 def validate_strip_class(setting, value, option_parser,
174 config_parser=None, config_section=None):
175 if config_parser: # validate all values
176 class_values = value
177 else: # just validate the latest value
178 class_values = [value[-1]]
179 for class_value in class_values:
180 normalized = docutils.nodes.make_id(class_value)
181 if class_value != normalized:
182 raise ValueError('invalid class value %r (perhaps %r?)'
183 % (class_value, normalized))
184 return value
186 def make_paths_absolute(pathdict, keys, base_path=None):
188 Interpret filesystem path settings relative to the `base_path` given.
190 Paths are values in `pathdict` whose keys are in `keys`. Get `keys` from
191 `OptionParser.relative_path_settings`.
193 if base_path is None:
194 base_path = os.getcwd()
195 for key in keys:
196 if pathdict.has_key(key):
197 value = pathdict[key]
198 if isinstance(value, types.ListType):
199 value = [make_one_path_absolute(base_path, path)
200 for path in value]
201 elif value:
202 value = make_one_path_absolute(base_path, value)
203 pathdict[key] = value
205 def make_one_path_absolute(base_path, path):
206 return os.path.abspath(os.path.join(base_path, path))
209 class Values(optparse.Values):
212 Updates list attributes by extension rather than by replacement.
213 Works in conjunction with the `OptionParser.lists` instance attribute.
216 def __init__(self, *args, **kwargs):
217 optparse.Values.__init__(self, *args, **kwargs)
218 if (not hasattr(self, 'record_dependencies')
219 or self.record_dependencies is None):
220 # Set up dependency list, in case it is needed.
221 self.record_dependencies = docutils.utils.DependencyList()
223 def update(self, other_dict, option_parser):
224 if isinstance(other_dict, Values):
225 other_dict = other_dict.__dict__
226 other_dict = other_dict.copy()
227 for setting in option_parser.lists.keys():
228 if (hasattr(self, setting) and other_dict.has_key(setting)):
229 value = getattr(self, setting)
230 if value:
231 value += other_dict[setting]
232 del other_dict[setting]
233 self._update_loose(other_dict)
235 def copy(self):
236 """Return a shallow copy of `self`."""
237 return self.__class__(defaults=self.__dict__)
240 class Option(optparse.Option):
242 ATTRS = optparse.Option.ATTRS + ['validator', 'overrides']
244 def process(self, opt, value, values, parser):
246 Call the validator function on applicable settings and
247 evaluate the 'overrides' option.
248 Extends `optparse.Option.process`.
250 result = optparse.Option.process(self, opt, value, values, parser)
251 setting = self.dest
252 if setting:
253 if self.validator:
254 value = getattr(values, setting)
255 try:
256 new_value = self.validator(setting, value, parser)
257 except Exception, error:
258 raise (optparse.OptionValueError(
259 'Error in option "%s":\n %s: %s'
260 % (opt, error.__class__.__name__, error)),
261 None, sys.exc_info()[2])
262 setattr(values, setting, new_value)
263 if self.overrides:
264 setattr(values, self.overrides, None)
265 return result
268 class OptionParser(optparse.OptionParser, docutils.SettingsSpec):
271 Parser for command-line and library use. The `settings_spec`
272 specification here and in other Docutils components are merged to build
273 the set of command-line options and runtime settings for this process.
275 Common settings (defined below) and component-specific settings must not
276 conflict. Short options are reserved for common settings, and components
277 are restrict to using long options.
280 standard_config_files = [
281 '/etc/docutils.conf', # system-wide
282 './docutils.conf', # project-specific
283 '~/.docutils'] # user-specific
284 """Docutils configuration files, using ConfigParser syntax. Filenames
285 will be tilde-expanded later. Later files override earlier ones."""
287 threshold_choices = 'info 1 warning 2 error 3 severe 4 none 5'.split()
288 """Possible inputs for for --report and --halt threshold values."""
290 thresholds = {'info': 1, 'warning': 2, 'error': 3, 'severe': 4, 'none': 5}
291 """Lookup table for --report and --halt threshold values."""
293 booleans={'1': 1, 'on': 1, 'yes': 1, 'true': 1,
294 '0': 0, 'off': 0, 'no': 0, 'false': 0, '': 0}
295 """Lookup table for boolean configuration file settings."""
297 if hasattr(codecs, 'backslashreplace_errors'):
298 default_error_encoding_error_handler = 'backslashreplace'
299 else:
300 default_error_encoding_error_handler = 'replace'
302 settings_spec = (
303 'General Docutils Options',
304 None,
305 (('Specify the document title as metadata.',
306 ['--title'], {}),
307 ('Include a "Generated by Docutils" credit and link.',
308 ['--generator', '-g'], {'action': 'store_true',
309 'validator': validate_boolean}),
310 ('Do not include a generator credit.',
311 ['--no-generator'], {'action': 'store_false', 'dest': 'generator'}),
312 ('Include the date at the end of the document (UTC).',
313 ['--date', '-d'], {'action': 'store_const', 'const': '%Y-%m-%d',
314 'dest': 'datestamp'}),
315 ('Include the time & date (UTC).',
316 ['--time', '-t'], {'action': 'store_const',
317 'const': '%Y-%m-%d %H:%M UTC',
318 'dest': 'datestamp'}),
319 ('Do not include a datestamp of any kind.',
320 ['--no-datestamp'], {'action': 'store_const', 'const': None,
321 'dest': 'datestamp'}),
322 ('Include a "View document source" link.',
323 ['--source-link', '-s'], {'action': 'store_true',
324 'validator': validate_boolean}),
325 ('Use <URL> for a source link; implies --source-link.',
326 ['--source-url'], {'metavar': '<URL>'}),
327 ('Do not include a "View document source" link.',
328 ['--no-source-link'],
329 {'action': 'callback', 'callback': store_multiple,
330 'callback_args': ('source_link', 'source_url')}),
331 ('Link from section headers to TOC entries. (default)',
332 ['--toc-entry-backlinks'],
333 {'dest': 'toc_backlinks', 'action': 'store_const', 'const': 'entry',
334 'default': 'entry'}),
335 ('Link from section headers to the top of the TOC.',
336 ['--toc-top-backlinks'],
337 {'dest': 'toc_backlinks', 'action': 'store_const', 'const': 'top'}),
338 ('Disable backlinks to the table of contents.',
339 ['--no-toc-backlinks'],
340 {'dest': 'toc_backlinks', 'action': 'store_false'}),
341 ('Link from footnotes/citations to references. (default)',
342 ['--footnote-backlinks'],
343 {'action': 'store_true', 'default': 1,
344 'validator': validate_boolean}),
345 ('Disable backlinks from footnotes and citations.',
346 ['--no-footnote-backlinks'],
347 {'dest': 'footnote_backlinks', 'action': 'store_false'}),
348 ('Enable section numbering. (default)',
349 ['--section-numbering'],
350 {'action': 'store_true', 'dest': 'sectnum_xform',
351 'default': 1, 'validator': validate_boolean}),
352 ('Disable section numbering.',
353 ['--no-section-numbering'],
354 {'action': 'store_false', 'dest': 'sectnum_xform'}),
355 ('Remove comment elements from the document tree.',
356 ['--strip-comments'],
357 {'action': 'store_true', 'validator': validate_boolean}),
358 ('Leave comment elements in the document tree. (default)',
359 ['--leave-comments'],
360 {'action': 'store_false', 'dest': 'strip_comments'}),
361 ('Remove all elements with classes="<class>" from the document tree. '
362 '(Multiple-use option.)',
363 ['--strip-elements-with-class'],
364 {'action': 'append', 'dest': 'strip_elements_with_classes',
365 'metavar': '<class>', 'validator': validate_strip_class}),
366 ('Remove all classes="<class>" attributes from elements in the '
367 'document tree. (Multiple-use option.)',
368 ['--strip-class'],
369 {'action': 'append', 'dest': 'strip_classes',
370 'metavar': '<class>', 'validator': validate_strip_class}),
371 ('Report system messages at or higher than <level>: "info" or "1", '
372 '"warning"/"2" (default), "error"/"3", "severe"/"4", "none"/"5"',
373 ['--report', '-r'], {'choices': threshold_choices, 'default': 2,
374 'dest': 'report_level', 'metavar': '<level>',
375 'validator': validate_threshold}),
376 ('Report all system messages. (Same as "--report=1".)',
377 ['--verbose', '-v'], {'action': 'store_const', 'const': 1,
378 'dest': 'report_level'}),
379 ('Report no system messages. (Same as "--report=5".)',
380 ['--quiet', '-q'], {'action': 'store_const', 'const': 5,
381 'dest': 'report_level'}),
382 ('Halt execution at system messages at or above <level>. '
383 'Levels as in --report. Default: 4 (severe).',
384 ['--halt'], {'choices': threshold_choices, 'dest': 'halt_level',
385 'default': 4, 'metavar': '<level>',
386 'validator': validate_threshold}),
387 ('Halt at the slightest problem. Same as "--halt=info".',
388 ['--strict'], {'action': 'store_const', 'const': 'info',
389 'dest': 'halt_level'}),
390 ('Enable a non-zero exit status for non-halting system messages at '
391 'or above <level>. Default: 5 (disabled).',
392 ['--exit-status'], {'choices': threshold_choices,
393 'dest': 'exit_status_level',
394 'default': 5, 'metavar': '<level>',
395 'validator': validate_threshold}),
396 ('Enable debug-level system messages and diagnostics.',
397 ['--debug'], {'action': 'store_true', 'validator': validate_boolean}),
398 ('Disable debug output. (default)',
399 ['--no-debug'], {'action': 'store_false', 'dest': 'debug'}),
400 ('Send the output of system messages to <file>.',
401 ['--warnings'], {'dest': 'warning_stream', 'metavar': '<file>'}),
402 ('Enable Python tracebacks when Docutils is halted.',
403 ['--traceback'], {'action': 'store_true', 'default': None,
404 'validator': validate_boolean}),
405 ('Disable Python tracebacks. (default)',
406 ['--no-traceback'], {'dest': 'traceback', 'action': 'store_false'}),
407 ('Specify the encoding and optionally the '
408 'error handler of input text. Default: <locale-dependent>:strict.',
409 ['--input-encoding', '-i'],
410 {'metavar': '<name[:handler]>',
411 'validator': validate_encoding_and_error_handler}),
412 ('Specify the error handler for undecodable characters. '
413 'Choices: "strict" (default), "ignore", and "replace".',
414 ['--input-encoding-error-handler'],
415 {'default': 'strict', 'validator': validate_encoding_error_handler}),
416 ('Specify the text encoding and optionally the error handler for '
417 'output. Default: UTF-8:strict.',
418 ['--output-encoding', '-o'],
419 {'metavar': '<name[:handler]>', 'default': 'utf-8',
420 'validator': validate_encoding_and_error_handler}),
421 ('Specify error handler for unencodable output characters; '
422 '"strict" (default), "ignore", "replace", '
423 '"xmlcharrefreplace", "backslashreplace" (Python 2.3+).',
424 ['--output-encoding-error-handler'],
425 {'default': 'strict', 'validator': validate_encoding_error_handler}),
426 ('Specify text encoding and error handler for error output. '
427 'Default: ASCII:%s.'
428 % default_error_encoding_error_handler,
429 ['--error-encoding', '-e'],
430 {'metavar': '<name[:handler]>', 'default': 'ascii',
431 'validator': validate_encoding_and_error_handler}),
432 ('Specify the error handler for unencodable characters in '
433 'error output. Default: %s.'
434 % default_error_encoding_error_handler,
435 ['--error-encoding-error-handler'],
436 {'default': default_error_encoding_error_handler,
437 'validator': validate_encoding_error_handler}),
438 ('Specify the language (as 2-letter code). Default: en.',
439 ['--language', '-l'], {'dest': 'language_code', 'default': 'en',
440 'metavar': '<name>'}),
441 ('Write output file dependencies to <file>.',
442 ['--record-dependencies'],
443 {'metavar': '<file>', 'validator': validate_dependency_file,
444 'default': None}), # default set in Values class
445 ('Read configuration settings from <file>, if it exists.',
446 ['--config'], {'metavar': '<file>', 'type': 'string',
447 'action': 'callback', 'callback': read_config_file}),
448 ("Show this program's version number and exit.",
449 ['--version', '-V'], {'action': 'version'}),
450 ('Show this help message and exit.',
451 ['--help', '-h'], {'action': 'help'}),
452 # Typically not useful for non-programmatical use:
453 (SUPPRESS_HELP, ['--id-prefix'], {'default': ''}),
454 (SUPPRESS_HELP, ['--auto-id-prefix'], {'default': 'id'}),
455 # Hidden options, for development use only:
456 (SUPPRESS_HELP, ['--dump-settings'], {'action': 'store_true'}),
457 (SUPPRESS_HELP, ['--dump-internals'], {'action': 'store_true'}),
458 (SUPPRESS_HELP, ['--dump-transforms'], {'action': 'store_true'}),
459 (SUPPRESS_HELP, ['--dump-pseudo-xml'], {'action': 'store_true'}),
460 (SUPPRESS_HELP, ['--expose-internal-attribute'],
461 {'action': 'append', 'dest': 'expose_internals',
462 'validator': validate_colon_separated_string_list}),
463 (SUPPRESS_HELP, ['--strict-visitor'], {'action': 'store_true'}),
465 """Runtime settings and command-line options common to all Docutils front
466 ends. Setting specs specific to individual Docutils components are also
467 used (see `populate_from_components()`)."""
469 settings_defaults = {'_disable_config': None,
470 '_source': None,
471 '_destination': None,
472 '_config_files': None}
473 """Defaults for settings that don't have command-line option equivalents."""
475 relative_path_settings = ('warning_stream',)
477 config_section = 'general'
479 version_template = ('%%prog (Docutils %s [%s], Python %s, on %s)'
480 % (docutils.__version__, docutils.__version_details__,
481 sys.version.split()[0], sys.platform))
482 """Default version message."""
484 def __init__(self, components=(), defaults=None, read_config_files=None,
485 *args, **kwargs):
487 `components` is a list of Docutils components each containing a
488 ``.settings_spec`` attribute. `defaults` is a mapping of setting
489 default overrides.
492 self.lists = {}
493 """Set of list-type settings."""
495 self.config_files = []
496 """List of paths of applied configuration files."""
498 optparse.OptionParser.__init__(
499 self, option_class=Option, add_help_option=None,
500 formatter=optparse.TitledHelpFormatter(width=78),
501 *args, **kwargs)
502 if not self.version:
503 self.version = self.version_template
504 # Make an instance copy (it will be modified):
505 self.relative_path_settings = list(self.relative_path_settings)
506 self.components = (self,) + tuple(components)
507 self.populate_from_components(self.components)
508 self.set_defaults_from_dict(defaults or {})
509 if read_config_files and not self.defaults['_disable_config']:
510 try:
511 config_settings = self.get_standard_config_settings()
512 except ValueError, error:
513 self.error(error)
514 self.set_defaults_from_dict(config_settings.__dict__)
516 def populate_from_components(self, components):
518 For each component, first populate from the `SettingsSpec.settings_spec`
519 structure, then from the `SettingsSpec.settings_defaults` dictionary.
520 After all components have been processed, check for and populate from
521 each component's `SettingsSpec.settings_default_overrides` dictionary.
523 for component in components:
524 if component is None:
525 continue
526 settings_spec = component.settings_spec
527 self.relative_path_settings.extend(
528 component.relative_path_settings)
529 for i in range(0, len(settings_spec), 3):
530 title, description, option_spec = settings_spec[i:i+3]
531 if title:
532 group = optparse.OptionGroup(self, title, description)
533 self.add_option_group(group)
534 else:
535 group = self # single options
536 for (help_text, option_strings, kwargs) in option_spec:
537 option = group.add_option(help=help_text, *option_strings,
538 **kwargs)
539 if kwargs.get('action') == 'append':
540 self.lists[option.dest] = 1
541 if component.settings_defaults:
542 self.defaults.update(component.settings_defaults)
543 for component in components:
544 if component and component.settings_default_overrides:
545 self.defaults.update(component.settings_default_overrides)
547 def get_standard_config_files(self):
548 """Return list of config files, from environment or standard."""
549 try:
550 config_files = os.environ['DOCUTILSCONFIG'].split(os.pathsep)
551 except KeyError:
552 config_files = self.standard_config_files
554 # If 'HOME' is not set, expandvars() requires the 'pwd' module which is
555 # not available under certain environments, for example, within
556 # mod_python. The publisher ends up in here, and we need to publish
557 # from within mod_python. Therefore we need to avoid expanding when we
558 # are in those environments.
559 expand = os.path.expanduser
560 if 'HOME' not in os.environ:
561 try:
562 import pwd
563 except ImportError:
564 expand = lambda x: x
565 return [expand(f) for f in config_files if f.strip()]
567 def get_standard_config_settings(self):
568 settings = Values()
569 for filename in self.get_standard_config_files():
570 settings.update(self.get_config_file_settings(filename), self)
571 return settings
573 def get_config_file_settings(self, config_file):
574 """Returns a dictionary containing appropriate config file settings."""
575 parser = ConfigParser()
576 parser.read(config_file, self)
577 self.config_files.extend(parser._files)
578 base_path = os.path.dirname(config_file)
579 applied = {}
580 settings = Values()
581 for component in self.components:
582 if not component:
583 continue
584 for section in (tuple(component.config_section_dependencies or ())
585 + (component.config_section,)):
586 if applied.has_key(section):
587 continue
588 applied[section] = 1
589 settings.update(parser.get_section(section), self)
590 make_paths_absolute(
591 settings.__dict__, self.relative_path_settings, base_path)
592 return settings.__dict__
594 def check_values(self, values, args):
595 """Store positional arguments as runtime settings."""
596 values._source, values._destination = self.check_args(args)
597 make_paths_absolute(values.__dict__, self.relative_path_settings,
598 os.getcwd())
599 values._config_files = self.config_files
600 return values
602 def check_args(self, args):
603 source = destination = None
604 if args:
605 source = args.pop(0)
606 if source == '-': # means stdin
607 source = None
608 if args:
609 destination = args.pop(0)
610 if destination == '-': # means stdout
611 destination = None
612 if args:
613 self.error('Maximum 2 arguments allowed.')
614 if source and source == destination:
615 self.error('Do not specify the same file for both source and '
616 'destination. It will clobber the source file.')
617 return source, destination
619 def set_defaults_from_dict(self, defaults):
620 self.defaults.update(defaults)
622 def get_default_values(self):
623 """Needed to get custom `Values` instances."""
624 defaults = Values(self.defaults)
625 defaults._config_files = self.config_files
626 return defaults
628 def get_option_by_dest(self, dest):
630 Get an option by its dest.
632 If you're supplying a dest which is shared by several options,
633 it is undefined which option of those is returned.
635 A KeyError is raised if there is no option with the supplied
636 dest.
638 for group in self.option_groups + [self]:
639 for option in group.option_list:
640 if option.dest == dest:
641 return option
642 raise KeyError('No option with dest == %r.' % dest)
645 class ConfigParser(CP.ConfigParser):
647 old_settings = {
648 'pep_stylesheet': ('pep_html writer', 'stylesheet'),
649 'pep_stylesheet_path': ('pep_html writer', 'stylesheet_path'),
650 'pep_template': ('pep_html writer', 'template')}
651 """{old setting: (new section, new setting)} mapping, used by
652 `handle_old_config`, to convert settings from the old [options] section."""
654 old_warning = """
655 The "[option]" section is deprecated. Support for old-format configuration
656 files may be removed in a future Docutils release. Please revise your
657 configuration files. See <http://docutils.sf.net/docs/user/config.html>,
658 section "Old-Format Configuration Files".
661 not_utf8_error = """\
662 Unable to read configuration file "%s": content not encoded as UTF-8.
663 Skipping "%s" configuration file.
666 def __init__(self, *args, **kwargs):
667 CP.ConfigParser.__init__(self, *args, **kwargs)
669 self._files = []
670 """List of paths of configuration files read."""
672 def read(self, filenames, option_parser):
673 if type(filenames) in (types.StringType, types.UnicodeType):
674 filenames = [filenames]
675 for filename in filenames:
676 try:
677 # Config files must be UTF-8-encoded:
678 fp = codecs.open(filename, 'r', 'utf-8')
679 except IOError:
680 continue
681 try:
682 CP.ConfigParser.readfp(self, fp, filename)
683 except UnicodeDecodeError:
684 sys.stderr.write(self.not_utf8_error % (filename, filename))
685 fp.close()
686 continue
687 fp.close()
688 self._files.append(filename)
689 if self.has_section('options'):
690 self.handle_old_config(filename)
691 self.validate_settings(filename, option_parser)
693 def handle_old_config(self, filename):
694 warnings.warn_explicit(self.old_warning, ConfigDeprecationWarning,
695 filename, 0)
696 options = self.get_section('options')
697 if not self.has_section('general'):
698 self.add_section('general')
699 for key, value in options.items():
700 if self.old_settings.has_key(key):
701 section, setting = self.old_settings[key]
702 if not self.has_section(section):
703 self.add_section(section)
704 else:
705 section = 'general'
706 setting = key
707 if not self.has_option(section, setting):
708 self.set(section, setting, value)
709 self.remove_section('options')
711 def validate_settings(self, filename, option_parser):
713 Call the validator function and implement overrides on all applicable
714 settings.
716 for section in self.sections():
717 for setting in self.options(section):
718 try:
719 option = option_parser.get_option_by_dest(setting)
720 except KeyError:
721 continue
722 if option.validator:
723 value = self.get(section, setting, raw=1)
724 try:
725 new_value = option.validator(
726 setting, value, option_parser,
727 config_parser=self, config_section=section)
728 except Exception, error:
729 raise (ValueError(
730 'Error in config file "%s", section "[%s]":\n'
731 ' %s: %s\n %s = %s'
732 % (filename, section, error.__class__.__name__,
733 error, setting, value)), None, sys.exc_info()[2])
734 self.set(section, setting, new_value)
735 if option.overrides:
736 self.set(section, option.overrides, None)
738 def optionxform(self, optionstr):
740 Transform '-' to '_' so the cmdline form of option names can be used.
742 return optionstr.lower().replace('-', '_')
744 def get_section(self, section):
746 Return a given section as a dictionary (empty if the section
747 doesn't exist).
749 section_dict = {}
750 if self.has_section(section):
751 for option in self.options(section):
752 section_dict[option] = self.get(section, option, raw=1)
753 return section_dict
756 class ConfigDeprecationWarning(DeprecationWarning):
757 """Warning for deprecated configuration file features."""