# Author: David Goodger # Contact: goodger@users.sourceforge.net # Revision: $Revision$ # Date: $Date$ # 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. - `Values`: Runtime settings; objects are simple structs (``object.attribute``). - `ConfigParser`: Standard Docutils config file processing. """ __docformat__ = 'reStructuredText' import os import os.path import ConfigParser as CP import docutils from docutils import optik from docutils.optik import Values 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 kwargs.items(): setattr(parser.values, key, value) def read_config_file(option, opt, value, parser): """ Read a configuration file during option processing. (Option callback.) """ config_parser = ConfigParser() config_parser.read(value) settings = config_parser.get_section('options') make_paths_absolute(settings, parser.relative_path_settings, os.path.dirname(value)) parser.values.__dict__.update(settings) 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() for key in keys: if pathdict.has_key(key) and pathdict[key]: pathdict[key] = os.path.normpath( os.path.abspath(os.path.join(base_path, pathdict[key]))) class OptionParser(optik.OptionParser, docutils.SettingsSpec): """ Parser for command-line and library use. The `settings_spec` specification here and in other Docutils components are merged to build the set of command-line options and runtime settings for this process. Common settings (defined below) and component-specific settings must not conflict. Short options are reserved for common settings, and components are restrict to using long options. """ threshold_choices = 'info 1 warning 2 error 3 severe 4 none 5'.split() """Possible inputs for for --report and --halt threshold values.""" thresholds = {'info': 1, 'warning': 2, 'error': 3, 'severe': 4, 'none': 5} """Lookup table for --report and --halt threshold values.""" settings_spec = ( 'General Docutils Options', None, (('Include a "Generated by Docutils" credit and link at the end ' 'of the document.', ['--generator', '-g'], {'action': 'store_true'}), ('Do not include a generator credit.', ['--no-generator'], {'action': 'store_false', 'dest': 'generator'}), ('Include the date at the end of the document (UTC).', ['--date', '-d'], {'action': 'store_const', 'const': '%Y-%m-%d', 'dest': 'datestamp'}), ('Include the time & date at the end of the document (UTC).', ['--time', '-t'], {'action': 'store_const', 'const': '%Y-%m-%d %H:%M UTC', 'dest': 'datestamp'}), ('Do not include a datestamp of any kind.', ['--no-datestamp'], {'action': 'store_const', 'const': None, 'dest': 'datestamp'}), ('Include a "View document source" link (relative to destination).', ['--source-link', '-s'], {'action': 'store_true'}), ('Use the supplied verbatim for a "View document source" ' 'link; implies --source-link.', ['--source-url'], {'metavar': ''}), ('Do not include a "View document source" link.', ['--no-source-link'], {'action': 'callback', 'callback': store_multiple, 'callback_args': ('source_link', 'source_url')}), ('Enable backlinks from section headers to table of contents ' 'entries. This is the default.', ['--toc-entry-backlinks'], {'dest': 'toc_backlinks', 'action': 'store_const', 'const': 'entry', 'default': 'entry'}), ('Enable backlinks from section headers to the top of the table of ' 'contents.', ['--toc-top-backlinks'], {'dest': 'toc_backlinks', 'action': 'store_const', 'const': 'top'}), ('Disable backlinks to the table of contents.', ['--no-toc-backlinks'], {'dest': 'toc_backlinks', 'action': 'store_false'}), ('Enable backlinks from footnotes and citations to their ' 'references. This is the default.', ['--footnote-backlinks'], {'action': 'store_true', 'default': 1}), ('Disable backlinks from footnotes and citations.', ['--no-footnote-backlinks'], {'dest': 'footnote_backlinks', 'action': 'store_false'}), ('Set verbosity threshold; report system messages at or higher than ' ' (by name or number: "info" or "1", warning/2, error/3, ' 'severe/4; also, "none" or "5"). Default is 2 (warning).', ['--report', '-r'], {'choices': threshold_choices, 'default': 2, 'dest': 'report_level', 'metavar': ''}), ('Report all system messages, info-level and higher. (Same as ' '"--report=info".)', ['--verbose', '-v'], {'action': 'store_const', 'const': 'info', 'dest': 'report_level'}), ('Do not report any system messages. (Same as "--report=none".)', ['--quiet', '-q'], {'action': 'store_const', 'const': 'none', 'dest': 'report_level'}), ('Set the threshold () at or above which system messages are ' 'converted to exceptions, halting execution immediately. Levels ' 'as in --report. Default is 4 (severe).', ['--halt'], {'choices': threshold_choices, 'dest': 'halt_level', 'default': 4, 'metavar': ''}), ('Same as "--halt=info": halt processing at the slightest problem.', ['--strict'], {'action': 'store_const', 'const': 'info', 'dest': 'halt_level'}), ('Report debug-level system messages.', ['--debug'], {'action': 'store_true'}), ('Do not report debug-level system messages.', ['--no-debug'], {'action': 'store_false', 'dest': 'debug'}), ('Send the output of system messages (warnings) to .', ['--warnings'], {'dest': 'warning_stream', 'metavar': ''}), ('Specify the encoding of input text. Default is locale-dependent.', ['--input-encoding', '-i'], {'metavar': ''}), ('Specify the encoding for output. Default is UTF-8.', ['--output-encoding', '-o'], {'metavar': '', 'default': 'utf-8'}), ('Specify the language of input text (ISO 639 2-letter identifier).' ' Default is "en" (English).', ['--language', '-l'], {'dest': 'language_code', 'default': 'en', 'metavar': ''}), ('Read configuration settings from , if it exists.', ['--config'], {'metavar': '', 'type': 'string', 'action': 'callback', 'callback': read_config_file}), ("Show this program's version number and exit.", ['--version', '-V'], {'action': 'version'}), ('Show this help message and exit.', ['--help', '-h'], {'action': 'help'}), # Hidden options, for development use only: (optik.SUPPRESS_HELP, ['--dump-settings'], {'action': 'store_true'}), (optik.SUPPRESS_HELP, ['--dump-internals'], {'action': 'store_true'}), (optik.SUPPRESS_HELP, ['--dump-transforms'], {'action': 'store_true'}), (optik.SUPPRESS_HELP, ['--dump-pseudo-xml'], {'action': 'store_true'}), (optik.SUPPRESS_HELP, ['--expose-internal-attribute'], {'action': 'append', 'dest': 'expose_internals'}),)) """Runtime settings and command-line options common to all Docutils front ends. Setting specs specific to individual Docutils components are also used (see `populate_from_components()`).""" relative_path_settings = ('warning_stream',) version_template = '%%prog (Docutils %s)' % docutils.__version__ """Default version message.""" def __init__(self, components=(), *args, **kwargs): """ `components` is a list of Docutils components each containing a ``.settings_spec`` attribute. `defaults` is a mapping of setting default overrides. """ optik.OptionParser.__init__( self, help=None, format=optik.Titled(), # Needed when Optik is updated (replaces above 2 lines): #self, add_help=None, #formatter=optik.TitledHelpFormatter(width=78), *args, **kwargs) if not self.version: self.version = self.version_template # Internal settings with no defaults from settings specifications; # initialize manually: self.set_defaults(_source=None, _destination=None) # Make an instance copy (it will be modified): self.relative_path_settings = list(self.relative_path_settings) self.populate_from_components(tuple(components) + (self,)) def populate_from_components(self, components): for component in components: if component is None: continue i = 0 settings_spec = component.settings_spec self.relative_path_settings.extend( component.relative_path_settings) while i < len(settings_spec): title, description, option_spec = settings_spec[i:i+3] if title: group = optik.OptionGroup(self, title, description) self.add_option_group(group) else: group = self # single options for (help_text, option_strings, kwargs) in option_spec: group.add_option(help=help_text, *option_strings, **kwargs) i += 3 for component in components: if component and component.settings_default_overrides: self.defaults.update(component.settings_default_overrides) def check_values(self, values, args): if hasattr(values, 'report_level'): values.report_level = self.check_threshold(values.report_level) if hasattr(values, 'halt_level'): values.halt_level = self.check_threshold(values.halt_level) values._source, values._destination = self.check_args(args) make_paths_absolute(values.__dict__, self.relative_path_settings, os.getcwd()) return values def check_threshold(self, level): try: return int(level) except ValueError: try: return self.thresholds[level.lower()] except (KeyError, AttributeError): self.error('Unknown threshold: %r.' % level) def check_args(self, args): source = destination = None if args: source = args.pop(0) if args: destination = args.pop(0) if args: self.error('Maximum 2 arguments allowed.') if source and source == destination: self.error('Do not specify the same file for both source and ' 'destination. It will clobber the source file.') return source, destination class ConfigParser(CP.ConfigParser): standard_config_files = ( '/etc/docutils.conf', # system-wide './docutils.conf', # project-specific os.path.expanduser('~/.docutils')) # user-specific """Docutils configuration files, using ConfigParser syntax (section 'options'). Later files override earlier ones.""" def read_standard_files(self): self.read(self.standard_config_files) def optionxform(self, optionstr): """ Transform '-' to '_' so the cmdline form of option names can be used. """ return optionstr.lower().replace('-', '_') def get_section(self, section, raw=0, vars=None): """ Return a given section as a dictionary (empty if the section doesn't exist). All % interpolations are expanded in the return values, based on the defaults passed into the constructor, unless the optional argument `raw` is true. Additional substitutions may be provided using the `vars` argument, which must be a dictionary whose contents overrides any pre-existing defaults. The section DEFAULT is special. """ section_dict = {} if self.has_section(section): for option in self.options(section): section_dict[option] = self.get(section, option, raw, vars) return section_dict