comparison env/lib/python3.9/site-packages/docutils/parsers/rst/directives/__init__.py @ 0:4f3585e2f14b draft default tip

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