mne-python: Add non-breaking spaces between units

Proposed documentation enhancement

There should be a non-breaking space between the number and its unit. I don’t know if this is supported here, I would use either   or   (preferably the latter). This should be consistent throughout our documents.

e.g. 250 ms

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 3
  • Comments: 16 (16 by maintainers)

Most upvoted comments

Hello…everyone. As the non-breaking space role is added, Now I will like to go through the documentations (As I plan to do to strengthen my understanding of the project, and future effective contributions), and fix all the places, that should contain non-breaking space, but doesn’t. I will probably list those somewhere(could be in this issue comment, as this is related to this topic, as well as you guys can check if all are appropriate) so that I can fix the all in one commit later. Will this be alright?

FYI one way it’s done (using html escape &nbsp) is seen in this tutorial:

https://github.com/mne-tools/mne-python/blob/11bc09911a2dbb6b73a86b31d8140ccd8a93b1c3/tutorials/preprocessing/25_background_filtering.py#L728

Other approach (using a \uXXXX style escape, in this case for optional hyphens) is seen here:

https://github.com/mne-tools/mne-python/blob/11bc09911a2dbb6b73a86b31d8140ccd8a93b1c3/doc/conf.py#L880-L884

Yet another approach is the rST substitution syntax:

https://github.com/mne-tools/mne-python/blob/11bc09911a2dbb6b73a86b31d8140ccd8a93b1c3/doc/conf.py#L1074

git grep "|ensp|" will show you that one in-use.

All of those seem to me to make the rST source rather difficult to read. Making a custom rST interpreted text role (this is the :unit:`ms` style mentioned above) might be the nicest approach, especially if it were designed to take both the number and the unit, like this: :unit:`5 ms` and automatically replace the ordinary space with a non-breaking (possibly thin) space.

this is a great start! Here’s a proposed cleanup:

from docutils import nodes
from mne.utils import _validate_type


def unit_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
    parts = text.split()

    def pass_error_to_sphinx(rawtext, text, lineno, inliner):
        msg = inliner.reporter.error(
            'The :unit: role requires a space-separated number and unit; '
            f'got {text}', line=lineno)
        prb = inliner.problematic(rawtext, rawtext, msg)
        return [prb], [msg]

    # ensure only two parts
    if len(parts) != 2:
        return pass_error_to_sphinx(rawtext, text, lineno, inliner)
    # ensure first part is a number
    try:
        _validate_type(parts[0], 'numeric', 'Numeric part of :unit: role')
    except TypeError:
        return pass_error_to_sphinx(rawtext, text, lineno, inliner)
    # input is well-formatted: proceed
    node = nodes.Text('\u202F'.join(parts))
    return [node], []


def setup(app):
    app.add_role('unit', unit_role)

Can you open a PR that adds this role, and uses it instead of the &nbsp here:

https://github.com/mne-tools/mne-python/blob/11bc09911a2dbb6b73a86b31d8140ccd8a93b1c3/tutorials/preprocessing/25_background_filtering.py#L728

I don’t think we test any of our sphinx extensions, but it ought to be fairly easy to test this. @larsoner where would you suggest adding a test for our sphinx extensions?

After taking some time for studying about rst and sphinx,I came up with this custom role.

from docutils import nodes


def unit_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
    try:
        value = float(text.split(' ')[0])
        if len(text.split(' ')) != 2:
            raise ValueError
    except ValueError:
        msg = inliner.reporter.error(
            'The structure should be a value(int/float) and a unit(str) separated by a normal space.Ex:`5 ms`', line=lineno)
        prb = inliner.problematic(rawtext, rawtext, msg)
        return [prb], [msg]
    node = nodes.Text("\u202F".join(text.split(' ')))
    return [node], []


def setup(app):
    app.add_role('unit', unit_role)

This results in :unit:`5 ms`. Does this seem okay?