|
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]) |