Initial title case plugin written and working, needs to apply to tags

This commit is contained in:
Henry 2025-10-04 21:29:02 -07:00
parent 347802afea
commit 8d11ed51d2
4 changed files with 125 additions and 1 deletions

51
beetsplug/titlecase.py Normal file
View file

@ -0,0 +1,51 @@
# This file is part of beets.
# Copyright 2025, Henry Oberholtzer
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
"""Apply NYT manual of style title case rules, to paths and tag text.
Title case logic is derived from the python-titlecase library."""
from beets.plugins import BeetsPlugin
from titlecase import titlecase
__author__ = "henryoberholtzer@gmail.com"
__version__ = "1.0"
class TitlecasePlugin(BeetsPlugin):
preserve: dict[str, str] = {}
def __init__(self) -> None:
super().__init__()
# Register template function
self.template_funcs["titlecase"] = self.titlecase
self.config.add(
{
"preserve": [],
"small_first_last": True
}
)
print(self.config)
for word in self.config["preserve"].as_str_seq():
self.preserve[word.upper()] = word
def __preserved__(self, word, **kwargs) -> str | None:
""" Callback function for words to preserve case of."""
if (preserved_word := self.preserve.get(word.upper(), "")):
return preserved_word
return None
def titlecase(self, text: str) -> str:
""" Titlecase the given text """
return titlecase(text,
small_first_last=self.config["small_first_last"],
callback=self.__preserved__)

18
poetry.lock generated
View file

@ -2182,6 +2182,8 @@ files = [
{file = "pycairo-1.28.0-cp313-cp313-win32.whl", hash = "sha256:d13352429d8a08a1cb3607767d23d2fb32e4c4f9faa642155383980ec1478c24"},
{file = "pycairo-1.28.0-cp313-cp313-win_amd64.whl", hash = "sha256:082aef6b3a9dcc328fa648d38ed6b0a31c863e903ead57dd184b2e5f86790140"},
{file = "pycairo-1.28.0-cp313-cp313-win_arm64.whl", hash = "sha256:026afd53b75291917a7412d9fe46dcfbaa0c028febd46ff1132d44a53ac2c8b6"},
{file = "pycairo-1.28.0-cp314-cp314-win32.whl", hash = "sha256:d0ab30585f536101ad6f09052fc3895e2a437ba57531ea07223d0e076248025d"},
{file = "pycairo-1.28.0-cp314-cp314-win_amd64.whl", hash = "sha256:94f2ed204999ab95a0671a0fa948ffbb9f3d6fb8731fe787917f6d022d9c1c0f"},
{file = "pycairo-1.28.0-cp39-cp39-win32.whl", hash = "sha256:3ed16d48b8a79cc584cb1cb0ad62dfb265f2dda6d6a19ef5aab181693e19c83c"},
{file = "pycairo-1.28.0-cp39-cp39-win_amd64.whl", hash = "sha256:da0d1e6d4842eed4d52779222c6e43d254244a486ca9fdab14e30042fd5bdf28"},
{file = "pycairo-1.28.0-cp39-cp39-win_arm64.whl", hash = "sha256:458877513eb2125513122e8aa9c938630e94bb0574f94f4fb5ab55eb23d6e9ac"},
@ -3364,6 +3366,19 @@ files = [
{file = "threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e"},
]
[[package]]
name = "titlecase"
version = "2.4.1"
description = "Python Port of John Gruber's titlecase.pl"
optional = true
python-versions = ">=3.7"
files = [
{file = "titlecase-2.4.1.tar.gz", hash = "sha256:7d83a277ccbbda11a2944e78a63e5ccaf3d32f828c594312e4862f9a07f635f5"},
]
[package.extras]
regex = ["regex (>=2020.4.4)"]
[[package]]
name = "toml"
version = "0.10.2"
@ -3624,9 +3639,10 @@ replaygain = ["PyGObject"]
scrub = ["mutagen"]
sonosupdate = ["soco"]
thumbnails = ["Pillow", "pyxdg"]
titlecase = ["titlecase"]
web = ["flask", "flask-cors"]
[metadata]
lock-version = "2.0"
python-versions = ">=3.9,<4"
content-hash = "faea27878ce1ca3f1335fd83e027b289351c51c73550bda72bf501a9c82166f7"
content-hash = "c5a6a4710beb5bf1e4bbd97f2a590ee020a567aea2341aad5f846eda13ebb94e"

View file

@ -76,6 +76,7 @@ requests = { version = "*", optional = true }
resampy = { version = ">=0.4.3", optional = true }
requests-oauthlib = { version = ">=0.6.1", optional = true }
soco = { version = "*", optional = true }
titlecase = { version = ">=2.4.1", optional = true }
pydata-sphinx-theme = { version = "*", optional = true }
sphinx = { version = "*", optional = true }
@ -151,6 +152,7 @@ replaygain = [
scrub = ["mutagen"]
sonosupdate = ["soco"]
thumbnails = ["Pillow", "pyxdg"]
titlecase = ["titlecase"]
web = ["flask", "flask-cors"]
[tool.poetry.scripts]

View file

@ -0,0 +1,55 @@
# This file is part of beets.
# Copyright 2025, Henry Oberholtzer
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
""" Tests for the 'titlecase' plugin"""
import pytest
from beets import config
from beets.test.helper import BeetsTestCase
from beetsplug.titlecase import TitlecasePlugin
@pytest.mark.parametrize("given, expected",
[("PENDULUM", "Pendulum"),
("Aaron-carl", "Aaron-Carl"),
("LTJ bukem", "LTJ Bukem"),
("Freaky chakra Vs. Single Cell Orchestra",
"Freaky Chakra vs. Single Cell Orchestra")
])
def test_basic_titlecase(given, expected):
""" Assert that general behavior is as expected. """
assert TitlecasePlugin().titlecase(given) == expected
class TitlecasePluginTest(BeetsTestCase):
def test_preserved_case(self):
""" Test using given strings to preserve case """
names_to_preserve = ["easyFun", "A.D.O.R.",
"D.R.", "ABBA", "LaTeX"]
config["titlecase"]["preserve"] = names_to_preserve
for name in names_to_preserve:
assert TitlecasePlugin().titlecase(
name.lower()) == name
def test_small_first_last(self):
config["titlecase"]["small_first_last"] = False
assert TitlecasePlugin().titlecase(
"A Simple Trial") == "a Simple Trial"
config["titlecase"]["small_first_last"] = True
assert TitlecasePlugin().titlecase(
"A simple Trial") == "A Simple Trial"