diff --git a/app/utils/tag_prefix.py b/app/utils/tag_prefix.py new file mode 100644 index 0000000..ec60cee --- /dev/null +++ b/app/utils/tag_prefix.py @@ -0,0 +1,43 @@ +"""Parse the user-facing `kind:name` shortcut used by the add-tag input. + +After the bare-name refactor, `Tag.name` no longer stores the `kind:` prefix. +The prefix lives only as an input convention in a handful of user-facing +places (modal add-tag input, bulk-add-tag form, import paths that receive +user-typed strings). This module owns that parser. +""" +from __future__ import annotations + +# The set of tag kinds the app recognizes as prefixable at the input boundary. +# `user` is intentionally excluded — user-typed general tags never carry a +# prefix. Strings like "http://example" keep their colon as literal text +# because "http" is not in this set. +KNOWN_KINDS: frozenset[str] = frozenset({ + 'character', + 'fandom', + 'artist', + 'series', + 'post', + 'archive', + 'meta', +}) + + +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 one of 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('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 in KNOWN_KINDS: + return prefix, rest.strip() + return None, raw.strip()