docutils.utils is now a package (providing a place for sub-modules)
[docutils.git] / docutils / parsers / rst / directives / body.py
blob4ff9fdc058a1efe6e614ee5474ad91b24a328828
1 # $Id$
2 # Author: David Goodger <goodger@python.org>
3 # Copyright: This module has been placed in the public domain.
5 """
6 Directives for additional body elements.
8 See `docutils.parsers.rst.directives` for API details.
9 """
11 __docformat__ = 'reStructuredText'
14 import sys
15 from docutils import nodes
16 from docutils.parsers.rst import Directive
17 from docutils.parsers.rst import directives
18 from docutils.parsers.rst.roles import set_classes
19 from docutils.utils.code_analyzer import Lexer, LexerError, NumberLines
21 class BasePseudoSection(Directive):
23 required_arguments = 1
24 optional_arguments = 0
25 final_argument_whitespace = True
26 option_spec = {'class': directives.class_option,
27 'name': directives.unchanged}
28 has_content = True
30 node_class = None
31 """Node class to be used (must be set in subclasses)."""
33 def run(self):
34 if not (self.state_machine.match_titles
35 or isinstance(self.state_machine.node, nodes.sidebar)):
36 raise self.error('The "%s" directive may not be used within '
37 'topics or body elements.' % self.name)
38 self.assert_has_content()
39 title_text = self.arguments[0]
40 textnodes, messages = self.state.inline_text(title_text, self.lineno)
41 titles = [nodes.title(title_text, '', *textnodes)]
42 # Sidebar uses this code.
43 if 'subtitle' in self.options:
44 textnodes, more_messages = self.state.inline_text(
45 self.options['subtitle'], self.lineno)
46 titles.append(nodes.subtitle(self.options['subtitle'], '',
47 *textnodes))
48 messages.extend(more_messages)
49 text = '\n'.join(self.content)
50 node = self.node_class(text, *(titles + messages))
51 node['classes'] += self.options.get('class', [])
52 self.add_name(node)
53 if text:
54 self.state.nested_parse(self.content, self.content_offset, node)
55 return [node]
58 class Topic(BasePseudoSection):
60 node_class = nodes.topic
63 class Sidebar(BasePseudoSection):
65 node_class = nodes.sidebar
67 option_spec = BasePseudoSection.option_spec.copy()
68 option_spec['subtitle'] = directives.unchanged_required
70 def run(self):
71 if isinstance(self.state_machine.node, nodes.sidebar):
72 raise self.error('The "%s" directive may not be used within a '
73 'sidebar element.' % self.name)
74 return BasePseudoSection.run(self)
77 class LineBlock(Directive):
79 option_spec = {'class': directives.class_option,
80 'name': directives.unchanged}
81 has_content = True
83 def run(self):
84 self.assert_has_content()
85 block = nodes.line_block(classes=self.options.get('class', []))
86 self.add_name(block)
87 node_list = [block]
88 for line_text in self.content:
89 text_nodes, messages = self.state.inline_text(
90 line_text.strip(), self.lineno + self.content_offset)
91 line = nodes.line(line_text, '', *text_nodes)
92 if line_text.strip():
93 line.indent = len(line_text) - len(line_text.lstrip())
94 block += line
95 node_list.extend(messages)
96 self.content_offset += 1
97 self.state.nest_line_block_lines(block)
98 return node_list
101 class ParsedLiteral(Directive):
103 option_spec = {'class': directives.class_option,
104 'name': directives.unchanged}
105 has_content = True
107 def run(self):
108 set_classes(self.options)
109 self.assert_has_content()
110 text = '\n'.join(self.content)
111 text_nodes, messages = self.state.inline_text(text, self.lineno)
112 node = nodes.literal_block(text, '', *text_nodes, **self.options)
113 node.line = self.content_offset + 1
114 self.add_name(node)
115 return [node] + messages
118 class CodeBlock(Directive):
119 """Parse and mark up content of a code block.
121 Configuration setting: syntax_highlight
122 Highlight Code content with Pygments?
123 Possible values: ('long', 'short', 'none')
126 optional_arguments = 1
127 option_spec = {'class': directives.class_option,
128 'name': directives.unchanged,
129 'number-lines': directives.unchanged # integer or None
131 has_content = True
133 def run(self):
134 self.assert_has_content()
135 if self.arguments:
136 language = self.arguments[0]
137 else:
138 language = ''
139 set_classes(self.options)
140 classes = ['code']
141 if language:
142 classes.append(language)
143 if 'classes' in self.options:
144 classes.extend(self.options['classes'])
146 # set up lexical analyzer
147 try:
148 tokens = Lexer(u'\n'.join(self.content), language,
149 self.state.document.settings.syntax_highlight)
150 except LexerError, error:
151 raise self.warning(error)
153 if 'number-lines' in self.options:
154 # optional argument `startline`, defaults to 1
155 try:
156 startline = int(self.options['number-lines'] or 1)
157 except ValueError:
158 raise self.error(':number-lines: with non-integer start value')
159 endline = startline + len(self.content)
160 # add linenumber filter:
161 tokens = NumberLines(tokens, startline, endline)
163 node = nodes.literal_block('\n'.join(self.content), classes=classes)
164 self.add_name(node)
165 # if called from "include", set the source
166 if 'source' in self.options:
167 node.attributes['source'] = self.options['source']
168 # analyze content and add nodes for every token
169 for classes, value in tokens:
170 # print (classes, value)
171 if classes:
172 node += nodes.inline(value, value, classes=classes)
173 else:
174 # insert as Text to decrease the verbosity of the output
175 node += nodes.Text(value, value)
177 return [node]
180 class MathBlock(Directive):
182 option_spec = {'class': directives.class_option,
183 'name': directives.unchanged}
184 ## TODO: Add Sphinx' ``mathbase.py`` option 'nowrap'?
185 # 'nowrap': directives.flag,
186 has_content = True
188 def run(self):
189 set_classes(self.options)
190 self.assert_has_content()
191 # join lines, separate blocks
192 content = '\n'.join(self.content).split('\n\n')
193 _nodes = []
194 for block in content:
195 if not block:
196 continue
197 node = nodes.math_block(self.block_text, block, **self.options)
198 node.line = self.content_offset + 1
199 self.add_name(node)
200 _nodes.append(node)
201 return _nodes
204 class Rubric(Directive):
206 required_arguments = 1
207 optional_arguments = 0
208 final_argument_whitespace = True
209 option_spec = {'class': directives.class_option,
210 'name': directives.unchanged}
212 def run(self):
213 set_classes(self.options)
214 rubric_text = self.arguments[0]
215 textnodes, messages = self.state.inline_text(rubric_text, self.lineno)
216 rubric = nodes.rubric(rubric_text, '', *textnodes, **self.options)
217 self.add_name(rubric)
218 return [rubric] + messages
221 class BlockQuote(Directive):
223 has_content = True
224 classes = []
226 def run(self):
227 self.assert_has_content()
228 elements = self.state.block_quote(self.content, self.content_offset)
229 for element in elements:
230 if isinstance(element, nodes.block_quote):
231 element['classes'] += self.classes
232 return elements
235 class Epigraph(BlockQuote):
237 classes = ['epigraph']
240 class Highlights(BlockQuote):
242 classes = ['highlights']
245 class PullQuote(BlockQuote):
247 classes = ['pull-quote']
250 class Compound(Directive):
252 option_spec = {'class': directives.class_option,
253 'name': directives.unchanged}
254 has_content = True
256 def run(self):
257 self.assert_has_content()
258 text = '\n'.join(self.content)
259 node = nodes.compound(text)
260 node['classes'] += self.options.get('class', [])
261 self.add_name(node)
262 self.state.nested_parse(self.content, self.content_offset, node)
263 return [node]
266 class Container(Directive):
268 optional_arguments = 1
269 final_argument_whitespace = True
270 option_spec = {'name': directives.unchanged}
271 has_content = True
273 def run(self):
274 self.assert_has_content()
275 text = '\n'.join(self.content)
276 try:
277 if self.arguments:
278 classes = directives.class_option(self.arguments[0])
279 else:
280 classes = []
281 except ValueError:
282 raise self.error(
283 'Invalid class attribute value for "%s" directive: "%s".'
284 % (self.name, self.arguments[0]))
285 node = nodes.container(text)
286 node['classes'].extend(classes)
287 self.add_name(node)
288 self.state.nested_parse(self.content, self.content_offset, node)
289 return [node]