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