diff options
| author | Craig Jennings <c@cjennings.net> | 2026-06-06 10:37:59 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-06-06 10:37:59 -0500 |
| commit | 04f9eb281529965c4aff9ca9176b549fac4ae30f (patch) | |
| tree | af3dcba1f2a0c26e6e80a31225db33c3d005b3c7 /duet.el | |
| parent | 95dbb5abdbb746cf5da9f7926740d17205ac8d55 (diff) | |
| download | duet-04f9eb281529965c4aff9ca9176b549fac4ae30f.tar.gz duet-04f9eb281529965c4aff9ca9176b549fac4ae30f.zip | |
feat: add duet--classify-path
duet--classify-path turns a path string into a plist describing where it lives and how to reach it: :locality (local or remote), :method, :user, :host, :port, :localname, and :hop for multi-hop paths. It's the pure foundation the transfer-spec and connection layers build on (Phase 1 in the design spec).
TRAMP does the dissection, so any path file-remote-p recognizes is remote and everything else is local. A local path is expanded, so a leading ~ resolves to the home directory. A remote localname is kept verbatim because a ~ there is the remote home, not this machine's. Classification never signals: an incomplete string like /ssh:host that TRAMP rejects as a remote name falls back to local, since validating raw TRAMP input belongs to the connection reader, not here.
I probed TRAMP's real contract before writing the tests (port comes back as a string, a multi-hop path reports the final host with the leading hops in :hop), so the Normal/Boundary/Error cases assert what TRAMP actually returns rather than what I'd have guessed.
Diffstat (limited to 'duet.el')
| -rw-r--r-- | duet.el | 42 |
1 files changed, 42 insertions, 0 deletions
@@ -33,11 +33,53 @@ ;;; Code: +(require 'tramp) + (defgroup duet nil "Dual-pane file commander over dirvish/dired." :group 'files :prefix "duet-") +;;; Path classification + +(defun duet--classify-path (path) + "Classify PATH into a plist describing its locality and components. + +The returned plist has these keys: + + :locality `local' or `remote' + :method TRAMP method (e.g. \"ssh\"), or nil when local + :user remote user, or nil + :host remote host (the final hop for a multi-hop path), or nil + :port remote port as a string, or nil + :localname the path on the target filesystem + :hop the leading-hops string for a multi-hop path, or nil + +TRAMP performs the dissection, so any path `file-remote-p' recognizes is +remote and everything else is local. A local PATH has its name expanded +\(so a leading ~ resolves to the home directory); a remote localname is kept +verbatim because a ~ there is the remote home, not this machine's. + +Classification is total: a TRAMP-looking string TRAMP does not accept as a +remote name (an incomplete \"/ssh:host\") is treated as a local path rather +than signaling. Validating raw TRAMP input is the connection reader's job." + (if (file-remote-p path) + (let ((v (tramp-dissect-file-name path))) + (list :locality 'remote + :method (tramp-file-name-method v) + :user (tramp-file-name-user v) + :host (tramp-file-name-host v) + :port (tramp-file-name-port v) + :localname (tramp-file-name-localname v) + :hop (tramp-file-name-hop v))) + (list :locality 'local + :method nil + :user nil + :host nil + :port nil + :localname (expand-file-name path) + :hop nil))) + ;;;###autoload (defun duet () "Launch the DUET dual-pane file commander." |
