2 # Author: David Goodger <goodger@python.org>
3 # Copyright: This module has been placed in the public domain.
6 Directives for additional body elements.
8 See `docutils.parsers.rst.directives` for API details.
11 __docformat__
= 'reStructuredText'
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
}
31 """Node class to be used (must be set in subclasses)."""
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'], '',
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', [])
54 self
.state
.nested_parse(self
.content
, self
.content_offset
, 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
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
}
84 self
.assert_has_content()
85 block
= nodes
.line_block(classes
=self
.options
.get('class', []))
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
)
93 line
.indent
= len(line_text
) - len(line_text
.lstrip())
95 node_list
.extend(messages
)
96 self
.content_offset
+= 1
97 self
.state
.nest_line_block_lines(block
)
101 class ParsedLiteral(Directive
):
103 option_spec
= {'class': directives
.class_option
,
104 'name': directives
.unchanged
}
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
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
134 self
.assert_has_content()
136 language
= self
.arguments
[0]
139 set_classes(self
.options
)
142 classes
.append(language
)
143 if 'classes' in self
.options
:
144 classes
.extend(self
.options
['classes'])
146 # set up lexical analyzer
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
156 startline
= int(self
.options
['number-lines'] or 1)
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
)
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)
172 node
+= nodes
.inline(value
, value
, classes
=classes
)
174 # insert as Text to decrease the verbosity of the output
175 node
+= nodes
.Text(value
, value
)
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,
189 set_classes(self
.options
)
190 self
.assert_has_content()
191 # join lines, separate blocks
192 content
= '\n'.join(self
.content
).split('\n\n')
194 for block
in content
:
197 node
= nodes
.math_block(self
.block_text
, block
, **self
.options
)
198 node
.line
= self
.content_offset
+ 1
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
}
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
):
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
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
}
257 self
.assert_has_content()
258 text
= '\n'.join(self
.content
)
259 node
= nodes
.compound(text
)
260 node
['classes'] += self
.options
.get('class', [])
262 self
.state
.nested_parse(self
.content
, self
.content_offset
, node
)
266 class Container(Directive
):
268 optional_arguments
= 1
269 final_argument_whitespace
= True
270 option_spec
= {'name': directives
.unchanged
}
274 self
.assert_has_content()
275 text
= '\n'.join(self
.content
)
278 classes
= directives
.class_option(self
.arguments
[0])
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
)
288 self
.state
.nested_parse(self
.content
, self
.content_offset
, node
)