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

Improve cross-reference documentation #12944

Merged
merged 4 commits into from
Oct 3, 2024
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
19 changes: 11 additions & 8 deletions doc/usage/domains/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -158,19 +158,22 @@ giving the domain name, i.e. ::
Cross-referencing syntax
~~~~~~~~~~~~~~~~~~~~~~~~

For cross-reference roles provided by domains, the same facilities exist as for
general cross-references. See :ref:`xref-syntax`.

For cross-reference roles provided by domains,
the same :ref:`cross-referencing modifiers <xref-modifiers>` exist
as for general cross-references.
In short:

* You may supply an explicit title and reference target: ``:role:`title
<target>``` will refer to *target*, but the link text will be *title*.
* You may supply an explicit title and reference target:
``:py:mod:`mathematical functions <math>``` will refer to the ``math`` module,
but the link text will be "mathematical functions".

* If you prefix the content with ``!``, no reference/hyperlink will be created.
* If you prefix the content with an exclamation mark (``!``),
no reference/hyperlink will be created.

* If you prefix the content with ``~``, the link text will only be the last
component of the target. For example, ``:py:meth:`~Queue.Queue.get``` will
refer to ``Queue.Queue.get`` but only display ``get`` as the link text.
component of the target.
For example, ``:py:meth:`~queue.Queue.get``` will
refer to ``queue.Queue.get`` but only display ``get`` as the link text.

Built-in domains
----------------
Expand Down
51 changes: 39 additions & 12 deletions doc/usage/domains/python.rst
Original file line number Diff line number Diff line change
Expand Up @@ -716,18 +716,45 @@ a matching identifier is found:

.. versionadded:: 0.4

The name enclosed in this markup can include a module name and/or a class name.
For example, ``:py:func:`filter``` could refer to a function named ``filter``
in the current module, or the built-in function of that name. In contrast,
``:py:func:`foo.filter``` clearly refers to the ``filter`` function in the
``foo`` module.

Normally, names in these roles are searched first without any further
qualification, then with the current module name prepended, then with the
current module and class name (if any) prepended. If you prefix the name with
a dot, this order is reversed. For example, in the documentation of Python's
:mod:`codecs` module, ``:py:func:`open``` always refers to the built-in
function, while ``:py:func:`.open``` refers to :func:`codecs.open`.

Target specification
^^^^^^^^^^^^^^^^^^^^

The target can be specified as a fully qualified name
(e.g. ``:py:meth:`my_module.MyClass.my_method```)
or any shortened version
(e.g. ``:py:meth:`MyClass.my_method``` or ``:py:meth:`my_method```).
See `target resolution`_ for details on the resolution of shortened names.

:ref:`Cross-referencing modifiers <xref-modifiers>` can be applied.
In short:

* You may supply an explicit title and reference target:
``:py:mod:`mathematical functions <math>``` will refer to the ``math`` module,
but the link text will be "mathematical functions".

* If you prefix the content with an exclamation mark (``!``),
no reference/hyperlink will be created.

* If you prefix the content with ``~``, the link text will only be the last
component of the target.
For example, ``:py:meth:`~queue.Queue.get``` will
refer to ``queue.Queue.get`` but only display ``get`` as the link text.


Target resolution
^^^^^^^^^^^^^^^^^

A given link target name is resolved to an object using the following strategy:

Names in these roles are searched first without any further qualification,
then with the current module name prepended,
then with the current module and class name (if any) prepended.

If you prefix the name with a dot (``.``), this order is reversed.
For example, in the documentation of Python's :py:mod:`codecs` module,
``:py:func:`open``` always refers to the built-in function,
while ``:py:func:`.open``` refers to :func:`codecs.open`.

A similar heuristic is used to determine whether the name is an attribute of
the currently documented class.
Expand Down
180 changes: 113 additions & 67 deletions doc/usage/referencing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,83 +4,84 @@
Cross-referencing syntax
========================

Cross-references are generated by many semantic interpreted text roles.
Basically, you only need to write ``:role:`target```, and a link will be
created to the item named *target* of the type indicated by *role*. The link's
text will be the same as *target*.

There are some additional facilities, however, that make cross-referencing
roles more versatile:

* You may supply an explicit title and reference target,
like in reStructuredText direct hyperlinks:
``:role:`title <target>``` will refer to *target*,
but the link text will be *title*.

* If you prefix the content with ``!``, no reference/hyperlink will be created.

* If you prefix the content with ``~``, the link text will only be the last
component of the target. For example, ``:py:meth:`~Queue.Queue.get``` will
refer to ``Queue.Queue.get`` but only display ``get`` as the link text. This
does not work with all cross-reference roles, but is domain specific.

In HTML output, the link's ``title`` attribute (that is e.g. shown as a
tool-tip on mouse-hover) will always be the full target name.


.. _any-role:

Cross-referencing anything
--------------------------

.. rst:role:: any

.. versionadded:: 1.3

This convenience role tries to do its best to find a valid target for its
reference text.

* First, it tries standard cross-reference targets that would be referenced
by :rst:role:`doc`, :rst:role:`ref` or :rst:role:`option`.

Custom objects added to the standard domain by extensions (see
:meth:`.Sphinx.add_object_type`) are also searched.

* Then, it looks for objects (targets) in all loaded domains. It is up to
the domains how specific a match must be. For example, in the Python
domain a reference of ``:any:`Builder``` would match the
``sphinx.builders.Builder`` class.

If none or multiple targets are found, a warning will be emitted. In the
case of multiple targets, you can change "any" to a specific role.

This role is a good candidate for setting :confval:`default_role`. If you
do, you can write cross-references without a lot of markup overhead. For
example, in this Python function documentation::

.. function:: install()

This function installs a `handler` for every signal known by the
`signal` module. See the section `about-signals` for more information.

there could be references to a glossary term (usually ``:term:`handler```), a
Python module (usually ``:py:mod:`signal``` or ``:mod:`signal```) and a
section (usually ``:ref:`about-signals```).

The :rst:role:`any` role also works together with the
:mod:`~sphinx.ext.intersphinx` extension: when no local cross-reference is
found, all object types of intersphinx inventories are also searched.
One of Sphinx's most useful features is creating automatic cross-references
through semantic cross-referencing roles.
A cross reference to an object description, such as ``:func:`spam```,
will create a link to the place where ``spam()`` is documented,
appropriate to each output format (HTML, PDF, ePUB, etc.).

Sphinx supports various cross-referencing roles to create links
to other elements in the documentation.
In general, writing ``:role:`target``` creates a link to
the object called *target* of the type indicated by *role*.
The link's text depends the role but is often the same as or similar to *target*.

.. _xref-modifiers:

The behavior can be modified in the following ways:

* **Custom link text:**
You can specify the link text explicitly using the same
notation as in reStructuredText :ref:`external links <rst-external-links>`:
``:role:`custom text <target>``` will refer to *target*
and display *custom text* as the text of the link.

* **Suppressed link:**
Prefixing with an exclamation mark (``!``) prevents the creation of a link
but otherwise keeps the visual output of the role.

For example, writing ``:py:func:`!target``` displays :py:func:`!target`,
with no link generated.

This is helpful for cases in which the link target does not exist;
e.g. changelog entries that describe removed functionality,
or third-party libraries that don't support :doc:`intersphinx
</usage/extensions/intersphinx>`.
Suppressing the link prevents warnings in :confval:`nitpicky` mode.

* **Modified domain reference:**
When :ref:`referencing domain objects <ref-objects>`,
a tilde ``~`` prefix shortens the link text the last component of the target.
For example, ``:py:meth:`~queue.Queue.get``` will
refer to ``queue.Queue.get`` but only display ``get`` as the link text.

In HTML output, the link's ``title`` attribute
(that is e.g. shown as a tool-tip on mouse-hover)
will always be the full target name.

Some of the built-in cross-reference roles are:

* :rst:role:`:any: <any>`,
:rst:role:`:doc: <doc>`,
:rst:role:`:ref: <ref>`
* :rst:role:`:confval: <confval>`,
:rst:role:`:envvar: <envvar>`,
:rst:role:`:option: <option>`
* :rst:role:`:manpage: <manpage>`,
:rst:role:`:pep: <pep>`,
:rst:role:`:rfc: <rfc>`
* :rst:role:`download`,
:rst:role:`:index: <index>`
:rst:role:`:numref: <numref>`,
:rst:role:`:keyword: <keyword>`,
:rst:role:`:term: <term>`,
:rst:role:`:token: <token>`
* :rst:role:`!:func:`
(this uses the :confval:`primary_domain`, e.g. :rst:role:`:py:func: <py:func>`)
* :ref:`Domain cross-reference roles <ref-objects>`
Comment on lines +52 to +71
Copy link
Contributor Author

@timhoffm timhoffm Oct 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a short feedback, I find this listing not too helpful for the following reasons:

  • The listing alomost replicates the toc of the the page.
    grafik
    The only missing items there are manpage, pep, rfc. But possibly they should also get a section on this page?
  • The roles are given without context. While names are partially self-explanatory, you could give some category names to the bullet points.
  • Stating "some" with a long list is unsettling. If possible we should make the list complete, or at the very least change the wording to "The most important ..." or similar.

Given the toc, I suggest that we can live without that list. But if you want to keep it, I suggest a bit of rework based on the above comments.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gone: 5eb68b2

The intent was to highlight the breath of roles that support cross referencing, but was perhaps ill-conceived.

A

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@timhoffm

typo:

(...) prefix shortens the link text the last component of the target.

should be:

(...) prefix shortens the link text to the last component of the target.



The roles are given without context. While names are partially self-explanatory, you could give some category names to the bullet points.

In this case less is more. I feel the TOC is repetitive by always starting with the verb "Cross-referencing XYZ" but the corresponding headings work well reading through the document. Adding more intro text under each heading would be a mistake because it would add clutter and waste the reader's time. (As a comparison: did you ever notice how the first half of most videos should be skipped because it just repeats an intro we already know? So lets skip the formalism of adding intros under the headings.)



The only missing items there are manpage, pep, rfc. But possibly they should also get a section on this page?

Untangling scope overlap of the Cross-referencing syntax doc with the Roles doc should be posted separately as an issue. It seems like a complex decision.



I'm using it in basically all of my Python module documentations as default_role, as recommended in the documentation, and it works great.

@mgeier We've had a number of users asking for help about ambiguous error messages caused by :any: when later something changes in the code (and they were using shortened syntax in the qualified name for example). Personally I always pinpoint the exact role to reduce ambiguity for myself and the reader -this also helps keep error messages more accurate- hence I don't use default_role.

This makes my Python docstrings more concise

I only write cross-references in the docstring when I absolutely must.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@electric-coder

This makes my Python docstrings more concise

I only write cross-references in the docstring when I absolutely must.

Well, that's your users' loss!

My docs are full of cross-references, see e.g. https://python-sounddevice.readthedocs.io/en/0.5.0/api/convenience-functions.html

Maybe you don't like writing them because they are so clunky when specifying the exact role?
Maybe you should do your users a favor and consider using default_role = 'any'?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A suitable default role depends on the type of documentation (size, how much and which kind of cross-links do you need, etc.) For example at matplotlib, we use the py:obj default role as a good middle-ground. We have a lot of code references and being able to write them often with just adding backticks keeps the doc sources very readable (particularly useful for docstrings that are often also used in plain text). For Python objects there is little ambiguity because the naming is determined by the code and you almost always want to have the function/class name exactly as is in the rendered docs.
We keep other references explicit, e.g. using :doc: or :ref: to prevent ambiguity. Also, by their nature, they are often longer and break the flow of reading anyway, so that adding the explicit prefixes does not make them significantly more clunky than they already are.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For example at matplotlib, we use the py:obj default role as a good middle-ground.

That is a good point, thanks for mentioning it!

When I experimented with this many years ago, py:obj had a severe limitation: it didn't allow me to use parentheses like this: `my_function()`. But it worked (and still works) fine with any.
That was the main reason why I used any in my documentations and I subsequently completely forgot about py:obj ... until now.

I just checked, and this limitation seems to have been lifted in the meantime!

Therefore, I now agree that :any: is a niche application and most Sphinx users should choose default_role = 'py:obj' in most of their (Python-based) projects!

I'm also doing that in the project I used as an example above: spatialaudio/python-sounddevice#567


.. _ref-objects:

Cross-referencing objects
-------------------------

These roles are described with their respective domains:

* :ref:`Python <python-xref-roles>`
* :ref:`C <c-xref-roles>`
* :ref:`C++ <cpp-xref-roles>`
* :ref:`JavaScript <js-xref-roles>`
* :ref:`reStructuredText <rst-xref-roles>`
* :ref:`Python <python-xref-roles>`


.. _ref-role:
Expand Down Expand Up @@ -267,3 +268,48 @@ The following role creates a cross-reference to a term in a

If you use a term that's not explained in a glossary, you'll get a warning
during build.


.. _any-role:

Cross-referencing anything
--------------------------

.. rst:role:: any

.. versionadded:: 1.3

This convenience role tries to do its best to find a valid target for its
reference text.

* First, it tries standard cross-reference targets that would be referenced
by :rst:role:`doc`, :rst:role:`ref` or :rst:role:`option`.

Custom objects added to the standard domain by extensions (see
:meth:`.Sphinx.add_object_type`) are also searched.

* Then, it looks for objects (targets) in all loaded domains. It is up to
the domains how specific a match must be. For example, in the Python
domain a reference of ``:any:`Builder``` would match the
``sphinx.builders.Builder`` class.

If none or multiple targets are found, a warning will be emitted. In the
case of multiple targets, you can change "any" to a specific role.

This role is a good candidate for setting :confval:`default_role`. If you
do, you can write cross-references without a lot of markup overhead. For
example, in this Python function documentation::

.. function:: install()

This function installs a `handler` for every signal known by the
`signal` module. See the section `about-signals` for more information.

there could be references to a glossary term (usually ``:term:`handler```), a
Python module (usually ``:py:mod:`signal``` or ``:mod:`signal```) and a
section (usually ``:ref:`about-signals```).

The :rst:role:`any` role also works together with the
:mod:`~sphinx.ext.intersphinx` extension: when no local cross-reference is
found, all object types of intersphinx inventories are also searched.

2 changes: 2 additions & 0 deletions doc/usage/restructuredtext/basics.rst
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,8 @@ Two more syntaxes are supported: *CSV tables* and *List tables*. They use an
Hyperlinks
----------

.. _rst-external-links:

External links
~~~~~~~~~~~~~~

Expand Down