Fix #789: `:samp:` role supports to escape curly braces with backslash

This commit is contained in:
Takeshi KOMIYA 2018-04-28 11:39:13 +09:00
parent 93837bb01e
commit 622119a9d4
4 changed files with 122 additions and 10 deletions

View File

@ -23,6 +23,7 @@ Incompatible changes
:py:meth:`.Sphinx.add_transform()`
* #4827: All ``substitution_definition`` nodes are removed from doctree on
reading phase
* #789: ``:samp:`` role supports to escape curly braces with backslash
Deprecated
----------

View File

@ -406,6 +406,9 @@ different style:
If you don't need the "variable part" indication, use the standard
````code```` instead.
.. versionchanged:: 1.8
Allowed to escape curly braces with backslash
There is also an :rst:role:`index` role to generate index entries.
The following roles generate external links:

View File

@ -284,6 +284,7 @@ def menusel_role(typ, rawtext, text, lineno, inliner, options={}, content=[]):
_litvar_re = re.compile('{([^}]+)}')
parens_re = re.compile(r'(\\*{|\\*})')
def emph_literal_role(typ, rawtext, text, lineno, inliner,
@ -296,17 +297,43 @@ def emph_literal_role(typ, rawtext, text, lineno, inliner,
else:
typ = typ.lower()
text = utils.unescape(text)
pos = 0
retnode = nodes.literal(role=typ.lower(), classes=[typ])
for m in _litvar_re.finditer(text): # type: ignore
if m.start() > pos:
txt = text[pos:m.start()]
retnode += nodes.Text(txt, txt)
retnode += nodes.emphasis(m.group(1), m.group(1))
pos = m.end()
if pos < len(text):
retnode += nodes.Text(text[pos:], text[pos:])
parts = list(parens_re.split(utils.unescape(text)))
stack = ['']
for part in parts:
matched = parens_re.match(part)
if matched:
backslashes = len(part) - 1
if backslashes % 2 == 1: # escaped
stack[-1] += "\\" * int((backslashes - 1) / 2) + part[-1]
elif part[-1] == '{': # rparen
stack[-1] += "\\" * int(backslashes / 2)
if len(stack) >= 2 and stack[-2] == "{":
# nested
stack[-1] += "{"
else:
# start emphasis
stack.append('{')
stack.append('')
else: # lparen
stack[-1] += "\\" * int(backslashes / 2)
if len(stack) == 3 and stack[1] == "{" and len(stack[2]) > 0:
# emphasized word found
if stack[0]:
retnode += nodes.Text(stack[0], stack[0])
retnode += nodes.emphasis(stack[2], stack[2])
stack = ['']
else:
# emphasized word not found; the rparen is not a special symbol
stack.append('}')
stack = [''.join(stack)]
else:
stack[-1] += part
if ''.join(stack):
# remaining is treated as Text
text = ''.join(stack)
retnode += nodes.Text(text, text)
return [retnode], []

81
tests/test_roles.py Normal file
View File

@ -0,0 +1,81 @@
# -*- coding: utf-8 -*-
"""
test_roles
~~~~~~~~~~
Test sphinx.roles
:copyright: Copyright 2007-2018 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
from docutils import nodes
from mock import Mock
from sphinx.roles import emph_literal_role
from sphinx.testing.util import assert_node
def test_samp():
# normal case
text = 'print 1+{variable}'
ret, msg = emph_literal_role('samp', text, text, 0, Mock())
assert_node(ret, ([nodes.literal, ("print 1+",
[nodes.emphasis, "variable"])],))
assert msg == []
# two emphasis items
text = 'print {1}+{variable}'
ret, msg = emph_literal_role('samp', text, text, 0, Mock())
assert_node(ret, ([nodes.literal, ("print ",
[nodes.emphasis, "1"],
"+",
[nodes.emphasis, "variable"])],))
assert msg == []
# empty curly brace
text = 'print 1+{}'
ret, msg = emph_literal_role('samp', text, text, 0, Mock())
assert_node(ret, ([nodes.literal, "print 1+{}"],))
assert msg == []
# half-opened variable
text = 'print 1+{variable'
ret, msg = emph_literal_role('samp', text, text, 0, Mock())
assert_node(ret, ([nodes.literal, "print 1+{variable"],))
assert msg == []
# nested
text = 'print 1+{{variable}}'
ret, msg = emph_literal_role('samp', text, text, 0, Mock())
assert_node(ret, ([nodes.literal, ("print 1+",
[nodes.emphasis, "{variable"],
"}")],))
assert msg == []
# emphasized item only
text = '{variable}'
ret, msg = emph_literal_role('samp', text, text, 0, Mock())
assert_node(ret, ([nodes.literal, nodes.emphasis, "variable"],))
assert msg == []
# escaping
text = r'print 1+\{variable}'
ret, msg = emph_literal_role('samp', text, text, 0, Mock())
assert_node(ret, ([nodes.literal, "print 1+{variable}"],))
assert msg == []
# escaping (2)
text = r'print 1+\{{variable}\}'
ret, msg = emph_literal_role('samp', text, text, 0, Mock())
assert_node(ret, ([nodes.literal, ("print 1+{",
[nodes.emphasis, "variable"],
"}")],))
assert msg == []
# escape a backslash
text = r'print 1+\\{variable}'
ret, msg = emph_literal_role('samp', text, text, 0, Mock())
assert_node(ret, ([nodes.literal, ("print 1+\\",
[nodes.emphasis, "variable"])],))
assert msg == []