| # $Id: __init__.py 5952 2009-05-19 08:45:27Z milde $ |
| # Author: David Goodger <goodger@python.org> |
| # Copyright: This module has been placed in the public domain. |
| |
| """ |
| This package contains directive implementation modules. |
| """ |
| |
| __docformat__ = 'reStructuredText' |
| |
| import re |
| import codecs |
| from docutils import nodes |
| from docutils.parsers.rst.languages import en as _fallback_language_module |
| |
| |
| _directive_registry = { |
| 'attention': ('admonitions', 'Attention'), |
| 'caution': ('admonitions', 'Caution'), |
| 'danger': ('admonitions', 'Danger'), |
| 'error': ('admonitions', 'Error'), |
| 'important': ('admonitions', 'Important'), |
| 'note': ('admonitions', 'Note'), |
| 'tip': ('admonitions', 'Tip'), |
| 'hint': ('admonitions', 'Hint'), |
| 'warning': ('admonitions', 'Warning'), |
| 'admonition': ('admonitions', 'Admonition'), |
| 'sidebar': ('body', 'Sidebar'), |
| 'topic': ('body', 'Topic'), |
| 'line-block': ('body', 'LineBlock'), |
| 'parsed-literal': ('body', 'ParsedLiteral'), |
| 'rubric': ('body', 'Rubric'), |
| 'epigraph': ('body', 'Epigraph'), |
| 'highlights': ('body', 'Highlights'), |
| 'pull-quote': ('body', 'PullQuote'), |
| 'compound': ('body', 'Compound'), |
| 'container': ('body', 'Container'), |
| #'questions': ('body', 'question_list'), |
| 'table': ('tables', 'RSTTable'), |
| 'csv-table': ('tables', 'CSVTable'), |
| 'list-table': ('tables', 'ListTable'), |
| 'image': ('images', 'Image'), |
| 'figure': ('images', 'Figure'), |
| 'contents': ('parts', 'Contents'), |
| 'sectnum': ('parts', 'Sectnum'), |
| 'header': ('parts', 'Header'), |
| 'footer': ('parts', 'Footer'), |
| #'footnotes': ('parts', 'footnotes'), |
| #'citations': ('parts', 'citations'), |
| 'target-notes': ('references', 'TargetNotes'), |
| 'meta': ('html', 'Meta'), |
| #'imagemap': ('html', 'imagemap'), |
| 'raw': ('misc', 'Raw'), |
| 'include': ('misc', 'Include'), |
| 'replace': ('misc', 'Replace'), |
| 'unicode': ('misc', 'Unicode'), |
| 'class': ('misc', 'Class'), |
| 'role': ('misc', 'Role'), |
| 'default-role': ('misc', 'DefaultRole'), |
| 'title': ('misc', 'Title'), |
| 'date': ('misc', 'Date'), |
| 'restructuredtext-test-directive': ('misc', 'TestDirective'),} |
| """Mapping of directive name to (module name, class name). The |
| directive name is canonical & must be lowercase. Language-dependent |
| names are defined in the ``language`` subpackage.""" |
| |
| _directives = {} |
| """Cache of imported directives.""" |
| |
| def directive(directive_name, language_module, document): |
| """ |
| Locate and return a directive function from its language-dependent name. |
| If not found in the current language, check English. Return None if the |
| named directive cannot be found. |
| """ |
| normname = directive_name.lower() |
| messages = [] |
| msg_text = [] |
| if normname in _directives: |
| return _directives[normname], messages |
| canonicalname = None |
| try: |
| canonicalname = language_module.directives[normname] |
| except AttributeError, error: |
| msg_text.append('Problem retrieving directive entry from language ' |
| 'module %r: %s.' % (language_module, error)) |
| except KeyError: |
| msg_text.append('No directive entry for "%s" in module "%s".' |
| % (directive_name, language_module.__name__)) |
| if not canonicalname: |
| try: |
| canonicalname = _fallback_language_module.directives[normname] |
| msg_text.append('Using English fallback for directive "%s".' |
| % directive_name) |
| except KeyError: |
| msg_text.append('Trying "%s" as canonical directive name.' |
| % directive_name) |
| # The canonical name should be an English name, but just in case: |
| canonicalname = normname |
| if msg_text: |
| message = document.reporter.info( |
| '\n'.join(msg_text), line=document.current_line) |
| messages.append(message) |
| try: |
| modulename, classname = _directive_registry[canonicalname] |
| except KeyError: |
| # Error handling done by caller. |
| return None, messages |
| try: |
| module = __import__(modulename, globals(), locals()) |
| except ImportError, detail: |
| messages.append(document.reporter.error( |
| 'Error importing directive module "%s" (directive "%s"):\n%s' |
| % (modulename, directive_name, detail), |
| line=document.current_line)) |
| return None, messages |
| try: |
| directive = getattr(module, classname) |
| _directives[normname] = directive |
| except AttributeError: |
| messages.append(document.reporter.error( |
| 'No directive class "%s" in module "%s" (directive "%s").' |
| % (classname, modulename, directive_name), |
| line=document.current_line)) |
| return None, messages |
| return directive, messages |
| |
| def register_directive(name, directive): |
| """ |
| Register a nonstandard application-defined directive function. |
| Language lookups are not needed for such functions. |
| """ |
| _directives[name] = directive |
| |
| def flag(argument): |
| """ |
| Check for a valid flag option (no argument) and return ``None``. |
| (Directive option conversion function.) |
| |
| Raise ``ValueError`` if an argument is found. |
| """ |
| if argument and argument.strip(): |
| raise ValueError('no argument is allowed; "%s" supplied' % argument) |
| else: |
| return None |
| |
| def unchanged_required(argument): |
| """ |
| Return the argument text, unchanged. |
| (Directive option conversion function.) |
| |
| Raise ``ValueError`` if no argument is found. |
| """ |
| if argument is None: |
| raise ValueError('argument required but none supplied') |
| else: |
| return argument # unchanged! |
| |
| def unchanged(argument): |
| """ |
| Return the argument text, unchanged. |
| (Directive option conversion function.) |
| |
| No argument implies empty string (""). |
| """ |
| if argument is None: |
| return u'' |
| else: |
| return argument # unchanged! |
| |
| def path(argument): |
| """ |
| Return the path argument unwrapped (with newlines removed). |
| (Directive option conversion function.) |
| |
| Raise ``ValueError`` if no argument is found. |
| """ |
| if argument is None: |
| raise ValueError('argument required but none supplied') |
| else: |
| path = ''.join([s.strip() for s in argument.splitlines()]) |
| return path |
| |
| def uri(argument): |
| """ |
| Return the URI argument with whitespace removed. |
| (Directive option conversion function.) |
| |
| Raise ``ValueError`` if no argument is found. |
| """ |
| if argument is None: |
| raise ValueError('argument required but none supplied') |
| else: |
| uri = ''.join(argument.split()) |
| return uri |
| |
| def nonnegative_int(argument): |
| """ |
| Check for a nonnegative integer argument; raise ``ValueError`` if not. |
| (Directive option conversion function.) |
| """ |
| value = int(argument) |
| if value < 0: |
| raise ValueError('negative value; must be positive or zero') |
| return value |
| |
| def percentage(argument): |
| """ |
| Check for an integer percentage value with optional percent sign. |
| """ |
| try: |
| argument = argument.rstrip(' %') |
| except AttributeError: |
| pass |
| return nonnegative_int(argument) |
| |
| length_units = ['em', 'ex', 'px', 'in', 'cm', 'mm', 'pt', 'pc'] |
| |
| def get_measure(argument, units): |
| """ |
| Check for a positive argument of one of the units and return a |
| normalized string of the form "<value><unit>" (without space in |
| between). |
| |
| To be called from directive option conversion functions. |
| """ |
| match = re.match(r'^([0-9.]+) *(%s)$' % '|'.join(units), argument) |
| try: |
| assert match is not None |
| float(match.group(1)) |
| except (AssertionError, ValueError): |
| raise ValueError( |
| 'not a positive measure of one of the following units:\n%s' |
| % ' '.join(['"%s"' % i for i in units])) |
| return match.group(1) + match.group(2) |
| |
| def length_or_unitless(argument): |
| return get_measure(argument, length_units + ['']) |
| |
| def length_or_percentage_or_unitless(argument, default=''): |
| """ |
| Return normalized string of a length or percentage unit. |
| |
| Add <default> if there is no unit. Raise ValueError if the argument is not |
| a positive measure of one of the valid CSS units (or without unit). |
| |
| >>> length_or_percentage_or_unitless('3 pt') |
| '3pt' |
| >>> length_or_percentage_or_unitless('3%', 'em') |
| '3%' |
| >>> length_or_percentage_or_unitless('3') |
| '3' |
| >>> length_or_percentage_or_unitless('3', 'px') |
| '3px' |
| """ |
| try: |
| return get_measure(argument, length_units + ['%']) |
| except ValueError: |
| return get_measure(argument, ['']) + default |
| |
| def class_option(argument): |
| """ |
| Convert the argument into a list of ID-compatible strings and return it. |
| (Directive option conversion function.) |
| |
| Raise ``ValueError`` if no argument is found. |
| """ |
| if argument is None: |
| raise ValueError('argument required but none supplied') |
| names = argument.split() |
| class_names = [] |
| for name in names: |
| class_name = nodes.make_id(name) |
| if not class_name: |
| raise ValueError('cannot make "%s" into a class name' % name) |
| class_names.append(class_name) |
| return class_names |
| |
| unicode_pattern = re.compile( |
| r'(?:0x|x|\\x|U\+?|\\u)([0-9a-f]+)$|&#x([0-9a-f]+);$', re.IGNORECASE) |
| |
| def unicode_code(code): |
| r""" |
| Convert a Unicode character code to a Unicode character. |
| (Directive option conversion function.) |
| |
| Codes may be decimal numbers, hexadecimal numbers (prefixed by ``0x``, |
| ``x``, ``\x``, ``U+``, ``u``, or ``\u``; e.g. ``U+262E``), or XML-style |
| numeric character entities (e.g. ``☮``). Other text remains as-is. |
| |
| Raise ValueError for illegal Unicode code values. |
| """ |
| try: |
| if code.isdigit(): # decimal number |
| return unichr(int(code)) |
| else: |
| match = unicode_pattern.match(code) |
| if match: # hex number |
| value = match.group(1) or match.group(2) |
| return unichr(int(value, 16)) |
| else: # other text |
| return code |
| except OverflowError, detail: |
| raise ValueError('code too large (%s)' % detail) |
| |
| def single_char_or_unicode(argument): |
| """ |
| A single character is returned as-is. Unicode characters codes are |
| converted as in `unicode_code`. (Directive option conversion function.) |
| """ |
| char = unicode_code(argument) |
| if len(char) > 1: |
| raise ValueError('%r invalid; must be a single character or ' |
| 'a Unicode code' % char) |
| return char |
| |
| def single_char_or_whitespace_or_unicode(argument): |
| """ |
| As with `single_char_or_unicode`, but "tab" and "space" are also supported. |
| (Directive option conversion function.) |
| """ |
| if argument == 'tab': |
| char = '\t' |
| elif argument == 'space': |
| char = ' ' |
| else: |
| char = single_char_or_unicode(argument) |
| return char |
| |
| def positive_int(argument): |
| """ |
| Converts the argument into an integer. Raises ValueError for negative, |
| zero, or non-integer values. (Directive option conversion function.) |
| """ |
| value = int(argument) |
| if value < 1: |
| raise ValueError('negative or zero value; must be positive') |
| return value |
| |
| def positive_int_list(argument): |
| """ |
| Converts a space- or comma-separated list of values into a Python list |
| of integers. |
| (Directive option conversion function.) |
| |
| Raises ValueError for non-positive-integer values. |
| """ |
| if ',' in argument: |
| entries = argument.split(',') |
| else: |
| entries = argument.split() |
| return [positive_int(entry) for entry in entries] |
| |
| def encoding(argument): |
| """ |
| Verfies the encoding argument by lookup. |
| (Directive option conversion function.) |
| |
| Raises ValueError for unknown encodings. |
| """ |
| try: |
| codecs.lookup(argument) |
| except LookupError: |
| raise ValueError('unknown encoding: "%s"' % argument) |
| return argument |
| |
| def choice(argument, values): |
| """ |
| Directive option utility function, supplied to enable options whose |
| argument must be a member of a finite set of possible values (must be |
| lower case). A custom conversion function must be written to use it. For |
| example:: |
| |
| from docutils.parsers.rst import directives |
| |
| def yesno(argument): |
| return directives.choice(argument, ('yes', 'no')) |
| |
| Raise ``ValueError`` if no argument is found or if the argument's value is |
| not valid (not an entry in the supplied list). |
| """ |
| try: |
| value = argument.lower().strip() |
| except AttributeError: |
| raise ValueError('must supply an argument; choose from %s' |
| % format_values(values)) |
| if value in values: |
| return value |
| else: |
| raise ValueError('"%s" unknown; choose from %s' |
| % (argument, format_values(values))) |
| |
| def format_values(values): |
| return '%s, or "%s"' % (', '.join(['"%s"' % s for s in values[:-1]]), |
| values[-1]) |