Skip to content

Allow creating custom targets in a domain #9662

@ghost

Description

I'd like to be able to define custom link targets for domains.

I'm doing literate programming in the spirit of Donald E. Knuth, e.g.

MyClass
=======

MyClass does foo and has an attribute ``bar``

.. code-block:: python

   class MyClass:
      bar = "foo"

[...] some text later [...]

The behaviour of :py:class:`MyClass` is defined by the value of :py:attr:`MyClass.bar`.

Since bar is not defined via the directive .. py:method:: MyClass.bar, the above :py:attr: does not produce a link since the target cannot be found.
I could add the directive right before the code-block but that would produce a "MyClass.bar" string in the output which a oddly misplaced as the definition is right in the code-block.

Thus, I'd like to have a new directive or new option to existing directives, which produces a target (and an index entry) but not any output, e.g.

.. py:class:: MyClass
   :tagetandindexonly:

MyClass
=======

MyClass does foo and has an attribute ``bar``

.. py:attr-target:: MyClass.foo
.. code-block:: python

   class MyClass:
      bar = "foo"

[...] some text later [...]

The behaviour of :py:class:`MyClass` is defined by the value of :py:attr:`MyClass.bar`.

Here :py:class:... and :py:attr:... would find and link to the corresponding targets.

The requested behaviour is sort of the opposite of what :noindex: currently provides, instead of producing output without target, produce a target but no output.

Some thoughts

  • This feature needs support per domain as the implementation of the reference/targets is completely handed to the derived domain (Python)class.
  • Consequently, custom domains are likely to not support this feature out of the box but they might join once this hits a Sphinx release

Implementation idea 1

  • The class ObjectDescription already has a add_target_and_index() which is essentially all what we need.
  • Subclasses of ObjectDescription might have overridden run(), these might need additional adjustments
  • Rewrite run() to support an option :targetandindexonly: (a bad name but will do for this report) and only generate a "target" node.
    Call add_target_and_index() with that target node.
  • This would only need a change in the class ObjectDescription and some minor changes in those classes overriding run().

Open questions:

  • Was add_target_and_index() ever part of an public API? Cannot find anything in the Domain API [1]. More precisely, was there a guarantee that the node passed is a desc_signature?
  • Unclear whether the target is applied to a following heading (see Class example above)

Implementation idea 2

  • Duplicate each .. py:whatever:: directive to a .. py:whatever-target: directive which takes a signature as argument and produces the corresponding target and index.
  • This directive should have the same semantics as .. _reference-label:
  • More flexibility as we do not need to relay on add_target_and_index(), e.g. works with headers as target.
  • Would need more implementation in the individual domains as they must implement those new directives

Implementation idea 3

  • Maybe go a step further and provide a generic .. domaintarget:: directive with options which would produce for any domain and any signature a suitable target.
.. domaintarget::
   :domain: py
   :subdomain: attr
   :signature: bar
   :signature: bar2  (event multiple signature might be allowed)

whatever is hier is the target
  • This directive should have the same semantics as a .. _targetname: label.

  • This needs the domains to expose references and targets such that a generic .. domaintarget:: directive can be implemented, e.g.

    • domain.subdomains() list of subdomains ("attr", "class", "foo", ...)
    • domain.get_target("attr", "class.attrname") where the first argument is the "subdomain" get target reference
    • domain.add_target("attr", "class.attrname") creates a target node
    • domain.add_index("attr", "class.attrname") creates an index node
  • Avoids duplicating code as all the reference/target logic would be centralised in the domain and the domain classes can use this as well.

  • Needs refactoring of implementation of existing domain classes.

How to implement

I've never hacked on Sphinx or docutils but if someone is willing to mentor me, I would try to implement this feature.

[1] https://www.sphinx-doc.org/en/master/extdev/domainapi.html

Metadata

Metadata

Assignees

No one assigned

    Labels

    type:enhancementenhance or introduce a new feature

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions