"""Normalization functions for token values.
Each public function in this module conforms to the ``Normalizer`` signature::
Callable[[object], str]
This means they accept any object (the raw token value before normalization,
which may be a ``str``, an ``Enum``, or any other type) and always return a
plain ``str``. Empty string signals "no value provided".
All functions are registered in ``NORMALIZER_MAP`` at the bottom of the module,
which is the single source of truth consumed by ``rules.build_normalizers()``.
Note on the ``if not value`` guard:
These functions use ``if not value: return ""`` as a fast-exit for falsy
inputs. This is intentional: the ``Manager`` never passes ``None`` to a
normalizer (it returns ``""`` before calling them), and a falsy non-None
value such as ``0`` or ``False`` is not a valid token value in this system.
If that assumption changes, the guard should be replaced with an explicit
``if value is None: return ""``.
"""
from typing import Callable
from openrig.naming import utils
# ---------------------------------------------------------------------------
# Internal helpers
# ---------------------------------------------------------------------------
_SIDE_MAPPING: dict[str, str] = {
"left": "l",
"l": "l",
"right": "r",
"r": "r",
"center": "c",
"c": "c",
"middle": "m",
"m": "m",
}
# ---------------------------------------------------------------------------
# Normalizer functions
# ---------------------------------------------------------------------------
[docs]
def side(value: object) -> str:
"""Normalizes a side value to its standard abbreviation.
Maps long-form or mixed-case side strings to their canonical short form
(e.g. ``'Left'`` → ``'l'``). Values not found in the mapping are
converted to camelCase as a fallback.
Args:
value: The raw side value to normalize.
Returns:
The normalized side string (e.g. ``'left'`` → ``'l'``),
or ``""`` if the value is falsy.
"""
if not value:
return ""
str_val = str(value)
normalized = _SIDE_MAPPING.get(str_val.lower())
return normalized if normalized else utils.to_camel_case(str_val)
[docs]
def descriptor(value: object) -> str:
"""Normalizes a descriptor to camelCase.
Converts any casing or separator style to camelCase to avoid conflicts
with the naming separator (e.g. ``'upper_arm'`` → ``'upperArm'``).
Args:
value: The raw descriptor value to normalize.
Returns:
The camelCase descriptor string, or ``""`` if the value is falsy.
"""
if not value:
return ""
return utils.to_camel_case(str(value))
[docs]
def normalize_type(value: object) -> str:
"""Normalizes a type token to camelCase.
Registered in ``NORMALIZER_MAP`` under the key ``"type"``. The function
is named ``normalize_type`` to avoid shadowing the ``type`` builtin.
Args:
value: The raw type value to normalize.
Returns:
The camelCase type string, or ``""`` if the value is falsy.
"""
if not value:
return ""
return utils.to_camel_case(str(value))
[docs]
def pascal_case(value: object) -> str:
"""Normalizes a token value to PascalCase.
Args:
value: The raw value to normalize.
Returns:
The PascalCase string, or ``""`` if the value is falsy.
"""
if not value:
return ""
return utils.to_pascal_case(str(value))
[docs]
def snake_case(value: object) -> str:
"""Normalizes a token value to snake_case.
Args:
value: The raw value to normalize.
Returns:
The snake_case string, or ``""`` if the value is falsy.
"""
if not value:
return ""
return utils.to_snake_case(str(value))
[docs]
def kebab_case(value: object) -> str:
"""Normalizes a token value to kebab-case.
Args:
value: The raw value to normalize.
Returns:
The kebab-case string, or ``""`` if the value is falsy.
"""
if not value:
return ""
return utils.to_kebab_case(str(value))
[docs]
def clean(value: object) -> str:
"""Cleans a token value by replacing invalid characters.
Args:
value: The raw value to normalize.
Returns:
The cleaned string, or ``""`` if the value is falsy.
"""
if not value:
return ""
return utils.clean_txt(str(value))
[docs]
def version(value: object) -> str:
"""Normalizes a version number to the ``vNNN`` format.
Accepts both raw integers (``1`` → ``'v001'``) and already-formatted
strings (``'v3'`` → ``'v003'``).
Args:
value: The raw version value to normalize (``int`` or ``str``).
Returns:
The normalized version string (e.g. ``'v001'``), or ``""`` if falsy.
"""
if not value:
return ""
str_val = str(value)
ver_num = utils.get_version(str_val)
if ver_num is not None:
return f"v{str(ver_num).zfill(3)}"
if str_val.isdigit():
return f"v{str_val.zfill(3)}"
return str_val
[docs]
def upper(value: object) -> str:
"""Normalizes a token value to uppercase.
Args:
value: The raw value to normalize.
Returns:
The uppercase string, or ``""`` if the value is falsy.
"""
if not value:
return ""
return utils.to_upper(str(value))
[docs]
def lower(value: object) -> str:
"""Normalizes a token value to lowercase.
Args:
value: The raw value to normalize.
Returns:
The lowercase string, or ``""`` if the value is falsy.
"""
if not value:
return ""
return utils.to_lower(str(value))
[docs]
def capitalize(value: object) -> str:
"""Normalizes a token value by capitalizing its first letter.
Args:
value: The raw value to normalize.
Returns:
The capitalized string, or ``""`` if the value is falsy.
"""
if not value:
return ""
return utils.capitalize_first(str(value))
[docs]
def strip_digits(value: object) -> str:
"""Removes all digit characters from a token value.
Args:
value: The raw value to normalize.
Returns:
The string with all digits removed, or ``""`` if the value is falsy.
"""
if not value:
return ""
return utils.strip_digits(str(value))
[docs]
def strip_namespace(value: object) -> str:
"""Removes the namespace prefix from a token value.
Strips everything up to and including the last ``:`` separator.
Args:
value: The raw value to normalize (e.g. ``'ns:nodeName'``).
Returns:
The string without its namespace prefix, or ``""`` if the value is falsy.
"""
if not value:
return ""
return utils.strip_namespace(str(value))
[docs]
def base_name(value: object) -> str:
"""Returns the leaf component of a path-like token value.
Splits on ``|`` and returns the last segment.
Args:
value: The raw value to normalize (e.g. ``'|group|node'``).
Returns:
The base name string, or ``""`` if the value is falsy.
"""
if not value:
return ""
return utils.get_base_name(str(value))
# ---------------------------------------------------------------------------
# Registry
# ---------------------------------------------------------------------------
# ``Normalizer`` is re-exported here so callers can import it from this module
# without creating a circular dependency through ``rules``.
Normalizer = Callable[[object], str]
NORMALIZER_MAP: dict[str, Normalizer] = {
"side": side,
"descriptor": descriptor,
"type": normalize_type,
"pascal_case": pascal_case,
"snake_case": snake_case,
"kebab_case": kebab_case,
"clean": clean,
"version": version,
"upper": upper,
"lower": lower,
"capitalize": capitalize,
"strip_digits": strip_digits,
"strip_namespace": strip_namespace,
"base_name": base_name,
}