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

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

197 statements  

1# $Id: body.py 10293 2026-01-21 15:18:32Z milde $ 

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

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

4 

5""" 

6Directives for additional body elements. 

7 

8See `docutils.parsers.rst.directives` for API details. 

9""" 

10 

11__docformat__ = 'reStructuredText' 

12 

13 

14from docutils import nodes 

15from docutils.parsers.rst import Directive 

16from docutils.parsers.rst import directives 

17from docutils.parsers.rst.roles import normalize_options 

18from docutils.utils.code_analyzer import Lexer, LexerError, NumberLines 

19 

20 

21class BasePseudoSection(Directive): 

22 """Base class for Topic and Sidebar.""" 

23 

24 final_argument_whitespace = True 

25 option_spec = {'class': directives.class_option, 

26 'name': directives.unchanged} 

27 has_content = True 

28 

29 node_class = None 

30 """Node class to be used (must be set in subclasses).""" 

31 

32 invalid_parents = (nodes.SubStructural, nodes.Bibliographic, 

33 nodes.Decorative, nodes.Body, nodes.Part, nodes.topic) 

34 """ 

35 Node categories where topics and sidebars are invalid children. 

36 

37 Sidebars are only valid in <document> and <section> elements, 

38 topics also in <sidebar> elements. However, during parsing, 

39 there may be wrapper nodes (like `sphinx.addnodes.only`). 

40 """ 

41 

42 def run(self): 

43 if (not isinstance(self.state_machine.node, 

44 (nodes.Root, nodes.section, nodes.sidebar)) 

45 and isinstance(self.state_machine.node, self.invalid_parents)): 

46 raise self.error('The "%s" directive may not be used within ' 

47 'topics or body elements.' % self.name) 

48 self.assert_has_content() 

49 if self.arguments: # title (in sidebars optional) 

50 title_text = self.arguments[0] 

51 textnodes, messages = self.state.inline_text( 

52 title_text, self.lineno) 

53 titles = [nodes.title(title_text, '', *textnodes)] 

54 # Sidebar uses this code. 

55 if 'subtitle' in self.options: 

56 textnodes, more_messages = self.state.inline_text( 

57 self.options['subtitle'], self.lineno) 

58 titles.append(nodes.subtitle(self.options['subtitle'], '', 

59 *textnodes)) 

60 messages.extend(more_messages) 

61 else: 

62 titles = [] 

63 messages = [] 

64 text = '\n'.join(self.content) 

65 node = self.node_class(text, *(titles + messages)) 

66 node['classes'] += self.options.get('class', []) 

67 (node.source, 

68 node.line) = self.state_machine.get_source_and_line(self.lineno) 

69 self.add_name(node) 

70 if text: 

71 self.state.nested_parse(self.content, self.content_offset, node) 

72 return [node] 

73 

74 

75class Topic(BasePseudoSection): 

76 

77 required_arguments = 1 

78 node_class = nodes.topic 

79 

80 

81class Sidebar(BasePseudoSection): 

82 

83 optional_arguments = 1 

84 node_class = nodes.sidebar 

85 option_spec = BasePseudoSection.option_spec | { 

86 'subtitle': directives.unchanged_required} 

87 

88 def run(self): 

89 if isinstance(self.state_machine.node, nodes.sidebar): 

90 raise self.error('The "%s" directive may not be used within a ' 

91 'sidebar element.' % self.name) 

92 if 'subtitle' in self.options and not self.arguments: 

93 raise self.error('The "subtitle" option may not be used ' 

94 'without a title.') 

95 

96 return BasePseudoSection.run(self) 

97 

98 

99class LineBlock(Directive): 

100 """Legacy directive for line blocks. 

101 

102 Use is deprecated in favour of the line block syntax, 

103 cf. `parsers.rst.states.Body.line_block()`. 

104 """ 

105 

106 option_spec = {'class': directives.class_option, 

107 'name': directives.unchanged} 

108 has_content = True 

109 

110 def run(self): 

111 self.assert_has_content() 

112 block = nodes.line_block(classes=self.options.get('class', [])) 

113 (block.source, 

114 block.line) = self.state_machine.get_source_and_line(self.lineno) 

115 self.add_name(block) 

116 node_list = [block] 

117 for i, line_text in enumerate(self.content): 

118 text_nodes, messages = self.state.inline_text( 

119 line_text.strip(), self.lineno + self.content_offset) 

120 line = nodes.line(line_text, '', *text_nodes) 

121 line.source = block.source 

122 line.line = block.line + i 

123 if line_text.strip(): 

124 line.indent = len(line_text) - len(line_text.lstrip()) 

125 block += line 

126 node_list.extend(messages) 

127 self.content_offset += 1 

128 self.state.nest_line_block_lines(block) 

129 return node_list 

130 

131 

132class ParsedLiteral(Directive): 

133 

134 option_spec = {'class': directives.class_option, 

135 'name': directives.unchanged} 

136 has_content = True 

137 

138 def run(self): 

139 options = normalize_options(self.options) 

140 self.assert_has_content() 

141 text = '\n'.join(self.content) 

142 text_nodes, messages = self.state.inline_text(text, self.lineno) 

143 node = nodes.literal_block(text, '', *text_nodes, **options) 

144 node.line = self.content_offset + 1 

145 self.add_name(node) 

146 return [node] + messages 

147 

148 

149class CodeBlock(Directive): 

150 """Parse and mark up content of a code block. 

151 

152 Configuration setting: syntax_highlight 

153 Highlight Code content with Pygments? 

154 Possible values: ('long', 'short', 'none') 

155 

156 """ 

157 optional_arguments = 1 

158 option_spec = {'class': directives.class_option, 

159 'name': directives.unchanged, 

160 'number-lines': directives.value_or((None,), int), 

161 } 

162 has_content = True 

163 

164 def run(self): 

165 self.assert_has_content() 

166 if self.arguments: 

167 language = self.arguments[0] 

168 else: 

169 language = '' 

170 options = normalize_options(self.options) 

171 classes = ['code'] 

172 if language: 

173 classes.append(language) 

174 if 'classes' in options: 

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

176 

177 # set up lexical analyzer 

178 try: 

179 tokens = Lexer('\n'.join(self.content), language, 

180 self.state.document.settings.syntax_highlight) 

181 except LexerError as error: 

182 if self.state.document.settings.report_level > 2: 

183 # don't report warnings -> insert without syntax highlight 

184 tokens = Lexer('\n'.join(self.content), language, 'none') 

185 else: 

186 raise self.warning(error) 

187 

188 if 'number-lines' in options: 

189 startline = self.options['number-lines'] 

190 if startline is None: 

191 startline = 1 

192 endline = startline + len(self.content) 

193 # add linenumber filter: 

194 tokens = NumberLines(tokens, startline, endline) 

195 

196 node = nodes.literal_block('\n'.join(self.content), classes=classes) 

197 self.add_name(node) 

198 # if called from "include", set the source 

199 if 'source' in options: 

200 node.attributes['source'] = options['source'] 

201 # analyze content and add nodes for every token 

202 for classes, value in tokens: 

203 if classes: 

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

205 else: 

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

207 node += nodes.Text(value) 

208 

209 return [node] 

210 

211 

212class MathBlock(Directive): 

213 

214 option_spec = {'class': directives.class_option, 

215 'name': directives.unchanged, 

216 # TODO: Add Sphinx' ``mathbase.py`` option 'nowrap'? 

217 # 'nowrap': directives.flag, 

218 } 

219 has_content = True 

220 

221 def run(self): 

222 options = normalize_options(self.options) 

223 self.assert_has_content() 

224 # join lines, separate blocks 

225 content = '\n'.join(self.content).split('\n\n') 

226 _nodes = [] 

227 for block in content: 

228 if not block: 

229 continue 

230 node = nodes.math_block(self.block_text, block, **options) 

231 (node.source, 

232 node.line) = self.state_machine.get_source_and_line(self.lineno) 

233 self.add_name(node) 

234 _nodes.append(node) 

235 return _nodes 

236 

237 

238class Rubric(Directive): 

239 

240 required_arguments = 1 

241 optional_arguments = 0 

242 final_argument_whitespace = True 

243 option_spec = {'class': directives.class_option, 

244 'name': directives.unchanged} 

245 

246 def run(self): 

247 options = normalize_options(self.options) 

248 rubric_text = self.arguments[0] 

249 textnodes, messages = self.state.inline_text(rubric_text, self.lineno) 

250 rubric = nodes.rubric(rubric_text, '', *textnodes, **options) 

251 (rubric.source, 

252 rubric.line) = self.state_machine.get_source_and_line(self.lineno) 

253 self.add_name(rubric) 

254 return [rubric] + messages 

255 

256 

257class BlockQuote(Directive): 

258 

259 has_content = True 

260 classes = [] 

261 

262 def run(self): 

263 self.assert_has_content() 

264 elements = self.state.block_quote(self.content, self.content_offset) 

265 for element in elements: 

266 if isinstance(element, nodes.block_quote): 

267 element['classes'] += self.classes 

268 return elements 

269 

270 

271class Epigraph(BlockQuote): 

272 

273 classes = ['epigraph'] 

274 

275 

276class Highlights(BlockQuote): 

277 

278 classes = ['highlights'] 

279 

280 

281class PullQuote(BlockQuote): 

282 

283 classes = ['pull-quote'] 

284 

285 

286class Compound(Directive): 

287 

288 option_spec = {'class': directives.class_option, 

289 'name': directives.unchanged} 

290 has_content = True 

291 

292 def run(self): 

293 self.assert_has_content() 

294 text = '\n'.join(self.content) 

295 node = nodes.compound(text) 

296 node['classes'] += self.options.get('class', []) 

297 (node.source, 

298 node.line) = self.state_machine.get_source_and_line(self.lineno) 

299 self.add_name(node) 

300 self.state.nested_parse(self.content, self.content_offset, node) 

301 return [node] 

302 

303 

304class Container(Directive): 

305 

306 optional_arguments = 1 

307 final_argument_whitespace = True 

308 option_spec = {'name': directives.unchanged} 

309 has_content = True 

310 

311 def run(self): 

312 self.assert_has_content() 

313 text = '\n'.join(self.content) 

314 try: 

315 if self.arguments: 

316 classes = directives.class_option(self.arguments[0]) 

317 else: 

318 classes = [] 

319 except ValueError: 

320 raise self.error( 

321 'Invalid class attribute value for "%s" directive: "%s".' 

322 % (self.name, self.arguments[0])) 

323 node = nodes.container(text) 

324 node['classes'].extend(classes) 

325 (node.source, 

326 node.line) = self.state_machine.get_source_and_line(self.lineno) 

327 self.add_name(node) 

328 self.state.nested_parse(self.content, self.content_offset, node) 

329 return [node]