2 # :Copyright: © 2020 Günter Milde.
3 # :License: Released under the terms of the `2-Clause BSD license`_, in short:
5 # Copying and distribution of this file, with or without modification,
6 # are permitted in any medium without royalty provided the copyright
7 # notice and this notice are preserved.
8 # This file is offered as-is, without any warranty.
10 # .. _2-Clause BSD license: https://opensource.org/licenses/BSD-2-Clause
12 # Revision: $Revision$
15 A parser for CommonMark Markdown text using `recommonmark`__.
17 __ https://pypi.org/project/recommonmark/
19 .. important:: This module is deprecated.
21 * The "recommonmark" package is unmaintained and deprecated.
22 This wrapper module will be removed in Docutils 1.0.
24 * The API is not settled and may change with any minor Docutils version.
27 from docutils
import Component
28 from docutils
import nodes
31 # If possible, import Sphinx's 'addnodes'
32 from sphinx
import addnodes
34 # stub to prevent errors if Sphinx isn't installed
38 class pending_xref(nodes
.Inline
, nodes
.Element
):
41 sys
.modules
['sphinx'] = sphinx
= types
.ModuleType('sphinx')
42 sphinx
.addnodes
= addnodes
= types
.SimpleNamespace()
43 addnodes
.pending_xref
= pending_xref
46 from recommonmark
.parser
import CommonMarkParser
47 except ImportError as err
:
49 'Parsing "recommonmark" Markdown flavour requires the\n'
50 ' package https://pypi.org/project/recommonmark.'
53 if recommonmark
.__version
__ < '0.6.0':
54 raise ImportError('The installed version of "recommonmark" is too old.'
55 ' Update with "pip install -U recommonmark".')
58 # auxiliary function for `document.findall()`
60 return isinstance(node
, (nodes
.literal
, nodes
.literal_block
))
63 class Parser(CommonMarkParser
):
64 """MarkDown parser based on recommonmark.
66 This parser is provisional:
67 the API is not settled and may change with any minor Docutils version.
69 supported
= ('recommonmark', 'commonmark', 'markdown', 'md')
70 """Formats this parser supports."""
72 config_section
= 'recommonmark parser'
73 config_section_dependencies
= ('parsers',)
75 def get_transforms(self
):
76 return Component
.get_transforms(self
) # + [AutoStructify]
78 def parse(self
, inputstring
, document
):
79 """Wrapper of upstream method.
81 Ensure "line-length-limt". Report errors with `document.reporter`.
83 # check for exorbitantly long lines
84 for i
, line
in enumerate(inputstring
.split('\n')):
85 if len(line
) > document
.settings
.line_length_limit
:
86 error
= document
.reporter
.error(
87 'Line %d exceeds the line-length-limit.'%(i
+1))
88 document
.append(error
)
91 # pass to upstream parser
93 CommonMarkParser
.parse(self
, inputstring
, document
)
94 except Exception as err
:
95 if document
.settings
.traceback
:
97 error
= document
.reporter
.error('Parsing with "recommonmark" '
98 'returned the error:\n%s'%err)
99 document
.append(error
)
104 def finish_parse(self
) -> None:
105 """Finalize parse details. Call at end of `self.parse()`."""
107 document
= self
.document
109 # merge adjoining Text nodes:
110 for node
in document
.findall(nodes
.TextElement
):
111 children
= node
.children
113 while i
+1 < len(children
):
114 if (isinstance(children
[i
], nodes
.Text
)
115 and isinstance(children
[i
+1], nodes
.Text
)):
116 children
[i
] = nodes
.Text(children
[i
]+children
.pop(i
+1))
117 children
[i
].parent
= node
121 # remove empty Text nodes:
122 for node
in document
.findall(nodes
.Text
):
124 node
.parent
.remove(node
)
126 # add "code" class argument to literal elements (inline and block)
127 for node
in document
.findall(is_literal
):
128 if 'code' not in node
['classes']:
129 node
['classes'].append('code')
130 # move "language" argument to classes
131 for node
in document
.findall(nodes
.literal_block
):
132 if 'language' in node
.attributes
:
133 node
['classes'].append(node
['language'])
136 # replace raw nodes if raw is not allowed
137 if not document
.settings
.raw_enabled
:
138 for node
in document
.findall(nodes
.raw
):
139 message
= document
.reporter
.warning('Raw content disabled.')
140 if isinstance(node
.parent
, nodes
.TextElement
):
141 msgid
= document
.set_id(message
)
142 problematic
= nodes
.problematic('', node
.astext(),
144 node
.parent
.replace(node
, problematic
)
145 prbid
= document
.set_id(problematic
)
146 message
.add_backref(prbid
)
147 document
.append(message
)
149 node
.parent
.replace(node
, message
)
151 # drop pending_xref (Sphinx cross reference extension)
152 for node
in document
.findall(addnodes
.pending_xref
):
153 reference
= node
.children
[0]
154 if 'name' not in reference
:
155 reference
['name'] = nodes
.fully_normalize_name(
157 node
.parent
.replace(node
, reference
)
158 # now we are ready to call the upstream function:
159 super().finish_parse()
161 def visit_document(self
, node
) -> None:
162 """Dummy function to prevent spurious warnings.
164 cf. https://github.com/readthedocs/recommonmark/issues/177
167 # Overwrite parent method with version that
168 # doesn't pass deprecated `rawsource` argument to nodes.Text:
169 def visit_text(self
, mdnode
) -> None:
170 self
.current_node
.append(nodes
.Text(mdnode
.literal
))