"""Convierte un nombre/titulo de Obsidian a un slug kebab-case estable. Funcion pura: transliteracion Unicode + normalizacion a [a-z0-9-]. No hace I/O. """ import re import unicodedata # Cualquier secuencia de caracteres que NO sea [a-z0-9] colapsa a un solo '-'. _NON_SLUG_RE = re.compile(r"[^a-z0-9]+") # Mapeo explicito de caracteres que NFKD no descompone como queremos. _TRANSLIT_MAP = { "ñ": "n", "Ñ": "n", } def slugify_obsidian_name(name: str) -> str: """Convierte un nombre o titulo a un slug kebab-case estable. Pasos, en orden: 1. Mapea caracteres especiales que NFKD no descompone (ñ/Ñ -> n). 2. Normaliza con ``unicodedata.normalize('NFKD', ...)`` y descarta los combining marks (acentos, diacriticos), transliterando a ASCII. 3. Pasa todo a minusculas. 4. Reemplaza cualquier secuencia de caracteres no ``[a-z0-9]`` por un unico ``-``. 5. Hace strip de ``-`` al inicio y al final. Nunca quedan guiones dobles. Es deterministica y pura: misma entrada -> mismo slug, sin efectos secundarios ni I/O. Args: name: Nombre o titulo arbitrario (puede llevar acentos, mayusculas, espacios, simbolos). Returns: El slug kebab-case correspondiente. Cadena vacia si ``name`` no contiene ningun caracter slugificable. """ if not name: return "" # 1. Mapeo explicito antes de NFKD (la cedilla de la enye se trataria mal). for src, dst in _TRANSLIT_MAP.items(): name = name.replace(src, dst) # 2. Transliteracion Unicode: descompone y descarta combining marks. decomposed = unicodedata.normalize("NFKD", name) ascii_only = "".join(ch for ch in decomposed if not unicodedata.combining(ch)) # 3. Minusculas. lowered = ascii_only.lower() # 4. Colapsar todo lo no [a-z0-9] a un unico '-'. slug = _NON_SLUG_RE.sub("-", lowered) # 5. Strip de guiones en los bordes. return slug.strip("-") if __name__ == "__main__": assert slugify_obsidian_name("Enmanuel Gutiérrez Pérez") == "enmanuel-gutierrez-perez" assert slugify_obsidian_name("Jose manuel camaño castro") == "jose-manuel-camano-castro" assert slugify_obsidian_name("DNI de María del Mar") == "dni-de-maria-del-mar" assert slugify_obsidian_name(" raro__nombre!! ") == "raro-nombre" assert slugify_obsidian_name("") == "" assert slugify_obsidian_name("!!!") == "" print("slugify_obsidian_name smoke OK")