Improve GitHub release rendering (#6356)

This PR hardens the release/changelog generation pipeline so ReST ->
Markdown conversion produces stable, correctly formatted links and
inline code.

- Fix broken rendering of generated references by ensuring RST
`~`-shortened forms like ``` `:class:`~beetsplug._utils...``` are
converted into real Markdown links.
- Updates `extra/release.py` to generate clearer link text for CLI
command refs (e.g., `list-cmd` becomes 'list command') while keeping the
same documentation targets.
- Simplifies the Markdown post-processing step by removing the redundant
`MD_REPLACEMENTS` layer, reducing ad-hoc formatting logic in the release
script.
- Standardizes changelog section header structure in
`docs/changelog.rst` (and the release template) to align with
Sphinx/ReST conventions and avoid ambiguous colon-based headers.
- Strengthens CI by extending `lint-docs` in `pyproject.toml` to fail on
single-backtick inline literals in `.rst`, enforcing double-backtick
literals so `pandoc` does not emit meaningless `<span
class="title-ref">...` output that breaks Markdown rendering.

Net effect: more predictable release notes output, fewer conversion edge
cases, and earlier detection of docs formatting that would render
incorrectly downstream.
This commit is contained in:
Šarūnas Nejus 2026-02-10 23:59:38 +00:00 committed by GitHub
commit bc52682428
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 380 additions and 204 deletions

File diff suppressed because it is too large Load diff

View file

@ -99,7 +99,7 @@ started.
write: no # Don't modify tags
4. **Add customization via plugins (optional):**
Beets comes with many plugins that extend its functionality. You can
enable plugins by adding a `plugins` section to your config file.
enable plugins by adding a ``plugins`` section to your config file.
We recommend adding at least one :ref:`Autotagger Plugin
<autotagger_extensions>` to help with fetching metadata during import.

View file

@ -1,7 +1,7 @@
MusicBrainz Pseudo-Release Plugin
=================================
The `mbpseudo` plugin can be used *instead of* the `musicbrainz` plugin to
The ``mbpseudo`` plugin can be used *instead of* the ``musicbrainz`` plugin to
search for MusicBrainz pseudo-releases_ during the import process, which are
added to the normal candidates from the MusicBrainz search.
@ -20,12 +20,12 @@ Configuration
-------------
Since this plugin first searches for official releases from MusicBrainz, all
options from the `musicbrainz` plugin's :ref:`musicbrainz-config` are supported,
but they must be specified under `mbpseudo` in the configuration file.
Additionally, the configuration expects an array of scripts that are desired for
the pseudo-releases. For ``artist`` in particular, keep in mind that even
pseudo-releases might specify it with the original script, so you should also
configure import :ref:`languages` to give artist aliases more priority.
options from the ``musicbrainz`` plugin's :ref:`musicbrainz-config` are
supported, but they must be specified under ``mbpseudo`` in the configuration
file. Additionally, the configuration expects an array of scripts that are
desired for the pseudo-releases. For ``artist`` in particular, keep in mind that
even pseudo-releases might specify it with the original script, so you should
also configure import :ref:`languages` to give artist aliases more priority.
Therefore, the minimum configuration for this plugin looks like this:
.. code-block:: yaml
@ -39,10 +39,10 @@ Therefore, the minimum configuration for this plugin looks like this:
scripts:
- Latn
Note that the `search_limit` configuration applies to the initial search for
official releases, and that the `data_source` in the database will be
"MusicBrainz". Nevertheless, `data_source_mismatch_penalty` must also be
specified under `mbpseudo` if desired (see also
Note that the ``search_limit`` configuration applies to the initial search for
official releases, and that the ``data_source`` in the database will be
"MusicBrainz". Nevertheless, ``data_source_mismatch_penalty`` must also be
specified under ``mbpseudo`` if desired (see also
:ref:`metadata-source-plugin-configuration`). An example with multiple data
sources may look like this:

View file

@ -145,7 +145,7 @@ Default
:default: yes
If a field name contains ``artist``, then any lowercase ``the`` will be
capitalized. Useful for bands with `The` as part of the proper name,
capitalized. Useful for bands with ``The`` as part of the proper name,
like ``Amyl and The Sniffers``.
.. conf:: all_caps

View file

@ -99,6 +99,8 @@ def create_rst_replacements() -> list[Replacement]:
refs = get_refs()
def make_ref_link(ref_id: str, name: str | None = None) -> str:
if ref_id.endswith("-cmd"):
name = f"{ref_id.removesuffix('-cmd')} command"
ref = refs[ref_id]
return rf"`{name or ref.name} <{ref.url}>`_"
@ -118,13 +120,14 @@ def create_rst_replacements() -> list[Replacement]:
lambda m: explicit_replacements.get(m[0], m[0]),
),
# Replace Sphinx directives by documentation URLs, e.g.,
# :ref:`/plugins/autobpm` -> [AutoBPM Plugin](DOCS/plugins/autobpm.html)
# :ref:`/plugins/autobpm` -> [AutoBPM Plugin](DOCS/plugins/autobpm.html) # noqa: E501
# :ref:`list-cmd` -> [list command](DOCS/reference/cli.html#list-cmd)
(
r":(?:ref|doc|class|conf):`+(?:([^`<]+)<)?/?([\w.:/_-]+)>?`+",
r":(?:ref|doc|class|conf):`+~?(?:([^`<]+)<)?/?([\w.:/_-]+)>?`+",
lambda m: make_ref_link(m[2], m[1]),
),
# Convert command references to documentation URLs
# `beet move` or `move` command -> [import](DOCS/reference/cli.html#import)
# `beet move` or `move` command -> [move command](DOCS/reference/cli.html#move-cmd) # noqa: E501
(
rf"`+beet ({commands})`+|`+({commands})`+(?= command)",
lambda m: make_ref_link(f"{m[1] or m[2]}-cmd"),
@ -139,14 +142,9 @@ def create_rst_replacements() -> list[Replacement]:
]
MD_REPLACEMENTS: list[Replacement] = [
(r"^(\w[^\n]{,80}):(?=\n\n[^ ])", r"### \1"), # format section headers
(r"^(\w[^\n]{81,}):(?=\n\n[^ ])", r"**\1**"), # and bolden too long ones
(r"### [^\n]+\n+(?=### )", ""), # remove empty sections
]
order_bullet_points = partial(
re.compile(r"(\n- .*?(?=\n(?! *(-|\d\.) )|$))", flags=re.DOTALL).sub,
lambda m: "\n- ".join(sorted(m.group().split("\n- "))),
lambda m: "\n- ".join(sorted(m.group().split("\n- "), key=str.lower)),
)
@ -165,13 +163,21 @@ def update_changelog(text: str, new: Version) -> str:
Unreleased
----------
New features:
..
New features
~~~~~~~~~~~~
Bug fixes:
..
Bug fixes
~~~~~~~~~
For packagers:
..
For plugin developers
~~~~~~~~~~~~~~~~~~~~~
Other changes:
..
Other changes
~~~~~~~~~~~~~
{new_header}
{"-" * len(new_header)}
@ -247,9 +253,6 @@ def changelog_as_markdown(rst: str) -> str:
md = rst2md(rst)
for pattern, repl in MD_REPLACEMENTS:
md = re.sub(pattern, repl, md, flags=re.M | re.DOTALL)
# order bullet points in each of the lists alphabetically to
# improve readability
return order_bullet_points(md)

View file

@ -238,7 +238,17 @@ cmd = "ruff check --config=pyproject.toml"
[tool.poe.tasks.lint-docs]
help = "Lint the documentation"
shell = "sphinx-lint --enable all --disable default-role $(git ls-files '*.rst')"
interpreter = "bash"
shell = """
set -o pipefail
files=$(git ls-files '*.rst')
grep -Eno ' `[^`][^`]+`[^_]' $files |
sed 's/ .*/ Use double backticks for inline literal (double-backticks-required)/' && failed=1
sphinx-lint --enable all --disable default-role $files || failed=1
exit ${failed:-0}
"""
[tool.poe.tasks.update-dependencies]
help = "Update dependencies to their latest versions."

View file

@ -20,12 +20,19 @@ pytestmark = pytest.mark.skipif(
@pytest.fixture
def rst_changelog():
return """New features:
return """
Unreleased
----------
New features
~~~~~~~~~~~~
- :doc:`/plugins/substitute`: Some substitute
multi-line change.
:bug:`5467`
- :ref:`list-cmd` Update.
- |BeetsPlugin| Some plugin change.
- See :class:`~beetsplug._utils.musicbrainz.MusicBrainzAPI` for documentation.
You can do something with this command:
@ -33,7 +40,8 @@ You can do something with this command:
$ do-something
Bug fixes:
Bug fixes
~~~~~~~~~
- Some fix that refers to an issue.
:bug:`5467`
@ -53,12 +61,14 @@ Bug fixes:
2. Second
and some details
Section naaaaaaaaaaaaaaaaaaaaaaaammmmmmmmmmmmmmmmeeeeeeeeeeeeeee with over 80
characters:
Long parapgraph naaaaaaaaaaaaaaaaaaaaaaaammmmmmmmmmmmmmmmeeeeeeeeeeeeeee ending
with a colon:
Empty section:
.. For plugin developers
.. ~~~~~~~~~~~~~~~~~~~~~
Other changes:
Other changes
~~~~~~~~~~~~~
- Changed ``bitesize`` label to ``good first issue``. Our `contribute`_ page is now
automatically populated with these issues. :bug:`4855`
@ -68,23 +78,28 @@ Other changes:
2.1.0 (November 22, 2024)
-------------------------
Bug fixes:
Bug fixes
~~~~~~~~~
- Fixed something."""
@pytest.fixture
def md_changelog():
return r"""### New features
return r"""# Unreleased
## New features
- [beets.plugins.BeetsPlugin](https://beets.readthedocs.io/en/stable/api/generated/beets.plugins.BeetsPlugin.html#beets.plugins.BeetsPlugin) Some plugin change.
- [list command](https://beets.readthedocs.io/en/stable/reference/cli.html#list-cmd) Update.
- [Substitute Plugin](https://beets.readthedocs.io/en/stable/plugins/substitute.html): Some substitute multi-line change. :bug: (#5467)
- [list](https://beets.readthedocs.io/en/stable/reference/cli.html#list-cmd) Update.
- See [beetsplug.\_utils.musicbrainz.MusicBrainzAPI](https://beets.readthedocs.io/en/stable/api/generated/beetsplug._utils.musicbrainz.MusicBrainzAPI.html#beetsplug._utils.musicbrainz.MusicBrainzAPI) for documentation.
You can do something with this command:
$ do-something
### Bug fixes
## Bug fixes
- Another fix with an enumerated list
1. First and some details
@ -96,15 +111,15 @@ You can do something with this command:
- First nested bullet point with some text that wraps to the next line
- Second nested bullet point
**Section naaaaaaaaaaaaaaaaaaaaaaaammmmmmmmmmmmmmmmeeeeeeeeeeeeeee with over 80 characters**
Long parapgraph naaaaaaaaaaaaaaaaaaaaaaaammmmmmmmmmmmmmmmeeeeeeeeeeeeeee ending with a colon:
### Other changes
## Other changes
- Changed `bitesize` label to `good first issue`. Our [contribute](https://github.com/beetbox/beets/contribute) page is now automatically populated with these issues. :bug: (#4855)
# 2.1.0 (November 22, 2024)
### Bug fixes
## Bug fixes
- Fixed something.""" # noqa: E501