eb8dbf66a1
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
98 lines
3.8 KiB
Python
98 lines
3.8 KiB
Python
"""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")
|