Add cpp:texpr role (style alternative to cpp:expr)

Simplified version of sphinx-doc/sphinx#4836,
thanks to mickk-on-cpp.
This commit is contained in:
Jakob Lykke Andersen 2018-05-26 17:25:41 +02:00
parent 46797104fa
commit f592483156
5 changed files with 131 additions and 27 deletions

View File

@ -116,6 +116,7 @@ Features added
* #4785: napoleon: Add strings to translation file for localisation
* #4927: Display a warning when invalid values are passed to linenothreshold
option of highlight directive
* C++, add a ``cpp:texpr`` role as a sibling to ``cpp:expr``.
Bugs fixed
----------

View File

@ -569,10 +569,10 @@ visibility statement (``public``, ``private`` or ``protected``).
Full and partial template specialisations can be declared::
.. cpp:class:: template<> \
std::array<bool, 256>
std::array<bool, 256>
.. cpp:class:: template<typename T> \
std::array<T, 42>
std::array<T, 42>
.. rst:directive:: .. cpp:function:: (member) function prototype
@ -815,24 +815,31 @@ Inline Expressions and Tpes
~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. rst:role:: cpp:expr
cpp:texpr
A role for inserting a C++ expression or type as inline text. For example::
Insert a C++ expression or type either as inline code (``cpp:expr``)
or inline text (``cpp:texpr``). For example::
.. cpp:var:: int a = 42
.. cpp:function:: int f(int i)
An expression: :cpp:expr:`a * f(a)`.
A type: :cpp:expr:`const MySortedContainer<int>&`.
An expression: :cpp:expr:`a * f(a)` (or as text: :cpp:texpr:`a * f(a)`).
A type: :cpp:expr:`const MySortedContainer<int>&`
(or as text :cpp:texpr:`const MySortedContainer<int>&`).
will be rendered as follows:
.. cpp:var:: int a = 42
.. cpp:var:: int a = 42
.. cpp:function:: int f(int i)
.. cpp:function:: int f(int i)
An expression: :cpp:expr:`a * f(a)` (or as text: :cpp:texpr:`a * f(a)`).
A type: :cpp:expr:`const MySortedContainer<int>&`
(or as text :cpp:texpr:`const MySortedContainer<int>&`).
An expression: :cpp:expr:`a * f(a)`. A type: :cpp:expr:`const
MySortedContainer<int>&`.
Namespacing
~~~~~~~~~~~
@ -880,7 +887,7 @@ The ``cpp:namespace-pop`` directive undoes the most recent
.. cpp:function:: std::size_t size() const
or:::
or::
.. cpp:class:: template<typename T> \
std::vector
@ -949,20 +956,23 @@ These roles link to the given declaration types:
.. admonition:: Note on References with Templates Parameters/Arguments
Sphinx's syntax to give references a custom title can interfere with linking
to class templates, if nothing follows the closing angle bracket, i.e. if
the link looks like this: ``:cpp:class:`MyClass<int>```. This is
interpreted as a link to ``int`` with a title of ``MyClass``. In this case,
please escape the opening angle bracket with a backslash, like this:
``:cpp:class:`MyClass\<int>```.
These roles follow the Sphinx :ref:`xref-syntax` rules. This means care must be
taken when referencing a (partial) template specialization, e.g. if the link looks like
this: ``:cpp:class:`MyClass<int>```.
This is interpreted as a link to ``int`` with a title of ``MyClass``.
In this case, escape the opening angle bracket with a backslash,
like this: ``:cpp:class:`MyClass\<int>```.
When a custom title is not needed it may be useful to use the roles for inline expressions,
:rst:role:`cpp:expr` and :rst:role:`cpp:texpr`, where angle brackets do not need escaping.
.. admonition:: Note on References to Overloaded Functions
It is currently impossible to link to a specific version of an overloaded
method. Currently the C++ domain is the first domain that has basic support
for overloaded methods and until there is more data for comparison we don't
want to select a bad syntax to reference a specific overload. Currently
Sphinx will link to the first overloaded version of the method / function.
function. Currently the C++ domain is the first domain that has basic
support for overloaded functions and until there is more data for comparison
we don't want to select a bad syntax to reference a specific overload.
Currently Sphinx will link to the first overloaded version of the function.
Declarations without template parameters and template arguments
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -983,13 +993,13 @@ Assume the following declarations.
.. cpp:class:: template<typename TInner> \
Inner
In general the reference must include the template paraemter declarations,
In general the reference must include the template parameter declarations,
e.g., ``template\<typename TOuter> Wrapper::Outer``
(:cpp:class:`template\<typename TOuter> Wrapper::Outer`). Currently the lookup
only succeed if the template parameter identifiers are equal strings. That is,
``template\<typename UOuter> Wrapper::Outer`` will not work.
The inner class template can not be directly referenced, unless the current
The inner class template cannot be directly referenced, unless the current
namespace is changed or the following shorthand is used. If a template
parameter list is omitted, then the lookup will assume either a template or a
non-template, but not a partial template specialisation. This means the

View File

@ -5965,6 +5965,16 @@ class CPPXRefRole(XRefRole):
class CPPExprRole(object):
def __init__(self, asCode):
if asCode:
# render the expression as inline code
self.class_type = 'cpp-expr'
self.node_type = nodes.literal
else:
# render the expression as inline text
self.class_type = 'cpp-texpr'
self.node_type = nodes.inline
def __call__(self, typ, rawtext, text, lineno, inliner, options={}, content=[]):
class Warner(object):
def warn(self, msg):
@ -5972,18 +5982,23 @@ class CPPExprRole(object):
text = utils.unescape(text).replace('\n', ' ')
env = inliner.document.settings.env
parser = DefinitionParser(text, Warner(), env.config)
# attempt to mimic XRefRole classes, except that...
classes = ['xref', 'cpp', self.class_type]
try:
ast = parser.parse_expression()
except DefinitionError as ex:
Warner().warn('Unparseable C++ expression: %r\n%s'
% (text, text_type(ex.description)))
return [nodes.literal(text)], []
# see below
return [self.node_type(text, text, classes=classes)], []
parentSymbol = env.temp_data.get('cpp:parent_symbol', None)
if parentSymbol is None:
parentSymbol = env.domaindata['cpp']['root_symbol']
p = nodes.literal()
ast.describe_signature(p, 'markType', env, parentSymbol)
return [p], []
# ...most if not all of these classes should really apply to the individual references,
# not the container node
signode = self.node_type(classes=classes)
ast.describe_signature(signode, 'markType', env, parentSymbol)
return [signode], []
class CPPDomain(Domain):
@ -6025,7 +6040,8 @@ class CPPDomain(Domain):
'concept': CPPXRefRole(),
'enum': CPPXRefRole(),
'enumerator': CPPXRefRole(),
'expr': CPPExprRole()
'expr': CPPExprRole(asCode=True),
'texpr': CPPExprRole(asCode=False)
}
initial_data = {
'root_symbol': Symbol(None, None, None, None, None, None),

View File

@ -0,0 +1,12 @@
xref consistency
----------------
.. cpp:namespace:: xref_consistency
.. cpp:class:: item
code-role: :code:`item`
any-role: :any:`item`
cpp-any-role: :cpp:any:`item`
cpp-expr-role: :cpp:expr:`item`
cpp-texpr-role: :cpp:texpr:`item`

View File

@ -735,3 +735,68 @@ def test_build_domain_cpp_with_add_function_parentheses_is_False(app, status, wa
t = (app.outdir / f).text()
for s in parenPatterns:
check(s, t, f)
@pytest.mark.sphinx(testroot='domain-cpp')
def test_xref_consistency(app, status, warning):
app.builder.build_all()
test = 'xref_consistency.html'
output = (app.outdir / test).text()
def classes(role, tag):
pattern = (r'{role}-role:.*?'
'<(?P<tag>{tag}) .*?class=["\'](?P<classes>.*?)["\'].*?>'
'.*'
'</(?P=tag)>').format(role=role, tag=tag)
result = re.search(pattern, output)
expect = '''\
Pattern for role `{role}` with tag `{tag}`
\t{pattern}
not found in `{test}`
'''.format(role=role, tag=tag, pattern=pattern, test=test)
assert result, expect
return set(result.group('classes').split())
class RoleClasses(object):
"""Collect the classes from the layout that was generated for a given role."""
def __init__(self, role, root, contents):
self.name = role
self.classes = classes(role, root)
self.content_classes = dict()
for tag in contents:
self.content_classes[tag] = classes(role, tag)
# not actually used as a reference point
#code_role = RoleClasses('code', 'code', [])
any_role = RoleClasses('any', 'a', ['code'])
cpp_any_role = RoleClasses('cpp-any', 'a', ['code'])
# NYI: consistent looks
#texpr_role = RoleClasses('cpp-texpr', 'span', ['a', 'code'])
expr_role = RoleClasses('cpp-expr', 'code', ['a'])
texpr_role = RoleClasses('cpp-texpr', 'span', ['a', 'span'])
# XRefRole-style classes
## any and cpp:any do not put these classes at the root
# n.b. the generic any machinery finds the specific 'cpp-class' object type
expect = 'any uses XRefRole classes'
assert {'xref', 'any', 'cpp', 'cpp-class'} <= any_role.content_classes['code'], expect
expect = 'cpp:any uses XRefRole classes'
assert {'xref', 'cpp-any', 'cpp'} <= cpp_any_role.content_classes['code'], expect
for role in (expr_role, texpr_role):
name = role.name
expect = '`{name}` puts the domain and role classes at its root'.format(name=name)
# NYI: xref should go in the references
assert {'xref', 'cpp', name} <= role.classes, expect
# reference classes
expect = 'the xref roles use the same reference classes'
assert any_role.classes == cpp_any_role.classes, expect
assert any_role.classes == expr_role.content_classes['a'], expect
assert any_role.classes == texpr_role.content_classes['a'], expect