From f5924831561d4f2c31f3c0a1c31be85229522b9d Mon Sep 17 00:00:00 2001 From: Jakob Lykke Andersen Date: Sat, 26 May 2018 17:25:41 +0200 Subject: [PATCH] Add cpp:texpr role (style alternative to cpp:expr) Simplified version of sphinx-doc/sphinx#4836, thanks to mickk-on-cpp. --- CHANGES | 1 + doc/usage/restructuredtext/domains.rst | 54 ++++++++------- sphinx/domains/cpp.py | 26 ++++++-- .../test-domain-cpp/xref_consistency.rst | 12 ++++ tests/test_domain_cpp.py | 65 +++++++++++++++++++ 5 files changed, 131 insertions(+), 27 deletions(-) create mode 100644 tests/roots/test-domain-cpp/xref_consistency.rst diff --git a/CHANGES b/CHANGES index 13a4ac151..bc4207f81 100644 --- a/CHANGES +++ b/CHANGES @@ -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 ---------- diff --git a/doc/usage/restructuredtext/domains.rst b/doc/usage/restructuredtext/domains.rst index 452cf63ae..d177ec643 100644 --- a/doc/usage/restructuredtext/domains.rst +++ b/doc/usage/restructuredtext/domains.rst @@ -569,10 +569,10 @@ visibility statement (``public``, ``private`` or ``protected``). Full and partial template specialisations can be declared:: .. cpp:class:: template<> \ - std::array + std::array .. cpp:class:: template \ - std::array + std::array .. 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&`. + An expression: :cpp:expr:`a * f(a)` (or as text: :cpp:texpr:`a * f(a)`). + + A type: :cpp:expr:`const MySortedContainer&` + (or as text :cpp:texpr:`const MySortedContainer&`). 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&` + (or as text :cpp:texpr:`const MySortedContainer&`). - An expression: :cpp:expr:`a * f(a)`. A type: :cpp:expr:`const - MySortedContainer&`. 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 \ 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```. 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\```. + 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```. + 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\```. + + 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 \ Inner -In general the reference must include the template paraemter declarations, +In general the reference must include the template parameter declarations, e.g., ``template\ Wrapper::Outer`` (:cpp:class:`template\ Wrapper::Outer`). Currently the lookup only succeed if the template parameter identifiers are equal strings. That is, ``template\ 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 diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 830f15f29..903a766bc 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -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), diff --git a/tests/roots/test-domain-cpp/xref_consistency.rst b/tests/roots/test-domain-cpp/xref_consistency.rst new file mode 100644 index 000000000..cb33000f7 --- /dev/null +++ b/tests/roots/test-domain-cpp/xref_consistency.rst @@ -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` diff --git a/tests/test_domain_cpp.py b/tests/test_domain_cpp.py index de72148a5..6680510a6 100644 --- a/tests/test_domain_cpp.py +++ b/tests/test_domain_cpp.py @@ -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}) .*?class=["\'](?P.*?)["\'].*?>' + '.*' + '').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