Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closes #2155: Support code directive #6132

Merged
merged 3 commits into from
Mar 10, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,13 @@ Bugs fixed
* #6147: classes attribute of ``citation_reference`` node is lost
* AssertionError is raised when custom ``citation_reference`` node having
classes attribute refers missing citation (refs: #6147)
* #2155: Support ``code`` directive

Testing
--------

* Add a helper function: ``sphinx.testing.restructuredtext.parse()``

Release 2.0.0 beta1 (in development)
====================================

Expand Down
37 changes: 25 additions & 12 deletions sphinx/directives/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,6 @@
from sphinx.util.docfields import DocFieldTransformer
from sphinx.util.docutils import SphinxDirective

# import all directives sphinx provides
from sphinx.directives.code import ( # noqa
Highlight, CodeBlock, LiteralInclude
)
from sphinx.directives.other import ( # noqa
TocTree, Author, Index, VersionChange, SeeAlso,
TabularColumns, Centered, Acks, HList, Only, Include, Class
)
from sphinx.directives.patches import ( # noqa
Figure, Meta
)

if False:
# For type annotation
from typing import Any, Dict # NOQA
Expand All @@ -44,6 +32,19 @@
strip_backslash_re = re.compile(r'\\(.)')


def optional_int(argument):
"""
Check for an integer argument or None value; raise ``ValueError`` if not.
"""
if argument is None:
return None
else:
value = int(argument)
if value < 0:
raise ValueError('negative value; must be positive or zero')
return value


class ObjectDescription(SphinxDirective):
"""
Directive to describe a class, function or similar object. Not used
Expand Down Expand Up @@ -241,6 +242,18 @@ def run(self):
self.env.temp_data['default_domain'] = self.env.domains.get(domain_name)
return []

# import all directives sphinx provides (for compatibility)
from sphinx.directives.code import ( # noqa
Highlight, CodeBlock, LiteralInclude
)
from sphinx.directives.other import ( # noqa
TocTree, Author, Index, VersionChange, SeeAlso,
TabularColumns, Centered, Acks, HList, Only, Include, Class
)
from sphinx.directives.patches import ( # noqa
Figure, Meta
)


def setup(app):
# type: (Sphinx) -> Dict[str, Any]
Expand Down
48 changes: 48 additions & 0 deletions sphinx/directives/patches.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from docutils.parsers.rst.directives import images, html, tables

from sphinx import addnodes
from sphinx.directives import optional_int
from sphinx.util.docutils import SphinxDirective
from sphinx.util.nodes import set_source_info

Expand Down Expand Up @@ -110,6 +111,52 @@ def make_title(self):
return title, message


class Code(SphinxDirective):
"""Parse and mark up content of a code block.

This is compatible with docutils' :rst:dir:`code` directive.
"""
optional_arguments = 1
option_spec = {
'class': directives.class_option,
'name': directives.unchanged,
'number-lines': optional_int,
}
has_content = True

def run(self):
# type: () -> List[nodes.Node]
self.assert_has_content()

code = '\n'.join(self.content)
node = nodes.literal_block(code, code,
classes=self.options.get('classes', []),
highlight_args={})
self.add_name(node)
set_source_info(self, node)

if self.arguments:
# highlight language specified
node['language'] = self.arguments[0]
node['force_highlighting'] = True
else:
# no highlight language specified. Then this directive refers the current
# highlight setting via ``highlight`` directive or ``highlight_language``
# configuration.
node['language'] = self.env.temp_data.get('highlight_language',
self.config.highlight_language)
node['force_highlighting'] = False

if 'number-lines' in self.options:
node['linenos'] = True

# if number given, treat as lineno-start.
if self.options['number-lines']:
node['highlight_args']['linenostart'] = self.options['number-lines']

return [node]


class MathDirective(SphinxDirective):
has_content = True
required_arguments = 0
Expand Down Expand Up @@ -174,6 +221,7 @@ def setup(app):
directives.register_directive('table', RSTTable)
directives.register_directive('csv-table', CSVTable)
directives.register_directive('list-table', ListTable)
directives.register_directive('code', Code)
directives.register_directive('math', MathDirective)

return {
Expand Down
38 changes: 38 additions & 0 deletions sphinx/testing/restructuredtext.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"""
sphinx.testing.restructuredtext
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

:copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""

from os import path

from docutils.core import publish_doctree

from sphinx.io import SphinxStandaloneReader
from sphinx.parsers import RSTParser
from sphinx.util.docutils import sphinx_domains


if False:
# For type annotation
from docutils import nodes # NOQA
from sphinx.application import Sphinx # NOQA


def parse(app, text, docname='index'):
# type: (Sphinx, str, str) -> nodes.document
"""Parse a string as reStructuredText with Sphinx application."""
try:
app.env.temp_data['docname'] = docname
parser = RSTParser()
parser.set_application(app)
with sphinx_domains(app.env):
return publish_doctree(text, path.join(app.srcdir, docname + '.rst'),
reader=SphinxStandaloneReader(app),
parser=parser,
settings_overrides={'env': app.env,
'gettext_compact': True})
finally:
app.env.temp_data.pop('docname', None)
31 changes: 9 additions & 22 deletions tests/test_directive_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,12 @@

import pytest
from docutils import nodes
from docutils.core import publish_doctree

from sphinx import addnodes
from sphinx.io import SphinxStandaloneReader
from sphinx.parsers import RSTParser
from sphinx.testing import restructuredtext
from sphinx.testing.util import assert_node


def parse(app, docname, text):
app.env.temp_data['docname'] = docname
parser = RSTParser()
parser.set_application(app)
return publish_doctree(text, app.srcdir / docname + '.rst',
reader=SphinxStandaloneReader(app),
parser=parser,
settings_overrides={'env': app.env,
'gettext_compact': True})


@pytest.mark.sphinx(testroot='toctree-glob')
def test_toctree(app):
text = (".. toctree::\n"
Expand All @@ -38,7 +25,7 @@ def test_toctree(app):
" baz\n")

app.env.find_files(app.config, app.builder)
doctree = parse(app, 'index', text)
doctree = restructuredtext.parse(app, text, 'index')
assert_node(doctree, [nodes.document, nodes.compound, addnodes.toctree])
assert_node(doctree[0][0],
entries=[(None, 'foo'), (None, 'bar/index'), (None, 'baz')],
Expand All @@ -55,7 +42,7 @@ def test_relative_toctree(app):
" ../quux\n")

app.env.find_files(app.config, app.builder)
doctree = parse(app, 'bar/index', text)
doctree = restructuredtext.parse(app, text, 'bar/index')
assert_node(doctree, [nodes.document, nodes.compound, addnodes.toctree])
assert_node(doctree[0][0],
entries=[(None, 'bar/bar_1'), (None, 'bar/bar_2'), (None, 'bar/bar_3'),
Expand All @@ -72,7 +59,7 @@ def test_toctree_urls_and_titles(app):
" The BAR <bar/index>\n")

app.env.find_files(app.config, app.builder)
doctree = parse(app, 'index', text)
doctree = restructuredtext.parse(app, text, 'index')
assert_node(doctree, [nodes.document, nodes.compound, addnodes.toctree])
assert_node(doctree[0][0],
entries=[('Sphinx', 'https://www.sphinx-doc.org/'),
Expand All @@ -89,7 +76,7 @@ def test_toctree_glob(app):
" *\n")

app.env.find_files(app.config, app.builder)
doctree = parse(app, 'index', text)
doctree = restructuredtext.parse(app, text, 'index')
assert_node(doctree, [nodes.document, nodes.compound, addnodes.toctree])
assert_node(doctree[0][0],
entries=[(None, 'baz'), (None, 'foo'), (None, 'quux')],
Expand All @@ -103,7 +90,7 @@ def test_toctree_glob(app):
" *\n")

app.env.find_files(app.config, app.builder)
doctree = parse(app, 'index', text)
doctree = restructuredtext.parse(app, text, 'index')
assert_node(doctree, [nodes.document, nodes.compound, addnodes.toctree])
assert_node(doctree[0][0],
entries=[(None, 'foo'), (None, 'baz'), (None, 'quux')],
Expand All @@ -117,7 +104,7 @@ def test_toctree_glob(app):
" foo\n")

app.env.find_files(app.config, app.builder)
doctree = parse(app, 'index', text)
doctree = restructuredtext.parse(app, text, 'index')
assert_node(doctree, [nodes.document, nodes.compound, addnodes.toctree])
assert_node(doctree[0][0],
entries=[(None, 'baz'), (None, 'foo'), (None, 'quux'), (None, 'foo')],
Expand All @@ -132,7 +119,7 @@ def test_toctree_glob_and_url(app):
" https://example.com/?q=sphinx\n")

app.env.find_files(app.config, app.builder)
doctree = parse(app, 'index', text)
doctree = restructuredtext.parse(app, text, 'index')
assert_node(doctree, [nodes.document, nodes.compound, addnodes.toctree])
assert_node(doctree[0][0],
entries=[(None, 'https://example.com/?q=sphinx')],
Expand All @@ -147,7 +134,7 @@ def test_toctree_twice(app):
" foo\n")

app.env.find_files(app.config, app.builder)
doctree = parse(app, 'index', text)
doctree = restructuredtext.parse(app, text, 'index')
assert_node(doctree, [nodes.document, nodes.compound, addnodes.toctree])
assert_node(doctree[0][0],
entries=[(None, 'foo'), (None, 'foo')],
Expand Down
54 changes: 54 additions & 0 deletions tests/test_directive_patch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"""
test_directive_patch
~~~~~~~~~~~~~~~~~~~

Test the patched directives.

:copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""

from docutils import nodes

from sphinx.testing import restructuredtext
from sphinx.testing.util import assert_node


def test_code_directive(app):
# normal case
text = ('.. code::\n'
'\n'
' print("hello world")\n')

doctree = restructuredtext.parse(app, text)
assert_node(doctree, [nodes.document, nodes.literal_block, 'print("hello world")'])
assert_node(doctree[0], language="default", highlight_args={})

# with language
text = ('.. code:: python\n'
'\n'
' print("hello world")\n')

doctree = restructuredtext.parse(app, text)
assert_node(doctree, [nodes.document, nodes.literal_block, 'print("hello world")'])
assert_node(doctree[0], language="python", highlight_args={})

# :number-lines: option
text = ('.. code:: python\n'
' :number-lines:\n'
'\n'
' print("hello world")\n')

doctree = restructuredtext.parse(app, text)
assert_node(doctree, [nodes.document, nodes.literal_block, 'print("hello world")'])
assert_node(doctree[0], language="python", linenos=True, highlight_args={})

# :number-lines: option
text = ('.. code:: python\n'
' :number-lines: 5\n'
'\n'
' print("hello world")\n')

doctree = restructuredtext.parse(app, text)
assert_node(doctree, [nodes.document, nodes.literal_block, 'print("hello world")'])
assert_node(doctree[0], language="python", linenos=True, highlight_args={'linenostart': 5})