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