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

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

192 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 

79 

80from docutils import nodes 

81from docutils.parsers.rst import directives 

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

83from docutils.utils.code_analyzer import Lexer, LexerError 

84 

85DEFAULT_INTERPRETED_ROLE = 'title-reference' 

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

87 

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

89""" 

90 

91_role_registry = {} 

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

93 

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

95""" 

96 

97_roles = {} 

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

99functions.""" 

100 

101 

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

103 """ 

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

105 with a list of system messages. 

106 

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

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

109 list of system messages. 

110 """ 

111 normname = role_name.lower() 

112 messages = [] 

113 msg_text = [] 

114 

115 if normname in _roles: 

116 return _roles[normname], messages 

117 

118 if role_name: 

119 canonicalname = None 

120 try: 

121 canonicalname = language_module.roles[normname] 

122 except AttributeError as error: 

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

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

125 except KeyError: 

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

127 % (role_name, language_module.__name__)) 

128 else: 

129 canonicalname = DEFAULT_INTERPRETED_ROLE 

130 

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

132 if not canonicalname: 

133 try: 

134 canonicalname = _fallback_language_module.roles[normname] 

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

136 % role_name) 

137 except KeyError: 

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

139 % role_name) 

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

141 canonicalname = normname 

142 

143 # Collect any messages that we generated. 

144 if msg_text: 

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

146 messages.append(message) 

147 

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

149 if canonicalname in _role_registry: 

150 role_fn = _role_registry[canonicalname] 

151 register_local_role(normname, role_fn) 

152 return role_fn, messages 

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

154 

155 

156def register_canonical_role(name, role_fn): 

157 """ 

158 Register an interpreted text role by its canonical name. 

159 

160 :Parameters: 

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

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

163 """ 

164 set_implicit_options(role_fn) 

165 _role_registry[name.lower()] = role_fn 

166 

167 

168def register_local_role(name, role_fn): 

169 """ 

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

171 

172 :Parameters: 

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

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

175 """ 

176 set_implicit_options(role_fn) 

177 _roles[name.lower()] = role_fn 

178 

179 

180def set_implicit_options(role_fn): 

181 """ 

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

183 disabled. 

184 """ 

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

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

187 elif 'class' not in role_fn.options: 

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

189 

190 

191def register_generic_role(canonical_name, node_class): 

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

193 role = GenericRole(canonical_name, node_class) 

194 register_canonical_role(canonical_name, role) 

195 

196 

197class GenericRole: 

198 """ 

199 Generic interpreted text role. 

200 

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

202 """ 

203 

204 def __init__(self, role_name, node_class): 

205 self.name = role_name 

206 self.node_class = node_class 

207 

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

209 options=None, content=None): 

210 options = normalized_role_options(options) 

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

212 

213 

214class CustomRole: 

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

216 

217 def __init__(self, role_name, base_role, options=None, content=None): 

218 self.name = role_name 

219 self.base_role = base_role 

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

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

222 self.supplied_options = options 

223 self.supplied_content = content 

224 

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

226 options=None, content=None): 

227 opts = normalized_role_options(self.supplied_options) 

228 try: 

229 opts.update(options) 

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

231 pass 

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

233 supplied_content = self.supplied_content or [] 

234 content = content or [] 

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

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

237 options=opts, 

238 content=supplied_content+delimiter+content) 

239 

240 

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

242 options=None, content=None): 

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

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

245 # recursively call inliner.nested_parse(). 

246 options = normalized_role_options(options) 

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

248 

249 

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

251 

252 

253###################################################################### 

254# Define and register the standard roles: 

255###################################################################### 

256 

257register_generic_role('abbreviation', nodes.abbreviation) 

258register_generic_role('acronym', nodes.acronym) 

259register_generic_role('emphasis', nodes.emphasis) 

260register_generic_role('literal', nodes.literal) 

261register_generic_role('strong', nodes.strong) 

262register_generic_role('subscript', nodes.subscript) 

263register_generic_role('superscript', nodes.superscript) 

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

265 

266 

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

268 options=None, content=None): 

269 options = normalized_role_options(options) 

270 try: 

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

272 if pepnum < 0 or pepnum > 9999: 

273 raise ValueError 

274 except ValueError: 

275 msg = inliner.reporter.error( 

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

277 % text, line=lineno) 

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

279 return [prb], [msg] 

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

281 ref = (inliner.document.settings.pep_base_url 

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

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

284 

285 

286register_canonical_role('pep-reference', pep_reference_role) 

287 

288 

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

290 options=None, content=None): 

291 options = normalized_role_options(options) 

292 if "#" in text: 

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

294 else: 

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

296 try: 

297 rfcnum = int(rfcnum) 

298 if rfcnum < 1: 

299 raise ValueError 

300 except ValueError: 

301 msg = inliner.reporter.error( 

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

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

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

305 return [prb], [msg] 

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

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

308 if section is not None: 

309 ref += "#" + section 

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

311 return [node], [] 

312 

313 

314register_canonical_role('rfc-reference', rfc_reference_role) 

315 

316 

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

318 options = normalized_role_options(options) 

319 if not inliner.document.settings.raw_enabled: 

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

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

322 return [prb], [msg] 

323 if 'format' not in options: 

324 msg = inliner.reporter.error( 

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

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

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

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

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

330 return [prb], [msg] 

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

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

333 return [node], [] 

334 

335 

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

337 

338register_canonical_role('raw', raw_role) 

339 

340 

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

342 options=None, content=None): 

343 options = normalized_role_options(options) 

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

345 classes = ['code'] 

346 if 'classes' in options: 

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

348 if language and language not in classes: 

349 classes.append(language) 

350 try: 

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

352 inliner.document.settings.syntax_highlight) 

353 except LexerError as error: 

354 msg = inliner.reporter.warning(error) 

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

356 return [prb], [msg] 

357 

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

359 

360 # analyse content and add nodes for every token 

361 for classes, value in tokens: 

362 if classes: 

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

364 else: 

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

366 node += nodes.Text(value) 

367 

368 return [node], [] 

369 

370 

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

372 

373register_canonical_role('code', code_role) 

374 

375 

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

377 options=None, content=None): 

378 options = normalized_role_options(options) 

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

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

381 return [node], [] 

382 

383 

384register_canonical_role('math', math_role) 

385 

386 

387###################################################################### 

388# Register roles that are currently unimplemented. 

389###################################################################### 

390 

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

392 options=None, content=None): 

393 msg = inliner.reporter.error( 

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

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

396 return [prb], [msg] 

397 

398 

399register_canonical_role('index', unimplemented_role) 

400register_canonical_role('named-reference', unimplemented_role) 

401register_canonical_role('anonymous-reference', unimplemented_role) 

402register_canonical_role('uri-reference', unimplemented_role) 

403register_canonical_role('footnote-reference', unimplemented_role) 

404register_canonical_role('citation-reference', unimplemented_role) 

405register_canonical_role('substitution-reference', unimplemented_role) 

406register_canonical_role('target', unimplemented_role) 

407 

408# This should remain unimplemented, for testing purposes: 

409register_canonical_role('restructuredtext-unimplemented-role', 

410 unimplemented_role) 

411 

412 

413def set_classes(options): 

414 """Deprecated. Obsoleted by ``normalized_role_options()``.""" 

415 # TODO: Change use in directives.py and uncomment. 

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

417 # ' by roles.normalized_role_options() and will be removed' 

418 # ' in Docutils 0.21 or later', DeprecationWarning, stacklevel=2) 

419 if options and 'class' in options: 

420 assert 'classes' not in options 

421 options['classes'] = options['class'] 

422 del options['class'] 

423 

424 

425def normalized_role_options(options): 

426 """ 

427 Return normalized dictionary of role options. 

428 

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

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

431 """ 

432 if options is None: 

433 return {} 

434 result = options.copy() 

435 if 'class' in result: 

436 assert 'classes' not in result 

437 result['classes'] = result['class'] 

438 del result['class'] 

439 return result