Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/docutils/parsers/rst/__init__.py: 60%

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

98 statements  

1# $Id: __init__.py 10407 2026-10-02 16:05:17Z milde $ 

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

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

4 

5""" 

6This is ``docutils.parsers.rst`` package. It exports a single class, `Parser`, 

7the reStructuredText parser. 

8 

9 

10Usage 

11===== 

12 

131. Create a parser:: 

14 

15 parser = docutils.parsers.rst.Parser() 

16 

17 Several optional arguments may be passed to modify the parser's behavior. 

18 Please see `Customizing the Parser`_ below for details. 

19 

202. Gather input (a multi-line string), by reading a file or the standard 

21 input:: 

22 

23 input = sys.stdin.read() 

24 

253. Create a new empty `docutils.nodes.document` tree:: 

26 

27 document = docutils.utils.new_document(source, settings) 

28 

29 See `docutils.utils.new_document()` for parameter details. 

30 

314. Run the parser, populating the document tree:: 

32 

33 parser.parse(input, document) 

34 

35 

36Parser Overview 

37=============== 

38 

39The reStructuredText parser is implemented as a state machine, examining its 

40input one line at a time. To understand how the parser works, please first 

41become familiar with the `docutils.statemachine` module, then see the 

42`states` module. 

43 

44 

45Customizing the Parser 

46---------------------- 

47 

48Anything that isn't already customizable is that way simply because that type 

49of customizability hasn't been implemented yet. Patches welcome! 

50 

51When instantiating an object of the `Parser` class, two parameters may be 

52passed: ``rfc2822`` and ``inliner``. Pass ``rfc2822=True`` to enable an 

53initial RFC-2822 style header block, parsed as a "field_list" element (with 

54"class" attribute set to "rfc2822"). Currently this is the only body-level 

55element which is customizable without subclassing. (Tip: subclass `Parser` 

56and change its "state_classes" and "initial_state" attributes to refer to new 

57classes. Contact the author if you need more details.) 

58 

59The ``inliner`` parameter takes an instance of `states.Inliner` or a subclass. 

60It handles inline markup recognition. A common extension is the addition of 

61further implicit hyperlinks, like "RFC 2822". This can be done by subclassing 

62`states.Inliner`, adding a new method for the implicit markup, and adding a 

63``(pattern, method)`` pair to the "implicit_dispatch" attribute of the 

64subclass. See `states.Inliner.implicit_inline()` for details. Explicit 

65inline markup can be customized in a `states.Inliner` subclass via the 

66``patterns.initial`` and ``dispatch`` attributes (and new methods as 

67appropriate). 

68""" 

69 

70from __future__ import annotations 

71 

72__docformat__ = 'reStructuredText' 

73 

74import docutils.parsers 

75import docutils.statemachine 

76from docutils.parsers.rst import roles, states 

77from docutils import frontend, nodes 

78from docutils.transforms import universal 

79 

80 

81class Parser(docutils.parsers.Parser): 

82 

83 """The reStructuredText parser.""" 

84 

85 supported = ('rst', 'restructuredtext', 'rest', 'restx', 'rtxt', 'rstx') 

86 """Aliases this parser supports.""" 

87 

88 settings_spec = docutils.parsers.Parser.settings_spec + ( 

89 'reStructuredText Parser Options', 

90 None, 

91 (('Recognize and link to standalone PEP references (like "PEP 258").', 

92 ['--pep-references'], 

93 {'action': 'store_true', 'validator': frontend.validate_boolean}), 

94 ('Base URL for PEP references ' 

95 '(default "https://peps.python.org/").', 

96 ['--pep-base-url'], 

97 {'metavar': '<URL>', 'default': 'https://peps.python.org/', 

98 'validator': frontend.validate_url_trailing_slash}), 

99 ('Template for PEP file part of URL. (default "pep-%04d")', 

100 ['--pep-file-url-template'], 

101 {'metavar': '<URL>', 'default': 'pep-%04d'}), 

102 ('Recognize and link to standalone RFC references (like "RFC 822").', 

103 ['--rfc-references'], 

104 {'action': 'store_true', 'validator': frontend.validate_boolean}), 

105 ('Base URL for RFC references ' 

106 '(default "https://www.rfc-editor.org/info/").', 

107 ['--rfc-base-url'], 

108 {'metavar': '<URL>', 'default': 'https://www.rfc-editor.org/info/', 

109 'validator': frontend.validate_url_trailing_slash}), 

110 ('Set number of spaces for tab expansion (default 8).', 

111 ['--tab-width'], 

112 {'metavar': '<width>', 'type': 'int', 'default': 8, 

113 'validator': frontend.validate_nonnegative_int}), 

114 ('Remove spaces before footnote references.', 

115 ['--trim-footnote-reference-space'], 

116 {'action': 'store_true', 'validator': frontend.validate_boolean}), 

117 ('Leave spaces before footnote references.', 

118 ['--leave-footnote-reference-space'], 

119 {'dest': 'trim_footnote_reference_space', 'action': 'store_false'}), 

120 ('Make the "figure" directive\'s "align" option an alias' 

121 ' to "figalign" (default).', 

122 ['--legacy-figure-align'], 

123 {'action': 'store_true', 'default': True, 

124 'validator': frontend.validate_boolean}), 

125 ('Pass the "figure" directive\'s "align" option to the ' 

126 ' included image.', 

127 ['--figure-image-align'], 

128 {'dest': 'trim_footnote_reference_space', 'action': 'store_false'}), 

129 ('Token name set for parsing code with Pygments: one of ' 

130 '"long", "short", or "none" (no parsing). (default: "long")', 

131 ['--syntax-highlight'], 

132 {'choices': ['long', 'short', 'none'], 

133 'default': 'long', 'metavar': '<format>'}), 

134 ('Change straight quotation marks to typographic form: ' 

135 'one of "yes", "no", "alt[ernative]". (default: "no")', 

136 ['--smart-quotes'], 

137 {'default': False, 'metavar': '<yes/no/alt>', 

138 'validator': frontend.validate_ternary}), 

139 ('Characters to use as "smart quotes" for <language>. ', 

140 ['--smartquotes-locales'], 

141 {'metavar': '<language:quotes[,language:quotes,...]>', 

142 'action': 'append', 

143 'validator': frontend.validate_smartquotes_locales}), 

144 ('Inline markup recognized at word boundaries only ' 

145 '(adjacent to punctuation or whitespace). ' 

146 'Force character-level inline markup recognition with ' 

147 '"\\ " (backslash + space). (default)', 

148 ['--word-level-inline-markup'], 

149 {'dest': 'character_level_inline_markup', 'action': 'store_false', 

150 'validator': frontend.validate_boolean}), 

151 ('Inline markup recognized anywhere, regardless of surrounding ' 

152 'characters. Backslash-escapes must be used to avoid unwanted ' 

153 'markup recognition. Useful for East Asian languages. ', 

154 ['--character-level-inline-markup'], 

155 {'action': 'store_true'}), 

156 ) 

157 ) 

158 

159 config_section = 'restructuredtext parser' 

160 config_section_dependencies = ('parsers',) 

161 

162 def __init__(self, rfc2822=False, inliner=None) -> None: 

163 if rfc2822: 

164 self.initial_state = 'RFC2822Body' 

165 else: 

166 self.initial_state = 'Body' 

167 self.state_classes = states.state_classes 

168 self.inliner = inliner 

169 

170 def get_transforms(self): 

171 return super().get_transforms() + [universal.SmartQuotes] 

172 

173 def parse(self, inputstring, document) -> None: 

174 """Parse `inputstring` and populate `document`, a document tree.""" 

175 self.setup_parse(inputstring, document) 

176 # provide fallbacks in case the document has only generic settings 

177 self.document.settings.setdefault('tab_width', 8) 

178 self.document.settings.setdefault('syntax_highlight', 'long') 

179 self.statemachine = states.RSTStateMachine( 

180 state_classes=self.state_classes, 

181 initial_state=self.initial_state, 

182 debug=document.reporter.debug_flag) 

183 inputlines = docutils.statemachine.string2lines( 

184 inputstring, tab_width=document.settings.tab_width, 

185 convert_whitespace=True) 

186 for i, line in enumerate(inputlines): 

187 if len(line) > self.document.settings.line_length_limit: 

188 error = self.document.reporter.error( 

189 'Line %d exceeds the line-length-limit.'%(i+1)) 

190 self.document.append(error) 

191 break 

192 else: 

193 self.statemachine.run(inputlines, document, inliner=self.inliner) 

194 # restore the "default" default role after parsing a document 

195 if '' in roles._roles: 

196 del roles._roles[''] 

197 self.finish_parse() 

198 

199 

200class DirectiveError(Exception): 

201 

202 """ 

203 Store a message and a system message level. 

204 

205 To be thrown from inside directive code. 

206 

207 Do not instantiate directly -- use `Directive.directive_error()` 

208 instead! 

209 """ 

210 

211 def __init__(self, level, message) -> None: 

212 """Set error `message` and `level`""" 

213 Exception.__init__(self) 

214 self.level = level 

215 self.msg = message 

216 

217 

218class Directive: 

219 

220 """ 

221 Base class for reStructuredText directives. 

222 

223 The following attributes may be set by subclasses. They are 

224 interpreted by the directive parser (which runs the directive 

225 class): 

226 

227 - `required_arguments`: The number of required arguments (default: 

228 0). 

229 

230 - `optional_arguments`: The number of optional arguments (default: 

231 0). 

232 

233 - `final_argument_whitespace`: A boolean, indicating if the final 

234 argument may contain whitespace (default: False). 

235 

236 - `option_spec`: A dictionary, mapping known option names to 

237 conversion functions such as `int` or `float` (default: {}, no 

238 options). Several conversion functions are defined in the 

239 directives/__init__.py module. 

240 

241 Option conversion functions take a single parameter, the option 

242 argument (a string or ``None``), validate it and/or convert it 

243 to the appropriate form. Conversion functions may raise 

244 `ValueError` and `TypeError` exceptions. 

245 

246 - `has_content`: A boolean; True if content is allowed. Client 

247 code must handle the case where content is required but not 

248 supplied (an empty content list will be supplied). 

249 

250 Arguments are normally single whitespace-separated words. The 

251 final argument may contain whitespace and/or newlines if 

252 `final_argument_whitespace` is True. 

253 

254 If the form of the arguments is more complex, specify only one 

255 argument (either required or optional) and set 

256 `final_argument_whitespace` to True; the client code must do any 

257 context-sensitive parsing. 

258 

259 When a directive implementation is being run, the directive class 

260 is instantiated, and the `run()` method is executed. During 

261 instantiation, the following instance variables are set: 

262 

263 - ``name`` is the directive type or name (string). 

264 

265 - ``arguments`` is the list of positional arguments (strings). 

266 

267 - ``options`` is a dictionary mapping option names (strings) to 

268 values (type depends on option conversion functions; see 

269 `option_spec` above). 

270 

271 - ``content`` is a list of strings, the directive content line by line. 

272 

273 - ``lineno`` is the absolute line number of the first line 

274 of the directive. 

275 

276 - ``content_offset`` is the line offset of the first line 

277 of the content from the beginning of the current input. 

278 Used when initiating a nested parse. 

279 

280 - ``block_text`` is a string containing the entire directive. 

281 

282 - ``state`` is the state which called the directive function. 

283 

284 - ``state_machine`` is the state machine which controls the state 

285 which called the directive function. 

286 

287 - ``reporter`` is the state machine's `reporter` instance. 

288 

289 Directive functions return a list of nodes which will be inserted 

290 into the document tree at the point where the directive was 

291 encountered. This can be an empty list if there is nothing to 

292 insert. 

293 

294 For ordinary directives, the list must contain body elements or 

295 structural elements. Some directives are intended specifically 

296 for substitution definitions, and must return a list of `Text` 

297 nodes and/or inline elements (suitable for inline insertion, in 

298 place of the substitution reference). Such directives must verify 

299 substitution definition context, typically using code like this:: 

300 

301 if not isinstance(state, states.SubstitutionDef): 

302 error = self.reporter.error( 

303 'Invalid context: the "%s" directive can only be used ' 

304 'within a substitution definition.' % (name), 

305 nodes.literal_block(block_text, block_text), line=lineno) 

306 return [error] 

307 """ 

308 

309 # There is a "Creating reStructuredText Directives" how-to at 

310 # <https://docutils.sourceforge.io/docs/howto/rst-directives.html>. If you 

311 # update this docstring, please update the how-to as well. 

312 

313 required_arguments = 0 

314 """Number of required directive arguments.""" 

315 

316 optional_arguments = 0 

317 """Number of optional arguments after the required arguments.""" 

318 

319 final_argument_whitespace = False 

320 """May the final argument contain whitespace?""" 

321 

322 option_spec = None 

323 """Mapping of option names to validator functions.""" 

324 

325 has_content = False 

326 """May the directive have content?""" 

327 

328 def __init__(self, name, arguments, options, content, lineno, 

329 content_offset, block_text, state, state_machine) -> None: 

330 self.name = name 

331 self.arguments = arguments 

332 self.options = options 

333 self.content = content 

334 self.lineno = lineno 

335 self.content_offset = content_offset 

336 self.block_text = block_text 

337 self.state = state 

338 self.state_machine = state_machine 

339 self.reporter = state_machine.reporter 

340 

341 def run(self): 

342 raise NotImplementedError('Must override run() in subclass.') 

343 

344 # Directive errors: 

345 

346 def directive_error(self, level, message): 

347 """ 

348 Return a DirectiveError suitable for being thrown as an exception. 

349 

350 Call "raise self.directive_error(level, message)" from within 

351 a directive implementation to return one single system message 

352 at level `level`, which automatically gets the directive block 

353 and the line number added. 

354 

355 Preferably use the `debug`, `info`, `warning`, `error`, or `severe` 

356 wrapper methods, e.g. ``self.error(message)`` to generate an 

357 ERROR-level directive error. 

358 """ 

359 return DirectiveError(level, message) 

360 

361 def debug(self, message): 

362 return self.directive_error(0, message) 

363 

364 def info(self, message): 

365 return self.directive_error(1, message) 

366 

367 def warning(self, message): 

368 return self.directive_error(2, message) 

369 

370 def error(self, message): 

371 return self.directive_error(3, message) 

372 

373 def severe(self, message): 

374 return self.directive_error(4, message) 

375 

376 # Convenience methods: 

377 

378 def assert_has_content(self): 

379 """ 

380 Throw an ERROR-level DirectiveError if the directive doesn't 

381 have contents. 

382 """ 

383 if not self.content: 

384 raise self.error('Content block expected for the "%s" directive; ' 

385 'none found.' % self.name) 

386 

387 def add_name(self, node) -> None: 

388 """Append self.options['name'] to node['names'] if it exists. 

389 

390 Also normalize the name string and register it as explicit target. 

391 """ 

392 if 'name' in self.options: 

393 name = nodes.fully_normalize_name(self.options.pop('name')) 

394 if 'name' in node: 

395 del node['name'] 

396 node['names'].append(name) 

397 self.state.document.note_explicit_target(node, node) 

398 

399 

400def convert_directive_function(directive_fn): 

401 """ 

402 Define & return a directive class generated from `directive_fn`. 

403 

404 `directive_fn` uses the old-style, functional interface. 

405 """ 

406 

407 class FunctionalDirective(Directive): 

408 

409 option_spec = getattr(directive_fn, 'options', None) 

410 has_content = getattr(directive_fn, 'content', False) 

411 _argument_spec = getattr(directive_fn, 'arguments', (0, 0, False)) 

412 required_arguments, optional_arguments, final_argument_whitespace \ 

413 = _argument_spec 

414 

415 def run(self): 

416 return directive_fn( 

417 self.name, self.arguments, self.options, self.content, 

418 self.lineno, self.content_offset, self.block_text, 

419 self.state, self.state_machine) 

420 

421 # Return new-style directive. 

422 return FunctionalDirective