Fix [3541369] Relative __import__ also with Python 3.3.
[docutils.git] / docutils / parsers / rst / directives / __init__.py
blobfdc70d70fa6adf62af561d52b50cdc8ad0795ac2
1 # $Id$
2 # Author: David Goodger <goodger@python.org>
3 # Copyright: This module has been placed in the public domain.
5 """
6 This package contains directive implementation modules.
7 """
9 __docformat__ = 'reStructuredText'
11 import re
12 import codecs
13 import sys
15 from docutils import nodes
16 from docutils.parsers.rst.languages import en as _fallback_language_module
17 if sys.version_info < (2,5):
18 from docutils._compat import __import__
21 _directive_registry = {
22 'attention': ('admonitions', 'Attention'),
23 'caution': ('admonitions', 'Caution'),
24 'code': ('body', 'CodeBlock'),
25 'danger': ('admonitions', 'Danger'),
26 'error': ('admonitions', 'Error'),
27 'important': ('admonitions', 'Important'),
28 'note': ('admonitions', 'Note'),
29 'tip': ('admonitions', 'Tip'),
30 'hint': ('admonitions', 'Hint'),
31 'warning': ('admonitions', 'Warning'),
32 'admonition': ('admonitions', 'Admonition'),
33 'sidebar': ('body', 'Sidebar'),
34 'topic': ('body', 'Topic'),
35 'line-block': ('body', 'LineBlock'),
36 'parsed-literal': ('body', 'ParsedLiteral'),
37 'math': ('body', 'MathBlock'),
38 'rubric': ('body', 'Rubric'),
39 'epigraph': ('body', 'Epigraph'),
40 'highlights': ('body', 'Highlights'),
41 'pull-quote': ('body', 'PullQuote'),
42 'compound': ('body', 'Compound'),
43 'container': ('body', 'Container'),
44 #'questions': ('body', 'question_list'),
45 'table': ('tables', 'RSTTable'),
46 'csv-table': ('tables', 'CSVTable'),
47 'list-table': ('tables', 'ListTable'),
48 'image': ('images', 'Image'),
49 'figure': ('images', 'Figure'),
50 'contents': ('parts', 'Contents'),
51 'sectnum': ('parts', 'Sectnum'),
52 'header': ('parts', 'Header'),
53 'footer': ('parts', 'Footer'),
54 #'footnotes': ('parts', 'footnotes'),
55 #'citations': ('parts', 'citations'),
56 'target-notes': ('references', 'TargetNotes'),
57 'meta': ('html', 'Meta'),
58 #'imagemap': ('html', 'imagemap'),
59 'raw': ('misc', 'Raw'),
60 'include': ('misc', 'Include'),
61 'replace': ('misc', 'Replace'),
62 'unicode': ('misc', 'Unicode'),
63 'class': ('misc', 'Class'),
64 'role': ('misc', 'Role'),
65 'default-role': ('misc', 'DefaultRole'),
66 'title': ('misc', 'Title'),
67 'date': ('misc', 'Date'),
68 'restructuredtext-test-directive': ('misc', 'TestDirective'),}
69 """Mapping of directive name to (module name, class name). The
70 directive name is canonical & must be lowercase. Language-dependent
71 names are defined in the ``language`` subpackage."""
73 _directives = {}
74 """Cache of imported directives."""
76 def directive(directive_name, language_module, document):
77 """
78 Locate and return a directive function from its language-dependent name.
79 If not found in the current language, check English. Return None if the
80 named directive cannot be found.
81 """
82 normname = directive_name.lower()
83 messages = []
84 msg_text = []
85 if normname in _directives:
86 return _directives[normname], messages
87 canonicalname = None
88 try:
89 canonicalname = language_module.directives[normname]
90 except AttributeError, error:
91 msg_text.append('Problem retrieving directive entry from language '
92 'module %r: %s.' % (language_module, error))
93 except KeyError:
94 msg_text.append('No directive entry for "%s" in module "%s".'
95 % (directive_name, language_module.__name__))
96 if not canonicalname:
97 try:
98 canonicalname = _fallback_language_module.directives[normname]
99 msg_text.append('Using English fallback for directive "%s".'
100 % directive_name)
101 except KeyError:
102 msg_text.append('Trying "%s" as canonical directive name.'
103 % directive_name)
104 # The canonical name should be an English name, but just in case:
105 canonicalname = normname
106 if msg_text:
107 message = document.reporter.info(
108 '\n'.join(msg_text), line=document.current_line)
109 messages.append(message)
110 try:
111 modulename, classname = _directive_registry[canonicalname]
112 except KeyError:
113 # Error handling done by caller.
114 return None, messages
115 try:
116 module = __import__(modulename, globals(), locals(), level=1)
117 except ImportError, detail:
118 messages.append(document.reporter.error(
119 'Error importing directive module "%s" (directive "%s"):\n%s'
120 % (modulename, directive_name, detail),
121 line=document.current_line))
122 return None, messages
123 try:
124 directive = getattr(module, classname)
125 _directives[normname] = directive
126 except AttributeError:
127 messages.append(document.reporter.error(
128 'No directive class "%s" in module "%s" (directive "%s").'
129 % (classname, modulename, directive_name),
130 line=document.current_line))
131 return None, messages
132 return directive, messages
134 def register_directive(name, directive):
136 Register a nonstandard application-defined directive function.
137 Language lookups are not needed for such functions.
139 _directives[name] = directive
141 def flag(argument):
143 Check for a valid flag option (no argument) and return ``None``.
144 (Directive option conversion function.)
146 Raise ``ValueError`` if an argument is found.
148 if argument and argument.strip():
149 raise ValueError('no argument is allowed; "%s" supplied' % argument)
150 else:
151 return None
153 def unchanged_required(argument):
155 Return the argument text, unchanged.
156 (Directive option conversion function.)
158 Raise ``ValueError`` if no argument is found.
160 if argument is None:
161 raise ValueError('argument required but none supplied')
162 else:
163 return argument # unchanged!
165 def unchanged(argument):
167 Return the argument text, unchanged.
168 (Directive option conversion function.)
170 No argument implies empty string ("").
172 if argument is None:
173 return u''
174 else:
175 return argument # unchanged!
177 def path(argument):
179 Return the path argument unwrapped (with newlines removed).
180 (Directive option conversion function.)
182 Raise ``ValueError`` if no argument is found.
184 if argument is None:
185 raise ValueError('argument required but none supplied')
186 else:
187 path = ''.join([s.strip() for s in argument.splitlines()])
188 return path
190 def uri(argument):
192 Return the URI argument with whitespace removed.
193 (Directive option conversion function.)
195 Raise ``ValueError`` if no argument is found.
197 if argument is None:
198 raise ValueError('argument required but none supplied')
199 else:
200 uri = ''.join(argument.split())
201 return uri
203 def nonnegative_int(argument):
205 Check for a nonnegative integer argument; raise ``ValueError`` if not.
206 (Directive option conversion function.)
208 value = int(argument)
209 if value < 0:
210 raise ValueError('negative value; must be positive or zero')
211 return value
213 def percentage(argument):
215 Check for an integer percentage value with optional percent sign.
217 try:
218 argument = argument.rstrip(' %')
219 except AttributeError:
220 pass
221 return nonnegative_int(argument)
223 length_units = ['em', 'ex', 'px', 'in', 'cm', 'mm', 'pt', 'pc']
225 def get_measure(argument, units):
227 Check for a positive argument of one of the units and return a
228 normalized string of the form "<value><unit>" (without space in
229 between).
231 To be called from directive option conversion functions.
233 match = re.match(r'^([0-9.]+) *(%s)$' % '|'.join(units), argument)
234 try:
235 assert match is not None
236 float(match.group(1))
237 except (AssertionError, ValueError):
238 raise ValueError(
239 'not a positive measure of one of the following units:\n%s'
240 % ' '.join(['"%s"' % i for i in units]))
241 return match.group(1) + match.group(2)
243 def length_or_unitless(argument):
244 return get_measure(argument, length_units + [''])
246 def length_or_percentage_or_unitless(argument, default=''):
248 Return normalized string of a length or percentage unit.
250 Add <default> if there is no unit. Raise ValueError if the argument is not
251 a positive measure of one of the valid CSS units (or without unit).
253 >>> length_or_percentage_or_unitless('3 pt')
254 '3pt'
255 >>> length_or_percentage_or_unitless('3%', 'em')
256 '3%'
257 >>> length_or_percentage_or_unitless('3')
259 >>> length_or_percentage_or_unitless('3', 'px')
260 '3px'
262 try:
263 return get_measure(argument, length_units + ['%'])
264 except ValueError:
265 return get_measure(argument, ['']) + default
267 def class_option(argument):
269 Convert the argument into a list of ID-compatible strings and return it.
270 (Directive option conversion function.)
272 Raise ``ValueError`` if no argument is found.
274 if argument is None:
275 raise ValueError('argument required but none supplied')
276 names = argument.split()
277 class_names = []
278 for name in names:
279 class_name = nodes.make_id(name)
280 if not class_name:
281 raise ValueError('cannot make "%s" into a class name' % name)
282 class_names.append(class_name)
283 return class_names
285 unicode_pattern = re.compile(
286 r'(?:0x|x|\\x|U\+?|\\u)([0-9a-f]+)$|&#x([0-9a-f]+);$', re.IGNORECASE)
288 def unicode_code(code):
289 r"""
290 Convert a Unicode character code to a Unicode character.
291 (Directive option conversion function.)
293 Codes may be decimal numbers, hexadecimal numbers (prefixed by ``0x``,
294 ``x``, ``\x``, ``U+``, ``u``, or ``\u``; e.g. ``U+262E``), or XML-style
295 numeric character entities (e.g. ``&#x262E;``). Other text remains as-is.
297 Raise ValueError for illegal Unicode code values.
299 try:
300 if code.isdigit(): # decimal number
301 return unichr(int(code))
302 else:
303 match = unicode_pattern.match(code)
304 if match: # hex number
305 value = match.group(1) or match.group(2)
306 return unichr(int(value, 16))
307 else: # other text
308 return code
309 except OverflowError, detail:
310 raise ValueError('code too large (%s)' % detail)
312 def single_char_or_unicode(argument):
314 A single character is returned as-is. Unicode characters codes are
315 converted as in `unicode_code`. (Directive option conversion function.)
317 char = unicode_code(argument)
318 if len(char) > 1:
319 raise ValueError('%r invalid; must be a single character or '
320 'a Unicode code' % char)
321 return char
323 def single_char_or_whitespace_or_unicode(argument):
325 As with `single_char_or_unicode`, but "tab" and "space" are also supported.
326 (Directive option conversion function.)
328 if argument == 'tab':
329 char = '\t'
330 elif argument == 'space':
331 char = ' '
332 else:
333 char = single_char_or_unicode(argument)
334 return char
336 def positive_int(argument):
338 Converts the argument into an integer. Raises ValueError for negative,
339 zero, or non-integer values. (Directive option conversion function.)
341 value = int(argument)
342 if value < 1:
343 raise ValueError('negative or zero value; must be positive')
344 return value
346 def positive_int_list(argument):
348 Converts a space- or comma-separated list of values into a Python list
349 of integers.
350 (Directive option conversion function.)
352 Raises ValueError for non-positive-integer values.
354 if ',' in argument:
355 entries = argument.split(',')
356 else:
357 entries = argument.split()
358 return [positive_int(entry) for entry in entries]
360 def encoding(argument):
362 Verfies the encoding argument by lookup.
363 (Directive option conversion function.)
365 Raises ValueError for unknown encodings.
367 try:
368 codecs.lookup(argument)
369 except LookupError:
370 raise ValueError('unknown encoding: "%s"' % argument)
371 return argument
373 def choice(argument, values):
375 Directive option utility function, supplied to enable options whose
376 argument must be a member of a finite set of possible values (must be
377 lower case). A custom conversion function must be written to use it. For
378 example::
380 from docutils.parsers.rst import directives
382 def yesno(argument):
383 return directives.choice(argument, ('yes', 'no'))
385 Raise ``ValueError`` if no argument is found or if the argument's value is
386 not valid (not an entry in the supplied list).
388 try:
389 value = argument.lower().strip()
390 except AttributeError:
391 raise ValueError('must supply an argument; choose from %s'
392 % format_values(values))
393 if value in values:
394 return value
395 else:
396 raise ValueError('"%s" unknown; choose from %s'
397 % (argument, format_values(values)))
399 def format_values(values):
400 return '%s, or "%s"' % (', '.join(['"%s"' % s for s in values[:-1]]),
401 values[-1])