Release 0.5: set version number to 0.6
[docutils.git] / docutils / frontend.py
blob40ba901034650fc7391deb97ab73a62c4928d9ee
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 'Warning: potentially dangerous; use with caution. '
363 '(Multiple-use option.)',
364 ['--strip-elements-with-class'],
365 {'action': 'append', 'dest': 'strip_elements_with_classes',
366 'metavar': '<class>', 'validator': validate_strip_class}),
367 ('Remove all classes="<class>" attributes from elements in the '
368 'document tree. Warning: potentially dangerous; use with caution. '
369 '(Multiple-use option.)',
370 ['--strip-class'],
371 {'action': 'append', 'dest': 'strip_classes',
372 'metavar': '<class>', 'validator': validate_strip_class}),
373 ('Report system messages at or higher than <level>: "info" or "1", '
374 '"warning"/"2" (default), "error"/"3", "severe"/"4", "none"/"5"',
375 ['--report', '-r'], {'choices': threshold_choices, 'default': 2,
376 'dest': 'report_level', 'metavar': '<level>',
377 'validator': validate_threshold}),
378 ('Report all system messages. (Same as "--report=1".)',
379 ['--verbose', '-v'], {'action': 'store_const', 'const': 1,
380 'dest': 'report_level'}),
381 ('Report no system messages. (Same as "--report=5".)',
382 ['--quiet', '-q'], {'action': 'store_const', 'const': 5,
383 'dest': 'report_level'}),
384 ('Halt execution at system messages at or above <level>. '
385 'Levels as in --report. Default: 4 (severe).',
386 ['--halt'], {'choices': threshold_choices, 'dest': 'halt_level',
387 'default': 4, 'metavar': '<level>',
388 'validator': validate_threshold}),
389 ('Halt at the slightest problem. Same as "--halt=info".',
390 ['--strict'], {'action': 'store_const', 'const': 'info',
391 'dest': 'halt_level'}),
392 ('Enable a non-zero exit status for non-halting system messages at '
393 'or above <level>. Default: 5 (disabled).',
394 ['--exit-status'], {'choices': threshold_choices,
395 'dest': 'exit_status_level',
396 'default': 5, 'metavar': '<level>',
397 'validator': validate_threshold}),
398 ('Enable debug-level system messages and diagnostics.',
399 ['--debug'], {'action': 'store_true', 'validator': validate_boolean}),
400 ('Disable debug output. (default)',
401 ['--no-debug'], {'action': 'store_false', 'dest': 'debug'}),
402 ('Send the output of system messages to <file>.',
403 ['--warnings'], {'dest': 'warning_stream', 'metavar': '<file>'}),
404 ('Enable Python tracebacks when Docutils is halted.',
405 ['--traceback'], {'action': 'store_true', 'default': None,
406 'validator': validate_boolean}),
407 ('Disable Python tracebacks. (default)',
408 ['--no-traceback'], {'dest': 'traceback', 'action': 'store_false'}),
409 ('Specify the encoding and optionally the '
410 'error handler of input text. Default: <locale-dependent>:strict.',
411 ['--input-encoding', '-i'],
412 {'metavar': '<name[:handler]>',
413 'validator': validate_encoding_and_error_handler}),
414 ('Specify the error handler for undecodable characters. '
415 'Choices: "strict" (default), "ignore", and "replace".',
416 ['--input-encoding-error-handler'],
417 {'default': 'strict', 'validator': validate_encoding_error_handler}),
418 ('Specify the text encoding and optionally the error handler for '
419 'output. Default: UTF-8:strict.',
420 ['--output-encoding', '-o'],
421 {'metavar': '<name[:handler]>', 'default': 'utf-8',
422 'validator': validate_encoding_and_error_handler}),
423 ('Specify error handler for unencodable output characters; '
424 '"strict" (default), "ignore", "replace", '
425 '"xmlcharrefreplace", "backslashreplace" (Python 2.3+).',
426 ['--output-encoding-error-handler'],
427 {'default': 'strict', 'validator': validate_encoding_error_handler}),
428 ('Specify text encoding and error handler for error output. '
429 'Default: ASCII:%s.'
430 % default_error_encoding_error_handler,
431 ['--error-encoding', '-e'],
432 {'metavar': '<name[:handler]>', 'default': 'ascii',
433 'validator': validate_encoding_and_error_handler}),
434 ('Specify the error handler for unencodable characters in '
435 'error output. Default: %s.'
436 % default_error_encoding_error_handler,
437 ['--error-encoding-error-handler'],
438 {'default': default_error_encoding_error_handler,
439 'validator': validate_encoding_error_handler}),
440 ('Specify the language (as 2-letter code). Default: en.',
441 ['--language', '-l'], {'dest': 'language_code', 'default': 'en',
442 'metavar': '<name>'}),
443 ('Write output file dependencies to <file>.',
444 ['--record-dependencies'],
445 {'metavar': '<file>', 'validator': validate_dependency_file,
446 'default': None}), # default set in Values class
447 ('Read configuration settings from <file>, if it exists.',
448 ['--config'], {'metavar': '<file>', 'type': 'string',
449 'action': 'callback', 'callback': read_config_file}),
450 ("Show this program's version number and exit.",
451 ['--version', '-V'], {'action': 'version'}),
452 ('Show this help message and exit.',
453 ['--help', '-h'], {'action': 'help'}),
454 # Typically not useful for non-programmatical use:
455 (SUPPRESS_HELP, ['--id-prefix'], {'default': ''}),
456 (SUPPRESS_HELP, ['--auto-id-prefix'], {'default': 'id'}),
457 # Hidden options, for development use only:
458 (SUPPRESS_HELP, ['--dump-settings'], {'action': 'store_true'}),
459 (SUPPRESS_HELP, ['--dump-internals'], {'action': 'store_true'}),
460 (SUPPRESS_HELP, ['--dump-transforms'], {'action': 'store_true'}),
461 (SUPPRESS_HELP, ['--dump-pseudo-xml'], {'action': 'store_true'}),
462 (SUPPRESS_HELP, ['--expose-internal-attribute'],
463 {'action': 'append', 'dest': 'expose_internals',
464 'validator': validate_colon_separated_string_list}),
465 (SUPPRESS_HELP, ['--strict-visitor'], {'action': 'store_true'}),
467 """Runtime settings and command-line options common to all Docutils front
468 ends. Setting specs specific to individual Docutils components are also
469 used (see `populate_from_components()`)."""
471 settings_defaults = {'_disable_config': None,
472 '_source': None,
473 '_destination': None,
474 '_config_files': None}
475 """Defaults for settings that don't have command-line option equivalents."""
477 relative_path_settings = ('warning_stream',)
479 config_section = 'general'
481 version_template = ('%%prog (Docutils %s [%s], Python %s, on %s)'
482 % (docutils.__version__, docutils.__version_details__,
483 sys.version.split()[0], sys.platform))
484 """Default version message."""
486 def __init__(self, components=(), defaults=None, read_config_files=None,
487 *args, **kwargs):
489 `components` is a list of Docutils components each containing a
490 ``.settings_spec`` attribute. `defaults` is a mapping of setting
491 default overrides.
494 self.lists = {}
495 """Set of list-type settings."""
497 self.config_files = []
498 """List of paths of applied configuration files."""
500 optparse.OptionParser.__init__(
501 self, option_class=Option, add_help_option=None,
502 formatter=optparse.TitledHelpFormatter(width=78),
503 *args, **kwargs)
504 if not self.version:
505 self.version = self.version_template
506 # Make an instance copy (it will be modified):
507 self.relative_path_settings = list(self.relative_path_settings)
508 self.components = (self,) + tuple(components)
509 self.populate_from_components(self.components)
510 self.set_defaults_from_dict(defaults or {})
511 if read_config_files and not self.defaults['_disable_config']:
512 try:
513 config_settings = self.get_standard_config_settings()
514 except ValueError, error:
515 self.error(error)
516 self.set_defaults_from_dict(config_settings.__dict__)
518 def populate_from_components(self, components):
520 For each component, first populate from the `SettingsSpec.settings_spec`
521 structure, then from the `SettingsSpec.settings_defaults` dictionary.
522 After all components have been processed, check for and populate from
523 each component's `SettingsSpec.settings_default_overrides` dictionary.
525 for component in components:
526 if component is None:
527 continue
528 settings_spec = component.settings_spec
529 self.relative_path_settings.extend(
530 component.relative_path_settings)
531 for i in range(0, len(settings_spec), 3):
532 title, description, option_spec = settings_spec[i:i+3]
533 if title:
534 group = optparse.OptionGroup(self, title, description)
535 self.add_option_group(group)
536 else:
537 group = self # single options
538 for (help_text, option_strings, kwargs) in option_spec:
539 option = group.add_option(help=help_text, *option_strings,
540 **kwargs)
541 if kwargs.get('action') == 'append':
542 self.lists[option.dest] = 1
543 if component.settings_defaults:
544 self.defaults.update(component.settings_defaults)
545 for component in components:
546 if component and component.settings_default_overrides:
547 self.defaults.update(component.settings_default_overrides)
549 def get_standard_config_files(self):
550 """Return list of config files, from environment or standard."""
551 try:
552 config_files = os.environ['DOCUTILSCONFIG'].split(os.pathsep)
553 except KeyError:
554 config_files = self.standard_config_files
556 # If 'HOME' is not set, expandvars() requires the 'pwd' module which is
557 # not available under certain environments, for example, within
558 # mod_python. The publisher ends up in here, and we need to publish
559 # from within mod_python. Therefore we need to avoid expanding when we
560 # are in those environments.
561 expand = os.path.expanduser
562 if 'HOME' not in os.environ:
563 try:
564 import pwd
565 except ImportError:
566 expand = lambda x: x
567 return [expand(f) for f in config_files if f.strip()]
569 def get_standard_config_settings(self):
570 settings = Values()
571 for filename in self.get_standard_config_files():
572 settings.update(self.get_config_file_settings(filename), self)
573 return settings
575 def get_config_file_settings(self, config_file):
576 """Returns a dictionary containing appropriate config file settings."""
577 parser = ConfigParser()
578 parser.read(config_file, self)
579 self.config_files.extend(parser._files)
580 base_path = os.path.dirname(config_file)
581 applied = {}
582 settings = Values()
583 for component in self.components:
584 if not component:
585 continue
586 for section in (tuple(component.config_section_dependencies or ())
587 + (component.config_section,)):
588 if applied.has_key(section):
589 continue
590 applied[section] = 1
591 settings.update(parser.get_section(section), self)
592 make_paths_absolute(
593 settings.__dict__, self.relative_path_settings, base_path)
594 return settings.__dict__
596 def check_values(self, values, args):
597 """Store positional arguments as runtime settings."""
598 values._source, values._destination = self.check_args(args)
599 make_paths_absolute(values.__dict__, self.relative_path_settings,
600 os.getcwd())
601 values._config_files = self.config_files
602 return values
604 def check_args(self, args):
605 source = destination = None
606 if args:
607 source = args.pop(0)
608 if source == '-': # means stdin
609 source = None
610 if args:
611 destination = args.pop(0)
612 if destination == '-': # means stdout
613 destination = None
614 if args:
615 self.error('Maximum 2 arguments allowed.')
616 if source and source == destination:
617 self.error('Do not specify the same file for both source and '
618 'destination. It will clobber the source file.')
619 return source, destination
621 def set_defaults_from_dict(self, defaults):
622 self.defaults.update(defaults)
624 def get_default_values(self):
625 """Needed to get custom `Values` instances."""
626 defaults = Values(self.defaults)
627 defaults._config_files = self.config_files
628 return defaults
630 def get_option_by_dest(self, dest):
632 Get an option by its dest.
634 If you're supplying a dest which is shared by several options,
635 it is undefined which option of those is returned.
637 A KeyError is raised if there is no option with the supplied
638 dest.
640 for group in self.option_groups + [self]:
641 for option in group.option_list:
642 if option.dest == dest:
643 return option
644 raise KeyError('No option with dest == %r.' % dest)
647 class ConfigParser(CP.ConfigParser):
649 old_settings = {
650 'pep_stylesheet': ('pep_html writer', 'stylesheet'),
651 'pep_stylesheet_path': ('pep_html writer', 'stylesheet_path'),
652 'pep_template': ('pep_html writer', 'template')}
653 """{old setting: (new section, new setting)} mapping, used by
654 `handle_old_config`, to convert settings from the old [options] section."""
656 old_warning = """
657 The "[option]" section is deprecated. Support for old-format configuration
658 files may be removed in a future Docutils release. Please revise your
659 configuration files. See <http://docutils.sf.net/docs/user/config.html>,
660 section "Old-Format Configuration Files".
663 not_utf8_error = """\
664 Unable to read configuration file "%s": content not encoded as UTF-8.
665 Skipping "%s" configuration file.
668 def __init__(self, *args, **kwargs):
669 CP.ConfigParser.__init__(self, *args, **kwargs)
671 self._files = []
672 """List of paths of configuration files read."""
674 def read(self, filenames, option_parser):
675 if type(filenames) in (types.StringType, types.UnicodeType):
676 filenames = [filenames]
677 for filename in filenames:
678 try:
679 # Config files must be UTF-8-encoded:
680 fp = codecs.open(filename, 'r', 'utf-8')
681 except IOError:
682 continue
683 try:
684 CP.ConfigParser.readfp(self, fp, filename)
685 except UnicodeDecodeError:
686 sys.stderr.write(self.not_utf8_error % (filename, filename))
687 fp.close()
688 continue
689 fp.close()
690 self._files.append(filename)
691 if self.has_section('options'):
692 self.handle_old_config(filename)
693 self.validate_settings(filename, option_parser)
695 def handle_old_config(self, filename):
696 warnings.warn_explicit(self.old_warning, ConfigDeprecationWarning,
697 filename, 0)
698 options = self.get_section('options')
699 if not self.has_section('general'):
700 self.add_section('general')
701 for key, value in options.items():
702 if self.old_settings.has_key(key):
703 section, setting = self.old_settings[key]
704 if not self.has_section(section):
705 self.add_section(section)
706 else:
707 section = 'general'
708 setting = key
709 if not self.has_option(section, setting):
710 self.set(section, setting, value)
711 self.remove_section('options')
713 def validate_settings(self, filename, option_parser):
715 Call the validator function and implement overrides on all applicable
716 settings.
718 for section in self.sections():
719 for setting in self.options(section):
720 try:
721 option = option_parser.get_option_by_dest(setting)
722 except KeyError:
723 continue
724 if option.validator:
725 value = self.get(section, setting, raw=1)
726 try:
727 new_value = option.validator(
728 setting, value, option_parser,
729 config_parser=self, config_section=section)
730 except Exception, error:
731 raise (ValueError(
732 'Error in config file "%s", section "[%s]":\n'
733 ' %s: %s\n %s = %s'
734 % (filename, section, error.__class__.__name__,
735 error, setting, value)), None, sys.exc_info()[2])
736 self.set(section, setting, new_value)
737 if option.overrides:
738 self.set(section, option.overrides, None)
740 def optionxform(self, optionstr):
742 Transform '-' to '_' so the cmdline form of option names can be used.
744 return optionstr.lower().replace('-', '_')
746 def get_section(self, section):
748 Return a given section as a dictionary (empty if the section
749 doesn't exist).
751 section_dict = {}
752 if self.has_section(section):
753 for option in self.options(section):
754 section_dict[option] = self.get(section, option, raw=1)
755 return section_dict
758 class ConfigDeprecationWarning(DeprecationWarning):
759 """Warning for deprecated configuration file features."""