123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304 |
- # $Id: body.py 9030 2022-03-05 23:28:32Z milde $
- # Author: David Goodger <goodger@python.org>
- # Copyright: This module has been placed in the public domain.
- """
- Directives for additional body elements.
- See `docutils.parsers.rst.directives` for API details.
- """
- __docformat__ = 'reStructuredText'
- from docutils import nodes
- from docutils.parsers.rst import Directive
- from docutils.parsers.rst import directives
- from docutils.parsers.rst.roles import set_classes
- from docutils.utils.code_analyzer import Lexer, LexerError, NumberLines
- class BasePseudoSection(Directive):
- required_arguments = 1
- optional_arguments = 0
- final_argument_whitespace = True
- option_spec = {'class': directives.class_option,
- 'name': directives.unchanged}
- has_content = True
- node_class = None
- """Node class to be used (must be set in subclasses)."""
- def run(self):
- if not (self.state_machine.match_titles
- or isinstance(self.state_machine.node, nodes.sidebar)):
- raise self.error('The "%s" directive may not be used within '
- 'topics or body elements.' % self.name)
- self.assert_has_content()
- if self.arguments: # title (in sidebars optional)
- title_text = self.arguments[0]
- textnodes, messages = self.state.inline_text(
- title_text, self.lineno)
- titles = [nodes.title(title_text, '', *textnodes)]
- # Sidebar uses this code.
- if 'subtitle' in self.options:
- textnodes, more_messages = self.state.inline_text(
- self.options['subtitle'], self.lineno)
- titles.append(nodes.subtitle(self.options['subtitle'], '',
- *textnodes))
- messages.extend(more_messages)
- else:
- titles = []
- messages = []
- text = '\n'.join(self.content)
- node = self.node_class(text, *(titles + messages))
- node['classes'] += self.options.get('class', [])
- self.add_name(node)
- if text:
- self.state.nested_parse(self.content, self.content_offset, node)
- return [node]
- class Topic(BasePseudoSection):
- node_class = nodes.topic
- class Sidebar(BasePseudoSection):
- node_class = nodes.sidebar
- required_arguments = 0
- optional_arguments = 1
- option_spec = BasePseudoSection.option_spec.copy()
- option_spec['subtitle'] = directives.unchanged_required
- def run(self):
- if isinstance(self.state_machine.node, nodes.sidebar):
- raise self.error('The "%s" directive may not be used within a '
- 'sidebar element.' % self.name)
- if 'subtitle' in self.options and not self.arguments:
- raise self.error('The "subtitle" option may not be used '
- 'without a title.')
- return BasePseudoSection.run(self)
- class LineBlock(Directive):
- option_spec = {'class': directives.class_option,
- 'name': directives.unchanged}
- has_content = True
- def run(self):
- self.assert_has_content()
- block = nodes.line_block(classes=self.options.get('class', []))
- self.add_name(block)
- node_list = [block]
- for line_text in self.content:
- text_nodes, messages = self.state.inline_text(
- line_text.strip(), self.lineno + self.content_offset)
- line = nodes.line(line_text, '', *text_nodes)
- if line_text.strip():
- line.indent = len(line_text) - len(line_text.lstrip())
- block += line
- node_list.extend(messages)
- self.content_offset += 1
- self.state.nest_line_block_lines(block)
- return node_list
- class ParsedLiteral(Directive):
- option_spec = {'class': directives.class_option,
- 'name': directives.unchanged}
- has_content = True
- def run(self):
- set_classes(self.options)
- self.assert_has_content()
- text = '\n'.join(self.content)
- text_nodes, messages = self.state.inline_text(text, self.lineno)
- node = nodes.literal_block(text, '', *text_nodes, **self.options)
- node.line = self.content_offset + 1
- self.add_name(node)
- return [node] + messages
- class CodeBlock(Directive):
- """Parse and mark up content of a code block.
- Configuration setting: syntax_highlight
- Highlight Code content with Pygments?
- Possible values: ('long', 'short', 'none')
- """
- optional_arguments = 1
- option_spec = {'class': directives.class_option,
- 'name': directives.unchanged,
- 'number-lines': directives.unchanged # integer or None
- }
- has_content = True
- def run(self):
- self.assert_has_content()
- if self.arguments:
- language = self.arguments[0]
- else:
- language = ''
- set_classes(self.options)
- classes = ['code']
- if language:
- classes.append(language)
- if 'classes' in self.options:
- classes.extend(self.options['classes'])
- # set up lexical analyzer
- try:
- tokens = Lexer('\n'.join(self.content), language,
- self.state.document.settings.syntax_highlight)
- except LexerError as error:
- if self.state.document.settings.report_level > 2:
- # don't report warnings -> insert without syntax highlight
- tokens = Lexer('\n'.join(self.content), language, 'none')
- else:
- raise self.warning(error)
- if 'number-lines' in self.options:
- # optional argument `startline`, defaults to 1
- try:
- startline = int(self.options['number-lines'] or 1)
- except ValueError:
- raise self.error(':number-lines: with non-integer start value')
- endline = startline + len(self.content)
- # add linenumber filter:
- tokens = NumberLines(tokens, startline, endline)
- node = nodes.literal_block('\n'.join(self.content), classes=classes)
- self.add_name(node)
- # if called from "include", set the source
- if 'source' in self.options:
- node.attributes['source'] = self.options['source']
- # analyze content and add nodes for every token
- for classes, value in tokens:
- if classes:
- node += nodes.inline(value, value, classes=classes)
- else:
- # insert as Text to decrease the verbosity of the output
- node += nodes.Text(value)
- return [node]
- class MathBlock(Directive):
- option_spec = {'class': directives.class_option,
- 'name': directives.unchanged,
- # TODO: Add Sphinx' ``mathbase.py`` option 'nowrap'?
- # 'nowrap': directives.flag,
- }
- has_content = True
- def run(self):
- set_classes(self.options)
- self.assert_has_content()
- # join lines, separate blocks
- content = '\n'.join(self.content).split('\n\n')
- _nodes = []
- for block in content:
- if not block:
- continue
- node = nodes.math_block(self.block_text, block, **self.options)
- node.line = self.content_offset + 1
- self.add_name(node)
- _nodes.append(node)
- return _nodes
- class Rubric(Directive):
- required_arguments = 1
- optional_arguments = 0
- final_argument_whitespace = True
- option_spec = {'class': directives.class_option,
- 'name': directives.unchanged}
- def run(self):
- set_classes(self.options)
- rubric_text = self.arguments[0]
- textnodes, messages = self.state.inline_text(rubric_text, self.lineno)
- rubric = nodes.rubric(rubric_text, '', *textnodes, **self.options)
- self.add_name(rubric)
- return [rubric] + messages
- class BlockQuote(Directive):
- has_content = True
- classes = []
- def run(self):
- self.assert_has_content()
- elements = self.state.block_quote(self.content, self.content_offset)
- for element in elements:
- if isinstance(element, nodes.block_quote):
- element['classes'] += self.classes
- return elements
- class Epigraph(BlockQuote):
- classes = ['epigraph']
- class Highlights(BlockQuote):
- classes = ['highlights']
- class PullQuote(BlockQuote):
- classes = ['pull-quote']
- class Compound(Directive):
- option_spec = {'class': directives.class_option,
- 'name': directives.unchanged}
- has_content = True
- def run(self):
- self.assert_has_content()
- text = '\n'.join(self.content)
- node = nodes.compound(text)
- node['classes'] += self.options.get('class', [])
- self.add_name(node)
- self.state.nested_parse(self.content, self.content_offset, node)
- return [node]
- class Container(Directive):
- optional_arguments = 1
- final_argument_whitespace = True
- option_spec = {'name': directives.unchanged}
- has_content = True
- def run(self):
- self.assert_has_content()
- text = '\n'.join(self.content)
- try:
- if self.arguments:
- classes = directives.class_option(self.arguments[0])
- else:
- classes = []
- except ValueError:
- raise self.error(
- 'Invalid class attribute value for "%s" directive: "%s".'
- % (self.name, self.arguments[0]))
- node = nodes.container(text)
- node['classes'].extend(classes)
- self.add_name(node)
- self.state.nested_parse(self.content, self.content_offset, node)
- return [node]
|