feat(infra): auto-commit con 88 cambios

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-11 00:16:46 +02:00
parent 6bc97df5c0
commit eb8dbf66a1
126 changed files with 10933 additions and 287 deletions
@@ -0,0 +1,97 @@
"""Resuelve el path absoluto real de un attachment embebido en un vault Obsidian.
Funcion impura: recorre el filesystem del vault. Obsidian resuelve los embeds
![[...]] por nombre de archivo unico (no por path), asi que esta funcion busca
recursivamente un archivo cuyo basename coincida con el nombre del embed.
"""
import os
# Directorios de maquinaria de Obsidian que nunca contienen attachments de usuario.
_EXCLUDED_DIRS = {".obsidian", ".trash"}
def resolve_obsidian_embed(vault_dir: str, embed_name: str) -> str:
"""Resuelve el path absoluto de un attachment embebido buscandolo por nombre.
Obsidian resuelve los embeds `![[archivo.jpg]]` por nombre de archivo unico
dentro del vault, no por ruta. Esta funcion replica ese comportamiento:
recorre ``vault_dir`` recursivamente (una sola pasada con ``os.walk``) y
devuelve el primer archivo cuyo basename coincida, de forma
case-insensitive, con ``embed_name``.
Si ``embed_name`` no trae extension (p.ej. ``"foto"``), se acepta cualquier
archivo cuyo basename sin extension coincida (p.ej. ``foto.jpg``).
Los directorios ``.obsidian/`` y ``.trash/`` se excluyen del recorrido.
Impura: lee el filesystem. NO lanza si el embed no se encuentra (devuelve
cadena vacia). SI lanza si ``vault_dir`` no existe.
Args:
vault_dir: Ruta a la raiz del vault Obsidian.
embed_name: Nombre del attachment embebido (lo que devuelve
``extract_obsidian_embeds``), con o sin extension.
Returns:
El path absoluto del primer archivo que coincide, o cadena vacia ``""``
si ningun archivo del vault coincide.
Raises:
FileNotFoundError: si ``vault_dir`` no existe.
NotADirectoryError: si ``vault_dir`` no es un directorio.
"""
if not os.path.exists(vault_dir):
raise FileNotFoundError(f"vault path does not exist: {vault_dir}")
if not os.path.isdir(vault_dir):
raise NotADirectoryError(f"vault path is not a directory: {vault_dir}")
target = embed_name.strip()
if not target:
return ""
target_lower = target.lower()
has_extension = os.path.splitext(target)[1] != ""
target_stem_lower = os.path.splitext(target_lower)[0]
for dirpath, dirnames, filenames in os.walk(vault_dir):
# Podar maquinaria de Obsidian in-place para no descender en ella.
dirnames[:] = [d for d in dirnames if d not in _EXCLUDED_DIRS]
for filename in filenames:
filename_lower = filename.lower()
if has_extension:
# Comparar basename completo, case-insensitive.
if filename_lower == target_lower:
return os.path.abspath(os.path.join(dirpath, filename))
else:
# Sin extension en el embed: comparar solo el stem del archivo.
stem_lower = os.path.splitext(filename_lower)[0]
if stem_lower == target_stem_lower:
return os.path.abspath(os.path.join(dirpath, filename))
return ""
if __name__ == "__main__":
import tempfile
with tempfile.TemporaryDirectory() as tmp:
os.makedirs(os.path.join(tmp, "attachments"))
os.makedirs(os.path.join(tmp, ".obsidian"))
with open(os.path.join(tmp, "attachments", "Foto.JPG"), "w") as f:
f.write("x")
with open(os.path.join(tmp, "doc.pdf"), "w") as f:
f.write("y")
# Match case-insensitive con extension.
hit = resolve_obsidian_embed(tmp, "foto.jpg")
assert hit.endswith(os.path.join("attachments", "Foto.JPG")), hit
# Match sin extension en el embed.
hit2 = resolve_obsidian_embed(tmp, "doc")
assert hit2.endswith("doc.pdf"), hit2
# No existe -> "".
assert resolve_obsidian_embed(tmp, "no_existe.png") == ""
print("resolve_obsidian_embed smoke OK")