Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/docutils/parsers/rst/directives/__init__.py: 20%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

175 statements  

1# $Id$ 

2# Author: David Goodger <goodger@python.org> 

3# Copyright: This module has been placed in the public domain. 

4 

5""" 

6This package contains directive implementation modules. 

7""" 

8 

9__docformat__ = 'reStructuredText' 

10 

11import re 

12import codecs 

13from importlib import import_module 

14 

15from docutils import nodes, parsers 

16from docutils.utils import split_escaped_whitespace, escape2null 

17from docutils.parsers.rst.languages import en as _fallback_language_module 

18 

19 

20_directive_registry = { 

21 'attention': ('admonitions', 'Attention'), 

22 'caution': ('admonitions', 'Caution'), 

23 'code': ('body', 'CodeBlock'), 

24 'danger': ('admonitions', 'Danger'), 

25 'error': ('admonitions', 'Error'), 

26 'important': ('admonitions', 'Important'), 

27 'note': ('admonitions', 'Note'), 

28 'tip': ('admonitions', 'Tip'), 

29 'hint': ('admonitions', 'Hint'), 

30 'warning': ('admonitions', 'Warning'), 

31 'admonition': ('admonitions', 'Admonition'), 

32 'sidebar': ('body', 'Sidebar'), 

33 'topic': ('body', 'Topic'), 

34 'line-block': ('body', 'LineBlock'), 

35 'parsed-literal': ('body', 'ParsedLiteral'), 

36 'math': ('body', 'MathBlock'), 

37 'rubric': ('body', 'Rubric'), 

38 'epigraph': ('body', 'Epigraph'), 

39 'highlights': ('body', 'Highlights'), 

40 'pull-quote': ('body', 'PullQuote'), 

41 'compound': ('body', 'Compound'), 

42 'container': ('body', 'Container'), 

43 # 'questions': ('body', 'question_list'), 

44 'table': ('tables', 'RSTTable'), 

45 'csv-table': ('tables', 'CSVTable'), 

46 'list-table': ('tables', 'ListTable'), 

47 'image': ('images', 'Image'), 

48 'figure': ('images', 'Figure'), 

49 'contents': ('parts', 'Contents'), 

50 'sectnum': ('parts', 'Sectnum'), 

51 'header': ('parts', 'Header'), 

52 'footer': ('parts', 'Footer'), 

53 # 'footnotes': ('parts', 'footnotes'), 

54 # 'citations': ('parts', 'citations'), 

55 'target-notes': ('references', 'TargetNotes'), 

56 'meta': ('misc', 'Meta'), 

57 # 'imagemap': ('html', 'imagemap'), 

58 'raw': ('misc', 'Raw'), 

59 'include': ('misc', 'Include'), 

60 'replace': ('misc', 'Replace'), 

61 'unicode': ('misc', 'Unicode'), 

62 'class': ('misc', 'Class'), 

63 'role': ('misc', 'Role'), 

64 'default-role': ('misc', 'DefaultRole'), 

65 'title': ('misc', 'Title'), 

66 'date': ('misc', 'Date'), 

67 'restructuredtext-test-directive': ('misc', 'TestDirective'), 

68 } 

69"""Mapping of directive name to (module name, class name). The 

70directive name is canonical & must be lowercase. Language-dependent 

71names are defined in the ``language`` subpackage.""" 

72 

73_directives = {} 

74"""Cache of imported directives.""" 

75 

76 

77def directive(directive_name, language_module, document): 

78 """ 

79 Locate and return a directive function from its language-dependent name. 

80 If not found in the current language, check English. Return None if the 

81 named directive cannot be found. 

82 """ 

83 normname = directive_name.lower() 

84 messages = [] 

85 msg_text = [] 

86 if normname in _directives: 

87 return _directives[normname], messages 

88 canonicalname = None 

89 try: 

90 canonicalname = language_module.directives[normname] 

91 except AttributeError as error: 

92 msg_text.append('Problem retrieving directive entry from language ' 

93 'module %r: %s.' % (language_module, error)) 

94 except KeyError: 

95 msg_text.append('No directive entry for "%s" in module "%s".' 

96 % (directive_name, language_module.__name__)) 

97 if not canonicalname: 

98 try: 

99 canonicalname = _fallback_language_module.directives[normname] 

100 msg_text.append('Using English fallback for directive "%s".' 

101 % directive_name) 

102 except KeyError: 

103 msg_text.append('Trying "%s" as canonical directive name.' 

104 % directive_name) 

105 # The canonical name should be an English name, but just in case: 

106 canonicalname = normname 

107 if msg_text: 

108 message = document.reporter.info( 

109 '\n'.join(msg_text), line=document.current_line) 

110 messages.append(message) 

111 try: 

112 modulename, classname = _directive_registry[canonicalname] 

113 except KeyError: 

114 # Error handling done by caller. 

115 return None, messages 

116 try: 

117 module = import_module('docutils.parsers.rst.directives.'+modulename) 

118 except ImportError as detail: 

119 messages.append(document.reporter.error( 

120 'Error importing directive module "%s" (directive "%s"):\n%s' 

121 % (modulename, directive_name, detail), 

122 line=document.current_line)) 

123 return None, messages 

124 try: 

125 directive = getattr(module, classname) 

126 _directives[normname] = directive 

127 except AttributeError: 

128 messages.append(document.reporter.error( 

129 'No directive class "%s" in module "%s" (directive "%s").' 

130 % (classname, modulename, directive_name), 

131 line=document.current_line)) 

132 return None, messages 

133 return directive, messages 

134 

135 

136def 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 

144# conversion functions for `Directive.option_spec` 

145# ------------------------------------------------ 

146# 

147# see also `parsers.rst.Directive` in ../__init__.py. 

148 

149 

150def flag(argument): 

151 """ 

152 Check for a valid flag option (no argument) and return ``None``. 

153 (Directive option conversion function.) 

154 

155 Raise ``ValueError`` if an argument is found. 

156 """ 

157 if argument and argument.strip(): 

158 raise ValueError('no argument is allowed; "%s" supplied' % argument) 

159 else: 

160 return None 

161 

162 

163def unchanged_required(argument): 

164 """ 

165 Return the argument text, unchanged. 

166 (Directive option conversion function.) 

167 

168 Raise ``ValueError`` if no argument is found. 

169 """ 

170 if argument is None: 

171 raise ValueError('argument required but none supplied') 

172 else: 

173 return argument # unchanged! 

174 

175 

176def unchanged(argument): 

177 """ 

178 Return the argument text, unchanged. 

179 (Directive option conversion function.) 

180 

181 No argument implies empty string (""). 

182 """ 

183 if argument is None: 

184 return '' 

185 else: 

186 return argument # unchanged! 

187 

188 

189def path(argument): 

190 """ 

191 Return the path argument unwrapped (with newlines removed). 

192 (Directive option conversion function.) 

193 

194 Raise ``ValueError`` if no argument is found. 

195 """ 

196 if argument is None: 

197 raise ValueError('argument required but none supplied') 

198 else: 

199 return ''.join(s.strip() for s in argument.splitlines()) 

200 

201 

202def uri(argument): 

203 """ 

204 Return the URI argument with unescaped whitespace removed. 

205 (Directive option conversion function.) 

206 

207 Raise ``ValueError`` if no argument is found. 

208 """ 

209 if argument is None: 

210 raise ValueError('argument required but none supplied') 

211 else: 

212 parts = split_escaped_whitespace(escape2null(argument)) 

213 return ' '.join(''.join(nodes.unescape(part).split()) 

214 for part in parts) 

215 

216 

217def nonnegative_int(argument): 

218 """ 

219 Check for a nonnegative integer argument; raise ``ValueError`` if not. 

220 (Directive option conversion function.) 

221 """ 

222 value = int(argument) 

223 if value < 0: 

224 raise ValueError('negative value; must be positive or zero') 

225 return value 

226 

227 

228def percentage(argument): 

229 """ 

230 Check for an integer percentage value with optional percent sign. 

231 (Directive option conversion function.) 

232 """ 

233 try: 

234 argument = argument.rstrip(' %') 

235 except AttributeError: 

236 pass 

237 return nonnegative_int(argument) 

238 

239 

240length_units = ['em', 'ex', 'px', 'in', 'cm', 'mm', 'pt', 'pc'] 

241 

242 

243def get_measure(argument, units): 

244 """ 

245 Check for a positive argument of one of the units and return a 

246 normalized string of the form "<value><unit>" (without space in 

247 between). 

248 (Directive option conversion function.) 

249 

250 To be called from directive option conversion functions. 

251 """ 

252 match = re.match(r'^([0-9.]+) *(%s)$' % '|'.join(units), argument) 

253 try: 

254 float(match.group(1)) 

255 except (AttributeError, ValueError): 

256 raise ValueError( 

257 'not a positive measure of one of the following units:\n%s' 

258 % ' '.join('"%s"' % i for i in units)) 

259 return match.group(1) + match.group(2) 

260 

261 

262def length_or_unitless(argument): 

263 return get_measure(argument, length_units + ['']) 

264 

265 

266def length_or_percentage_or_unitless(argument, default=''): 

267 """ 

268 Return normalized string of a length or percentage unit. 

269 (Directive option conversion function.) 

270 

271 Add <default> if there is no unit. Raise ValueError if the argument is not 

272 a positive measure of one of the valid CSS units (or without unit). 

273 

274 >>> length_or_percentage_or_unitless('3 pt') 

275 '3pt' 

276 >>> length_or_percentage_or_unitless('3%', 'em') 

277 '3%' 

278 >>> length_or_percentage_or_unitless('3') 

279 '3' 

280 >>> length_or_percentage_or_unitless('3', 'px') 

281 '3px' 

282 """ 

283 try: 

284 return get_measure(argument, length_units + ['%']) 

285 except ValueError: 

286 try: 

287 return get_measure(argument, ['']) + default 

288 except ValueError: 

289 # raise ValueError with list of valid units: 

290 return get_measure(argument, length_units + ['%']) 

291 

292 

293def class_option(argument): 

294 """ 

295 Convert the argument into a list of ID-compatible strings and return it. 

296 (Directive option conversion function.) 

297 

298 Raise ``ValueError`` if no argument is found. 

299 """ 

300 if argument is None: 

301 raise ValueError('argument required but none supplied') 

302 names = argument.split() 

303 class_names = [] 

304 for name in names: 

305 class_name = nodes.make_id(name) 

306 if not class_name: 

307 raise ValueError('cannot make "%s" into a class name' % name) 

308 class_names.append(class_name) 

309 return class_names 

310 

311 

312unicode_pattern = re.compile( 

313 r'(?:0x|x|\\x|U\+?|\\u)([0-9a-f]+)$|&#x([0-9a-f]+);$', re.IGNORECASE) 

314 

315 

316def unicode_code(code): 

317 r""" 

318 Convert a Unicode character code to a Unicode character. 

319 (Directive option conversion function.) 

320 

321 Codes may be decimal numbers, hexadecimal numbers (prefixed by ``0x``, 

322 ``x``, ``\x``, ``U+``, ``u``, or ``\u``; e.g. ``U+262E``), or XML-style 

323 numeric character entities (e.g. ``&#x262E;``). Other text remains as-is. 

324 

325 Raise ValueError for illegal Unicode code values. 

326 """ 

327 try: 

328 if code.isdigit(): # decimal number 

329 return chr(int(code)) 

330 else: 

331 match = unicode_pattern.match(code) 

332 if match: # hex number 

333 value = match.group(1) or match.group(2) 

334 return chr(int(value, 16)) 

335 else: # other text 

336 return code 

337 except OverflowError as detail: 

338 raise ValueError('code too large (%s)' % detail) 

339 

340 

341def single_char_or_unicode(argument): 

342 """ 

343 A single character is returned as-is. Unicode character codes are 

344 converted as in `unicode_code`. (Directive option conversion function.) 

345 """ 

346 char = unicode_code(argument) 

347 if len(char) > 1: 

348 raise ValueError('%r invalid; must be a single character or ' 

349 'a Unicode code' % char) 

350 return char 

351 

352 

353def single_char_or_whitespace_or_unicode(argument): 

354 """ 

355 As with `single_char_or_unicode`, but "tab" and "space" are also supported. 

356 (Directive option conversion function.) 

357 """ 

358 if argument == 'tab': 

359 char = '\t' 

360 elif argument == 'space': 

361 char = ' ' 

362 else: 

363 char = single_char_or_unicode(argument) 

364 return char 

365 

366 

367def positive_int(argument): 

368 """ 

369 Converts the argument into an integer. Raises ValueError for negative, 

370 zero, or non-integer values. (Directive option conversion function.) 

371 """ 

372 value = int(argument) 

373 if value < 1: 

374 raise ValueError('negative or zero value; must be positive') 

375 return value 

376 

377 

378def positive_int_list(argument): 

379 """ 

380 Converts a space- or comma-separated list of values into a Python list 

381 of integers. 

382 (Directive option conversion function.) 

383 

384 Raises ValueError for non-positive-integer values. 

385 """ 

386 if ',' in argument: 

387 entries = argument.split(',') 

388 else: 

389 entries = argument.split() 

390 return [positive_int(entry) for entry in entries] 

391 

392 

393def encoding(argument): 

394 """ 

395 Verifies the encoding argument by lookup. 

396 (Directive option conversion function.) 

397 

398 Raises ValueError for unknown encodings. 

399 """ 

400 try: 

401 codecs.lookup(argument) 

402 except LookupError: 

403 raise ValueError('unknown encoding: "%s"' % argument) 

404 return argument 

405 

406 

407def choice(argument, values): 

408 """ 

409 Directive option utility function, supplied to enable options whose 

410 argument must be a member of a finite set of possible values (must be 

411 lower case). A custom conversion function must be written to use it. For 

412 example:: 

413 

414 from docutils.parsers.rst import directives 

415 

416 def yesno(argument): 

417 return directives.choice(argument, ('yes', 'no')) 

418 

419 Raise ``ValueError`` if no argument is found or if the argument's value is 

420 not valid (not an entry in the supplied list). 

421 """ 

422 try: 

423 value = argument.lower().strip() 

424 except AttributeError: 

425 raise ValueError('must supply an argument; choose from %s' 

426 % format_values(values)) 

427 if value in values: 

428 return value 

429 else: 

430 raise ValueError('"%s" unknown; choose from %s' 

431 % (argument, format_values(values))) 

432 

433 

434def format_values(values): 

435 return '%s, or "%s"' % (', '.join('"%s"' % s for s in values[:-1]), 

436 values[-1]) 

437 

438 

439def value_or(values, other): 

440 """ 

441 Directive option conversion function. 

442 

443 The argument can be any of `values` or `argument_type`. 

444 """ 

445 def auto_or_other(argument): 

446 if argument in values: 

447 return argument 

448 else: 

449 return other(argument) 

450 return auto_or_other 

451 

452 

453def parser_name(argument): 

454 """ 

455 Return a docutils parser whose name matches the argument. 

456 (Directive option conversion function.) 

457 

458 Return `None`, if the argument evaluates to `False`. 

459 Raise `ValueError` if importing the parser module fails. 

460 """ 

461 if not argument: 

462 return None 

463 try: 

464 return parsers.get_parser_class(argument) 

465 except ImportError as err: 

466 raise ValueError(str(err))