Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.10/site-packages/docutils/parsers/rst/roles.py: 44%

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

200 statements  

1# $Id$ 

2# Author: Edward Loper <edloper@gradient.cis.upenn.edu> 

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

4 

5""" 

6This module defines standard interpreted text role functions, a registry for 

7interpreted text roles, and an API for adding to and retrieving from the 

8registry. See also `Creating reStructuredText Interpreted Text Roles`__. 

9 

10__ https://docutils.sourceforge.io/docs/ref/rst/roles.html 

11 

12 

13The interface for interpreted role functions is as follows:: 

14 

15 def role_fn(name, rawtext, text, lineno, inliner, 

16 options=None, content=None): 

17 code... 

18 

19 # Set function attributes for customization: 

20 role_fn.options = ... 

21 role_fn.content = ... 

22 

23Parameters: 

24 

25- ``name`` is the local name of the interpreted text role, the role name 

26 actually used in the document. 

27 

28- ``rawtext`` is a string containing the entire interpreted text construct. 

29 Return it as a ``problematic`` node linked to a system message if there is a 

30 problem. 

31 

32- ``text`` is the interpreted text content, with backslash escapes converted 

33 to nulls (``\x00``). 

34 

35- ``lineno`` is the line number where the text block containing the 

36 interpreted text begins. 

37 

38- ``inliner`` is the Inliner object that called the role function. 

39 It defines the following useful attributes: ``reporter``, 

40 ``problematic``, ``memo``, ``parent``, ``document``. 

41 

42- ``options``: A dictionary of directive options for customization, to be 

43 interpreted by the role function. Used for additional attributes for the 

44 generated elements and other functionality. 

45 

46- ``content``: A list of strings, the directive content for customization 

47 ("role" directive). To be interpreted by the role function. 

48 

49Function attributes for customization, interpreted by the "role" directive: 

50 

51- ``options``: A dictionary, mapping known option names to conversion 

52 functions such as `int` or `float`. ``None`` or an empty dict implies no 

53 options to parse. Several directive option conversion functions are defined 

54 in the `directives` module. 

55 

56 All role functions implicitly support the "class" option, unless disabled 

57 with an explicit ``{'class': None}``. 

58 

59- ``content``: A boolean; true if content is allowed. Client code must handle 

60 the case where content is required but not supplied (an empty content list 

61 will be supplied). 

62 

63Note that unlike directives, the "arguments" function attribute is not 

64supported for role customization. Directive arguments are handled by the 

65"role" directive itself. 

66 

67Interpreted role functions return a tuple of two values: 

68 

69- A list of nodes which will be inserted into the document tree at the 

70 point where the interpreted role was encountered (can be an empty 

71 list). 

72 

73- A list of system messages, which will be inserted into the document tree 

74 immediately after the end of the current inline block (can also be empty). 

75""" 

76 

77__docformat__ = 'reStructuredText' 

78 

79import warnings 

80 

81from docutils import nodes 

82from docutils.parsers.rst import directives 

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

84from docutils.utils.code_analyzer import Lexer, LexerError 

85 

86DEFAULT_INTERPRETED_ROLE = 'title-reference' 

87"""The canonical name of the default interpreted role. 

88 

89This role is used when no role is specified for a piece of interpreted text. 

90""" 

91 

92_role_registry = {} 

93"""Mapping of canonical role names to role functions. 

94 

95Language-dependent role names are defined in the ``language`` subpackage. 

96""" 

97 

98_roles = {} 

99"""Mapping of local or language-dependent interpreted text role names to role 

100functions.""" 

101 

102 

103def role(role_name, language_module, lineno, reporter): 

104 """ 

105 Locate and return a role function from its language-dependent name, along 

106 with a list of system messages. 

107 

108 If the role is not found in the current language, check English. Return a 

109 2-tuple: role function (``None`` if the named role cannot be found) and a 

110 list of system messages. 

111 """ 

112 normname = role_name.lower() 

113 messages = [] 

114 msg_text = [] 

115 

116 if normname in _roles: 

117 return _roles[normname], messages 

118 

119 if role_name: 

120 canonicalname = None 

121 try: 

122 canonicalname = language_module.roles[normname] 

123 except AttributeError as error: 

124 msg_text.append('Problem retrieving role entry from language ' 

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

126 except KeyError: 

127 msg_text.append('No role entry for "%s" in module "%s".' 

128 % (role_name, language_module.__name__)) 

129 else: 

130 canonicalname = DEFAULT_INTERPRETED_ROLE 

131 

132 # If we didn't find it, try English as a fallback. 

133 if not canonicalname: 

134 try: 

135 canonicalname = _fallback_language_module.roles[normname] 

136 msg_text.append('Using English fallback for role "%s".' 

137 % role_name) 

138 except KeyError: 

139 msg_text.append('Trying "%s" as canonical role name.' 

140 % role_name) 

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

142 canonicalname = normname 

143 

144 # Collect any messages that we generated. 

145 if msg_text: 

146 message = reporter.info('\n'.join(msg_text), line=lineno) 

147 messages.append(message) 

148 

149 # Look the role up in the registry, and return it. 

150 if canonicalname in _role_registry: 

151 role_fn = _role_registry[canonicalname] 

152 register_local_role(normname, role_fn) 

153 return role_fn, messages 

154 return None, messages # Error message will be generated by caller. 

155 

156 

157def register_canonical_role(name, role_fn) -> None: 

158 """ 

159 Register an interpreted text role by its canonical name. 

160 

161 :Parameters: 

162 - `name`: The canonical name of the interpreted role. 

163 - `role_fn`: The role function. See the module docstring. 

164 """ 

165 set_implicit_options(role_fn) 

166 _role_registry[name.lower()] = role_fn 

167 

168 

169def register_local_role(name, role_fn) -> None: 

170 """ 

171 Register an interpreted text role by its local or language-dependent name. 

172 

173 :Parameters: 

174 - `name`: The local or language-dependent name of the interpreted role. 

175 - `role_fn`: The role function. See the module docstring. 

176 """ 

177 set_implicit_options(role_fn) 

178 _roles[name.lower()] = role_fn 

179 

180 

181def set_implicit_options(role_fn) -> None: 

182 """ 

183 Add customization options to role functions, unless explicitly set or 

184 disabled. 

185 """ 

186 if not hasattr(role_fn, 'options') or role_fn.options is None: 

187 role_fn.options = {'class': directives.class_option} 

188 elif 'class' not in role_fn.options: 

189 role_fn.options['class'] = directives.class_option 

190 

191 

192def register_generic_role(canonical_name, node_class) -> None: 

193 """For roles which simply wrap a given `node_class` around the text.""" 

194 role = GenericRole(canonical_name, node_class) 

195 register_canonical_role(canonical_name, role) 

196 

197 

198class GenericRole: 

199 """ 

200 Generic interpreted text role. 

201 

202 The interpreted text is simply wrapped with the provided node class. 

203 """ 

204 

205 def __init__(self, role_name, node_class) -> None: 

206 self.name = role_name 

207 self.node_class = node_class 

208 

209 def __call__(self, role, rawtext, text, lineno, inliner, 

210 options=None, content=None): 

211 options = normalize_options(options) 

212 return [self.node_class(rawtext, text, **options)], [] 

213 

214 

215class CustomRole: 

216 """Wrapper for custom interpreted text roles.""" 

217 

218 def __init__( 

219 self, role_name, base_role, options=None, content=None, 

220 ) -> None: 

221 self.name = role_name 

222 self.base_role = base_role 

223 self.options = getattr(base_role, 'options', None) 

224 self.content = getattr(base_role, 'content', None) 

225 self.supplied_options = options 

226 self.supplied_content = content 

227 

228 def __call__(self, role, rawtext, text, lineno, inliner, 

229 options=None, content=None): 

230 opts = normalize_options(self.supplied_options) 

231 try: 

232 opts.update(options) 

233 except TypeError: # options may be ``None`` 

234 pass 

235 # pass concatenation of content from instance and call argument: 

236 supplied_content = self.supplied_content or [] 

237 content = content or [] 

238 delimiter = ['\n'] if supplied_content and content else [] 

239 return self.base_role(role, rawtext, text, lineno, inliner, 

240 options=opts, 

241 content=supplied_content+delimiter+content) 

242 

243 

244def generic_custom_role(role, rawtext, text, lineno, inliner, 

245 options=None, content=None): 

246 """Base for custom roles if no other base role is specified.""" 

247 # Once nested inline markup is implemented, this and other methods should 

248 # recursively call inliner.nested_parse(). 

249 options = normalize_options(options) 

250 return [nodes.inline(rawtext, text, **options)], [] 

251 

252 

253generic_custom_role.options = {'class': directives.class_option} 

254 

255 

256###################################################################### 

257# Define and register the standard roles: 

258###################################################################### 

259 

260register_generic_role('abbreviation', nodes.abbreviation) 

261register_generic_role('acronym', nodes.acronym) 

262register_generic_role('emphasis', nodes.emphasis) 

263register_generic_role('literal', nodes.literal) 

264register_generic_role('strong', nodes.strong) 

265register_generic_role('subscript', nodes.subscript) 

266register_generic_role('superscript', nodes.superscript) 

267register_generic_role('title-reference', nodes.title_reference) 

268 

269 

270def pep_reference_role(role, rawtext, text, lineno, inliner, 

271 options=None, content=None): 

272 options = normalize_options(options) 

273 try: 

274 pepnum = int(nodes.unescape(text)) 

275 if pepnum < 0 or pepnum > 9999: 

276 raise ValueError 

277 except ValueError: 

278 msg = inliner.reporter.error( 

279 'PEP number must be a number from 0 to 9999; "%s" is invalid.' 

280 % text, line=lineno) 

281 prb = inliner.problematic(rawtext, rawtext, msg) 

282 return [prb], [msg] 

283 # Base URL mainly used by inliner.pep_reference; so this is correct: 

284 ref = (inliner.document.settings.pep_base_url 

285 + inliner.document.settings.pep_file_url_template % pepnum) 

286 return [nodes.reference(rawtext, 'PEP ' + text, refuri=ref, **options)], [] 

287 

288 

289register_canonical_role('pep-reference', pep_reference_role) 

290 

291 

292def rfc_reference_role(role, rawtext, text, lineno, inliner, 

293 options=None, content=None): 

294 options = normalize_options(options) 

295 if "#" in text: 

296 rfcnum, section = nodes.unescape(text).split("#", 1) 

297 else: 

298 rfcnum, section = nodes.unescape(text), None 

299 try: 

300 rfcnum = int(rfcnum) 

301 if rfcnum < 1: 

302 raise ValueError 

303 except ValueError: 

304 msg = inliner.reporter.error( 

305 'RFC number must be a number greater than or equal to 1; ' 

306 '"%s" is invalid.' % text, line=lineno) 

307 prb = inliner.problematic(rawtext, rawtext, msg) 

308 return [prb], [msg] 

309 # Base URL mainly used by inliner.rfc_reference, so this is correct: 

310 ref = inliner.document.settings.rfc_base_url + inliner.rfc_url % rfcnum 

311 if section is not None: 

312 ref += "#" + section 

313 node = nodes.reference(rawtext, 'RFC '+str(rfcnum), refuri=ref, **options) 

314 return [node], [] 

315 

316 

317register_canonical_role('rfc-reference', rfc_reference_role) 

318 

319 

320def raw_role(role, rawtext, text, lineno, inliner, options=None, content=None): 

321 options = normalize_options(options) 

322 if not inliner.document.settings.raw_enabled: 

323 msg = inliner.reporter.warning('raw (and derived) roles disabled') 

324 prb = inliner.problematic(rawtext, rawtext, msg) 

325 return [prb], [msg] 

326 if 'format' not in options: 

327 msg = inliner.reporter.error( 

328 'No format (Writer name) is associated with this role: "%s".\n' 

329 'The "raw" role cannot be used directly.\n' 

330 'Instead, use the "role" directive to create a new role with ' 

331 'an associated format.' % role, line=lineno) 

332 prb = inliner.problematic(rawtext, rawtext, msg) 

333 return [prb], [msg] 

334 node = nodes.raw(rawtext, nodes.unescape(text, True), **options) 

335 node.source, node.line = inliner.reporter.get_source_and_line(lineno) 

336 return [node], [] 

337 

338 

339raw_role.options = {'format': directives.unchanged} 

340 

341register_canonical_role('raw', raw_role) 

342 

343 

344def code_role(role, rawtext, text, lineno, inliner, 

345 options=None, content=None): 

346 options = normalize_options(options) 

347 language = options.get('language', '') 

348 classes = ['code'] 

349 if 'classes' in options: 

350 classes.extend(options['classes']) 

351 if language and language not in classes: 

352 classes.append(language) 

353 try: 

354 tokens = Lexer(nodes.unescape(text, True), language, 

355 inliner.document.settings.syntax_highlight) 

356 except LexerError as error: 

357 msg = inliner.reporter.warning(error) 

358 prb = inliner.problematic(rawtext, rawtext, msg) 

359 return [prb], [msg] 

360 

361 node = nodes.literal(rawtext, '', classes=classes) 

362 

363 # analyse content and add nodes for every token 

364 for classes, value in tokens: 

365 if classes: 

366 node += nodes.inline(value, value, classes=classes) 

367 else: 

368 # insert as Text to decrease the verbosity of the output 

369 node += nodes.Text(value) 

370 

371 return [node], [] 

372 

373 

374code_role.options = {'language': directives.unchanged} 

375 

376register_canonical_role('code', code_role) 

377 

378 

379def math_role(role, rawtext, text, lineno, inliner, 

380 options=None, content=None): 

381 options = normalize_options(options) 

382 text = nodes.unescape(text, True) # raw text without inline role markup 

383 node = nodes.math(rawtext, text, **options) 

384 return [node], [] 

385 

386 

387register_canonical_role('math', math_role) 

388 

389 

390###################################################################### 

391# Register roles that are currently unimplemented. 

392###################################################################### 

393 

394def unimplemented_role(role, rawtext, text, lineno, inliner, 

395 options=None, content=None): 

396 msg = inliner.reporter.error( 

397 'Interpreted text role "%s" not implemented.' % role, line=lineno) 

398 prb = inliner.problematic(rawtext, rawtext, msg) 

399 return [prb], [msg] 

400 

401 

402register_canonical_role('index', unimplemented_role) 

403register_canonical_role('named-reference', unimplemented_role) 

404register_canonical_role('anonymous-reference', unimplemented_role) 

405register_canonical_role('uri-reference', unimplemented_role) 

406register_canonical_role('footnote-reference', unimplemented_role) 

407register_canonical_role('citation-reference', unimplemented_role) 

408register_canonical_role('substitution-reference', unimplemented_role) 

409register_canonical_role('target', unimplemented_role) 

410 

411# This should remain unimplemented, for testing purposes: 

412register_canonical_role('restructuredtext-unimplemented-role', 

413 unimplemented_role) 

414 

415 

416def set_classes(options) -> None: 

417 """Deprecated. Obsoleted by ``normalize_options()``.""" 

418 warnings.warn('The auxiliary function roles.set_classes() is obsoleted' 

419 ' by roles.normalize_options() and will be removed' 

420 ' in Docutils 1.0', DeprecationWarning, stacklevel=2) 

421 if options and 'class' in options: 

422 assert 'classes' not in options 

423 options['classes'] = options['class'] 

424 del options['class'] 

425 

426 

427def normalized_role_options(options): 

428 warnings.warn('The auxiliary function roles.normalized_role_options() is ' 

429 'obsoleted by roles.normalize_options() and will be removed' 

430 ' in Docutils 1.0', DeprecationWarning, stacklevel=2) 

431 return normalize_options(options) 

432 

433 

434def normalize_options(options): 

435 """ 

436 Return normalized dictionary of role/directive options. 

437 

438 * ``None`` is replaced by an empty dictionary. 

439 * The key 'class' is renamed to 'classes'. 

440 """ 

441 if options is None: 

442 return {} 

443 n_options = options.copy() 

444 if 'class' in n_options: 

445 assert 'classes' not in n_options 

446 n_options['classes'] = n_options['class'] 

447 del n_options['class'] 

448 return n_options