2 # Author: David Goodger <goodger@python.org>
3 # Copyright: This module has been placed in the public domain.
6 This package contains directive implementation modules.
9 __docformat__
= 'reStructuredText'
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."""
74 """Cache of imported directives."""
76 def directive(directive_name
, language_module
, document
):
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.
82 normname
= directive_name
.lower()
85 if normname
in _directives
:
86 return _directives
[normname
], messages
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
))
94 msg_text
.append('No directive entry for "%s" in module "%s".'
95 % (directive_name
, language_module
.__name
__))
98 canonicalname
= _fallback_language_module
.directives
[normname
]
99 msg_text
.append('Using English fallback for directive "%s".'
102 msg_text
.append('Trying "%s" as canonical directive name.'
104 # The canonical name should be an English name, but just in case:
105 canonicalname
= normname
107 message
= document
.reporter
.info(
108 '\n'.join(msg_text
), line
=document
.current_line
)
109 messages
.append(message
)
111 modulename
, classname
= _directive_registry
[canonicalname
]
113 # Error handling done by caller.
114 return None, messages
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
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
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
)
153 def unchanged_required(argument
):
155 Return the argument text, unchanged.
156 (Directive option conversion function.)
158 Raise ``ValueError`` if no argument is found.
161 raise ValueError('argument required but none supplied')
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 ("").
175 return argument
# unchanged!
179 Return the path argument unwrapped (with newlines removed).
180 (Directive option conversion function.)
182 Raise ``ValueError`` if no argument is found.
185 raise ValueError('argument required but none supplied')
187 path
= ''.join([s
.strip() for s
in argument
.splitlines()])
192 Return the URI argument with whitespace removed.
193 (Directive option conversion function.)
195 Raise ``ValueError`` if no argument is found.
198 raise ValueError('argument required but none supplied')
200 uri
= ''.join(argument
.split())
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
)
210 raise ValueError('negative value; must be positive or zero')
213 def percentage(argument
):
215 Check for an integer percentage value with optional percent sign.
218 argument
= argument
.rstrip(' %')
219 except AttributeError:
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
231 To be called from directive option conversion functions.
233 match
= re
.match(r
'^([0-9.]+) *(%s)$' % '|'.join(units
), argument
)
235 float(match
.group(1))
236 except (AttributeError, ValueError):
238 'not a positive measure of one of the following units:\n%s'
239 % ' '.join(['"%s"' % i
for i
in units
]))
240 return match
.group(1) + match
.group(2)
242 def length_or_unitless(argument
):
243 return get_measure(argument
, length_units
+ [''])
245 def length_or_percentage_or_unitless(argument
, default
=''):
247 Return normalized string of a length or percentage unit.
249 Add <default> if there is no unit. Raise ValueError if the argument is not
250 a positive measure of one of the valid CSS units (or without unit).
252 >>> length_or_percentage_or_unitless('3 pt')
254 >>> length_or_percentage_or_unitless('3%', 'em')
256 >>> length_or_percentage_or_unitless('3')
258 >>> length_or_percentage_or_unitless('3', 'px')
262 return get_measure(argument
, length_units
+ ['%'])
265 return get_measure(argument
, ['']) + default
267 # raise ValueError with list of valid units:
268 return get_measure(argument
, length_units
+ ['%'])
270 def class_option(argument
):
272 Convert the argument into a list of ID-compatible strings and return it.
273 (Directive option conversion function.)
275 Raise ``ValueError`` if no argument is found.
278 raise ValueError('argument required but none supplied')
279 names
= argument
.split()
282 class_name
= nodes
.make_id(name
)
284 raise ValueError('cannot make "%s" into a class name' % name
)
285 class_names
.append(class_name
)
288 unicode_pattern
= re
.compile(
289 r
'(?:0x|x|\\x|U\+?|\\u)([0-9a-f]+)$|&#x([0-9a-f]+);$', re
.IGNORECASE
)
291 def unicode_code(code
):
293 Convert a Unicode character code to a Unicode character.
294 (Directive option conversion function.)
296 Codes may be decimal numbers, hexadecimal numbers (prefixed by ``0x``,
297 ``x``, ``\x``, ``U+``, ``u``, or ``\u``; e.g. ``U+262E``), or XML-style
298 numeric character entities (e.g. ``☮``). Other text remains as-is.
300 Raise ValueError for illegal Unicode code values.
303 if code
.isdigit(): # decimal number
304 return unichr(int(code
))
306 match
= unicode_pattern
.match(code
)
307 if match
: # hex number
308 value
= match
.group(1) or match
.group(2)
309 return unichr(int(value
, 16))
312 except OverflowError, detail
:
313 raise ValueError('code too large (%s)' % detail
)
315 def single_char_or_unicode(argument
):
317 A single character is returned as-is. Unicode characters codes are
318 converted as in `unicode_code`. (Directive option conversion function.)
320 char
= unicode_code(argument
)
322 raise ValueError('%r invalid; must be a single character or '
323 'a Unicode code' % char
)
326 def single_char_or_whitespace_or_unicode(argument
):
328 As with `single_char_or_unicode`, but "tab" and "space" are also supported.
329 (Directive option conversion function.)
331 if argument
== 'tab':
333 elif argument
== 'space':
336 char
= single_char_or_unicode(argument
)
339 def positive_int(argument
):
341 Converts the argument into an integer. Raises ValueError for negative,
342 zero, or non-integer values. (Directive option conversion function.)
344 value
= int(argument
)
346 raise ValueError('negative or zero value; must be positive')
349 def positive_int_list(argument
):
351 Converts a space- or comma-separated list of values into a Python list
353 (Directive option conversion function.)
355 Raises ValueError for non-positive-integer values.
358 entries
= argument
.split(',')
360 entries
= argument
.split()
361 return [positive_int(entry
) for entry
in entries
]
363 def encoding(argument
):
365 Verfies the encoding argument by lookup.
366 (Directive option conversion function.)
368 Raises ValueError for unknown encodings.
371 codecs
.lookup(argument
)
373 raise ValueError('unknown encoding: "%s"' % argument
)
376 def choice(argument
, values
):
378 Directive option utility function, supplied to enable options whose
379 argument must be a member of a finite set of possible values (must be
380 lower case). A custom conversion function must be written to use it. For
383 from docutils.parsers.rst import directives
386 return directives.choice(argument, ('yes', 'no'))
388 Raise ``ValueError`` if no argument is found or if the argument's value is
389 not valid (not an entry in the supplied list).
392 value
= argument
.lower().strip()
393 except AttributeError:
394 raise ValueError('must supply an argument; choose from %s'
395 % format_values(values
))
399 raise ValueError('"%s" unknown; choose from %s'
400 % (argument
, format_values(values
)))
402 def format_values(values
):
403 return '%s, or "%s"' % (', '.join(['"%s"' % s
for s
in values
[:-1]]),
406 def value_or(values
, other
):
408 The argument can be any of `values` or `argument_type`.
410 def auto_or_other(argument
):
411 if argument
in values
:
414 return other(argument
)