Commit graph

13794 commits

Author SHA1 Message Date
Brock Grassy
c51f68be15 Fix rebase in tests 2026-03-16 06:52:35 +00:00
Brock Grassy
a3b7cfa1b3 Add IOMixin to fix failures 2026-03-16 06:52:35 +00:00
Brock Grassy
4cef8c4093 Address review comments 2026-03-16 06:52:35 +00:00
Brock Grassy
7027060a0b Fix lint and mypy 2026-03-16 06:52:35 +00:00
Brock Grassy
6b62380b62 Change missing plugin to allow for filtering albums by release type 2026-03-16 06:52:35 +00:00
Šarūnas Nejus
0dd6df849d
Move utils from ui to util (#6440)
## Refactor: Extract UI utilities into `beets/util`

This PR decouples terminal/display utilities from `beets/ui` by moving
them into focused modules under `beets/util`. No user-facing behaviour
changes.

### What moved where

| Utility | From | To |
|---|---|---|
| `colorize`, `uncolorize`, `color_len`, `color_split`, ANSI constants |
`beets/ui/__init__.py` | `beets/util/color.py` |
| `colordiff`, `_field_diff`, `get_model_changes` |
`beets/ui/__init__.py` | `beets/util/diff.py` |
| `indent`, `split_into_lines`, `print_column_layout`,
`print_newline_layout` | `beets/ui/__init__.py` | `beets/util/layout.py`
|

### Notable design change in `layout.py`

`print_column_layout` / `print_newline_layout` previously called
`ui.print_()` internally, creating a hard dependency on `beets.ui`. They
are now renamed to `get_column_layout` / `get_newline_layout` and
converted to **generators**, yielding lines instead of printing them.
The caller (`display.py`) is responsible for printing via `ui.print_()`.

### New public API

`get_model_changes` is introduced in `beets/util/diff.py` as the pure,
testable function for computing field-level diffs. `show_model_changes`
in `beets/ui` now delegates to it.

### Tests

- Moved alongside the code: `test/util/test_color.py`,
`test/util/test_diff.py`, `test/util/test_layout.py`.
- Removed duplicate `ShowModelChangeTest` from `test/ui/test_ui.py` —
coverage is preserved in `test/util/test_diff.py`.
2026-03-16 01:28:40 +00:00
Šarūnas Nejus
41c835d9e9
Add types to moved utils 2026-03-14 14:08:51 +00:00
Šarūnas Nejus
3aaa4d0ce9
Add commits to .git-ignore-blame-revs 2026-03-14 12:02:31 +00:00
Šarūnas Nejus
72eaa98eaf
Refactor layout to remove dependency on ui 2026-03-14 12:02:30 +00:00
Šarūnas Nejus
ffb4329006
Fix layout references 2026-03-14 12:02:30 +00:00
Šarūnas Nejus
6a93bf54af
Move layout utils to beets/util/layout.py 2026-03-14 12:02:30 +00:00
Šarūnas Nejus
fc39ab791c
Create a dedicated get_model_changes function in beets/util/diff.py
- Add `get_model_changes` as the public API for computing diffs
- Remove tests for `show_model_changes` as test_diff.py fully covers them.
2026-03-14 12:02:30 +00:00
Šarūnas Nejus
1d54f2bf66
Fix diff references 2026-03-14 12:02:30 +00:00
Šarūnas Nejus
756d5c9921
Move diff utils to beets/util/diff.py 2026-03-14 11:58:04 +00:00
Šarūnas Nejus
a6fcb7ba0f
Fix references to color utils 2026-03-14 11:57:18 +00:00
Šarūnas Nejus
aa52aaca56
Move colorisation to beets/util/color.py 2026-03-14 11:57:18 +00:00
Šarūnas Nejus
3bcc539b2c
Feat: Smartplaylist: Add dest_regen option for regenerating destination path (#5621)
## Problem

1. When items are imported into the library in a don't copy-move mode
(`-C -M` options), they will be registered inside the Beets database
using their **original paths**. However, during subsequent processing
(*e.g.*, a `convert` operation), a path following the Beets path format
can be generated.
2. When generating playlists using `smartplaylist` plugin, only the path
registered inside the Beets database (the **original path**) can be use
inside the output playlist. This block the compatibility with other
plugins.

## Solution

I added a a new optional configuration option known as ``dest_regen``
(as well as its equivalent ``dest-regen`` on the CLI) to regenerate
items' path in the generated playlist instead of using the ones of the
library, just like a `convert` or a `move` operation would have done.
This operation will happen before the ``relative_to`` and ``prefix``
options, which makes sense to do so and not in another order, otherwise
this new option (``dest_regen``)) would overwrite the desired behavior
of the other mentioned options (``relative_to`` and ``prefix``). It is
then helpful to generate playlists compatible with the ``convert``
plugin.
2026-03-14 09:11:51 +00:00
Pierre Ayoub
78bc2d9d5e
chore(changelog): Move new feature under Unreleased section 2026-03-14 09:56:02 +01:00
Pierre Ayoub
49cb2f5544 chore(test_smartplaylist): Add future TODO about Mocks usage 2026-03-13 19:17:56 +00:00
Pierre Ayoub
074f4d4281 chore(changelog): Foramt with docstrfmt 2026-03-13 19:17:56 +00:00
Pierre Ayoub
fae242249a Update docs/changelog.rst
Rephrasing by Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-13 19:17:56 +00:00
Pierre Ayoub
5157ba6021 Update docs/plugins/smartplaylist.rst
Styling by Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-13 19:17:56 +00:00
Pierre Ayoub
1393cd0ae7 fix(test_smartplaylist.py): mypy type error as commit 002a051d0 2026-03-13 19:17:56 +00:00
Pierre Ayoub
7dfed4429f chore(docs): Lint double backquotes 2026-03-13 19:17:56 +00:00
Pierre Ayoub
523e504f5e fix(test_smartplaylist): Adapt to changes from commit d01727019
The aforementioned commits introduced a nmuber of changes since I
implemented this test:
- The syntax `self.assertExists(m3u_filepath)` was an old and now invalid
  way of checking existence of a path using assertion, change to `assert
  m3u_filepath.exists()` which now use string instead of bytes
- Use of `Path()` and strings instead of `path.join` and bytes for
  handling directory path
2026-03-13 19:17:56 +00:00
Pierre Ayoub
b798465a55 chore(docs): Format with docstrfmt 2026-03-13 19:17:56 +00:00
Pierre Ayoub
f5a08712b1 chore(test): Lint test_smartplaylist.py 2026-03-13 19:17:56 +00:00
Pierre Ayoub
e80fa746af chore(test): Format test_smartplaylist.py 2026-03-13 19:17:56 +00:00
Pierre Ayoub
2dcd8f4066 fix(test): Linting issues in smartplaylist 2026-03-13 19:17:56 +00:00
Pierre Ayoub
760271c456 chore(test): format with poe 2026-03-13 19:17:56 +00:00
Pierre Ayoub
ad2529adb0 test(plugins): write dest_regen test for smartplaylist
Test functions inspired from `test_playlist_update_output_extm3u()` in
`test_smartplaylist.py`.
Test successfully passed using:
`poetry run pytest test/plugins/test_smartplaylist.py`
2026-03-13 19:17:56 +00:00
Pierre Ayoub
ca9990c8c2 [beetsplug/spl] Fix linting error 2026-03-13 19:17:56 +00:00
Pierre Ayoub
a7aac37f66 [docs/changelog] Add log for dest_regen option 2026-03-13 19:17:56 +00:00
Pierre Ayoub
d81c0518c8 [docs/spl] Add documentation for new dest_regen option 2026-03-13 19:17:56 +00:00
Pierre Ayoub
aae0260484 [beetsplug/spl] Add the dest_regen/dest-regen option 2026-03-13 19:17:56 +00:00
Šarūnas Nejus
2c6f239ac5
Use va_name config for all artist fields on VA releases (#6438)
Fixes #6316

When importing compilations/various artists albums, several fields used the
hardcoded string "Various Artists" instead of the user-configured `va_name`
setting:

- In the **musicbrainz plugin**, only `info.artist` was overridden with `va_name`
when a release was identified as VA. The `artist_sort`, `artists_sort`,
`artist_credit`, `artists_credit`, and `artists` fields were left with the raw 
MusicBrainz value ("Various Artists"), which then propagated to
 `albumartist_sort`, `albumartists_sort`, `albumartist_credit`,
 `albumartists_credit`, and `albumartists` on items.

- In the **beatport plugin**, the VA artist name was hardcoded to
  "Various Artists" instead of reading from config.

## Changes

- `beetsplug/musicbrainz.py`: When `info.va` is true, override all artist-related
  fields (`artist_sort`, `artists`, `artists_sort`, `artist_credit`,
  `artists_credit`) with `va_name`, not just `artist`.
- `beetsplug/beatport.py`: Replace hardcoded "Various Artists" with
  `config["va_name"].as_str()`.
- `docs/changelog.rst`: Add changelog entries for both fixes.
2026-03-13 19:09:17 +00:00
Carson Jones
be722b564e Use va_name config for all artist fields on VA releases
When importing compilations, albumartist_sort, albumartists_sort,
albumartist_credit, albumartists_credit, and albumartists were
hardcoded to "Various Artists" instead of using the user-configured
va_name setting. This also fixes the same issue in the beatport plugin.

Fixes #6316
2026-03-12 23:00:32 -04:00
Šarūnas Nejus
f203bc5241
Add extra_tags support to Discogs plugin (#6433)
Fixes #6412.

This is my first time submitting a PR for an open source project so
please point out any mistakes!

## Summary

- Add `discogs.extra_tags` configuration option to narrow Discogs search
queries using existing tag values.
- Map supported tags (`barcode`, `catalognum`, `country`, `label`,
`media`, `year`) to corresponding Discogs search parameters.
- Update Discogs plugin documentation and tests to cover the new
behavior.

## Details

The Discogs plugin now mirrors `musicbrainz.extra_tags` by allowing
users to specify additional tags that should be used when building
Discogs search filters.

- New config option: `discogs.extra_tags` (default: `[]`).
- Supported tags and their Discogs search parameters:
  - `barcode` → `barcode`
  - `catalognum` → `catno` (whitespace removed)
  - `country` → `country`
  - `label` → `label`
  - `media` → `format`
  - `year` → `year`
- Tags `alias` and `tracks` are recognized but intentionally ignored for
Discogs, since the Discogs API does not provide direct equivalents for
these MusicBrainz-specific fields.

When `extra_tags` are configured, the plugin uses `beets.util.plurality`
over the items in the import session to select the most common value for
each configured tag and adds the corresponding Discogs filter.

## Testing

- Added unit tests in `test/plugins/test_discogs.py` to verify:
- Default search filters remain unchanged when `extra_tags` is not set.
- `discogs.extra_tags: [label, catalognum]` results in `label` and
`catno` filters populated from library items (with catalog number
whitespace stripped).
- Ran:
  - `pytest test/plugins/test_discogs.py`
  - `pytest test/plugins/test_musicbrainz.py`
2026-03-13 00:31:03 +00:00
john doe
1c3b58c7d8 Update discogs.rst
Formatting fix
2026-03-13 00:25:39 +00:00
Jack
c0c84cebeb Format discogs plugin after review changes 2026-03-13 00:25:39 +00:00
Jack
ce451d5f34 Remove alias/tracks special case handling for new Discogs extra_tags function 2026-03-13 00:25:39 +00:00
john doe
acda1755c0 Update test/plugins/test_discogs.py
Co-authored-by: Šarūnas Nejus <snejus@protonmail.com>
2026-03-13 00:25:39 +00:00
Jack
76e862e691 Fix formatting: run ruff format on discogs plugin 2026-03-13 00:25:39 +00:00
Jack
59b636ac8c CI: install discogs extra so discogs tests run; remove skip logic 2026-03-13 00:25:39 +00:00
Jack
da32d64599 Fix module-level skip: use pytest.skip(allow_module_level=True) for discogs 2026-03-13 00:25:39 +00:00
Jack
04e5d0083d CI: skip discogs tests when discogs_client missing, guard empty items, reset config in test 2026-03-13 00:25:39 +00:00
jdoe29103
99eec9ec25 Update changelog 2026-03-13 00:25:39 +00:00
jdoe29103
809744e8f4 Fix test class structure: move DGSearchQueryTest after DGAlbumInfoTest 2026-03-13 00:25:39 +00:00
jdoe29103
0d8d3bfadf Add discogs.extra_tags and updated documentation 2026-03-13 00:25:39 +00:00
Šarūnas Nejus
6af84bcfb6
Ensure changelog entries are under Unreleased section (#6432)
## Enforce Changelog Entries Under 'Unreleased' Section

I've had enough checking this manually 😆. Adds a CI lint step
that prevents contributors from accidentally adding changelog entries
under an already-released version header in `docs/changelog.rst`.

### How it works

The check runs `git diff --word-diff=plain -U1000` against the base
branch, then pipes through `awk` to scan the diff for new list entries
(`{+- ...`) that appear after any versioned release header (e.g. `1.2.3
(`). If such an entry is found, the step fails with a human-readable
error pointing to the offending line which GitHub should show in the
diff view.

* `--word-diff=plain` is required to match _truly new_ changelog entries
instead of some formatting adjustments in the middle of the line.
* `-U1000` should ensure that we grab the first 1000 lines in the
changelog to reliably match the headers.
2026-03-11 06:55:16 +00:00