mirror of
https://github.com/beetbox/beets.git
synced 2025-12-23 00:54:03 +01:00
Titlecase Plugin Improvements (#6220)
- Add preserving strings that are all lowercase or all upper case - Fix spelling of 'separator' in config, docs and code - Move most of the logging for the plugin to debug to keep log cleaner. Improvements I found a need for in my daily use with the plugin. - [x] Documentation. (If you've added a new command-line flag, for example, find the appropriate page under `docs/` to describe it.) - [x] Changelog. (Skipping as the plugin has not been released yet) - [x] Tests. (Very much encouraged but not strictly required.)
This commit is contained in:
commit
09476bdad9
3 changed files with 75 additions and 17 deletions
|
|
@ -47,10 +47,12 @@ class TitlecasePlugin(BeetsPlugin):
|
|||
"preserve": [],
|
||||
"fields": [],
|
||||
"replace": [],
|
||||
"seperators": [],
|
||||
"separators": [],
|
||||
"force_lowercase": False,
|
||||
"small_first_last": True,
|
||||
"the_artist": True,
|
||||
"all_caps": False,
|
||||
"all_lowercase": False,
|
||||
"after_choice": False,
|
||||
}
|
||||
)
|
||||
|
|
@ -60,14 +62,16 @@ class TitlecasePlugin(BeetsPlugin):
|
|||
preserve - Provide a list of strings with specific case requirements.
|
||||
fields - Fields to apply titlecase to.
|
||||
replace - List of pairs, first is the target, second is the replacement
|
||||
seperators - Other characters to treat like periods.
|
||||
force_lowercase - Lowercases the string before titlecasing.
|
||||
separators - Other characters to treat like periods.
|
||||
force_lowercase - Lowercase the string before titlecase.
|
||||
small_first_last - If small characters should be cased at the start of strings.
|
||||
the_artist - If the plugin infers the field to be an artist field
|
||||
(e.g. the field contains "artist")
|
||||
It will capitalize a lowercase The, helpful for the artist names
|
||||
that start with 'The', like 'The Who' or 'The Talking Heads' when
|
||||
they are not at the start of a string. Superceded by preserved phrases.
|
||||
they are not at the start of a string. Superseded by preserved phrases.
|
||||
all_caps - If the alphabet in the string is all uppercase, do not modify.
|
||||
all_lowercase - If the alphabet in the string is all lowercase, do not modify.
|
||||
"""
|
||||
# Register template function
|
||||
self.template_funcs["titlecase"] = self.titlecase
|
||||
|
|
@ -121,17 +125,25 @@ class TitlecasePlugin(BeetsPlugin):
|
|||
return preserved
|
||||
|
||||
@cached_property
|
||||
def seperators(self) -> re.Pattern[str] | None:
|
||||
if seperators := "".join(
|
||||
dict.fromkeys(self.config["seperators"].as_str_seq())
|
||||
def separators(self) -> re.Pattern[str] | None:
|
||||
if separators := "".join(
|
||||
dict.fromkeys(self.config["separators"].as_str_seq())
|
||||
):
|
||||
return re.compile(rf"(.*?[{re.escape(seperators)}]+)(\s*)(?=.)")
|
||||
return re.compile(rf"(.*?[{re.escape(separators)}]+)(\s*)(?=.)")
|
||||
return None
|
||||
|
||||
@cached_property
|
||||
def small_first_last(self) -> bool:
|
||||
return self.config["small_first_last"].get(bool)
|
||||
|
||||
@cached_property
|
||||
def all_caps(self) -> bool:
|
||||
return self.config["all_caps"].get(bool)
|
||||
|
||||
@cached_property
|
||||
def all_lowercase(self) -> bool:
|
||||
return self.config["all_lowercase"].get(bool)
|
||||
|
||||
@cached_property
|
||||
def the_artist_regexp(self) -> re.Pattern[str]:
|
||||
return re.compile(r"\bthe\b")
|
||||
|
|
@ -180,7 +192,7 @@ class TitlecasePlugin(BeetsPlugin):
|
|||
]
|
||||
if cased_list != init_field:
|
||||
setattr(item, field, cased_list)
|
||||
self._log.info(
|
||||
self._log.debug(
|
||||
f"{field}: {', '.join(init_field)} ->",
|
||||
f"{', '.join(cased_list)}",
|
||||
)
|
||||
|
|
@ -188,7 +200,7 @@ class TitlecasePlugin(BeetsPlugin):
|
|||
cased: str = self.titlecase(init_field, field)
|
||||
if cased != init_field:
|
||||
setattr(item, field, cased)
|
||||
self._log.info(f"{field}: {init_field} -> {cased}")
|
||||
self._log.debug(f"{field}: {init_field} -> {cased}")
|
||||
else:
|
||||
self._log.debug(f"{field}: no string present")
|
||||
else:
|
||||
|
|
@ -197,8 +209,8 @@ class TitlecasePlugin(BeetsPlugin):
|
|||
def titlecase(self, text: str, field: str = "") -> str:
|
||||
"""Titlecase the given text."""
|
||||
# Check we should split this into two substrings.
|
||||
if self.seperators:
|
||||
if len(splits := self.seperators.findall(text)):
|
||||
if self.separators:
|
||||
if len(splits := self.separators.findall(text)):
|
||||
split_cased = "".join(
|
||||
[self.titlecase(s[0], field) + s[1] for s in splits]
|
||||
)
|
||||
|
|
@ -206,6 +218,11 @@ class TitlecasePlugin(BeetsPlugin):
|
|||
return split_cased + self.titlecase(
|
||||
text[len(split_cased) :], field
|
||||
)
|
||||
# Check if A-Z is all uppercase or all lowercase
|
||||
if self.all_lowercase and text.islower():
|
||||
return text
|
||||
elif self.all_caps and text.isupper():
|
||||
return text
|
||||
# Any necessary replacements go first, mainly punctuation.
|
||||
titlecased = text.lower() if self.force_lowercase else text
|
||||
for pair in self.replace:
|
||||
|
|
|
|||
|
|
@ -57,10 +57,12 @@ Default
|
|||
fields: []
|
||||
preserve: []
|
||||
replace: []
|
||||
seperators: []
|
||||
separators: []
|
||||
force_lowercase: no
|
||||
small_first_last: yes
|
||||
the_artist: yes
|
||||
all_lowercase: no
|
||||
all_caps: no
|
||||
after_choice: no
|
||||
|
||||
.. conf:: auto
|
||||
|
|
@ -120,7 +122,7 @@ Default
|
|||
- "“": '"'
|
||||
- "”": '"'
|
||||
|
||||
.. conf:: seperators
|
||||
.. conf:: separators
|
||||
:default: []
|
||||
|
||||
A list of characters to treat as markers of new sentences. Helpful for split titles
|
||||
|
|
@ -146,6 +148,19 @@ Default
|
|||
capitalized. Useful for bands with `The` as part of the proper name,
|
||||
like ``Amyl and The Sniffers``.
|
||||
|
||||
.. conf:: all_caps
|
||||
:default: no
|
||||
|
||||
If the letters a-Z in a string are all caps, do not modify the string. Useful
|
||||
if you encounter a lot of acronyms.
|
||||
|
||||
.. conf:: all_lowercase
|
||||
:default: no
|
||||
|
||||
If the letters a-Z in a string are all lowercase, do not modify the string.
|
||||
Useful if you encounter a lot of stylized lowercase spellings, but otherwise
|
||||
want titlecase applied.
|
||||
|
||||
.. conf:: after_choice
|
||||
:default: no
|
||||
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ class TestTitlecasePlugin(PluginTestCase):
|
|||
assert TitlecasePlugin().titlecase(word.upper()) == word
|
||||
assert TitlecasePlugin().titlecase(word.lower()) == word
|
||||
|
||||
def test_seperators(self):
|
||||
def test_separators(self):
|
||||
testcases = [
|
||||
([], "it / a / in / of / to / the", "It / a / in / of / to / The"),
|
||||
(["/"], "it / the test", "It / The Test"),
|
||||
|
|
@ -129,8 +129,34 @@ class TestTitlecasePlugin(PluginTestCase):
|
|||
),
|
||||
]
|
||||
for testcase in testcases:
|
||||
seperators, given, expected = testcase
|
||||
with self.configure_plugin({"seperators": seperators}):
|
||||
separators, given, expected = testcase
|
||||
with self.configure_plugin({"separators": separators}):
|
||||
assert TitlecasePlugin().titlecase(given) == expected
|
||||
|
||||
def test_all_caps(self):
|
||||
testcases = [
|
||||
(True, "Unaffected", "Unaffected"),
|
||||
(True, "RBMK1000", "RBMK1000"),
|
||||
(False, "RBMK1000", "Rbmk1000"),
|
||||
(True, "P A R I S!", "P A R I S!"),
|
||||
(True, "pillow dub...", "Pillow Dub..."),
|
||||
(False, "P A R I S!", "P a R I S!"),
|
||||
]
|
||||
for testcase in testcases:
|
||||
all_caps, given, expected = testcase
|
||||
with self.configure_plugin({"all_caps": all_caps}):
|
||||
assert TitlecasePlugin().titlecase(given) == expected
|
||||
|
||||
def test_all_lowercase(self):
|
||||
testcases = [
|
||||
(True, "Unaffected", "Unaffected"),
|
||||
(True, "RBMK1000", "Rbmk1000"),
|
||||
(True, "pillow dub...", "pillow dub..."),
|
||||
(False, "pillow dub...", "Pillow Dub..."),
|
||||
]
|
||||
for testcase in testcases:
|
||||
all_lowercase, given, expected = testcase
|
||||
with self.configure_plugin({"all_lowercase": all_lowercase}):
|
||||
assert TitlecasePlugin().titlecase(given) == expected
|
||||
|
||||
def test_received_info_handler(self):
|
||||
|
|
|
|||
Loading…
Reference in a new issue