The toctree for plugins now has the same sections as the index page.

Added an new page showing all plugins (including external ones) in
alphabetical order.
This commit is contained in:
Sebastian Mohr 2025-07-17 16:42:15 +02:00
parent 4d648510cc
commit b373b16ee9
5 changed files with 387 additions and 82 deletions

View file

@ -86,6 +86,8 @@ Other changes:
case is shown on separate lines.
* Refactored library.py file by splitting it into multiple modules within the
beets/library directory.
* Plugin docs slightly reorganized: added a new `all_plugins` page that lists all
plugins in alphabetical order, by default plugins are now categorized
2.3.1 (May 14, 2025)
--------------------

View file

@ -6,6 +6,7 @@
# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
from docs.pluginlist import PluginListDirective
project = "beets"
AUTHOR = "Adrian Sampson"
@ -96,3 +97,4 @@ def skip_member(app, what, name, obj, skip, options):
def setup(app):
app.connect("autodoc-skip-member", skip_member)
app.add_directive("pluginlist", PluginListDirective)

168
docs/pluginlist.py Normal file
View file

@ -0,0 +1,168 @@
import os
from pathlib import Path
from docutils import nodes
from docutils.parsers.rst import Directive, directives
class PluginListDirective(Directive):
"""Directive to list all .rst files in a given folder.
Along with their top-level header, with optional extra user-defined entries.
Usage:
.. filelist::
:path: path/to/folder
:exclude: file1.rst, file2.rst
:extra:
overview.rst # file link with implicit title from filename
Overview: overview.rst # file link with custom title
:ref:`user-guide` # arbitrary reST reference
"""
has_content = False
required_arguments = 0
optional_arguments = 0
option_spec = {
"path": directives.unchanged_required,
"exclude": directives.unchanged,
"extra": directives.unchanged,
}
def _extract_title(self, path: Path):
"""Extract the first section title from an rst file."""
with open(path, encoding="utf-8") as f:
lines = [ln.rstrip() for ln in f]
for idx, line in enumerate(lines):
if not line:
continue
# Check for underline-style title
if idx + 1 < len(lines) and set(lines[idx + 1]) <= set(
"= - `:'~^_*+#<>"
):
underline = lines[idx + 1]
if len(underline) >= len(line):
return line
# Or overline/underline style
if idx >= 1 and set(lines[idx - 1]) <= set("= - `:'~^_*+#<>"):
overline = lines[idx - 1]
if len(overline) >= len(line):
return line
# Fallback: filename without extension
return os.path.splitext(os.path.basename(path))[0]
def _get_current_src(self) -> Path:
"""Get the current source file path."""
current_doc = self.state.document.current_source
if not current_doc:
raise ValueError("Current document source could not be determined.")
return Path(current_doc).resolve()
def run(self):
folder_option = self.options.get("path")
if not folder_option:
error = self.state_machine.reporter.error(
'The "path" option is required for the pluginlist directive.',
nodes.literal_block(self.block_text, self.block_text),
line=self.lineno,
)
return [error]
# Resolve folder path relative to current doc file
cur_path = self._get_current_src()
target_folder = cur_path.joinpath(folder_option).resolve().parent
if not os.path.isdir(target_folder):
error = self.state_machine.reporter.error(
f'Path "{folder_option}" resolved to "{target_folder}". '
"Could not found or is not a directory.",
nodes.literal_block(self.block_text, self.block_text),
line=self.lineno,
)
return [error]
excludes_raw = self.options.get("exclude", "")
excludes = [x.strip() for x in excludes_raw.split(",") if x.strip()]
# Find .rst files, excluding specified
files = [
f
for f in os.listdir(target_folder)
if f.endswith(".rst") and f not in excludes
]
refs = []
for filename in files:
# Reference to the rst file
refuri = (
os.path.splitext(os.path.join(folder_option, filename))[
0
].replace(os.sep, "/")
+ ".html"
)
# Title for the link
title = self._extract_title(target_folder.joinpath(filename))
ref = nodes.reference("", title, internal=True, refuri=refuri)
refs.append(ref)
# Extra entries into refs
extra_option = self.options.get("extra", "")
if extra_option:
from docutils.statemachine import ViewList
for line in extra_option.splitlines():
entry = line.strip()
if not entry:
continue
para = nodes.paragraph()
# If entry is pure reST (contains role/backticks and no file .rst)
if (
"`" in entry or entry.strip().startswith(":ref")
) and ".rst" not in entry:
vl = ViewList()
vl.append(entry, self.block_text)
self.state.nested_parse(vl, self.content_offset, para)
else:
# file link: either 'file.rst' or 'Title: file.rst'
if ":" in entry:
title, target = [p.strip() for p in entry.split(":", 1)]
else:
target = entry
title = Path(entry).stem
if target.endswith(".rst"):
title = self._extract_title(
target_folder.joinpath(target)
)
rel = Path(self.options["path"]) / target
refuri = str(rel.with_suffix(".html")).replace(
os.sep, "/"
)
ref = nodes.reference(
"", title, internal=True, refuri=refuri
)
para += ref
else:
# fallback parse
vl = ViewList()
vl.append(entry, self.block_text)
self.state.nested_parse(vl, self.content_offset, para)
refs.append(para)
# Sort refs
refs.sort(key=lambda x: x.astext().lower())
# Build bullet list of links
bullet_list = nodes.bullet_list()
for ref in refs:
item = nodes.list_item()
para = nodes.paragraph()
para += ref
item += para
bullet_list += item
return [bullet_list]

View file

@ -0,0 +1,60 @@
All plugins
===========
..
README: The plugin list is automatically generated from all plugin
files in the plugins directory. If you want to add an external
plugin, please add it to the extra section of the pluginlist
directive.
The pluginlist directive is defined in
:file:`docs/pluglist.py` file.
.. pluginlist::
:path: .
:exclude: index.rst, all_plugins.rst
:extra:
`beets-yearfixer`_
`beets-alternatives`_
`beet-amazon`_
`beets-artistcountry`_
`beets-autofix`_
`beets-autogenre`_
`beets-audible`_
`beets-barcode`_
`beetcamp`_
`beetstream`_
`beets-bpmanalyser`_
`beets-check`_
`A cmus plugin`_
`beets-copyartifacts`_
`beets-describe`_
`drop2beets`_
`dsedivec`_
`beets-filetote`_
`beets-follow`_
`beetFs`_
`beets-goingrunning`_
`beets-ibroadcast`_
`beets-id3extract`_
`beets-importreplace`_
`beets-jiosaavn`_
`beets-more`_
`beets-mosaic`_
`beets-mpd-utils`_
`beets-noimport`_
`beets-originquery`_
`beets-plexsync`_
`beets-setlister`_
`beet-summarize`_
`beets-usertag`_
`beets-webm3u`_
`beets-webrouter`_
`whatlastgenre`_
`beets-xtractor`_
`beets-ydl`_
`beets-ytimport`_
`beets-yearfixer`_
`beets-youtube`_
.. include:: index.rst
:start-after: other_links

View file

@ -63,89 +63,47 @@ following to your configuration:
source_weight: 0.0
.. _autotagger_extensions:
Available Plugins
-----------------
We have organized the plugins into several categories to help you find what
you need. The categories are as follows:
.. contents::
:local:
:depth: 2
:backlinks: none
If you prefer to browse the plugins by their names, you can make use of the
:doc:`all_plugins <all_plugins>` page, which lists all plugins in alphabetical order.
.. toctree::
:hidden:
absubmit
acousticbrainz
advancedrewrite
albumtypes
aura
autobpm
badfiles
bareasc
beatport
bpd
bpm
bpsync
bucket
chroma
convert
deezer
discogs
duplicates
edit
embedart
embyupdate
export
fetchart
filefilter
fish
freedesktop
fromfilename
ftintitle
fuzzy
gmusic
hook
ihate
importadded
importfeeds
info
inline
ipfs
keyfinder
kodiupdate
lastgenre
lastimport
limit
listenbrainz
loadext
lyrics
mbcollection
mbsubmit
mbsync
metasync
missing
mpdstats
mpdupdate
musicbrainz
parentwork
permissions
play
playlist
plexupdate
random
replace
replaygain
rewrite
scrub
smartplaylist
sonosupdate
spotify
subsonicplaylist
subsonicupdate
substitute
the
thumbnails
types
unimported
web
zero
all_plugins
.. _autotagger_extensions:
Autotagger Extensions
---------------------
^^^^^^^^^^^^^^^^^^^^^
.. toctree::
:hidden:
:caption: Autotagger Extensions
:maxdepth: 1
chroma
deezer
discogs
fromfilename
musicbrainz
spotify
:doc:`chroma <chroma>`
Use acoustic fingerprinting to identify audio files with
@ -173,7 +131,31 @@ Autotagger Extensions
.. _Spotify: https://www.spotify.com
Metadata
--------
^^^^^^^^
.. toctree::
:hidden:
:caption: Metadata
:maxdepth: 1
absubmit
acousticbrainz
advancedrewrite
albumtypes
autobpm
bpsync
bpm
edit
embedart
fetchart
ftintitle
keyfinder
lastgenre
lyrics
metasync
replaygain
scrub
zero
:doc:`absubmit <absubmit>`
Analyse audio with the `streaming_extractor_music`_ program and submit the metadata to an AcousticBrainz server
@ -247,7 +229,19 @@ Metadata
.. _streaming_extractor_music: https://acousticbrainz.org/download
Path Formats
------------
^^^^^^^^^^^^
.. toctree::
:hidden:
:caption: Path Formats
:maxdepth: 1
albumtypes
advancedrewrite
inline
rewrite
substitute
the
:doc:`albumtypes <albumtypes>`
Format album type in path formats.
@ -275,7 +269,21 @@ Path Formats
end).
Interoperability
----------------
^^^^^^^^^^^^^^^^
.. toctree::
:hidden:
:caption: Interoperability
:maxdepth: 1
aura
embyupdate
kodiupdate
mpdupdate
plexupdate
sonosupdate
subsonicupdate
thumbnails
:doc:`aura <aura>`
A server implementation of the `AURA`_ specification.
@ -337,7 +345,28 @@ Interoperability
.. _Subsonic: http://www.subsonic.org/
Miscellaneous
-------------
^^^^^^^^^^^^^
.. toctree::
:hidden:
:caption: Miscellaneous
:maxdepth: 1
bpd
convert
duplicates
filefilter
fuzzy
hook
ihate
info
loadext
mbcollection
mbsubmit
missing
random
types
web
:doc:`bareasc <bareasc>`
Search albums and tracks with bare ASCII string matching.
@ -403,10 +432,47 @@ Miscellaneous
.. _MPD clients: https://mpd.wikia.com/wiki/Clients
.. _mstream: https://github.com/IrosTheBeggar/mStream
The following plugins are not categorized yet. If you have a strong
opinion about where they should go, please open a `PR or issue <https://github.com/beetbox/beets>`_.
.. toctree::
:caption: Uncategorized Plugins
:maxdepth: 1
badfiles.rst
bareasc.rst
beatport.rst
bucket.rst
export.rst
fish.rst
freedesktop.rst
gmusic.rst
importadded.rst
importfeeds.rst
ipfs.rst
lastimport.rst
limit.rst
listenbrainz.rst
mbsync.rst
mpdstats.rst
parentwork.rst
permissions.rst
play.rst
playlist.rst
replace.rst
smartplaylist.rst
subsonicplaylist.rst
unimported.rst
.. _other-plugins:
Other Plugins
-------------
^^^^^^^^^^^^^
In addition to the plugins that come with beets, there are several plugins
that are maintained by the beets community. To use an external plugin, there
@ -562,6 +628,9 @@ Here are a few of the plugins written by the beets community:
`beets-youtube`_
Adds YouTube Music as a tagger data source.
..
other_links
.. _beets-barcode: https://github.com/8h2a/beets-barcode
.. _beetcamp: https://github.com/snejus/beetcamp
.. _beetstream: https://github.com/BinaryBrain/Beetstream
@ -609,3 +678,7 @@ Here are a few of the plugins written by the beets community:
.. _beets-webm3u: https://github.com/mgoltzsche/beets-webm3u
.. _beets-webrouter: https://github.com/mgoltzsche/beets-webrouter
.. _beets-autogenre: https://github.com/mgoltzsche/beets-autogenre