feat(tag-prefix): parse_kind_prefix util — IR-style \kind:name\ parser at the input boundary; KNOWN_KINDS = artist/character/fandom/series/meta/rating (excludes default \general\ and system-managed archive/post)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,43 @@
|
|||||||
|
"""Parse the user-facing `kind:name` shortcut used by the add-tag input.
|
||||||
|
|
||||||
|
Mirrors IR's app/utils/tag_prefix.py. Tag.name in FC is stored bare;
|
||||||
|
the `kind:` prefix only exists as an input convention at user-facing
|
||||||
|
places (image-modal add-tag input, future bulk-add forms). The parser
|
||||||
|
is the single owner of the kind-string list — anything not in
|
||||||
|
KNOWN_KINDS keeps its colon as literal text.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
# Kinds the user can type as a prefix at the input boundary.
|
||||||
|
# `general` is intentionally excluded — it's the default for un-prefixed
|
||||||
|
# input. `archive` and `post` are system-managed and never user-typed.
|
||||||
|
KNOWN_KINDS: frozenset[str] = frozenset({
|
||||||
|
"artist",
|
||||||
|
"character",
|
||||||
|
"fandom",
|
||||||
|
"series",
|
||||||
|
"meta",
|
||||||
|
"rating",
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def parse_kind_prefix(raw: str) -> tuple[str | None, str]:
|
||||||
|
"""Split a raw user-typed tag string into (kind, name).
|
||||||
|
|
||||||
|
Returns (kind, name) where kind is lowercase canonical and in
|
||||||
|
KNOWN_KINDS, or (None, raw.strip()) if no recognized prefix is
|
||||||
|
present. `name` is always whitespace-stripped.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
parse_kind_prefix("character:Saber") -> ("character", "Saber")
|
||||||
|
parse_kind_prefix("Character:Saber") -> ("character", "Saber")
|
||||||
|
parse_kind_prefix("sunset") -> (None, "sunset")
|
||||||
|
parse_kind_prefix("http://example") -> (None, "http://example")
|
||||||
|
parse_kind_prefix("artist: Eric ") -> ("artist", "Eric")
|
||||||
|
"""
|
||||||
|
if ":" in raw:
|
||||||
|
prefix, rest = raw.split(":", 1)
|
||||||
|
if prefix.lower() in KNOWN_KINDS:
|
||||||
|
return prefix.lower(), rest.strip()
|
||||||
|
return None, raw.strip()
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
"""parse_kind_prefix — IR-style `kind:name` shortcut at the input boundary."""
|
||||||
|
|
||||||
|
from backend.app.utils.tag_prefix import KNOWN_KINDS, parse_kind_prefix
|
||||||
|
|
||||||
|
|
||||||
|
def test_recognized_kinds_match_user_input_set():
|
||||||
|
# System-managed kinds (archive, post) and the default (general)
|
||||||
|
# are intentionally excluded — the user can't type those at the
|
||||||
|
# input boundary.
|
||||||
|
assert KNOWN_KINDS == frozenset({
|
||||||
|
"artist", "character", "fandom", "series", "meta", "rating",
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def test_character_prefix_parsed():
|
||||||
|
assert parse_kind_prefix("character:Saber") == ("character", "Saber")
|
||||||
|
|
||||||
|
|
||||||
|
def test_case_insensitive_prefix():
|
||||||
|
assert parse_kind_prefix("Character:Saber") == ("character", "Saber")
|
||||||
|
assert parse_kind_prefix("CHARACTER:Saber") == ("character", "Saber")
|
||||||
|
|
||||||
|
|
||||||
|
def test_no_prefix_returns_none_kind():
|
||||||
|
assert parse_kind_prefix("sunset") == (None, "sunset")
|
||||||
|
|
||||||
|
|
||||||
|
def test_unknown_prefix_kept_as_literal():
|
||||||
|
# 'http' is not a known kind — preserve the literal text.
|
||||||
|
assert parse_kind_prefix("http://example.com") == (None, "http://example.com")
|
||||||
|
|
||||||
|
|
||||||
|
def test_whitespace_stripped():
|
||||||
|
assert parse_kind_prefix("artist: Eric ") == ("artist", "Eric")
|
||||||
|
assert parse_kind_prefix(" sunset ") == (None, "sunset")
|
||||||
|
|
||||||
|
|
||||||
|
def test_empty_string():
|
||||||
|
assert parse_kind_prefix("") == (None, "")
|
||||||
|
|
||||||
|
|
||||||
|
def test_just_colon():
|
||||||
|
# Empty prefix → "" not in KNOWN_KINDS → falls through to (None, "...")
|
||||||
|
assert parse_kind_prefix(":foo") == (None, ":foo")
|
||||||
Reference in New Issue
Block a user