mirror of
https://github.com/beetbox/beets.git
synced 2025-12-06 08:39:17 +01:00
Move sanitize_pairs/choices from plugins to util module
This commit is contained in:
parent
0f76312f31
commit
509cbdcbe4
6 changed files with 85 additions and 72 deletions
|
|
@ -654,66 +654,6 @@ def feat_tokens(for_artist: bool = True) -> str:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def sanitize_choices(
|
|
||||||
choices: Sequence[str], choices_all: Sequence[str]
|
|
||||||
) -> list[str]:
|
|
||||||
"""Clean up a stringlist configuration attribute: keep only choices
|
|
||||||
elements present in choices_all, remove duplicate elements, expand '*'
|
|
||||||
wildcard while keeping original stringlist order.
|
|
||||||
"""
|
|
||||||
seen: set[str] = set()
|
|
||||||
others = [x for x in choices_all if x not in choices]
|
|
||||||
res: list[str] = []
|
|
||||||
for s in choices:
|
|
||||||
if s not in seen:
|
|
||||||
if s in list(choices_all):
|
|
||||||
res.append(s)
|
|
||||||
elif s == "*":
|
|
||||||
res.extend(others)
|
|
||||||
seen.add(s)
|
|
||||||
return res
|
|
||||||
|
|
||||||
|
|
||||||
def sanitize_pairs(
|
|
||||||
pairs: Sequence[tuple[str, str]], pairs_all: Sequence[tuple[str, str]]
|
|
||||||
) -> list[tuple[str, str]]:
|
|
||||||
"""Clean up a single-element mapping configuration attribute as returned
|
|
||||||
by Confuse's `Pairs` template: keep only two-element tuples present in
|
|
||||||
pairs_all, remove duplicate elements, expand ('str', '*') and ('*', '*')
|
|
||||||
wildcards while keeping the original order. Note that ('*', '*') and
|
|
||||||
('*', 'whatever') have the same effect.
|
|
||||||
|
|
||||||
For example,
|
|
||||||
|
|
||||||
>>> sanitize_pairs(
|
|
||||||
... [('foo', 'baz bar'), ('key', '*'), ('*', '*')],
|
|
||||||
... [('foo', 'bar'), ('foo', 'baz'), ('foo', 'foobar'),
|
|
||||||
... ('key', 'value')]
|
|
||||||
... )
|
|
||||||
[('foo', 'baz'), ('foo', 'bar'), ('key', 'value'), ('foo', 'foobar')]
|
|
||||||
"""
|
|
||||||
pairs_all = list(pairs_all)
|
|
||||||
seen: set[tuple[str, str]] = set()
|
|
||||||
others = [x for x in pairs_all if x not in pairs]
|
|
||||||
res: list[tuple[str, str]] = []
|
|
||||||
for k, values in pairs:
|
|
||||||
for v in values.split():
|
|
||||||
x = (k, v)
|
|
||||||
if x in pairs_all:
|
|
||||||
if x not in seen:
|
|
||||||
seen.add(x)
|
|
||||||
res.append(x)
|
|
||||||
elif k == "*":
|
|
||||||
new = [o for o in others if o not in seen]
|
|
||||||
seen.update(new)
|
|
||||||
res.extend(new)
|
|
||||||
elif v == "*":
|
|
||||||
new = [o for o in others if o not in seen and o[0] == k]
|
|
||||||
seen.update(new)
|
|
||||||
res.extend(new)
|
|
||||||
return res
|
|
||||||
|
|
||||||
|
|
||||||
def get_distance(
|
def get_distance(
|
||||||
config: ConfigView, data_source: str, info: AlbumInfo | TrackInfo
|
config: ConfigView, data_source: str, info: AlbumInfo | TrackInfo
|
||||||
) -> Distance:
|
) -> Distance:
|
||||||
|
|
|
||||||
66
beets/util/config.py
Normal file
66
beets/util/config.py
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Collection, Sequence
|
||||||
|
|
||||||
|
|
||||||
|
def sanitize_choices(
|
||||||
|
choices: Sequence[str], choices_all: Collection[str]
|
||||||
|
) -> list[str]:
|
||||||
|
"""Clean up a stringlist configuration attribute: keep only choices
|
||||||
|
elements present in choices_all, remove duplicate elements, expand '*'
|
||||||
|
wildcard while keeping original stringlist order.
|
||||||
|
"""
|
||||||
|
seen: set[str] = set()
|
||||||
|
others = [x for x in choices_all if x not in choices]
|
||||||
|
res: list[str] = []
|
||||||
|
for s in choices:
|
||||||
|
if s not in seen:
|
||||||
|
if s in list(choices_all):
|
||||||
|
res.append(s)
|
||||||
|
elif s == "*":
|
||||||
|
res.extend(others)
|
||||||
|
seen.add(s)
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
def sanitize_pairs(
|
||||||
|
pairs: Sequence[tuple[str, str]], pairs_all: Sequence[tuple[str, str]]
|
||||||
|
) -> list[tuple[str, str]]:
|
||||||
|
"""Clean up a single-element mapping configuration attribute as returned
|
||||||
|
by Confuse's `Pairs` template: keep only two-element tuples present in
|
||||||
|
pairs_all, remove duplicate elements, expand ('str', '*') and ('*', '*')
|
||||||
|
wildcards while keeping the original order. Note that ('*', '*') and
|
||||||
|
('*', 'whatever') have the same effect.
|
||||||
|
|
||||||
|
For example,
|
||||||
|
|
||||||
|
>>> sanitize_pairs(
|
||||||
|
... [('foo', 'baz bar'), ('key', '*'), ('*', '*')],
|
||||||
|
... [('foo', 'bar'), ('foo', 'baz'), ('foo', 'foobar'),
|
||||||
|
... ('key', 'value')]
|
||||||
|
... )
|
||||||
|
[('foo', 'baz'), ('foo', 'bar'), ('key', 'value'), ('foo', 'foobar')]
|
||||||
|
"""
|
||||||
|
pairs_all = list(pairs_all)
|
||||||
|
seen: set[tuple[str, str]] = set()
|
||||||
|
others = [x for x in pairs_all if x not in pairs]
|
||||||
|
res: list[tuple[str, str]] = []
|
||||||
|
for k, values in pairs:
|
||||||
|
for v in values.split():
|
||||||
|
x = (k, v)
|
||||||
|
if x in pairs_all:
|
||||||
|
if x not in seen:
|
||||||
|
seen.add(x)
|
||||||
|
res.append(x)
|
||||||
|
elif k == "*":
|
||||||
|
new = [o for o in others if o not in seen]
|
||||||
|
seen.update(new)
|
||||||
|
res.extend(new)
|
||||||
|
elif v == "*":
|
||||||
|
new = [o for o in others if o not in seen and o[0] == k]
|
||||||
|
seen.update(new)
|
||||||
|
res.extend(new)
|
||||||
|
return res
|
||||||
|
|
@ -32,6 +32,7 @@ from mediafile import image_mime_type
|
||||||
from beets import config, importer, plugins, ui, util
|
from beets import config, importer, plugins, ui, util
|
||||||
from beets.util import bytestring_path, get_temp_filename, sorted_walk, syspath
|
from beets.util import bytestring_path, get_temp_filename, sorted_walk, syspath
|
||||||
from beets.util.artresizer import ArtResizer
|
from beets.util.artresizer import ArtResizer
|
||||||
|
from beets.util.config import sanitize_pairs
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from collections.abc import Iterable, Iterator, Sequence
|
from collections.abc import Iterable, Iterator, Sequence
|
||||||
|
|
@ -1396,7 +1397,7 @@ class FetchArtPlugin(plugins.BeetsPlugin, RequestMixin):
|
||||||
if s_cls.available(self._log, self.config)
|
if s_cls.available(self._log, self.config)
|
||||||
for c in s_cls.VALID_MATCHING_CRITERIA
|
for c in s_cls.VALID_MATCHING_CRITERIA
|
||||||
]
|
]
|
||||||
sources = plugins.sanitize_pairs(
|
sources = sanitize_pairs(
|
||||||
self.config["sources"].as_pairs(default_value="*"),
|
self.config["sources"].as_pairs(default_value="*"),
|
||||||
available_sources,
|
available_sources,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ from unidecode import unidecode
|
||||||
import beets
|
import beets
|
||||||
from beets import plugins, ui
|
from beets import plugins, ui
|
||||||
from beets.autotag.hooks import string_dist
|
from beets.autotag.hooks import string_dist
|
||||||
|
from beets.util.config import sanitize_choices
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from logging import Logger
|
from logging import Logger
|
||||||
|
|
@ -957,7 +958,7 @@ class LyricsPlugin(RequestHandler, plugins.BeetsPlugin):
|
||||||
def backends(self) -> list[Backend]:
|
def backends(self) -> list[Backend]:
|
||||||
user_sources = self.config["sources"].get()
|
user_sources = self.config["sources"].get()
|
||||||
|
|
||||||
chosen = plugins.sanitize_choices(user_sources, self.BACKEND_BY_NAME)
|
chosen = sanitize_choices(user_sources, self.BACKEND_BY_NAME)
|
||||||
if "google" in chosen and not self.config["google_API_key"].get():
|
if "google" in chosen and not self.config["google_API_key"].get():
|
||||||
self.warn("Disabling Google source: no API key configured.")
|
self.warn("Disabling Google source: no API key configured.")
|
||||||
chosen.remove("google")
|
chosen.remove("google")
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@
|
||||||
|
|
||||||
import itertools
|
import itertools
|
||||||
import os
|
import os
|
||||||
import unittest
|
|
||||||
from unittest.mock import ANY, Mock, patch
|
from unittest.mock import ANY, Mock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
@ -215,15 +214,6 @@ class EventsTest(PluginImportTestCase):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class HelpersTest(unittest.TestCase):
|
|
||||||
def test_sanitize_choices(self):
|
|
||||||
assert plugins.sanitize_choices(["A", "Z"], ("A", "B")) == ["A"]
|
|
||||||
assert plugins.sanitize_choices(["A", "A"], ("A")) == ["A"]
|
|
||||||
assert plugins.sanitize_choices(
|
|
||||||
["D", "*", "A"], ("A", "B", "C", "D")
|
|
||||||
) == ["D", "B", "C", "A"]
|
|
||||||
|
|
||||||
|
|
||||||
class ListenersTest(PluginLoaderTestCase):
|
class ListenersTest(PluginLoaderTestCase):
|
||||||
def test_register(self):
|
def test_register(self):
|
||||||
class DummyPlugin(plugins.BeetsPlugin):
|
class DummyPlugin(plugins.BeetsPlugin):
|
||||||
|
|
|
||||||
15
test/util/test_config.py
Normal file
15
test/util/test_config.py
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from beets.util.config import sanitize_choices
|
||||||
|
|
||||||
|
|
||||||
|
class HelpersTest(unittest.TestCase):
|
||||||
|
def test_sanitize_choices(self):
|
||||||
|
assert sanitize_choices(["A", "Z"], ("A", "B")) == ["A"]
|
||||||
|
assert sanitize_choices(["A", "A"], ("A")) == ["A"]
|
||||||
|
assert sanitize_choices(["D", "*", "A"], ("A", "B", "C", "D")) == [
|
||||||
|
"D",
|
||||||
|
"B",
|
||||||
|
"C",
|
||||||
|
"A",
|
||||||
|
]
|
||||||
Loading…
Reference in a new issue