# $Id: frontend.py 7339 2012-02-03 12:23:27Z milde $ # Author: David Goodger # Copyright: This module has been placed in the public domain. """ Command-line and common processing for Docutils front-end tools. Exports the following classes: * `OptionParser`: Standard Docutils command-line processing. * `Option`: Customized version of `optparse.Option`; validation support. * `Values`: Runtime settings; objects are simple structs (``object.attribute``). Supports cumulative list settings (attributes). * `ConfigParser`: Standard Docutils config file processing. Also exports the following functions: * Option callbacks: `store_multiple`, `read_config_file`. * Setting validators: `validate_encoding`, `validate_encoding_error_handler`, `validate_encoding_and_error_handler`, `validate_boolean`, `validate_threshold`, `validate_colon_separated_string_list`, `validate_dependency_file`. * `make_paths_absolute`. * SettingSpec manipulation: `filter_settings_spec`. """ __docformat__ = 'reStructuredText' import os import os.path import sys import warnings import configparser as CP import codecs import optparse from optparse import SUPPRESS_HELP import docutils import docutils.utils import docutils.nodes from docutils.error_reporting import locale_encoding, ErrorOutput, ErrorString def store_multiple(option, opt, value, parser, *args, **kwargs): """ Store multiple values in `parser.values`. (Option callback.) Store `None` for each attribute named in `args`, and store the value for each key (attribute name) in `kwargs`. """ for attribute in args: setattr(parser.values, attribute, None) for key, value in list(kwargs.items()): setattr(parser.values, key, value) def read_config_file(option, opt, value, parser): """ Read a configuration file during option processing. (Option callback.) """ try: new_settings = parser.get_config_file_settings(value) except ValueError as error: parser.error(error) parser.values.update(new_settings, parser) def validate_encoding(setting, value, option_parser, config_parser=None, config_section=None): try: codecs.lookup(value) except LookupError: raise LookupError('setting "%s": unknown encoding: "%s"' % (setting, value)) return value def validate_encoding_error_handler(setting, value, option_parser, config_parser=None, config_section=None): try: codecs.lookup_error(value) except LookupError: raise LookupError( 'unknown encoding error handler: "%s" (choices: ' '"strict", "ignore", "replace", "backslashreplace", ' '"xmlcharrefreplace", and possibly others; see documentation for ' 'the Python ``codecs`` module)' % value) return value def validate_encoding_and_error_handler( setting, value, option_parser, config_parser=None, config_section=None): """ Side-effect: if an error handler is included in the value, it is inserted into the appropriate place as if it was a separate setting/option. """ if ':' in value: encoding, handler = value.split(':') validate_encoding_error_handler( setting + '_error_handler', handler, option_parser, config_parser, config_section) if config_parser: config_parser.set(config_section, setting + '_error_handler', handler) else: setattr(option_parser.values, setting + '_error_handler', handler) else: encoding = value validate_encoding(setting, encoding, option_parser, config_parser, config_section) return encoding def validate_boolean(setting, value, option_parser, config_parser=None, config_section=None): if isinstance(value, str): try: return option_parser.booleans[value.strip().lower()] except KeyError: raise LookupError('unknown boolean value: "%s"' % value) return value def validate_nonnegative_int(setting, value, option_parser, config_parser=None, config_section=None): value = int(value) if value < 0: raise ValueError('negative value; must be positive or zero') return value def validate_threshold(setting, value, option_parser, config_parser=None, config_section=None): try: return int(value) except ValueError: try: return option_parser.thresholds[value.lower()] except (KeyError, AttributeError): raise LookupError('unknown threshold: %r.' % value) def validate_colon_separated_string_list( setting, value, option_parser, config_parser=None, config_section=None): if isinstance(value, str): value = value.split(':') else: last = value.pop() value.extend(last.split(':')) return value def validate_url_trailing_slash( setting, value, option_parser, config_parser=None, config_section=None): if not value: return './' elif value.endswith('/'): return value else: return value + '/' def validate_dependency_file(setting, value, option_parser, config_parser=None, config_section=None): try: return docutils.utils.DependencyList(value) except IOError: return docutils.utils.DependencyList(None) def validate_strip_class(setting, value, option_parser, config_parser=None, config_section=None): # convert to list: if isinstance(value, str): value = [value] class_values = [_f for _f in [v.strip() for v in value.pop().split(',')] if _f] # validate: for class_value in class_values: normalized = docutils.nodes.make_id(class_value) if class_value != normalized: raise ValueError('invalid class value %r (perhaps %r?)' % (class_value, normalized)) value.extend(class_values) return value def make_paths_absolute(pathdict, keys, base_path=None): """ Interpret filesystem path settings relative to the `base_path` given. Paths are values in `pathdict` whose keys are in `keys`. Get `keys` from `OptionParser.relative_path_settings`. """ if base_path is None: base_path = os.getcwd() # type(base_path) == unicode # to allow combining non-ASCII cwd with unicode values in `pathdict` for key in keys: if key in pathdict: value = pathdict[key] if isinstance(value, list): value = [make_one_path_absolute(base_path, path) for path in value] elif value: value = make_one_path_absolute(base_path, value) pathdict[key] = value def make_one_path_absolute(base_path, path): return os.path.abspath(os.path.join(base_path, path)) def filter_settings_spec(settings_spec, *exclude, **replace): """Return a copy of `settings_spec` excluding/replacing some settings. `settings_spec` is a tuple of configuration settings with a structure described for docutils.SettingsSpec.settings_spec. Optional positional arguments are names of to-be-excluded settings. Keyword arguments are option specification replacements. (See the html4strict writer for an example.) """ settings = list(settings_spec) # every third item is a sequence of option tuples for i in range(2, len(settings), 3): newopts = [] for opt_spec in settings[i]: # opt_spec is ("", [