Allow spaced desktop Name=; restore Sick days

Rule 2 now takes one space between words; the phone label
rule is unchanged. Reverts the 5b36e84 rename.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WzxdWY1k4muZfdVJfb7YGJ
This commit is contained in:
Krzysztof Rudnicki 2026-11-02 12:01:43 +01:00
parent af3c98a50e
commit 2bbb428f2c
5 changed files with 70 additions and 15 deletions

View File

@ -122,7 +122,10 @@ def desktop_problems(repo: Path) -> list[str]:
if not names:
out.append(f"{rel}: no Name= in [Desktop Entry]")
for name in names:
out += [f"{rel}: Name={p}" for p in launcher_label.label_problems(name)]
out += [
f"{rel}: Name={p}"
for p in launcher_label.label_problems(name, words=True)
]
return out

View File

@ -7,7 +7,8 @@ the launcher font is capped too (`label_widths.py`): FocusOwner, 10
characters, was still cut. The same rules live
in `~/src/testsAndMisc/python_pkg/app_icons/labels.py` (`label_problems`);
the messages are mirrored here, not imported, because utils must not depend
on another repo.
on another repo. A desktop entry's `Name=` (rule 2) may hold single spaces
between words (`words=True`); the phone launcher may not.
The name is `<application android:label>` of the main manifest, plus the
`android:label` of any activity (or alias) whose intent-filter holds MAIN +
@ -38,27 +39,39 @@ from repo_contract.label_widths import MAX_WIDTH, label_width
MAX_LENGTH = 10
STRINGS = Path("android/app/src/main/res/values/strings.xml")
_ASCII_LETTERS = re.compile(r"[A-Za-z]+")
_ASCII_WORDS = re.compile(r"[A-Za-z]+( [A-Za-z]+)*")
_STRING_REF = re.compile(r"@string/(\w+)")
_PLACEHOLDER = re.compile(r"\$\{(\w+)\}")
_UNRESOLVED = "cannot resolve the launcher name"
def label_problems(name: str) -> list[str]:
"""Every launcher-name rule `name` breaks (app_icons labels.py wording)."""
def label_problems(name: str, *, words: bool = False) -> list[str]:
"""Every launcher-name rule `name` breaks (app_icons labels.py wording).
`words=True` is for a desktop entry's `Name=` (rule 2), shown in a menu or
dmenu where "Sick days" reads better than "Sickdays": one space between
words is fine. The phone launcher (rule 1) never passes it.
"""
if not name:
return ["label is empty"]
problems: list[str] = []
letters_only = _ASCII_LETTERS.fullmatch(name) is not None
if not letters_only:
problems.append(
f"{name!r} must contain only ASCII letters (no spaces, _ or symbols)"
if words:
valid = _ASCII_WORDS.fullmatch(name) is not None
message = (
f"{name!r} must be ASCII letters, one space between words (no _ or symbols)"
)
else:
valid = _ASCII_LETTERS.fullmatch(name) is not None
message = f"{name!r} must contain only ASCII letters (no spaces, _ or symbols)"
if not valid:
problems.append(message)
if not name[0].isupper():
problems.append(f"{name!r} must start with a capital letter")
# The width table only knows ASCII letters; other names already failed.
if letters_only and label_width(name) > MAX_WIDTH:
letters = name.replace(" ", "")
if valid and label_width(letters) > MAX_WIDTH:
problems.append(
f"{name!r} is {label_width(name)} wide in the launcher font; "
f"{name!r} is {label_width(letters)} wide in the launcher font; "
f"at most {MAX_WIDTH} fit"
)
if len(name) > MAX_LENGTH:

View File

@ -98,6 +98,7 @@ def test_no_flutter_app_no_rules(repo: Path) -> None:
def test_desktop_names_checked(repo: Path) -> None:
flutter_app(repo)
write(repo, "linux/good.desktop", DESKTOP.format(name="Todo"))
write(repo, "linux/spaced.desktop", DESKTOP.format(name="Sick days"))
write(repo, "linux/bad.desktop", DESKTOP.format(name="todo app"))
write(
repo,
@ -108,10 +109,6 @@ def test_desktop_names_checked(repo: Path) -> None:
track(repo)
write(repo, "linux/untracked.desktop", DESKTOP.format(name="bad name"))
assert launcher.problems(repo) == [
(
"launcher: linux/bad.desktop: Name='todo app' must contain only ASCII"
" letters (no spaces, _ or symbols)"
),
"launcher: linux/bad.desktop: Name='todo app' must start with a capital letter",
"launcher: linux/none.desktop: no Name= in [Desktop Entry]",
]

View File

@ -177,3 +177,45 @@ def test_manifest_without_application(tmp_path: Path) -> None:
assert ll.problems(app) == [
f"{MAIN}: <application> has no android:label; cannot resolve the launcher name"
]
WORDS_RULE = "must be ASCII letters, one space between words (no _ or symbols)"
@pytest.mark.parametrize(
("name", "expected"),
[
("Sick days", []),
("Todo", []),
("sick days", ["'sick days' must start with a capital letter"]),
(
"Sick days",
[f"'Sick days' {WORDS_RULE}"],
),
(
"Sick days ",
[f"'Sick days ' {WORDS_RULE}"],
),
(
"Sick_days",
[f"'Sick_days' {WORDS_RULE}"],
),
(
"Focus owner x",
[
"'Focus owner x' is 607 wide in the launcher font; at most 522 fit",
"'Focus owner x' has 13 characters; at most 10 fit",
],
),
],
)
def test_desktop_names_may_have_spaces_between_words(
name: str, expected: list[str]
) -> None:
assert ll.label_problems(name, words=True) == expected
def test_the_phone_label_still_refuses_a_space() -> None:
assert ll.label_problems("Sick days") == [
"'Sick days' must contain only ASCII letters (no spaces, _ or symbols)"
]