Skip to content

feat: add Obsidian opener and per-opener filter metadata (0.2.0) - #11

Merged
sksizer merged 8 commits into
mainfrom
feat/uri-scheme-and-obsidian
May 17, 2026
Merged

sksizer merged 8 commits into
mainfrom
feat/uri-scheme-and-obsidian

Conversation

@sksizer

@sksizer sksizer commented May 17, 2026

Copy link
Copy Markdown
Owner

Summary

  • Adds an Obsidian opener that builds obsidian:// URIs by reading the user's own obsidian.json to discover registered vaults and routing the path through a three-way strategy ladder (vault root → ?vault=Name; file inside a vault → ?vault=Name&file=rel; outside any known vault → ?path=abs). All Obsidian/URI specifics stay inside src/obsidian.rs — no Vault or URI scheme leaks to the public API.
  • Adds per-opener filter metadata so consumers can build "what can I open this with?" UIs: accepts_directories: bool plus a concrete enum FileSupport { Any, NotSupported, Extensions(Vec<String>) }. Every built-in declares both fields explicitly via macro-required arguments — no silent defaults.
  • Adds an open(path: &Path, app_id: &str) convenience that resolves the opener from the registry internally. Legacy open_path(command, path) from 0.1.x is retained as a primitive. Existing open_with(opener, path) stays as the lower-level entry point.

Bump to 0.2.0 (minor — PathOpener field shape changed). Fixes the Cargo.toml repository/homepage URLs that pointed to a non-existent rs-path-opener repo.

Task

Tracked in the consuming project at docs/planning/tasks/2026-05-16-path-opener-uri-scheme-and-obsidian.md (in sksizer/polish). That document carries the full Goal / Today / Proposed / Approach / ACs / Out-of-scope / Discovery-context, including the design pivot away from a public LaunchStrategy/TargetKind enum.

Acceptance criteria

  • AC-1: Public API is exactly open, open_default, open_with, open_path (legacy primitive retained per user call), detect_installed_apps, PathOpener, FileSupport. No LaunchStrategy, no TargetKind. grep "[Vv]ault" src/lib.rs returns only one hit, an internal // (not ///) comment inside the private Launch enum — not rendered in docs. pub(crate) mod obsidian and pub(crate) struct Vault keep that vocabulary crate-private.
  • AC-2: enum FileSupport { Any, NotSupported, Extensions(Vec<String>) } derives specta::Type under the specta feature. Variant name stayed Extensions; Vec<String> reshape (vs &'static [&'static str]) was the spec's Approach-8 escape-hatch (b) — forced by serde Deserialize on PathOpener. Private FileSupportSpec sibling carries the &'static form so the const KNOWN_APPS table doesn't allocate; conversion happens at detect_installed_apps() time. README migration notes document.
  • AC-3: Every built-in declares both metadata fields, enforced at the macro signature. Audit test every_known_app_declares_expected_metadata walks the registry. Editors / file-managers → (true, Any); terminals → (true, NotSupported); Obsidian → (true, Extensions(["md","markdown","canvas"])).
  • AC-4: Obsidian registered with Launch::Custom(obsidian::build_command). macOS detection via MacAppBundle("Obsidian.app"). Linux/Windows use new Detection::AlwaysUnavailable variant so they always report is_available: false (avoids false-positive on any unrelated obsidian binary on PATH) with TODO pointing to the follow-up.
  • AC-5: uri_for_vault_root_uses_vault_query_only — ?vault=<Name>.
  • AC-6: uri_for_file_inside_vault_uses_vault_and_file — ?vault=<Name>&file=<rel>.
  • AC-7: uri_for_path_outside_any_vault_falls_through_to_path_query — ?path=<abs>.
  • AC-8: parse_handles_missing_unreadable_malformed_inputs covers the JSON-parse error branch; discover_does_not_panic covers the runtime path; missing-file / unreadable-bytes branches are simple let Ok(..) = .. else { return Vec::new() }.
  • AC-9: 5 doctests pass under cargo test --doc, including the README's openers_for(path) example that filters by both accepts_directories and file_support. README is include_str!'d into src/lib.rs so its examples are exercised.
  • AC-10: cargo test (16 unit + 5 doc) green; cargo check --all-features green.
  • AC-11: Cargo.toml repository and homepage corrected to https://github.com/sksizer/rust-path-opener.
  • AC-12: 0.2.0 published on crates.io + v0.2.0 tag — not yet. Cargo.toml bumped, tree clean; to be done by maintainer after merge via cargo publish + git tag v0.2.0 && git push --tags.

Test plan

  • Pull the branch, just ci runs green locally.
  • On macOS with Obsidian installed and at least one registered vault: open(vault_root, "obsidian") opens Obsidian to that vault; open(vault/sub/file.md, "obsidian") opens the note. Unregistered path still launches Obsidian (it'll prompt).
  • detect_installed_apps() filtered by accepts_directories: true + file_support: Extensions("md") matches md files to Obsidian + any general editors (VS Code, Zed, ...), excludes terminals.
  • Review the README rewrite for tone / examples / migration-notes clarity.

sksizer added 8 commits May 16, 2026 21:01
Imported WIP state: Launch::{Argv, Custom(fn)} on KnownApp, obsidian.rs with discover_vaults() reading obsidian.json + build_command() walking the three-way strategy ladder (vault root / file-in-vault / outside). Not yet: accepts_directories + file_support fields, Obsidian registration in KnownApp registry, public open(path, app_id) convenience. Tracked by polish task 2026-05-16-path-opener-uri-scheme-and-obsidian.
…its Vault struct were exposed as pub on the WIP commit, but vault is Obsidian-domain terminology that does not belong in path-opener's general vocabulary. Callers go through open(path, app_id) and never need to see vault metadata.
…s two flat metadata fields on PathOpener that callers use to build 'what can I open this with?' UIs: - accepts_directories: bool — can this opener open a directory path? - file_support: FileSupport — Any | NotSupported | Extensions(Vec<String>) Every built-in declares both fields explicitly (no silent defaults). Editors and file managers get (true, Any); terminals get (true, NotSupported); Obsidian gets (true, Extensions(['md','markdown','canvas'])). The static registry uses a sibling FileSupportSpec with &'static [&'static str] (consts can't allocate); detect_installed_apps converts to the owned FileSupport on the public surface. This keeps serde Deserialize working on PathOpener — the alternative shape using &'static [&'static str] on FileSupport directly does not derive Deserialize. A FileSupport::accepts_extension(ext) helper provides case-insensitive extension matching, consistent with how Path::extension yields a bare extension. Adds AlwaysUnavailable Detection so Linux/Windows Obsidian reports is_available=false (detection follow-up tracked in the task).
…d) as the primary entry point — callers hand path-opener a (path, app_id) and let it own the strategy. Internally resolves the registered KnownApp, picks the platform entry, and dispatches through the same Launch::{Argv, Custom} machinery as open_with. open_with stays as the lower-level form for callers that already have a PathOpener struct in hand. Extracts a build_command_for helper so upcoming Obsidian strategy tests can inspect the constructed Command without spawning a process.
… pure URI-building logic in obsidian::build_command into a private build_uri(path, vaults) helper. The launch step (uri_launcher(&uri)) is unchanged but the URI assembly is now testable without spawning a process. New tests cover the three strategy-ladder arms: - path equals a known vault root → ?vault=Name - path is inside a known vault → ?vault=Name&file=rel - path outside every vault → ?path=abs - empty vault list (obsidian.json absent) → ?path=abs Plus a parser test confirming discover_vaults' malformed-JSON branch returns no vaults (the underlying serde_json::from_slice errors are swallowed into Vec::new()).
…categories ('we know editors / terminals / file managers') to explicit per-opener metadata (accepts_directories + file_support). Includes: - open(path, app_id) as the primary entry point - the FileSupport variants table with concrete examples - a doctest showing how to filter detect_installed_apps() by (accepts_directories, file_support) to build a 'what can I open this with?' list for a given path - Obsidian opener with internal vault-discovery routing called out as experimental - Linux/Windows Obsidian detection follow-up noted - migration notes for the field additions and the FileSupport::Extensions(Vec<String>) shape The README is included into the crate-level rustdoc via include_str!, so its example snippets are exercised by cargo test --doc.
….1.1 -> 0.2.0 (new API surface: open(path, app_id), FileSupport, accepts_directories, Obsidian opener) - repository URL was a typo (rs-path-opener); real repo is rust-path-opener - description gains 'Markdown apps' to reflect the Obsidian addition
@sksizer
sksizer merged commit 96dc1cf into main May 17, 2026
1 check passed
@sksizer
sksizer deleted the feat/uri-scheme-and-obsidian branch May 17, 2026 13:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant