aboutsummaryrefslogtreecommitdiff
path: root/duet.el
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-06-06 10:37:59 -0500
committerCraig Jennings <c@cjennings.net>2026-06-06 10:37:59 -0500
commit04f9eb281529965c4aff9ca9176b549fac4ae30f (patch)
treeaf3dcba1f2a0c26e6e80a31225db33c3d005b3c7 /duet.el
parent95dbb5abdbb746cf5da9f7926740d17205ac8d55 (diff)
downloadduet-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.el42
1 files changed, 42 insertions, 0 deletions
diff --git a/duet.el b/duet.el
index 364a7c4..9ab858b 100644
--- a/duet.el
+++ b/duet.el
@@ -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."