Mercurial > hg > xemacs-beta
diff lisp/hyperbole/hpath.el @ 0:376386a54a3c r19-14
Import from CVS: tag r19-14
author | cvs |
---|---|
date | Mon, 13 Aug 2007 08:45:50 +0200 |
parents | |
children | 4103f0995bd7 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/hyperbole/hpath.el Mon Aug 13 08:45:50 2007 +0200 @@ -0,0 +1,690 @@ +;;!emacs +;; +;; FILE: hpath.el +;; SUMMARY: Hyperbole support routines for handling UNIX paths. +;; USAGE: GNU Emacs Lisp Library +;; KEYWORDS: comm, hypermedia, unix +;; +;; AUTHOR: Bob Weiner +;; ORG: Brown U. +;; +;; ORIG-DATE: 1-Nov-91 at 00:44:23 +;; LAST-MOD: 10-Oct-95 at 23:31:56 by Bob Weiner +;; +;; This file is part of Hyperbole. +;; Available for use and distribution under the same terms as GNU Emacs. +;; +;; Copyright (C) 1991-1995, Free Software Foundation, Inc. +;; Developed with support from Motorola Inc. +;; +;; DESCRIPTION: +;; DESCRIP-END. + +;;; ************************************************************************ +;;; Public variables +;;; ************************************************************************ + +(defvar hpath:rfc "/anonymous@ds.internic.net:rfc/rfc%s.txt" + "*String to be used in the call: (hpath:rfc rfc-num) +to create an path to the RFC document for 'rfc-num'.") + +(defvar hpath:suffixes '(".gz" ".Z") + "*List of filename suffixes to add or remove within (hpath:exists-p) calls.") + +(defvar hpath:tmp-prefix "/tmp/remote-" + "*Pathname prefix to attach to remote files copied locally for use with external viewers.") + +;;; ************************************************************************ +;;; Public functions +;;; ************************************************************************ + +(defun hpath:absolute-to (path &optional default-dirs) + "Returns PATH as an absolute path relative to one directory from optional DEFAULT-DIRS or 'default-directory'. +Returns PATH unchanged when it is not a valid path or when DEFAULT-DIRS +is invalid. DEFAULT-DIRS when non-nil may be a single directory or a list of +directories. The first one in which PATH is found is used." + (if (not (and (stringp path) (hpath:is-p path nil t))) + path + (if (not (cond ((null default-dirs) + (setq default-dirs (cons default-directory nil))) + ((stringp default-dirs) + (setq default-dirs (cons default-dirs nil))) + ((listp default-dirs)) + (t nil))) + path + (let ((rtn) dir) + (while (and default-dirs (null rtn)) + (setq dir (expand-file-name + (file-name-as-directory (car default-dirs))) + rtn (expand-file-name path dir) + default-dirs (cdr default-dirs)) + (or (file-exists-p rtn) (setq rtn nil))) + (or rtn path))))) + +(defun hpath:ange-ftp-at-p () + "Returns an ange-ftp pathname that point is within or nil. +See the 'ange-ftp' or 'efs' Elisp packages for pathname format details. +Always returns nil if (hpath:ange-ftp-available-p) returns nil." + (if (hpath:ange-ftp-available-p) + (let ((user (hpath:ange-ftp-default-user)) + path) + (setq path + (save-excursion + (skip-chars-backward "^[ \t\n\"`'\(\{<") + (cond + ((hpath:url-at-p) + (if (string-equal + (buffer-substring (match-beginning 1) (match-end 1)) + "ftp") + (concat + "/" + ;; user + (if (match-beginning 2) + (buffer-substring + (match-beginning 2) (match-end 2)) + (concat user "@")) + ;; domain + (hpath:delete-trailer + (buffer-substring (match-beginning 3) (match-end 3))) + ":" + ;; path + (if (match-beginning 5) + (buffer-substring (match-beginning 5) + (match-end 5)))) + ;; else ignore this other type of WWW path + )) + ;; user, domain and path + ((looking-at "/?[^/:@ \t\n\^M\"`']+@[^/:@ \t\n\^M\"`']+:[^]@ \t\n\^M\"`'\)\}]*") + (buffer-substring (match-beginning 0) (match-end 0))) + ;; @domain and path + ((looking-at "@[^/:@ \t\n\^M\"`']+:[^]@ \t\n\^M\"`'\)\}]*") + (concat "/" user (buffer-substring + (match-beginning 0) (match-end 0)))) + ;; domain and path + ((and (looking-at + "/?\\(\\([^/:@ \t\n\^M\"`']+\\):[^]@:, \t\n\^M\"`'\)\}]*\\)[] \t\n\^M,.\"`'\)\}]") + (setq path (buffer-substring + (match-beginning 1) (match-end 1))) + (string-match "[^.]\\.[^.]" + (buffer-substring (match-beginning 2) + (match-end 2)))) + (concat "/" user "@" path)) + ;; host and path + ((and (looking-at + "/\\([^/:@ \t\n\^M\"`']+:[^]@:, \t\n\^M\"`'\)\}]*\\)") + (setq path (buffer-substring + (match-beginning 1) (match-end 1)))) + (concat "/" user "@" path)) + ))) + (hpath:delete-trailer path)))) + +(defun hpath:ange-ftp-p (path) + "Returns non-nil iff PATH is an ange-ftp pathname. +See the 'ange-ftp' or 'efs' Elisp package for pathname format details. +Always returns nil if (hpath:ange-ftp-available-p) returns nil." + (and (stringp path) + (or (featurep 'ange-ftp) (featurep 'efs)) + (let ((user (hpath:ange-ftp-default-user)) + result) + (setq result + (cond + ((hpath:url-p path) + (if (string-equal + (substring path (match-beginning 1) (match-end 1)) + "ftp") + (concat + "/" + ;; user + (if (match-beginning 2) + (substring path (match-beginning 2) (match-end 2)) + (concat user "@")) + ;; domain + (hpath:delete-trailer + (substring path (match-beginning 3) (match-end 3))) + ":" + ;; path + (if (match-beginning 5) + (substring path (match-beginning 5) + (match-end 5)))) + ;; else ignore this other type of WWW path + )) + ;; user, domain and path + ((string-match "/?[^/:@ \t\n\^M\"`']+@[^/:@ \t\n\^M\"`']+:[^]@ \t\n\^M\"`'\)\}]*" + path) + (substring path (match-beginning 0) (match-end 0))) + ;; @domain and path + ((string-match "@[^/:@ \t\n\^M\"`']+:[^]@ \t\n\^M\"`'\)\}]*" + path) + (concat "/" user + (substring path (match-beginning 0) (match-end 0)))) + ;; domain and path + ((and (string-match + "/?\\(\\([^/:@ \t\n\^M\"`']+\\):[^]@:, \t\n\^M\"`'\)\}]*\\)" + path) + (setq result (substring path + (match-beginning 1) (match-end 1))) + (string-match "[^.]\\.[^.]" + (substring path (match-beginning 2) + (match-end 2)))) + (concat "/" user "@" result)) + ;; host and path + ((and (string-match + "/\\([^/:@ \t\n\^M\"`']+:[^]@:, \t\n\^M\"`'\)\}]*\\)" + path) + (setq result (substring + path + (match-beginning 1) (match-end 1)))) + (concat "/" user "@" result)) + )) + (hpath:delete-trailer result)))) + +(defun hpath:at-p (&optional type non-exist) + "Returns delimited path or non-delimited ange-ftp path at point, if any. +World wide web urls are treated as non-paths so they are handled elsewhere. +Delimiters may be: double quotes, open and close single quote, or +Texinfo file references. +If optional TYPE is the symbol 'file or 'directory, then only that path type is +accepted as a match. Only locally reachable paths are checked for existence. +With optional NON-EXIST, nonexistent local paths are allowed. +Absolute pathnames must begin with a '/' or '~'. Relative pathnames +must begin with a './' or '../' to be recognized." + (cond (;; Local file URLs + (hpath:is-p (hargs:delimited + "file://localhost" "[ \t\n\^M\"\'\}]" nil t))) + ((hpath:ange-ftp-at-p)) + ((hpath:www-at-p) nil) + ((hpath:is-p (or (hargs:delimited "\"" "\"") + ;; Filenames in Info docs + (hargs:delimited "\`" "\'") + ;; Filenames in TexInfo docs + (hargs:delimited "@file{" "}")) + type non-exist)))) + +(defun hpath:find (filename &optional other-window-p) + "Edit file FILENAME using program from hpath:find-alist if available. +Otherwise, switch to a buffer visiting file FILENAME, creating one if none +already exists. + +FILENAME may start with a special prefix character which is +handled as follows: + !filename - execute as a non-windowed program within a shell; + &filename - execute as a windowed program; + -filename - load as an Emacs Lisp program. + +Return non-nil iff file is displayed within a buffer (not with an external +program)." + (interactive "FFind file: ") + (let (modifier) + (if (string-match hpath:prefix-regexp filename) + (setq modifier (aref filename 0) + filename (substring filename (match-end 0)))) + (setq filename (hpath:substitute-value filename)) + (cond (modifier (cond ((eq modifier ?!) + (hact 'exec-shell-cmd filename)) + ((eq modifier ?&) + (hact 'exec-window-cmd filename)) + ((eq modifier ?-) + (load filename))) + nil) + (t (let ((find-program (hpath:find-program filename))) + (cond ((stringp find-program) + (hact 'exec-window-cmd find-program) + nil) + ((hypb:functionp find-program) + (funcall find-program filename) + nil) + (t (setq filename (hpath:validate filename)) + (funcall (if (and other-window-p + (not (br-in-browser))) + 'switch-to-buffer-other-window + 'switch-to-buffer) + (find-file-noselect filename)) + t))))))) + +(defun hpath:find-other-window (filename) + "Edit file FILENAME, in another window or using program from hpath:find-alist. +May create a new window, or reuse an existing one; see the function display-buffer. + +Alternatively, FILENAME may start with a prefix character to indicate special +handling. See documentation of `hpath:find' for details. + +Return non-nil iff file is displayed within a buffer." + (interactive "FFind file in other window: ") + (hpath:find filename t)) + +(defun hpath:is-p (path &optional type non-exist) + "Returns PATH if PATH is a Unix path, else nil. +If optional TYPE is the symbol 'file or 'directory, then only that path type +is accepted as a match. The existence of the path is checked only for +locally reachable paths (Info paths are not checked). Single spaces are +permitted in middle of existing pathnames, but not at the start or end. Tabs +and newlines are converted to space before the pathname is checked, this +normalized path form is what is returned for PATH. With optional NON-EXIST, +nonexistent local paths are allowed." + (let ((rtn-path path) + (suffix)) + (and (stringp path) + ;; Path may be a link reference with other components other than a + ;; pathname. These components always follow a comma, so strip them, + ;; if any, before checking path. + (if (string-match "[ \t\n\^M]*," path) + (setq rtn-path (concat (substring path 0 (match-beginning 0)) + "%s" (substring path (match-beginning 0))) + path (substring path 0 (match-beginning 0))) + (setq rtn-path (concat rtn-path "%s"))) + (if (string-match hpath:prefix-regexp path) + (setq path (substring path (match-end 0))) + t) + (not (or (string= path "") + (string-match "\\`\\s \\|\\s \\'" path))) + ;; Convert tabs and newlines to space. + (setq path (hbut:key-to-label (hbut:label-to-key path))) + (or (not (string-match "[()]" path)) + (string-match "\\`([^ \t\n\^M\)]+)[ *A-Za-z0-9]" path)) + (if (string-match "\\${[^}]+}" path) + (setq path (hpath:substitute-value path)) + t) + (not (string-match "[\t\n\^M\"`'{}|\\]" path)) + (or (not (hpath:www-p path)) + (string-match "^ftp:" path)) + (let ((remote-path (string-match "@.+:\\|^/.+:\\|.+:/" path))) + (if (cond (remote-path + (cond ((eq type 'file) + (not (string-equal "/" (substring path -1)))) + ((eq type 'directory) + (string-equal "/" (substring path -1))) + (t))) + ((or (and non-exist + (or + ;; Info or ange-ftp path, so don't check for. + (string-match "[()]" path) + (hpath:ange-ftp-p path) + (setq suffix (hpath:exists-p path t)) + ;; Don't allow spaces in non-existent + ;; pathnames. + (not (string-match " " path)))) + (setq suffix (hpath:exists-p path t))) + (cond ((eq type 'file) + (not (file-directory-p path))) + ((eq type 'directory) + (file-directory-p path)) + (t))) + ) + ;; Return path if non-nil return value + (if (stringp suffix) ;; suffix could = t, which we ignore + (if (string-match + (concat (regexp-quote suffix) "%s") rtn-path) + ;; remove suffix + (concat (substring rtn-path 0 (match-beginning 0)) + (substring rtn-path (match-end 0))) + ;; add suffix + (format rtn-path suffix)) + (format rtn-path ""))))))) + +(defun hpath:relative-to (path &optional default-dir) + "Returns PATH relative to optional DEFAULT-DIR or 'default-directory'. +Returns PATH unchanged when it is not a valid path." + (if (not (and (stringp path) (hpath:is-p path))) + path + (setq default-dir + (expand-file-name + (file-name-as-directory (or default-dir default-directory))) + path (expand-file-name path)) + (and path default-dir + (if (string-equal "/" default-dir) + path + (let ((end-dir (min (length path) (length default-dir)))) + (cond + ((string-equal (substring path 0 end-dir) default-dir) + (concat "./" (substring path end-dir))) + ((progn (setq default-dir (file-name-directory (directory-file-name default-dir)) + end-dir (min (length path) (length default-dir))) + (string-equal (substring path 0 end-dir) default-dir)) + (concat "../" (substring path end-dir))) + ((progn (setq default-dir (file-name-directory (directory-file-name default-dir)) + end-dir (min (length path) (length default-dir))) + (string-equal (substring path 0 end-dir) default-dir)) + (concat "../../" (substring path end-dir))) + (t path))))))) + +(defun hpath:rfc (rfc-num) + "Return pathname to textual rfc document indexed by RFC-NUM. +See the documentation of the 'hpath:rfc' variable." + (format hpath:rfc rfc-num)) + +(defun hpath:substitute-value (path) + "Substitutes matching value for Emacs Lisp variables and environment variables in PATH. +Returns path with variable values substituted." + (substitute-in-file-name + (hypb:replace-match-string + "\\${[^}]+}" + path + (function + (lambda (str) + (let* ((var-group (substring path match start)) + (var-name (substring path (+ match 2) (1- start))) + (rest-of-path (substring path start)) + (sym (intern-soft var-name))) + (if (file-name-absolute-p rest-of-path) + (setq rest-of-path (substring rest-of-path 1))) + (if (and sym (boundp sym)) + (directory-file-name + (hpath:substitute-dir var-name rest-of-path)) + var-group)))) + t))) + +(defun hpath:substitute-var (path) + "Replaces up to one match in PATH with first matching variable from 'hpath:variables'." + (if (not (and (stringp path) (string-match "/" path) (hpath:is-p path))) + path + (setq path (hpath:symlink-referent path)) + (let ((new-path) + (vars hpath:variables) + result var val) + (while (and vars (null new-path)) + (setq var (car vars) vars (cdr vars)) + (if (boundp var) + (progn (setq val (symbol-value var)) + (cond ((stringp val) + (if (setq result + (hpath:substitute-var-name var val path)) + (setq new-path result))) + ((null val)) + ((listp val) + (while (and val (null new-path)) + (if (setq result + (hpath:substitute-var-name var (car val) path)) + (setq new-path result)) + (setq val (cdr val)))) + (t (error "(hpath:substitute-var): '%s' has invalid value for hpath:variables" var)))))) + (or new-path path) + ))) + +;; +;; The following function recursively resolves all UNIX links to their +;; final referents. +;; Works with Apollo's variant and other strange links like: +;; /usr/local -> $(SERVER_LOCAL)/usr/local, /usr/bin -> +;; ../$(SYSTYPE)/usr/bin and /tmp -> `node_data/tmp. It also handles +;; relative links properly as in /usr/local/emacs -> gnu/emacs which must +;; be resolved relative to the '/usr/local' directory. +;; It will fail on Apollos if the '../' notation is used to move just +;; above the '/' directory level. This is fairly uncommon and so the +;; problem has not been fixed. +;; +(defun hpath:symlink-referent (linkname) + "Returns expanded file or directory referent of LINKNAME. +LINKNAME should not end with a directory delimiter. +Returns nil if LINKNAME is not a string. +Returns LINKNAME unchanged if it is not a symbolic link but is a pathname." + (if (stringp linkname) + (or (file-symlink-p linkname) linkname))) + +(defun hpath:symlink-expand (referent dirname) + "Returns expanded file or directory REFERENT relative to DIRNAME." + (let ((var-link) + (dir dirname)) + (while (string-match "\\$(\\([^\)]*\\))" referent) + (setq var-link (getenv (substring referent (match-beginning 1) + (match-end 1))) + referent (concat (substring referent 0 (match-beginning 0)) + var-link + (substring referent (match-end 0))))) + ;; If referent is not an absolute path + (let ((nd-abbrev (string-match "`node_data" referent))) + (if (and nd-abbrev (= nd-abbrev 0)) + (setq referent (concat + ;; Prepend node name given in dirname, if any + (and (string-match "^//[^/]+" dirname) + (substring dirname 0 (match-end 0))) + "/sys/" (substring referent 1))))) + (while (string-match "\\(^\\|/\\)\\.\\.\\(/\\|$\\)" referent) + ;; Match to "//.." or "/.." at the start of link referent + (while (string-match "^\\(//\\.\\.\\|/\\.\\.\\)\\(/\\|$\\)" referent) + (setq referent (substring referent (match-end 1)))) + ;; Match to "../" or ".." at the start of link referent + (while (string-match "^\\.\\.\\(/\\|$\\)" referent) + (setq dir (file-name-directory (directory-file-name dir)) + referent (concat dir (substring referent (match-end 0))))) + ;; Match to rest of "../" in link referent + (while (string-match "[^/]+/\\.\\./" referent) + (setq referent (concat (substring referent 0 (match-beginning 0)) + (substring referent (match-end 0)))))) + (and (/= (aref referent 0) ?~) + (/= (aref referent 0) ?/) + (setq referent (expand-file-name referent dirname)))) + referent) + +(defun hpath:validate (path) + "Returns PATH if PATH is a valid, readable path, else signals error. +Info and ange-ftp remote pathnames are considered readable without any +validation checks. +Default-directory should be equal to current Hyperbole button source +directory when called, so that PATH is expanded relative to it." + (cond ((not (stringp path)) + (error "(hpath:validate): \"%s\" is not a pathname." path)) + ((or (string-match "[()]" path) (hpath:ange-ftp-p path)) + ;; info or ange-ftp path, so don't validate + path) + ((if (not (hpath:www-p path)) + ;; Otherwise, must not be a WWW link ref and must be a readable + ;; path. + (let ((return-path (hpath:exists-p path))) + (and return-path (file-readable-p return-path) + return-path)))) + (t (error "(hpath:validate): \"%s\" is not readable." path)))) + +(defun hpath:url-at-p () + "Return world-wide-web universal resource locator (url) that point immediately precedes or nil. +Use buffer-substring with match-beginning and match-end on the following +groupings: + 1 = access protocol + 2 = optional username + 3 = host and domain to connect to + 4 = optional port number to use + 5 = pathname to access." + ;; WWW URL format: <protocol>:/[<user>@]<domain>[:<port>]/<path> + ;; or <protocol>://[<user>@]<domain>[:<port>]<path> + ;; Avoid [a-z]:/path patterns since these may be disk paths on OS/2, DOS or + ;; Windows. + (if (looking-at "\\([a-zA-Z][a-zA-Z]+\\)://?\\([^@/: \t\n\^M]+@\\)?\\([^/:@ \t\n\^M\"`']+\\)\\(:[0-9]+\\)?\\([/~][^]@ \t\n\^M\"`'\)\}>]*\\)?") + (save-excursion + (goto-char (match-end 0)) + (skip-chars-backward ".?#!*()") + (buffer-substring (match-beginning 0) (point))))) + +(defun hpath:url-p (obj) + "Return t if OBJ is a world-wide-web universal resource locator (url) string, else nil. +Use string-match with match-beginning and match-end on the following groupings: + 1 = access protocol + 2 = optional username + 3 = host and domain to connect to + 4 = optional port number to use + 5 = pathname to access." + ;; WWW URL format: <protocol>:/[<user>@]<domain>[:<port>]/<path> + ;; or <protocol>://[<user>@]<domain>[:<port>]<path> + ;; Avoid [a-z]:/path patterns since these may be disk paths on OS/2, DOS or + ;; Windows. + (and (stringp obj) + (string-match "\\`<?\\([a-zA-Z][a-zA-Z]+\\)://?\\([^@/: \t\n\^M]+@\\)?\\([^/:@ \t\n\^M\"`']+\\)\\(:[0-9]+\\)?\\([/~][^]@ \t\n\^M\"`'\)\}>]*\\)?>?\\'" + obj) + t)) + +(defun hpath:www-at-p (&optional include-start-and-end-p) + "Returns a world-wide-web link reference that point is within or nil. +With optional INCLUDE-START-AND-END-P non-nil, returns list of: + (link-string begin-position end-position)." + (save-excursion + (skip-chars-backward "^[ \t\n\"`'\(\{<") + (cond ((not include-start-and-end-p) + (hpath:url-at-p)) + ((hpath:url-at-p) + (list (buffer-substring (match-beginning 0) (match-end 0)) + (match-beginning 0) + (match-end 0)))))) + +(defun hpath:www-p (path) + "Returns non-nil iff PATH is a world-wide-web link reference." + (and (stringp path) (hpath:url-p path) path)) + +;;; ************************************************************************ +;;; Private functions +;;; ************************************************************************ + +(defun hpath:ange-ftp-available-p () + "Return t if the ange-ftp or efs package is available, nil otherwise. +Either the package must have been loaded already or under versions of Emacs +19, it must be set for autoloading via 'file-name-handler-alist'." + (or (featurep 'ange-ftp) (featurep 'efs) + (and (boundp 'file-name-handler-alist) ; v19 + (or (rassq 'ange-ftp-hook-function file-name-handler-alist) + (rassq 'efs-file-handler-function file-name-handler-alist)) + t))) + + + +(defun hpath:ange-ftp-default-user () + "Return default user account for remote file access with ange-ftp or efs. +Returns \"anonymous\" if neither ange-ftp-default-user nor efs-default-user +is set." + (cond ((and (boundp 'ange-ftp-default-user) + (stringp ange-ftp-default-user)) + ange-ftp-default-user) + ((and (boundp 'efs-default-user) + (stringp efs-default-user)) + efs-default-user) + (t "anonymous"))) + +(defun hpath:delete-trailer (string) + "Return string minus any trailing .?#!*() characters." + (save-match-data + (if (and (stringp string) (> (length string) 0) + (string-match "[.?#!*()]+\\'" string)) + (substring string 0 (match-beginning 0)) + string))) + +(defun hpath:exists-p (path &optional suffix-flag) + "Return PATH if it exists. (This does not mean you can read it.) +If PATH exists with or without a suffix from hpath:suffixes, then that +pathname is returned. + +With optional SUFFIX-FLAG and PATH exists, return suffix added or removed +from path or t." + (let ((return-path) + (suffix) suffixes) + (if (file-exists-p path) + (setq return-path path) + (setq suffixes hpath:suffixes) + (while suffixes + (setq suffix (car suffixes)) + (if (string-match (concat (regexp-quote suffix) "\\'") path) + ;; Remove suffix + (setq return-path (substring path 0 (match-beginning 0))) + ;; Add suffix + (setq return-path (concat path suffix))) + (if (file-exists-p return-path) + (setq suffixes nil);; found a match + (setq suffix nil + suffixes (cdr suffixes) + return-path nil)))) + (if return-path + (if suffix-flag + (or suffix t) + return-path)))) + +(defun hpath:find-program (filename) + "Return shell or Lisp command to execute to display FILENAME or nil. +Return nil if FILENAME is a directory name. +See also documentation for 'hpath:find-alist' and 'hpath:display-alist'." + (let ((cmd)) + (cond ((and (stringp filename) (file-directory-p filename)) + nil) + ((stringp (setq cmd (hpath:match filename hpath:find-alist))) + (let ((orig-path filename)) + ;; If filename is a remote path, we have to copy it to a + ;; temporary local file and then display that. + (if (hpath:ange-ftp-p filename) + (copy-file orig-path + (setq filename + (concat hpath:tmp-prefix + (file-name-nondirectory orig-path))) + t t)) + ;; + ;; Permit %s substitution of filename within program. + (if (string-match "[^%]%s" cmd) + (format cmd filename) + (concat cmd " " filename)))) + ((null cmd) + (hpath:match filename hpath:display-alist)) + (t cmd)))) + +(defun hpath:match (filename regexp-alist) + "If FILENAME matches the car of any element in REGEXP-ALIST, return its cdr. +REGEXP-ALIST elements must be of the form (<filename-regexp> +. <command-to-display-file>). <command-to-display-file> may be a string +representing an external window-system command to run or it may be a Lisp +function to call with FILENAME as its single argument." + (let ((cmd) + elt) + (while (and (not cmd) regexp-alist) + (if (string-match (car (setq elt (car regexp-alist))) filename) + (setq cmd (cdr elt))) + (setq regexp-alist (cdr regexp-alist))) + cmd)) + +(defun hpath:substitute-dir (var-name rest-of-path) + "Returns a dir for VAR-NAME using REST-OF-PATH to find match or triggers an error when no match. +VAR-NAME's value may be a directory or a list of directories. If it is a +list, the first directory prepended to REST-OF-PATH which produces a valid +local pathname is returned." + (let (sym val) + (cond ((not (stringp var-name)) + (error "(hpath:substitute-dir): VAR-NAME arg, '%s', must be a string" var-name)) + ((not (and (setq sym (intern-soft var-name)) + (boundp sym))) + (error "(hpath:substitute-dir): VAR-NAME arg, \"%s\", is not a bound variable" + var-name)) + ((stringp (setq val (symbol-value sym))) + (if (hpath:validate (expand-file-name rest-of-path val)) + val)) + ((listp val) + (let ((dir)) + (while (and val (not dir)) + (setq dir (car val) val (cdr val)) + (or (and (stringp dir) + (file-name-absolute-p dir) + (file-readable-p (expand-file-name rest-of-path dir))) + (setq dir nil))) + (if dir (hpath:validate (directory-file-name dir)) + (error "(hpath:substitute-dir): Can't find match for \"%s\"" + (concat "${" var-name "}/" rest-of-path)) + ))) + (t (error "(hpath:substitute-dir): Value of VAR-NAME, \"%s\", must be a string or list" var-name)) + ))) + +(defun hpath:substitute-var-name (var-symbol var-dir-val path) + "Replaces with VAR-SYMBOL any occurrences of VAR-DIR-VAL in PATH. +Replacement is done iff VAR-DIR-VAL is an absolute path. +If PATH is modified, returns PATH, otherwise returns nil." + (if (and (stringp var-dir-val) (file-name-absolute-p var-dir-val)) + (let ((new-path (hypb:replace-match-string + (regexp-quote (file-name-as-directory + (or var-dir-val default-directory))) + path (concat "${" (symbol-name var-symbol) "}/") + t))) + (if (equal new-path path) nil new-path)))) + + +;;; ************************************************************************ +;;; Private variables +;;; ************************************************************************ + + +(defvar hpath:prefix-regexp "\\`[-!&][ ]*" + "Regexp matching command characters which may precede a pathname. +These are used to indicate how to display or execute the pathname. + - means evaluate it as Emacs Lisp; + ! means execute it as a shell script + & means run it under the current window system.") + +(provide 'hpath)