"""Abre un vault en la app de escritorio Obsidian via el esquema obsidian://. Construye el URI `obsidian://open?vault=` y lanza la app para abrir ese vault. Opcionalmente lo registra antes en la config de la app si recibe una ruta existente no registrada (compone register_obsidian_vault del grupo obsidian). Funcion impura: puede escribir la config de la app (al registrar) y lanza un proceso externo (xdg-open) de forma desacoplada para abrir el URI en Obsidian. """ import os import subprocess import urllib.parse from obsidian import register_obsidian_vault def open_obsidian_vault( vault: str, register_if_missing: bool = True, launch: bool = True, config_path: str = "", ) -> dict: """Abre un vault en la app Obsidian construyendo y lanzando un URI obsidian://. Args: vault: ruta absoluta a un vault o su nombre (basename). Si es una ruta existente, el nombre del URI es su basename; si no parece una ruta existente se trata como nombre tal cual. register_if_missing: si True (default) y vault es una ruta existente no registrada en la app, la registra (open=True) antes de abrir. launch: si True (default) lanza la app via `xdg-open ` desacoplado. Si False (util en tests) NO lanza nada, solo construye el URI. config_path: ruta al obsidian.json de la app. Vacio -> default ~/.config/obsidian/obsidian.json. Se pasa a register_obsidian_vault. Returns: dict con: - vault: el argumento original recibido. - uri: el URI obsidian://open?vault= construido. - name: nombre del vault usado en el URI (basename o el propio vault). - launched: True si se lanzo xdg-open. - registered_now: True si se registro el vault en esta llamada. Raises: OSError: si el lanzamiento del proceso o el registro fallan por I/O. """ registered_now = False is_path = os.path.sep in vault or vault.startswith("~") abs_path = os.path.abspath(os.path.expanduser(vault)) if is_path else "" # Si es una ruta existente, opcionalmente registrarla y usar su basename. if abs_path and os.path.isdir(abs_path): name = os.path.basename(abs_path.rstrip(os.path.sep)) if register_if_missing: res = register_obsidian_vault(abs_path, open=True, config_path=config_path) registered_now = bool(res.get("registered")) elif is_path: # Parece ruta pero no existe: usar su basename como nombre. name = os.path.basename(abs_path.rstrip(os.path.sep)) else: # Es un nombre, no una ruta. name = vault uri = "obsidian://open?vault=" + urllib.parse.quote(name) launched = False if launch: env = dict(os.environ) subprocess.Popen( ["xdg-open", uri], stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, start_new_session=True, env=env, ) launched = True return { "vault": vault, "uri": uri, "name": name, "launched": launched, "registered_now": registered_now, } if __name__ == "__main__": import tempfile tmp = tempfile.mkdtemp() cfg = os.path.join(tmp, "obsidian.json") vault = os.path.join(tmp, "Mi Vault") os.makedirs(vault, exist_ok=True) r = open_obsidian_vault(vault, launch=False, config_path=cfg) assert r["launched"] is False, r assert r["registered_now"] is True, r assert r["name"] == "Mi Vault", r assert r["uri"] == "obsidian://open?vault=Mi%20Vault", r # Por nombre, sin lanzar ni registrar. r2 = open_obsidian_vault("MiVaultPorNombre", launch=False, config_path=cfg) assert r2["uri"] == "obsidian://open?vault=MiVaultPorNombre", r2 assert r2["registered_now"] is False, r2 print("open_obsidian_vault smoke OK")