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(
|
||||
config: ConfigView, data_source: str, info: AlbumInfo | TrackInfo
|
||||
) -> 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.util import bytestring_path, get_temp_filename, sorted_walk, syspath
|
||||
from beets.util.artresizer import ArtResizer
|
||||
from beets.util.config import sanitize_pairs
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterable, Iterator, Sequence
|
||||
|
|
@ -1396,7 +1397,7 @@ class FetchArtPlugin(plugins.BeetsPlugin, RequestMixin):
|
|||
if s_cls.available(self._log, self.config)
|
||||
for c in s_cls.VALID_MATCHING_CRITERIA
|
||||
]
|
||||
sources = plugins.sanitize_pairs(
|
||||
sources = sanitize_pairs(
|
||||
self.config["sources"].as_pairs(default_value="*"),
|
||||
available_sources,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ from unidecode import unidecode
|
|||
import beets
|
||||
from beets import plugins, ui
|
||||
from beets.autotag.hooks import string_dist
|
||||
from beets.util.config import sanitize_choices
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from logging import Logger
|
||||
|
|
@ -957,7 +958,7 @@ class LyricsPlugin(RequestHandler, plugins.BeetsPlugin):
|
|||
def backends(self) -> list[Backend]:
|
||||
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():
|
||||
self.warn("Disabling Google source: no API key configured.")
|
||||
chosen.remove("google")
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@
|
|||
|
||||
import itertools
|
||||
import os
|
||||
import unittest
|
||||
from unittest.mock import ANY, Mock, patch
|
||||
|
||||
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):
|
||||
def test_register(self):
|
||||
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