311 lines
13 KiB
Python
311 lines
13 KiB
Python
# 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 <URL> verbatim for a "View document source" '
|
|
'link; implies --source-link.',
|
|
['--source-url'], {'metavar': '<URL>'}),
|
|
('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 '
|
|
'<level> (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': '<level>'}),
|
|
('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 (<level>) 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': '<level>'}),
|
|
('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 <file>.',
|
|
['--warnings'], {'dest': 'warning_stream', 'metavar': '<file>'}),
|
|
('Specify the encoding of input text. Default is locale-dependent.',
|
|
['--input-encoding', '-i'], {'metavar': '<name>'}),
|
|
('Specify the encoding for output. Default is UTF-8.',
|
|
['--output-encoding', '-o'],
|
|
{'metavar': '<name>', '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': '<name>'}),
|
|
('Read configuration settings from <file>, if it exists.',
|
|
['--config'], {'metavar': '<file>', '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
|